graph LR A["Client(Browser)"] -->|HTTP Request| B[@Controller] B --> C[@Service] C --> D[@Repository] D --> E[(Database)] E --> D D --> C C --> B B -->|HTTP Reponse| A
- spring은 이 구조를 정말 철저하게 지킴!
- controller, service, repository는 다 spring bean임
Diagram
graph LR subgraph User Application BR[Browser] JA[Java Application] end Service Layer subgraph Service Layer subgraph ProductService PS1["findAllProducts()"] PS2["findProductById()"] PS3["saveProduct()"] PS4["updateProduct()"] PS5["deleteProduct()"] end end Database subgraph Database DB1[(Product Table)] DB2[(User Table)] end %% 흐름 연결 BR --> PC1 BR --> PC2 BR --> PC3 BR --> PC4 BR --> PC5 JA --> PC1 JA --> PC3 PC1 --> PS1 --> PR1 --> DB1 PC2 --> PS2 --> PR2 --> DB1 PC3 --> PS3 --> PR3 --> DB1 PC4 --> PS4 --> PR3 --> DB1 PC5 --> PS5 --> PR4 --> DB1
Application Layers
Layer | Key Responsibility | Description |
---|---|---|
Controller | Receives requests, returns responses | Controls the flow of processing by accepting client requests. |
Service | Processes business logic | Handles core domain logic and manages transactions. |
Repository | Accesses data | Performs direct communication with the database (using JPA, MyBatis, etc.). |
@Controller
/ @RestController
- 요청과 응답을 처리
- CRUD
- 조회할때 pw 빼기
Annotation | Description |
---|---|
@Controller | Used in View-based MVC applications. - Typically paired with template engines like Thymeleaf (which is often the default choice)- When a method returns a String , it’s interpreted as a view name to render. |
@RestController | Designed for REST API responses. - Automatically includes @ResponseBody , meaning that whatever a method returns will be directly written into the HTTP response body (e.g., as JSON or XML), rather than being used to resolve a view.- @RestController = @Controller + @ResponseBody |
@ResponseBody
- Java object → JSON, serializes return using Jackson
- Used in methods inside
@Controller
@RequestBody
- JSON → Java object, deserializes using Jackson
- Used on method params for input parsing
@RestController // This implies @Controller + @ResponseBody
@RequestMapping("/hello")
public class HelloController {
@GetMapping // Handles GET requests to /hello
public String sayHello() {
// Returns the string "Hello, World!" directly as the HTTP response body
// Spring (via Jackson, implicitly) converts this to a JSON string if needed by the client.
return "Hello, World!";
}
}
@Service
- The
@Service
annotation explicitly marks a class as a service component. - It’s where you implement your application’s core business logic, such as handling transactions and enforcing domain-specific rules
- Use
@Transactional
annotation. Transactions are very important in backend applications (나중에 깊게 봄)- Uses AOP (Aspect Oriented Programming) internally
- Business logic = Our requirements of the application
- Use
- Depends on
repository
@Service
public class ProductService {
private final ProductRepository productRepository; // Injected by Spring
// Constructor for dependency injection
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// Method containing business logic
public List<Product> findAllProducts() {
return productRepository.findAll(); // Delegates to the repository for data access
}
}
@Repository
- The
@Repository
annotation marks a class as a Data Access Object (DAO), meaning it directly communicates with the database.- This is the only component that directly communicates with the db
- It essentially includes
@Component
internally and provides automatic exception translation. This means database-specific exceptions (like aSQLException
) are converted into Spring’s consistentDataAccessException
hierarchy, making your error handling more uniform - Commonly used with technology like Spring Data JPA (ORM 같은 기술 - 데이터를 객채로 나눔), MyBatis Mapper (SQL Mapper), etc
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
// Spring Data JPA automatically provides basic methods like findById, findAll, save, etc.
}
Dependency Relationships Between Layers
MUST follow a unidirectional dependencies between layers
Controller → Service → Repository
- Things you MUST KEEP
- You must avoid structures where the Repository refers to the Service, or the Service refers to the Controller.
- Such circular dependencies can lead to structural issues and become a source of errors during testing and maintenance.
Layer | Depends On | Dependency Type |
---|---|---|
Controller | Service | Constructor Injection or Field Injection |
Service | Repository | Constructor Injection |
Repository | None (no lower layer) | N/A |
An incorrect dependency direction:
// BAD EXAMPLE: Avoid this!
@Repository
public class ProductRepository {
@Autowired
private ProductService productService; // Prohibited: Do not reference Service from Repository
}
Dependencies Between Services in the Same Layer
- It’s actually common for one Service to depend on another Service.
- Ex) if you’re looking up order details and need information about the customer who placed the order, your
OrderService
might need to callMemberService
@Service
public class OrderService {
private final MemberService memberService; // OrderService depends on MemberService
@Autowired
public OrderService(MemberService memberService) {
this.memberService = memberService;
}
/* Business logic to place an order */
public void placeOrder(Long memberId) {
// 1. Retrieve information for the currently logged-in user
// (ID provided as a parameter)
Member member = memberService.findById(memberId); // Calling MemberService
// 2. Order placement logic...
}
}
- When you’re doing something like this, still keep these rules
- Unidirectional Flow (no circular dependencies, Spring will give u error)
- Clear responsibilities - Don’t let unrelated domains call each other directly—separate logic if needed
- Respect Hierarchy - E.g.,
OrderService → MemberService
is OK, but reverse might indicate poor design.
- But what if we need to make
MemberService
also depend onOrderService
?- If
MemberService
also depends onOrderService
, there would be a circular dependency (순환참조), and Spring will not proceed & give an error. - Things you can do:
- Make
MemberService
depend onOrderRepository
instead- But this is not recommended → tight coupling, hard to test/debug
MemberService
will also have responsibilities ofOrderRepository
, leading to unclear boundary of responsibilities- If possible, a layer should only depend on another domain of the same layer
- But this is not recommended → tight coupling, hard to test/debug
- Make one of those 2 an Event
- Make an intermediary class (explained below)
- Make
- If
Intermediary class
Alternatively, you can design a structure where multiple domains can cooperate through a dedicated component:
/* Example of separating a collaboration-specific component */
@Component
public class OrderProcessManager {
private final OrderService orderService;
private final MemberService memberService;
@Autowired
public OrderProcessManager(OrderService orderService, MemberService memberService) {
this.orderService = orderService;
this.memberService = memberService;
}
public void placeOrderForMember(Long memberId) {
Member member = memberService.findById(memberId);
orderService.createOrder(member);
}
}
- This avoids direct mutual references between the two services by using
OrderProcessManager
- Put the collaborative logic to a separate coordinator.
- The
OrderProcessManager
automatically has its dependencies wired by Spring via constructor injection, and it internally still referencesOrderService
andMemberService
- The key is that the services aren’t directly coupled; they interact through an intermediary (the coordinator). This allows each service to focus solely on its own responsibilities.
Practical Design Tips
- Each layer should only depend on the layer(s) below it.
- For DI (Dependency Injection), primarily use Constructor Injection, implemented with Lombok’s
@RequiredArgsConstructor
or Spring’s@Autowired
. - When writing test code, clearly separating dependencies between layers makes Mocking and unit testing significantly easier.
- Slice Test: Tests a specific layer in isolation (e.g., testing only the
Controller
). Dependent objects are replaced with fake objects (Mocking). - Unit Test: The smallest type of test, verifying that a single method works correctly.
- Slice Test: Tests a specific layer in isolation (e.g., testing only the