[OOP] Controller, Service, Repository, Domain의 역할을 알아보자

구본탁·2025년 4월 10일

OOP

목록 보기
2/2

🤔 계층형 아키텍처??

소프트웨어 개발에서 가장 일반적으로 널리 사용되고 있는 아키텍처입니다.
각 계층은 어플리케이션 내에서의 특정 역할과 관심사 별로 구분되고, 특정 계층의 구성요소는 해당 계층에 관련된 기능만 수행합니다.

❓ 계층을 나누지 않고 서버를 구축하면 어떤 문제가 발생할까?

서버를 설계할 때 Controller, Service, Repository, Domain과 같이 계층을 나누는 것은 소프트웨어 아키텍처에서 매주 중요하지만, 만약 계층을 나누지 않고 모든 로직을 한 곳에 몰아넣는다면....

(가독성, 코드 복잡성, 장애 대응이 어려움, 확작성 부족, 보안 취약, 유지보수의 어려움......)

등등의 많은 문제점이 발생합니다. 그렇기 때문에 항상 계층을 나누고 각 계층별 관심사와 책임을 나눠주는 것입니다.


💪 Controller, Service, Repository, Domain의 역할을 알아보자

각 계층은 명확한 역할을 가지고 있으며, 이를 올바르게 분리하면 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다.

1. Controller

애플리케이션의 진입점으로, 사용자 요청을 처리하고 응답을 반환하는 역할을 담당합니다.

  • 사용자로부터 입력을 받고 이를 처리할 적절한 서비스 메서드를 호출
  • 요청 데이터를 검증하고 필요한 경우 변환
  • 클라이언트에게 응답을 반환

🎯 객체 지향적 관점

  1. 단일 책임 원칙(SRP) :
    Controller는 HTTP 요청 및 응답 처리에만 집중해야 합니다. 비즈니스 로직이나 데이터베이스 작업은 다른 계층(Service, Repository)에서 처리하도록 분리합니다.

  2. 얇은 컨트롤러(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());
        }
    }
}

2. Service

애플리케이션의 핵심 비즈니스 로직을 처리하는 계층입니다.

  • 비즈니스 로직 구현.
  • 여러 Repository를 조합하여 데이터를 처리.
  • 트랜잭션 관리 및 데이터 흐름 제어.
  • 도메인 객체와 DTO 간 변환 작업 수행.

🎯 객체 지향적 관점

  1. 단일 책임 원칙(SRP) :
    Service는 비즈니스 로직에만 집중하며, 데이터 저장/조회 작업은 Repository로 위임합니다.

  2. 응집도(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);
    }
}

3. Repository

데이터 접근 및 영속성을 관리하는 계층입니다.

  • 데이터베이스와 상호작용(CRUD 작업 수행).
  • 데이터를 저장하거나 조회하는 로직 캡슐화.
  • 도메인 모델과 데이터베이스 간 매핑 작업 수행.

🎯 객체 지향적 관점

  1. 단일 책임 원칙(SRP) :
    Repository는 데이터 접근에만 집중하며, 비즈니스 로직은 Service 계층에서 처리합니다.

  2. 영속성 무관성(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);
    }
}

4. Domain

애플리케이션의 핵심 데이터를 표현하며, 객체 지향 설계의 중심이 되는 계층입니다.

  • 애플리케이션의 핵심 엔티티를 정의.
  • 비즈니스 규칙과 제약 조건을 포함.
  • 상태와 행동을 캡슐화하여 객체 지향적인 설계를 지원.

🎯 객체 지향적 관점

  1. 풍부한 도메인 모델(Rich Domain Model) :
    Domain 객체는 데이터를 단순히 저장하는 것뿐만 아니라, 해당 데이터를 다루는 메서드와 규칙도 포함해야 합니다.

  2. 캡슐화(Encapsulation) :
    상태와 행동을 내부적으로 숨기고 필요한 인터페이스만 외부에 제공하여 객체를 보호합니다.

🌵 예시 코드

public class Post {
    private Long id;       // 게시물 ID
    private String title;  // 게시물 제목
    private String content; // 게시물 내용

    // 생성자 및 getter/setter
    }
}

✨ 역할 분리의 중요성

  • 🪡 유지보수성:
    코드가 잘 조직화되어 변경이 쉬워지고, 특정 계층만 수정해도 다른 계층에 영향을 주지 않습니다.

  • 🥊 테스트 용이성:
    각 계층별로 독립적인 테스트가 가능해집니다.

  • 🛒 재사용성:
    Service와 Repository 계층의 로직은 다른 컨텍스트에서도 재사용할 수 있습니다.

  • 📢 확장성:
    새로운 기능 추가 시 기존 코드를 최소한으로 수정하면서 확장이 가능합니다.


🌱 마지막 한마디

책임 분리... 생각보다 어렵다.....

profile
성공의 반대는 실패가 아니라 포기다

0개의 댓글