들어가며

소프트웨어 개발에서 흔히 접하는 계층형 아키텍처는 코드의 가독성, 유지보수성, 확장성을 높이는 데 필수적인 설계 방식ㄴ입니다. 특히, Spring Boot와 같은 프레임워크에서는 이 계층 구조를 통해 **책임 분리(Separation of Concerns)**를 명확히 하여, 코드를 체계적이고 효율적으로 관리할 수 있습니다.

이 글에서는 게시판 애플리케이션을 예로 들어, Controller, Service, Domain, Repository 계층이 각각 어떤 역할을 담당하며, 데이터가 어떻게 흐르는지 하나씩 풀어보겠습니다.


왜 계층형 아키텍처가 중요한가?

  1. 책임 분리: 각 계층이 고유한 역할을 수행하므로, 코드의 가독성과 유지보수성이 높아집니다.
  2. 유연한 확장성: 새로운 기능 추가나 변경이 필요한 경우, 다른 계층에 최소한의 영향을 주며 확장할 수 있습니다.
  3. 테스트 용이성: 특정 계층만 따로 테스트할 수 있어, 코드의 품질을 높이는 데 유리합니다.
  4. 코드 재사용성: 공통 로직을 적절히 계층에 배치하면, 중복 코드를 줄이고 재사용성을 높일 수 있습니다.

게시판 예제: 글 저장과 수정 흐름

이해하기 쉽게, 여기서는 게시판에서 새 글 저장글 수정을 처리하는 과정을 통해 각 계층의 역할과 데이터 흐름을 살펴보겠습니다.


1. Controller 계층: 사용자 요청의 입구

1.1 Controller의 역할

1.2 Controller에서의 데이터 흐름

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에서의 핵심


2. Service 계층: 비즈니스 로직의 중심

2.1 Service의 역할

2.2 Service에서의 데이터 흐름

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에서의 핵심


3. Domain 계층: 핵심 데이터와 규칙

3.1 Domain의 역할

3.2 Domain에서의 데이터 흐름

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에서의 핵심


4. Repository 계층: 데이터베이스와의 연결

4.1 Repository의 역할

4.2 Repository에서의 데이터 흐름

4.4 Repository 코드 예제

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    // Spring Data JPA가 기본적인 CRUD 메서드를 제공
}

4.5 Repository에서의 핵심


다음 단계로

1부에서는 계층별 역할과 데이터 흐름에 대해 설명했습니다. 2부에서는 이를 기반으로 데이터 흐름을 시각적으로 정리하고, 각 계층에서 발생할 수 있는 문제와 이를 해결하는 방법에 대해 알아보겠습니다.

다음 단계를 진행해줘라고 요청하시면 이어서 작성하겠습니다!