Spring

빈약한 도메인 모델 vs 풍성한 도메인 모델

99duuk 2024. 12. 19. 11:34

빈약한 도메인 모델 (Anemic Domain Model)

  • 특징
    • VO(또는 엔티티): 데이터를 담는 역할만 함. (getter/setter 중심)
    • 비즈니스 로직은 서비스 계층에 존재.
  • 장점
    • 객체가 단순하고 테스트하기 쉬움.
    • 서비스 계층에서 로직을 한눈에 파악 가능.
  • 단점
    • 도메인 객체가 비즈니스와 분리됨 → 로직 응집도가 낮아짐.
    • 도메인 객체의 사용 의도가 불명확할 수 있음.

풍성한 도메인 모델 (Rich Domain Model)

  • 특징
    • 도메인 객체가 데이터를 담고 비즈니스 로직도 포함.
    • 서비스 계층은 도메인의 비즈니스 메서드를 호출하여 작업 위임.
  • 장점
    • 도메인 객체가 비즈니스 로직을 캡슐화 → 높은 응집도.
    • 도메인 중심 설계(DDD)에 적합.
  • 단점
    • 설계 복잡도 상승.
    • 객체 간 의존성이 증가할 가능성 있음.

VO와 DTO의 역할 구분

  1. VO (Value Object)
    • 역할: 비즈니스 로직과 데이터를 캡슐화.
    • MyBatis에서 DB 결과 매핑받고, 서비스 계층에서 비즈니스 작업 수행.
    • 생성자 또는 팩토리 메서드를 통해 데이터 무결성 유지.
    • 비즈니스 규칙 포함 가능 (ex: 유효성 검사, 상태 변경 메서드).
  2. DTO (Data Transfer Object)
    • 역할: 데이터 전송 목적.
    • 외부 인터페이스(View/Controller/API 응답)와 통신 시 사용.
    • VO의 데이터를 받아 필요한 필드만 선택적으로 포함.
    • VO로부터 생성되며 비즈니스 로직은 포함하지 않음.

빈약한 도메인 모델 예시

  1. VO (데이터만 포함)
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Example {
    private Long id;
    private String name;
    private String status;

    // 데이터만 포함 (getter/setter 제공)
}

 

2. Service (비즈니스 로직 포함)

 

@Service
public class ExampleService {
    @Autowired
    private ExampleMapper exampleMapper;

    public void activateExample(Long id) {
        // VO 조회
        Example example = exampleMapper.findById(id);

        // 비즈니스 로직 처리
        if (!"INACTIVE".equals(example.getStatus())) {
            throw new IllegalStateException("Only inactive items can be activated.");
        }
        example.setStatus("ACTIVE");

        // 업데이트 쿼리 호출
        exampleMapper.update(example);
    }
}

 

특징

  • VO는 데이터를 담는 역할만 수행(getter/setter).
  • 모든 비즈니스 로직이 서비스 계층에 집중.
  • VO는 단순한 데이터 컨테이너로 사용됨.

풍성한 도메인 모델 예시

  1. VO (비즈니스 로직 포함)
import lombok.Getter;

@Getter
public class Example {
    private Long id;
    private String name;
    private String status;

    // 비즈니스 로직 포함
    public void activate() {
        if (!"INACTIVE".equals(this.status)) {
            throw new IllegalStateException("Only inactive items can be activated.");
        }
        this.status = "ACTIVE";
    }
}

 

2. DTO (데이터 전송 용도)

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ExampleDto {
    private Long id;
    private String name;

    // VO로부터 DTO 생성
    public ExampleDto(Example vo) {
        this.id = vo.getId();
        this.name = vo.getName();
    }
}

 

3. Service (비즈니스 로직 위임)

@Service
public class ExampleService {
    @Autowired
    private ExampleMapper exampleMapper;

    public ExampleDto activateExample(Long id) {
        // VO 조회
        Example example = exampleMapper.findById(id);

        // 비즈니스 로직을 VO로 위임
        example.activate();

        // 업데이트 쿼리 호출
        exampleMapper.update(example);

        // DTO로 변환 후 반환
        return new ExampleDto(example);
    }
}

 

  • VO가 데이터를 캡슐화하고 비즈니스 로직을 포함.
  • 서비스 계층은 VO의 메서드를 호출하여 로직을 위임.
  • DTO는 클라이언트에 데이터를 전달하는 역할만 수행.

 


 

비즈니스 로직 분리와 역할 명확화

  • 빈약한 도메인 모델은 단순하지만 응집도가 낮음.
  • 풍성한 도메인 모델은 VO의 책임이 명확하고 재사용성 높음.

장점

  1. 책임 분리
    • VO: 비즈니스 로직 캡슐화.
    • DTO: 데이터 전달에만 집중.
  2. 안전한 데이터 전송
    • DTO를 통해 불필요한 정보 노출 방지.
    • VO는 내부 비즈니스 용도로만 활용.
  3. 유지보수 용이성
    • VO와 DTO가 분리되어 변경 사항이 국한됨.
    • 서비스 계층 로직 간소화.