Spring Boot by Building · Chapter 1
Before a single line of code: the one picture that untangles controllers, services,
repositories, models — and where JPA, Lombok, Mockito, @Autowired, and "which database" each hang on
that picture. Come back to this page whenever a Spring project feels like a pile of unrelated files.
Study the same The Layer Map explanation with a different purpose: build and verify it in Learn deeply, or retrieve and defend it in Prepare for interviews.
The chapter below remains the canonical explanation for both goals. Keep its model, examples, and trade-offs shared; only the deliberate practice changes with the route.
Use this chapter’s TaskFlow work to make one small, observable change. Run the stated acceptance checks before recording practice.
Without notes, explain the boundary this chapter protects, one concrete implementation decision, and the cost of a tempting shortcut. Then answer one follow-up using the chapter’s own example.
A Spring Boot backend is not a pile of files. It is one short assembly line that
a web request walks down and back. There are only about four kinds of class, each with exactly one job,
always in the same order. The annotations (@RestController, @Service, …) are just
name tags that tell Spring which kind a class is. Learn the line once and every unfamiliar
Spring project becomes readable — because it is always the same line.
Most tutorials introduce the pieces alphabetically or by feature — a bit of JPA here, an
@Autowired there, Mockito in a testing video — so they arrive as a bag of unrelated
words. They are not unrelated. Every one of them lives at a specific seam of the same
assembly line. This chapter puts each word back where it belongs on one map. Once each term has a home, "getting
messed up" mostly stops.
Every request to a Spring backend does the same trip: it enters at the top, is handed down one layer at a time, reaches the database, and the answer is handed back up the same steps in reverse. Read this diagram top-to-bottom, then bottom-to-top.
Fig 1 — The assembly line. Grey arrows: the request going down. Ochre arrows: the answer coming back up. The Model is the parcel that rides the belt.
The Controller is the waiter: takes your order, brings your plate, never cooks. The Service is the chef: decides how the dish is made, the actual rules. The Repository is the pantry clerk: the only one allowed into the storeroom (the database). The Model is the dish that moves between them. You would never ask the waiter to unlock the pantry — and you never let a Controller touch the database. That single rule explains the whole structure.
That is the entire cast. Memorise this table; it is the Rosetta Stone for reading any Spring file. When you open an unfamiliar class, your first question is always "which row is this?"
| Layer | One job | Name tag | Is allowed to talk to… | Restaurant |
|---|---|---|---|---|
| Controller | Translate HTTP ⇄ Java. Read the URL/body, call a service, return the result. | @RestController |
the Service (below it) | Waiter |
| Service | The business rules. Decide, validate, combine. No HTTP words, no SQL words. | @Service |
one or more Repositories | Chef |
| Repository | Load and save data. The only layer that knows the database exists. | @Repository / extends JpaRepository |
the Database | Pantry clerk |
| Model | Hold the data. A plain object — a Task, a User. Rides through every layer. |
@Entity (when it maps to a table) |
nobody — it's just data | The dish |
Each layer may only call the layer directly below it. Controller → Service → Repository → DB. A Controller must never call a Repository directly, and nothing skips the Service. When you feel tempted to shortcut, that temptation is exactly the "tangle" — resisting it is what keeps the four kinds separate and testable.
You now have four kinds of class. But who creates them, and how does the Controller get hold of the Service? This is the part that mystifies everyone, and it is genuinely two ideas wearing scary names.
You stop calling new. Spring creates your objects for you and holds them in a
box called the application context. One shared instance of each is called a bean.
Spring hands each object the others it needs. Your Controller declares "I need a TaskService" and Spring passes one in. You never build the chain yourself.
The two name tags do the two jobs. @Service (and its siblings) says "Spring, please build one
of these and keep it in the box." A constructor says "and to build me, you'll need to hand me
these."
@RestController // tag: "I'm a controller — build me, and route HTTP to me"
public class TaskController {
private final TaskService service; // I depend on the chef
// Spring sees this constructor and injects a TaskService bean automatically.
public TaskController(TaskService service) {
this.service = service; // DI just happened — no `new` anywhere
}
}
@RestController → Spring makes exactly one TaskController at startup and keeps it.TaskService. Spring already made one (because of its
@Service tag), so it passes that same instance in.new TaskController(...) and no new TaskService(...). That absence
is Inversion of Control.You will see @Autowired on fields in older code — it is the older way to ask for a dependency.
Modern Spring injects through the constructor automatically (as above), and that is what this
course uses: it needs no annotation, it makes the field final, and — crucially — it lets you
create the class yourself in a test by just passing a fake in. Same idea, cleaner seam. When you meet
@Autowired, read it as "inject here"; when you write new code, prefer the constructor.
This is the de-tangler. Every intimidating word you listed is pinned to one seam of the same line. Read the "sits at" column and the fog lifts.
| Word | Sits at | What it actually does |
|---|---|---|
| Spring Boot | the whole app | Runs the assembly line: creates the beans, wires DI, starts a web server inside your app. The factory floor. |
| @RestController / @GetMapping | Controller ↔ HTTP | Map a URL + HTTP verb to a Java method; turn the return value into JSON. |
| @Service, @Autowired, constructor | Controller ↔ Service seam | Declare a bean and inject it. The connective tissue between layers. |
| Repository / JpaRepository | Service ↔ DB seam | An interface of load/save methods. You declare it; Spring writes the implementation. |
| JPA / Spring Data JPA | Repository ↔ relational DB | The rulebook for turning Java objects into table rows and back, so you rarely write SQL by hand. |
| @Entity, @Id, @Column | the Model | Tags on a model class that say "this class is a table, this field is the primary key," etc. |
| Lombok (@Getter, @Data, @Builder) | the Model (mostly) | A helper that generates getters/setters/constructors at compile time so your model isn't 200 lines of boilerplate. Pure convenience — removes nothing conceptual. |
| DTO | Controller edge | A small "shape" object for the request/response body, so the outside world never sees your raw entity. Optional early on; matters as apps grow. |
| application.properties | outside the layers | The settings sheet: which database, what port, log levels. Change the DB here without touching a single class. |
| Mockito / @Mock | tests only | In a unit test, replaces a real neighbour (e.g. the Repository) with a fake you control, so you can test the Service alone. Never ships in your running app. |
| H2 / MySQL / MongoDB | the Database box | Interchangeable back-ends behind the Repository seam. Same seam, different driver + config. |
When a new term shows up, don't ask "what is this?" first. Ask "which seam does it sit at?" Testing word → tests column. Table word → the Model. Load/save word → the Repository seam. Injection word → between two layers. Almost every Spring term answers to one seam, and once it has a seat it stops floating around confusing everything else.
This one deserves its own section because it trips everyone. The key insight: the database is behind a
seam. Your Service asks the Repository to "save this task" and does not care what is on the other
side. Swapping databases is mostly a change to application.properties, not to your classes.
| Option | What it is | Setup cost | Use it when |
|---|---|---|---|
| H2 | A tiny database written in Java that runs inside your app, usually in memory. "Java's internal DB." | Zero — no install, add one dependency. | Learning & tests. We use this for every walkthrough so nothing external can break. |
| MySQL / PostgreSQL | Real relational databases (tables, rows, SQL) running as a separate program. | Install/run the server + set a URL & password. | Real apps with structured, related data. Same JPA code as H2. |
| MongoDB | A document database — stores JSON-like documents, not tables. Different query model. | Install/run + a different Spring Data dependency. | Loose or nested data. Uses MongoRepository instead of JpaRepository. |
H2 and MySQL are the same JPA code — you literally change three lines of config to go from
one to the other (Chapter 4 shows the exact diff). Mongo is the one real fork: tables become documents and
JpaRepository becomes MongoRepository. So there are really only two persistence
worlds to hold in your head — "relational (JPA)" and "document (Mongo)" — not five. Start relational; it's the
bigger, more transferable half.
Layers are also folders (Java calls them packages). The convention is one package per kind. When you open a real project, this is what you'll see — and now you can predict every folder's job:
The folder tree is Fig 1 turned sideways. controller/, service/,
repository/, model/ — the same four kinds, now as directories. Tests sit in a
parallel src/test world so test-only tools like Mockito can never leak into what ships. If you
ever feel lost in a repo, find these four folders first; they orient you instantly.
You said you can read Spring code now but not crystal clear. Here is a repeatable routine to make it crisp. Run these four questions, in order, on any class you open:
@RestController,
@Service, @Repository/JpaRepository, or @Entity → you now
know its one job and where it sits on the line.@Autowired fields). Those
are the layers below it that Spring will inject. That's the class's outgoing arrows.@GetMapping/@PostMapping
methods are its public URLs. For a Service, its public methods are the rules it offers upward.You don't have code to try this on yet — but you will after Chapter 2. Keep this routine open in a tab; run it on the very first file you build. The confusion you described is mostly the absence of a fixed reading order. This is that order.
Hand-copyable revision sheet · the whole chapter on one page
@RestController) → HTTP ⇄ Java@Service) → the rulesJpaRepository) → load/save, the only DB-talker@Entity) → the data that rides the beltnew; Spring makes the objects (beans).@Entity / Lombok → around the Model + Repository seam@Mock → tests only, never in main/application.properties → which DB + port, outside the layersChapter 2 — From an empty folder to a running endpoint. You'll generate a real project, meet every generated file, and get one URL responding in the browser. Fully guided, end to end.
Current state: no project yet. Outcome: a layer map and package plan for the TaskFlow API. Prerequisite: you can read a Java interface; continue if you cannot name every Spring annotation yet. Java bridge: interfaces make collaborator boundaries explicit. Estimate: 20 minutes.
Sketch controller, service, repository, and model. Trace GET /tasks down and its JSON response back up. The artifact is a package plan, not a running app.
Task model travelling through the flow.Likely files next: controller/PingController.java, TaskflowApplication.java. Suggested commit: docs: map taskflow layers.
Before opening the solution: draw the request arrow, name every layer once, and say where a validation rule would live.
controller → service → repository is the dependency direction; model is shared data, not a fourth caller.The next workshop turns this plan into one observable URL, so the map gains a real request to trace.