소프트웨어 개발에서 가장 일반적으로 널리 사용되고 있는 아키텍처입니다.
각 계층은 어플리케이션 내에서의 특정 역할과 관심사 별로 구분되고, 특정 계층의 구성요소는 해당 계층에 관련된 기능만 수행합니다.
서버를 설계할 때 Controller, Service, Repository, Domain과 같이 계층을 나누는 것은 소프트웨어 아키텍처에서 매주 중요하지만, 만약 계층을 나누지 않고 모든 로직을 한 곳에 몰아넣는다면....
(가독성, 코드 복잡성, 장애 대응이 어려움, 확작성 부족, 보안 취약, 유지보수의 어려움......)
등등의 많은 문제점이 발생합니다. 그렇기 때문에 항상 계층을 나누고 각 계층별 관심사와 책임을 나눠주는 것입니다.

각 계층은 명확한 역할을 가지고 있으며, 이를 올바르게 분리하면 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다.
애플리케이션의 진입점으로, 사용자 요청을 처리하고 응답을 반환하는 역할을 담당합니다.
단일 책임 원칙(SRP) :
Controller는 HTTP 요청 및 응답 처리에만 집중해야 합니다. 비즈니스 로직이나 데이터베이스 작업은 다른 계층(Service, Repository)에서 처리하도록 분리합니다.
얇은 컨트롤러(Thin Controller) :
Controller는 가능한 한 가벼워야 하며, 모든 비즈니스 로직을 Service 계층으로 위임해야 합니다.
public class PostController {
private final PostService postService = new PostService();
// 게시물 생성 API
public void createPost(String title, String content) {
try {
postService.createPost(title, content);
System.out.println("Post created successfully: " + title);
} catch (IllegalArgumentException e) {
System.out.println("Error creating post: " + e.getMessage());
}
}
}
애플리케이션의 핵심 비즈니스 로직을 처리하는 계층입니다.
단일 책임 원칙(SRP) :
Service는 비즈니스 로직에만 집중하며, 데이터 저장/조회 작업은 Repository로 위임합니다.
응집도(Coherence) :
Service는 특정 도메인과 관련된 비즈니스 로직만 포함해야 합니다.
public class PostService {
private final PostRepository postRepository = new PostRepository();
// 게시물 저장 비즈니스 로직
public void createPost(String title, String content) {
if (title == null || title.isEmpty()) {
throw new IllegalArgumentException("Title cannot be empty");
}
if (content == null || content.isEmpty()) {
throw new IllegalArgumentException("Content cannot be empty");
}
Post post = new Post(title, content); // post 객체 생성
postRepository.save(post);
}
}
데이터 접근 및 영속성을 관리하는 계층입니다.
단일 책임 원칙(SRP) :
Repository는 데이터 접근에만 집중하며, 비즈니스 로직은 Service 계층에서 처리합니다.
영속성 무관성(Persistence Ignorance) :
Repository는 도메인 모델과 데이터베이스 간의 세부 사항을 캡슐화하여 도메인 모델이 데이터베이스 기술에 의존하지 않도록 합니다.
import java.util.ArrayList;
import java.util.List;
public class PostRepository {
private List<Post> posts = new ArrayList<>(); // 메모리 기반 저장소
// 게시물 저장
public void save(Post post) {
post.setId((long) (posts.size() + 1)); // ID 자동 생성
posts.add(post);
}
}
애플리케이션의 핵심 데이터를 표현하며, 객체 지향 설계의 중심이 되는 계층입니다.
풍부한 도메인 모델(Rich Domain Model) :
Domain 객체는 데이터를 단순히 저장하는 것뿐만 아니라, 해당 데이터를 다루는 메서드와 규칙도 포함해야 합니다.
캡슐화(Encapsulation) :
상태와 행동을 내부적으로 숨기고 필요한 인터페이스만 외부에 제공하여 객체를 보호합니다.
public class Post {
private Long id; // 게시물 ID
private String title; // 게시물 제목
private String content; // 게시물 내용
// 생성자 및 getter/setter
}
}
🪡 유지보수성:
코드가 잘 조직화되어 변경이 쉬워지고, 특정 계층만 수정해도 다른 계층에 영향을 주지 않습니다.
🥊 테스트 용이성:
각 계층별로 독립적인 테스트가 가능해집니다.
🛒 재사용성:
Service와 Repository 계층의 로직은 다른 컨텍스트에서도 재사용할 수 있습니다.
📢 확장성:
새로운 기능 추가 시 기존 코드를 최소한으로 수정하면서 확장이 가능합니다.
책임 분리... 생각보다 어렵다.....