Ensiklopedia VibeKoding: Principles of Backend Layered Architecture.Ensiklopedia VibeKoding: Principles of Backend Layered Architecture.
> Core Question: As code grows more chaotic, how should you organize it to stay clear and understandable?> Core Question: As code grows more chaotic, how should you organize it to stay clear and understandable?
When a project expands from dozens of lines to tens of thousands, from solo development to team collaboration, and from simple CRUD to complex business logic, the way code is organized directly determines the project's survival. Layered architecture is not about showing off or following dogma β it exists to resolve a fundamental contradiction in software engineering: the clash between the natural growth of business complexity and the limited capacity of human cognition.When a project expands from dozens of lines to tens of thousands, from solo development to team collaboration, and from simple CRUD to complex business logic, the way code is organized directly determines the project's survival. Layered architecture is not about showing off or following dogma β it exists to resolve a fundamental contradiction in software engineering: the clash between the natural growth of business complexity and the limited capacity of human cognition.
------
Early version (100 lines of code):Early version (100 lines of code):
java @PostMapping("/register") public Result register(@RequestBody User user) { // 1. Check if username already exists if (userRepository.findByUsername(user.getUsername()) != null) { return Result.error("Username already exists"); } // 2. Encrypt password user.setPassword(encrypt(user.getPassword())); // 3. Save user userRepository.save(user); // 4. Send welcome email emailService.sendWelcome(user.getEmail()); // 5. Log log.info("User registered: {}", user.getUsername()); return Result.success(); }
6 months later (500 lines of code):6 months later (500 lines of code):
Now this method is 500 lines, and every change is nerve-wracking because:Now this method is 500 lines, and every change is nerve-wracking because:
The essence of the problem: code has no "boundaries"; all responsibilities are mixed together.The essence of the problem: code has no "boundaries"; all responsibilities are mixed together.
The compounding effect of technical debt:The compounding effect of technical debt:
Layered architecture draws clear boundaries for code:Layered architecture draws clear boundaries for code:
CODE βββββββββββββββββββββββββββββββββββββββ β Accept requests β Controller β Only responsible for "taking orders" βββββββββββββββββββββββββββββββββββββββ€ β Business orchestration β Service β Only responsible for "cooking" βββββββββββββββββββββββββββββββββββββββ€ β Data access β Repository β Only responsible for "fetching ingredients" βββββββββββββββββββββββββββββββββββββββ€ β Business definition β Domain β Only responsible for "recipe standards" βββββββββββββββββββββββββββββββββββββββ
Key principles:Key principles:
Engineering value of layered architecture:Engineering value of layered architecture:
------
The essence of layered architecture is Separation of Concerns and dependency direction control:The essence of layered architecture is Separation of Concerns and dependency direction control:
CODE βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Frontend request β ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ β HTTP Request βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Controller Layer β β - Accept requests, validate parameters β β - DTO conversion β β - Call Service β β - Return response β ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ β Business call βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Service Layer β β - Business logic orchestration β β - Transaction management β β - Coordinate multiple Repositories β β - Cross-module coordination β ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ β Data access βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Repository Layer β β - Database CRUD β β - Query encapsulation β β - ORM mapping β ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ β Domain objects βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Domain Layer β β - Entity β β - Value Object β β - Business rules β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Dependency direction: Code dependencies must point toward more stable, more abstract directionsDependency direction: Code dependencies must point toward more stable, more abstract directions
Responsibility: The "receptionist" for requestsResponsibility: The "receptionist" for requests
What it should NOT do:What it should NOT do:
Design philosophy:Design philosophy:
The Controller is the system's "facade," serving as an adapter β translating the external HTTP protocol into internal business calls. It should contain no business decisions, because business decisions embody domain knowledge and should be decoupled from the transport protocol.The Controller is the system's "facade," serving as an adapter β translating the external HTTP protocol into internal business calls. It should contain no business decisions, because business decisions embody domain knowledge and should be decoupled from the transport protocol.
Example:Example:
java @RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; @PostMapping public UserResponse createUser( @RequestBody @Valid UserRequest request) { // 1. Request DTO β Param DTO UserParam param = UserParam.builder() .username(request.getUsername()) .password(encrypt(request.getPassword())) .email(request.getEmail()) .build(); // 2. Call Service User user = userService.createUser(param); // 3. Entity β Response DTO return UserResponse.from(user); } }
Key points:Key points:
@Valid for automatic parameter validationUse @Valid for automatic parameter validationResponsibility: The "chef" of the businessResponsibility: The "chef" of the business
What it should NOT do:What it should NOT do:
Design philosophy:Design philosophy:
The Service layer carries business logic and should remain pure. It does not depend on any framework or transport protocol, which enables:The Service layer carries business logic and should remain pure. It does not depend on any framework or transport protocol, which enables:
Example:Example:
java @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; private final EmailService emailService; @Transactional public User createUser(UserParam param) { // 1. Business rule: check if username already exists if (userRepository.existsByUsername(param.getUsername())) { throw new UserAlreadyExistsException(); } // 2. Create user entity User user = new User(); user.setUsername(param.getUsername()); user.setPassword(param.getPassword()); user.setEmail(param.getEmail()); // 3. Save to database userRepository.save(user); // 4. Send welcome email (cross-module coordination) emailService.sendWelcomeEmail(user); return user; } }
Key points:Key points:
@Transactional to guarantee transaction consistencyUse @Transactional to guarantee transaction consistencyResponsibility: The "warehouse keeper" of dataResponsibility: The "warehouse keeper" of data
What it should NOT do:What it should NOT do:
Design philosophy:Design philosophy:
The Repository is an abstraction layer for data access that hides the details of the underlying database. The value of this abstraction lies in:The Repository is an abstraction layer for data access that hides the details of the underlying database. The value of this abstraction lies in:
Example:Example:
java @Repository public interface UserRepository extends JpaRepository<User, Long> { // Automatically implemented by Spring Data JPA Optional<User> findByUsername(String username); boolean existsByUsername(String username); // Custom complex query @Query("SELECT u FROM User u WHERE u.email = :email AND u.deleted = false") Optional<User> findActiveByEmail(@Param("email") String email); }
Key points:Key points:
@Query for custom complex queriesUse @Query for custom complex queriesResponsibility: The "recipe standards" of the businessResponsibility: The "recipe standards" of the business
Important characteristics:Important characteristics:
Design philosophy:Design philosophy:
The Domain layer is the business core of the entire system, expressing domain knowledge and business rules. Its purity is critical:The Domain layer is the business core of the entire system, expressing domain knowledge and business rules. Its purity is critical:
Example:Example:
java @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String username; @Column(nullable = false) private String password; // β
Business method: encapsulates business rules public boolean isPasswordCorrect(String rawPassword) { return BCrypt.checkpw(rawPassword, this.password); } public void changePassword(String oldPassword, String newPassword) { if (!isPasswordCorrect(oldPassword)) { throw new IncorrectPasswordException(); } this.password = BCrypt.hashpw(newPassword); } }
Key points:Key points:
------
The problem: If you return database entities directly to the frontend:The problem: If you return database entities directly to the frontend:
java // β Wrong: directly returning Entity @Entity public class User { private Long id; private String username; private String password; // Sensitive information! private Boolean isDeleted; // Internal field! }
The frontend would receive fields that should never be exposed, creating security risks.The frontend would receive fields that should never be exposed, creating security risks.
The solution: Use DTOs as "translators"The solution: Use DTOs as "translators"
CODE Database Entity β Service Param/Result β Controller Request/Response β Frontend
| Type | Purpose | Example |
|---|---|---|
| Request DTO | Controller receives parameters | UserCreateRequest |
| Response DTO | Controller returns data | UserResponse |
| Param DTO | Service method parameters | UserParam |
| Result DTO | Service returns results | UserResult |
| Entity | Database mapping | User |
Key principle:Key principle:
Each layer uses its own DTOs β never pass entities directly. DTOs contain only necessary fields, which avoids exposing internal implementation details and preserves the independence of each layer.Each layer uses its own DTOs β never pass entities directly. DTOs contain only necessary fields, which avoids exposing internal implementation details and preserves the independence of each layer.
------
Wrong approach:Wrong approach:
CODE Controller β UserServiceImpl β UserDaoImpl β UserEntity
Correct approach:Correct approach:
CODE Controller β UserService (interface) β UserRepository (interface) β UserEntity
Dependency direction:Dependency direction:
The correct dependency direction has all layers depending on more abstract, more stable layers. Specifically, Controller depends on the Service interface, Service depends on the Repository interface, all layers depend on the Domain layer, and the Domain layer depends on no other layer. This dependency direction ensures the independence and testability of business logic.The correct dependency direction has all layers depending on more abstract, more stable layers. Specifically, Controller depends on the Service interface, Service depends on the Repository interface, all layers depend on the Domain layer, and the Domain layer depends on no other layer. This dependency direction ensures the independence and testability of business logic.
Wrong practices include Service directly depending on a Repository implementation class, Controller directly accessing the database, or the Domain layer depending on other layers β all of which increase coupling and reduce system maintainability.Wrong practices include Service directly depending on a Repository implementation class, Controller directly accessing the database, or the Domain layer depending on other layers β all of which increase coupling and reduce system maintainability.
java // β
Correct: depends on interfaces @Service public class OrderService { private final OrderRepository orderRepository; // interface private final PaymentService paymentService; // interface } // β
Implementation class injected automatically by Spring @Repository public class OrderRepositoryImpl implements OrderRepository { // Implementation details }
------
Creating an order:Creating an order:
Domain Layer:Domain Layer:
java @Entity public class Order { @Id private Long id; private Long userId; private List<OrderItem> items; private Money totalAmount; private OrderStatus status; public void calculateTotal() { Money total = Money.zero(); for (OrderItem item : items) { total = total.add(item.getSubTotal()); } this.totalAmount = total; } public void cancel() { if (this.status != OrderStatus.PENDING_PAYMENT) { throw new IllegalStateException("Only pending-payment orders can be cancelled"); } this.status = OrderStatus.CANCELLED; } }
Repository Layer:Repository Layer:
java @Repository public interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByUserIdOrderByCreatedAtDesc(Long userId); }
Service Layer:Service Layer:
java @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final InventoryService inventoryService; @Transactional public OrderDTO createOrder(OrderParam param) { // 1. Validate products and reserve inventory for (OrderItemParam item : param.getItems()) { inventoryService.reserveStock(item.getProductId(), item.getQuantity()); } // 2. Create order Order order = new Order(); order.setUserId(param.getUserId()); order.calculateTotal(); // 3. Save order orderRepository.save(order); return OrderDTO.from(order); } }
Controller Layer:Controller Layer:
java @RestController @RequestMapping("/api/orders") public class OrderController { private final OrderService orderService; @PostMapping public OrderResponse createOrder(@RequestBody @Valid OrderRequest request) { OrderParam param = OrderParam.builder() .userId(request.getUserId()) .items(request.getItems()) .build(); OrderDTO order = orderService.createOrder(param); return OrderResponse.from(order); } }
------
The Controller should not contain business logic β it is only responsible for accepting requests and returning responses. Business logic should be encapsulated in the Service layer. The benefit is that code can be reused: for example, scheduled tasks or message queue consumers can directly call the Service without going through HTTP. Additionally, business logic concentrated in one place is easier to test and maintain, avoiding inconsistencies caused by scattered logic.The Controller should not contain business logic β it is only responsible for accepting requests and returning responses. Business logic should be encapsulated in the Service layer. The benefit is that code can be reused: for example, scheduled tasks or message queue consumers can directly call the Service without going through HTTP. Additionally, business logic concentrated in one place is easier to test and maintain, avoiding inconsistencies caused by scattered logic.
The Anemic Domain Model means entity classes contain only properties and their corresponding getter/setter methods, with no business logic β all business rules reside in the Service layer. This model is simple in structure, easy to understand, and is the approach adopted by most projects.The Anemic Domain Model means entity classes contain only properties and their corresponding getter/setter methods, with no business logic β all business rules reside in the Service layer. This model is simple in structure, easy to understand, and is the approach adopted by most projects.
The Rich Domain Model means entity classes contain not only properties but also business methods related to the entity, encapsulating business rules within the entity itself. This approach aligns better with object-oriented design principles, keeping data and behavior together and improving code cohesion.The Rich Domain Model means entity classes contain not only properties but also business methods related to the entity, encapsulating business rules within the entity itself. This approach aligns better with object-oriented design principles, keeping data and behavior together and improving code cohesion.
It is recommended to choose the model based on the team's technical background and project complexity. Whichever you choose, maintain consistency, and the Domain layer should at least include basic behavioral methods rather than being a completely empty shell.It is recommended to choose the model based on the team's technical background and project complexity. Whichever you choose, maintain consistency, and the Domain layer should at least include basic behavioral methods rather than being a completely empty shell.
When a business operation needs to span multiple Services, use a @Transactional annotation on the upper-level Service method, and within that method, call the lower-level Services in sequence. This ensures all operations execute within the same transaction context β either all succeed or all fail, maintaining data consistency. Note that transaction boundaries should be as small as possible, including only necessary operations, to avoid holding database locks for extended periods and affecting concurrency performance.When a business operation needs to span multiple Services, use a @Transactional annotation on the upper-level Service method, and within that method, call the lower-level Services in sequence. This ensures all operations execute within the same transaction context β either all succeed or all fail, maintaining data consistency. Note that transaction boundaries should be as small as possible, including only necessary operations, to avoid holding database locks for extended periods and affecting concurrency performance.
------
| Layer | Responsibility | Keywords |
|---|---|---|
| Controller | Accept requests, validate parameters, call Service, return response | Receptionist |
| Service | Business logic orchestration, transaction management, coordinate Repository | Chef |
| Repository | Data access, ORM mapping, query encapsulation | Warehouse Keeper |
| Domain | Entity definition, business rules, value objects | Recipe Standards |
Core principles:Core principles:
------
This article introduces Layered Architecture, the most common and easiest backend architecture pattern to get started with. But backend architecture goes far beyond this one pattern β depending on the business context, there are other architectural patterns worth understanding:This article introduces Layered Architecture, the most common and easiest backend architecture pattern to get started with. But backend architecture goes far beyond this one pattern β depending on the business context, there are other architectural patterns worth understanding:
| Pattern | Use Case | Characteristics |
|---|---|---|
| Monolithic Architecture | Small projects, MVP | All functionality in a single application, simple deployment |
| Microservices Architecture | Large, complex systems | Split into multiple independent services, each independently deployable |
| Event-Driven Architecture | High concurrency, async processing | Processing flows triggered by events, highly decoupled |
| Clean Architecture | Complex business systems | Business logic at the center, dependencies only point inward, frameworks on the outermost layer |
| Hexagonal Architecture | Systems needing diverse external adapters | Isolates core from external systems through ports and adapters |
| Onion Architecture | Domain-Driven Design | Concentric layers, domain model innermost, infrastructure outermost |
Let's explore each one:Let's explore each one:
All functionality packaged in a single application, sharing one database and one process.All functionality packaged in a single application, sharing one database and one process.
CODE ββββββββββββββββββββββββββββββββ β Monolithic App β β ββββββ ββββββ ββββββ β β βUserβ βOrderβ βPay β ... β β ββββ¬ββ ββββ¬ββ ββββ¬ββ β β ββββββββΌβββββββ β β Shared Database β ββββββββββββββββββββββββββββββββ
Splits the system into multiple independent services, each with its own data and business logic, independently deployable and scalable.Splits the system into multiple independent services, each with its own data and business logic, independently deployable and scalable.
CODE ββββββββββ ββββββββββ ββββββββββ βUser Svcβ βOrder Svcβ βPay Svc β β DB-1 β β DB-2 β β DB-3 β βββββ¬βββββ βββββ¬βββββ βββββ¬βββββ βββββββββββββΌββββββββββββ API Gateway
Communication through asynchronous events β producers emit events, consumers respond to events, with highly decoupled components.Communication through asynchronous events β producers emit events, consumers respond to events, with highly decoupled components.
CODE Producer βββ [Event Bus / Message Queue] βββ Consumer A βββ Consumer B βββ Consumer C
Proposed by Robert C. Martin, the system is divided into four concentric layers with dependencies pointing only inward:Proposed by Robert C. Martin, the system is divided into four concentric layers with dependencies pointing only inward:
CODE βββββββββββββββββββββββββββββββββββββββ β Frameworks & Drivers β β βββββββββββββββββββββββββββββββ β β β Interface Adapters β β β β βββββββββββββββββββββββ β β β β β Use Cases β β β β β β βββββββββββββββ β β β β β β β Entities β β β β β β β β (Domain) β β β β β β β βββββββββββββββ β β β β β βββββββββββββββββββββββ β β β βββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββ Dependency direction: outer β inner
Defines input/output interfaces for the core business through "ports," and connects external systems through "adapters":Defines input/output interfaces for the core business through "ports," and connects external systems through "adapters":
CODE βββββββββββββββ HTTP βββ Port β CLI βββ (Inbound) β Core Business β (Outbound) βββ Database MQ βββ β Logic β Port βββ External API βββββββββββββββ
Similar to Clean Architecture, emphasizes the domain model at the innermost layer and infrastructure at the outermost, with dependencies only pointing inward:Similar to Clean Architecture, emphasizes the domain model at the innermost layer and infrastructure at the outermost, with dependencies only pointing inward:
CODE ββββββββββββββββββββββββββββββββ β Infrastructure β β ββββββββββββββββββββββββββ β β β Application Services β β β β ββββββββββββββββββββ β β β β β Domain Services β β β β β β ββββββββββββββ β β β β β β βDomain Modelβ β β β β β β ββββββββββββββ β β β β β ββββββββββββββββββββ β β β ββββββββββββββββββββββββββ β ββββββββββββββββββββββββββββββββ
These architectures are not mutually exclusive alternatives β they represent a gradual evolution:These architectures are not mutually exclusive alternatives β they represent a gradual evolution:
text Traditional Layered Architecture (N-Layered) β Problem: inter-layer coupling, hard to replace external dependencies βΌ Hexagonal Architecture (Ports & Adapters) β Improvement: use ports and adapters to isolate external systems βΌ Onion Architecture β Improvement: explicit concentric layering, domain model at the center βΌ Clean Architecture β Improvement: unified dependency rules, clear four-layer responsibilities βΌ Choose the right architecture based on business needs
text Users < 1k, Code < 5,000 lines β Monolithic + Simple Layering β Users 1kβ100k, requires multi-team collaboration β Layered Architecture (this article) β Users > 100k, high business complexity β Microservices / Event-Driven Architecture
More detailed selection dimensions:More detailed selection dimensions:
| Factor | Simple Layering | Clean/Hexagonal Architecture | Microservices |
|---|---|---|---|
| Team size | 1β5 people | 5β20 people | 20+ people |
| Business complexity | Low | MediumβHigh | High |
| Deployment frequency | Low | Medium | High (independent deployment) |
| Technology stack diversity | Single | Single | Can be diverse |
| Operations cost | Low | Medium | High |
backend-project-architecture.md](./backend-project-architecture.md) for the evolution from scripts to monolithsMonolithic Architecture: See the companion article [backend-project-architecture.md](./backend-project-architecture.md) for the evolution from scripts to monolithsRemember this principle: Architecture serves the business β don't do architecture for architecture's sake.Remember this principle: Architecture serves the business β don't do architecture for architecture's sake.
------
| Layer | Responsibility | Keywords |
|---|---|---|
| Controller | Accept requests, validate parameters, call Service, return response | Receptionist |
| Service | Business logic orchestration, transaction management, coordinate Repository | Chef |
| Repository | Data access, ORM mapping, query encapsulation | Warehouse Keeper |
| Domain | Entity definition, business rules, value objects | Recipe Standards |
Core principles:Core principles:
The core of layered architecture lies in clear responsibility division and dependency direction control. Each layer focuses only on its own responsibilities, communicates with adjacent layers through interfaces, concentrates business logic in the Service and Domain layers, concentrates data access logic in the Repository layer, and isolates data structures between layers through DTOs to avoid directly exposing internal implementation. This design makes the system easier to understand, test, and maintain, capable of supporting continuous business evolution.The core of layered architecture lies in clear responsibility division and dependency direction control. Each layer focuses only on its own responsibilities, communicates with adjacent layers through interfaces, concentrates business logic in the Service and Domain layers, concentrates data access logic in the Repository layer, and isolates data structures between layers through DTOs to avoid directly exposing internal implementation. This design makes the system easier to understand, test, and maintain, capable of supporting continuous business evolution.
------