The Layer Map
The map for the whole workshop: controllers, services, repositories, models, JPA, Lombok, Mockito, dependency injection, and where each concern belongs.
After this you should be able to
- Name the main application layers
- Place a concern in its layer
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.
Choose your route#
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.
- Name the main application layers
- Place a concern in its layer
Shared core#
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.
Learn deeply: build evidence#
Use this chapter’s TaskFlow work to make one small, observable change. Run the stated acceptance checks before recording practice.
Prepare for interviews: explain the trade-off#
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.
Section 1The line a request walks#
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.
Section 2The four kinds of class#
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.
Section 3How the pieces get connected — IoC & DI, concretely#
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 oneTaskControllerat startup and keeps it.- The constructor asks for a
TaskService. Spring already made one (because of its@Servicetag), so it passes that same instance in. - You wrote no
new TaskController(...)and nonew 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.
Section 4Where every buzzword lives on the map#
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.
Section 5"MySQL or Mongo or Java's own DB?" — untangled#
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.
Section 6Where the files physically live#
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.
Section 7A method: read any Spring class in ten seconds#
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:
- Which kind is it? Look at the class annotation.
@RestController,@Service,@Repository/JpaRepository, or@Entity→ you now know its one job and where it sits on the line. - What does it depend on? Look at the constructor (or
@Autowiredfields). Those are the layers below it that Spring will inject. That's the class's outgoing arrows. - What does it expose? For a Controller, the
@GetMapping/@PostMappingmethods are its public URLs. For a Service, its public methods are the rules it offers upward. - Where does the data stop? Follow the calls down until you hit the Repository — that's the floor, where it meets the database. Everything above is just handing the parcel along.
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.
Notebook Summary#
Hand-copyable revision sheet · the whole chapter on one page
The line#
- Client → Controller → Service → Repository → DB, and the answer back up.
- Each layer calls only the one directly below. No skipping. No Controller touching the DB.
- Aid: waiter → chef → pantry clerk → storeroom.
The four kinds#
- Controller (
@RestController) → HTTP ⇄ Java - Service (
@Service) → the rules - Repository (
JpaRepository) → load/save, the only DB-talker - Model (
@Entity) → the data that rides the belt
Wiring#
- IoC → you stop calling
new; Spring makes the objects (beans). - DI → Spring hands each object what it needs, via the constructor.
- @Autowired → the older field-injection way; prefer the constructor.
Where the words live#
- JPA /
@Entity/ Lombok → around the Model + Repository seam - Mockito /
@Mock→ tests only, never inmain/ application.properties→ which DB + port, outside the layers- H2 = in-app DB (learning); MySQL = same JPA code; Mongo = the one real fork (documents)
Box these#
- ⭐Ask "which seam?" before "what is it?". Every term has a home on the line.
- ⭐Read any class with the 4 questions: kind → depends on → exposes → where data stops.
- ⚠Controller calling a Repository directly = the tangle. Always go through the Service.
- 💡Swapping H2 → MySQL is config, not code. Relational vs document is the only real fork.
Chapter 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.
TaskFlow checkpoint#
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.
Mission: label the first slice before coding#
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.
- You can point to the one layer that owns HTTP, rules, and persistence.
- You can explain why a controller does not call a repository.
- Your map names the
Taskmodel travelling through the flow.
Likely files next: controller/PingController.java, TaskflowApplication.java. Suggested commit: docs: map taskflow layers.
Hint 1
Hint 2
Hint 3
Before opening the solution: draw the request arrow, name every layer once, and say where a validation rule would live.
Reference patch — package plan
controller → service → repository is the dependency direction; model is shared data, not a fourth caller.Recall before continuing#
- Which layer translates HTTP?
- Where do business rules belong?
- Who is allowed to talk to the database?
The next workshop turns this plan into one observable URL, so the map gains a real request to trace.