들어가며
소프트웨어 개발에서 흔히 접하는 계층형 아키텍처는 코드의 가독성, 유지보수성, 확장성을 높이는 데 필수적인 설계 방식ㄴ입니다. 특히, Spring Boot와 같은 프레임워크에서는 이 계층 구조를 통해 **책임 분리(Separation of Concerns)**를 명확히 하여, 코드를 체계적이고 효율적으로 관리할 수 있습니다.
이 글에서는 게시판 애플리케이션을 예로 들어, Controller, Service, Domain, Repository 계층이 각각 어떤 역할을 담당하며, 데이터가 어떻게 흐르는지 하나씩 풀어보겠습니다.
왜 계층형 아키텍처가 중요한가?
- 책임 분리: 각 계층이 고유한 역할을 수행하므로, 코드의 가독성과 유지보수성이 높아집니다.
- 유연한 확장성: 새로운 기능 추가나 변경이 필요한 경우, 다른 계층에 최소한의 영향을 주며 확장할 수 있습니다.
- 테스트 용이성: 특정 계층만 따로 테스트할 수 있어, 코드의 품질을 높이는 데 유리합니다.
- 코드 재사용성: 공통 로직을 적절히 계층에 배치하면, 중복 코드를 줄이고 재사용성을 높일 수 있습니다.
게시판 예제: 글 저장과 수정 흐름
이해하기 쉽게, 여기서는 게시판에서 새 글 저장과 글 수정을 처리하는 과정을 통해 각 계층의 역할과 데이터 흐름을 살펴보겠습니다.
1. Controller 계층: 사용자 요청의 입구
1.1 Controller의 역할
- 사용자로부터 요청을 받는 계층입니다.
- HTTP 요청을 처리하고, 필요한 데이터를 Service 계층에 전달합니다.
- Service에서 처리한 결과를 사용자에게 반환합니다.
1.2 Controller에서의 데이터 흐름
-
입력
- 사용자가 HTTP 요청(
POST /posts,PUT /posts/{id})을 보냅니다. - 요청으로 데이터를 보낼때는 보통 JSON 데이터(예: 제목, 내용)가 포함됩니다.
- 사용자가 HTTP 요청(
-
처리
- 요청 URL과 메서드에 따라 적절한 라우팅을 결정합니다.
- 요청 데이터를 검증한 후, Service 계층에 처리 요청을 보냅니다.
-
리턴(결과)
- Service 계층에서 처리한 결과를 JSON 데이터로 반환합니다.
1.3 Controller 코드 예제
@RestController
@RequestMapping("/posts")
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
// 새 글 저장
@PostMapping
public ResponseEntity<PostDto> savePost(@RequestBody PostDto postDto) {
PostDto savedPost = postService.savePost(postDto); // 서비스 호출
return ResponseEntity.ok(savedPost); // 저장된 게시글 반환
}
// 기존 글 수정
@PutMapping("/{id}")
public ResponseEntity<PostDto> updatePost(@PathVariable Long id, @RequestBody PostDto postDto) {
PostDto updatedPost = postService.updatePost(id, postDto); // 서비스 호출
return ResponseEntity.ok(updatedPost); // 수정된 게시글 반환
}
}
1.4 Controller에서의 핵심
- 요청 라우팅: URL과 HTTP 메서드에 따라 적절한 메서드 실행.
- 데이터 전달: Service 계층에 데이터를 전달하여 처리 요청.
2. Service 계층: 비즈니스 로직의 중심
2.1 Service의 역할
- 비즈니스 로직을 담당하는 계층입니다.
- Controller에서 받은 요청을 처리하고, 필요한 데이터를 Repository 계층과 연동합니다.
- 데이터 검증, 규칙 적용, 도메인 객체 생성 등 주요 로직을 처리합니다.
2.2 Service에서의 데이터 흐름
-
입력
- Controller로부터 전달받은 사용자의 요청 데이터(예: 제목, 내용).
-
처리
- 비즈니스 로직을 처리합니다(예: 제목 검증, 데이터 수정 등).
- 필요 시, Repository 계층을 호출하여 데이터를 저장하거나 수정합니다.
-
리턴(결과)
- 저장된 데이터 객체 또는 수정된 데이터 객체를 Controller에 반환합니다.
2.3 Service 코드 예제
@Service
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
// 새 글 저장
public PostDto savePost(PostDto postDto) {
// 제목 검증 로직
if (postDto.getTitle() == null || postDto.getTitle().isEmpty()) {
throw new IllegalArgumentException("제목은 필수입니다.");
}
// 도메인 객체 생성
Post post = new Post(postDto.getTitle(), postDto.getContent());
// 데이터 저장 요청
Post savedPost = postRepository.save(post);
// 결과 반환
return new PostDto(savedPost.getId(), savedPost.getTitle(), savedPost.getContent());
}
// 기존 글 수정
public PostDto updatePost(Long id, PostDto postDto) {
// 기존 게시글 조회
Post post = postRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글을 찾을 수 없습니다."));
// 데이터 수정
post.update(postDto.getTitle(), postDto.getContent());
// 수정된 데이터 저장
Post updatedPost = postRepository.save(post);
// 결과 반환
return new PostDto(updatedPost.getId(), updatedPost.getTitle(), updatedPost.getContent());
}
}
2.4 Service에서의 핵심
- 비즈니스 로직 구현: 요청 데이터를 검증하고, 도메인 객체를 활용해 규칙 적용.
- Repository 호출: 데이터 저장 및 수정 작업을 Repository 계층에 위임.
3. Domain 계층: 핵심 데이터와 규칙
3.1 Domain의 역할
- 애플리케이션의 핵심 데이터 구조를 담당합니다.
- 데이터를 정의하고, 데이터와 관련된 규칙(예: 제목 길이 제한)을 적용합니다.
- 데이터베이스 테이블과 매핑되는 구조를 정의합니다.
- JPA와 함께 사용하면, 이 도메인 객체는 테이블과 자동으로 매핑됩니다.
- 참고로, - 데이터베이스 작업(저장, 수정, 삭제)은 Repository 계층이 담당하고, Domain 계층은 데이터를 표현하고 조작하는 데 집중합니다.
3.2 Domain에서의 데이터 흐름
-
입력
- Service 계층에서 전달받은 데이터(예: 제목, 내용).
-
처리
- 도메인 객체를 생성하고, 데이터 규칙을 적용합니다(예: 제목이 100자 초과인지 확인).
-
리턴(결과)
- 처리된 도메인 객체를 Repository 계층으로 전달.
3.3 Domain 코드 예제
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
public Post(String title, String content) {
if (title.length() > 100) {
throw new IllegalArgumentException("제목은 100자 이내여야 합니다.");
}
this.title = title;
this.content = content;
}
// 데이터 수정 메서드
public void update(String title, String content) {
this.title = title;
this.content = content;
}
// Getter/Setter
}
3.4 Domain에서의 핵심
- 데이터 규칙 적용: 데이터를 정의하고, 유효성 검사를 수행.
- 핵심 데이터 구조 제공: Service 및 Repository 계층에서 사용할 객체를 제공합니다.
4. Repository 계층: 데이터베이스와의 연결
4.1 Repository의 역할
- 데이터베이스와 직접적으로 상호작용하는 계층입니다.
- 데이터를 저장, 수정, 삭제, 검색하는 작업을 수행합니다.
- 데이터 저장방식을 쉽게 변경할 수 있도록 인터페이스로 정의합니다.
4.2 Repository에서의 데이터 흐름
-
입력
- Service 계층에서 전달받은 도메인 객체.
-
처리
- 데이터베이스에 데이터를 저장하거나 수정(INSERT 또는 UPDATE 쿼리 실행).
- 필요한 데이터를 조회(SELECT 쿼리 실행).
-
리턴(결과)
- 데이터베이스에서 처리된 결과를 Service 계층에 반환.
4.4 Repository 코드 예제
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// Spring Data JPA가 기본적인 CRUD 메서드를 제공
}
4.5 Repository에서의 핵심
- 데이터 저장/조회 작업 담당: 데이터베이스와의 직접적인 상호작용.
- Service 계층에 데이터 제공: Service 계층이 비즈니스 로직을 처리할 수 있도록 데이터를 제공합니다.
다음 단계로
1부에서는 계층별 역할과 데이터 흐름에 대해 설명했습니다. 2부에서는 이를 기반으로 데이터 흐름을 시각적으로 정리하고, 각 계층에서 발생할 수 있는 문제와 이를 해결하는 방법에 대해 알아보겠습니다.
다음 단계를 진행해줘라고 요청하시면 이어서 작성하겠습니다!