개발

DDD(Domain-Driven Design)와 Clean Architecture의 원칙을 반영한 이유

kdozlo 2025. 1. 10. 23:31

이번에 데이터베이스를 변경하면서 DDDClean Architecture의 필요성을 느꼈습니다. 

참고: 데이터베이스를 변경한 이유는 아래 포스팅에 있습니다. 

https://kdozlo.tistory.com/74 

 

친구 정보, Redis에서 MySQL로 옮긴 이유

상황현재 사용자의 친구 정보들을 "카카오톡 친구 API"를 통해 받은 뒤, redis에 저장해 놓고 있었다.하지만 "자신의 친구들 중 친한 친구를 표시할 수 있다."라는 기능을 제공하고 있고, 이 정보 또

kdozlo.tistory.com

 

기존 방식의 문제점 

1. DB 엔티티와 도메인 로직의 결합 

  • 기존 방식에서는 DB 엔티티(Friend)에 도메인 로직(예: toggleFavorite)을 포함하여 구현했습니다. 
    • 이로 인해 데이터베이스와 도메인 로직 간의 강결합이 발생하며, 다음과 같은 문제가 생길 수 있습니다: 
      • Redis에서 JPA로 전환할 경우, @RedisHash, @Indexed 등의 어노테이션을 JPA 어노테이션으로 변경해야 합니다. 또한 Redis에서는 ID 값이 friend:consumerId:toConsumerId 형식으로 저장되지만, JPA에서는 다른 방식으로 변경해야 합니다. 
      • 이러한 변경은 단순히 엔티티 수정에 그치지 않고, 서비스 레이어까지 영향을 줄 가능성이 있습니다. 
        • 예: 서비스에서 Redis의 ID 형식에 의존하는 로직을 수정해야 함. 
@Getter
@RedisHash(value = "friend")
public class Friend implements Serializable {

    @Id
    private String id; //friend:consumerId:toConsumerId
    @Indexed
    private Long consumerId;

    @Indexed
    private Long toConsumerId;
    @Indexed
    private Boolean isFavorite;

    @Builder
    private Friend(String id, Long consumerId, Long toConsumerId, Boolean isFavorite) {
        this.id = id;
        this.consumerId = consumerId;
        this.toConsumerId = toConsumerId;
        this.isFavorite = isFavorite;
    }

    public void toggleFavorite() {
        this.isFavorite = !this.isFavorite;
    }

    public static Friend from(Long consumerId, FriendDto friendDto, Long toConsumerId) {
        return Friend.builder()
                .id(consumerId + ":" + toConsumerId)
                .consumerId(consumerId)
                .toConsumerId(toConsumerId)
                .isFavorite(friendDto.getFavorite())
                .build();
    }
}

2. 서비스 로직과 데이터베이스 강결합 

  • FriendService는 직접적으로 FriendRepository를 호출하고, 엔티티(Friend)를 사용해 데이터를 조회 및 수정합니다. 
  • 이로 인해 3가지 문제점이 존재합니다. 
    • 책임 분리 부족: 서비스 로직에서 데이터베이스 세부 사항(쿼리 방식, 저장소 구현 등)을 알아야 하므로, 서비스와 저장소 간의 책임이 명확히 분리되지 않음. 
    • 변경에 취약: 데이터베이스가 변경되거나 저장소의 동작 방식이 수정되면, 서비스 로직도 함께 변경되어야 함. 
    • 테스트 어려움: 서비스 계층의 비즈니스 로직 테스트가 데이터베이스에 의존하게 되어, Mock Repository를 사용한 테스트를 작성하는 데 복잡성이 증가함. 

 

DDD와 클린 아키텍처의 필요성 

1.  DDD (Domain-Driven Design) 

  • 도메인 모델과 DB 엔티티의 분리 
    • 도메인 모델이 데이터베이스 기술에 종속되지 않도록 설계합니다. 
    • 도메인 모델: Friend는 toggleFavorite과 같은 도메인 로직만 포함하며, 데이터 저장 방식에 대한 구체적인 구현은 없습니다. 
    • DB 엔티티: 데이터베이스 저장소에 필요한 어노테이션이나 ID 저장 방식은 별도의 엔티티 객체에서 처리합니다. 

2. 클린 아키텍처 (Clean Architecture) 

  • 의존성 역전 원칙 (DIP, Dependency Inversion Principle) 
    • FriendService는 데이터 저장소(FriendRepository)의 인터페이스에만 의존하며, 저장소의 구현체(FriendRepositoryImpl)는 infrastructure 계층에서 제공합니다. 
    • 따라서 데이터 저장 방식(JPA, Redis, MongoDB 등)을 변경하더라도, 서비스 로직은 영향을 받지 않습니다. 
  • 서비스 레이어 비즈니스 로직의 테스트 가능성 향상 
    • 비즈니스 로직이 외부 의존성(데이터베이스, 외부 API 등)과 분리되므로, 독립적으로 테스트할 수 있습니다. 
    • 서비스 계층 테스트 시 Mock Repository나 Fake Repository 사용하여 테스트를 작성할 수 있습니다. 
    • 데이터베이스가 필요 없는 서비스 로직 테스트가 가능해집니다. 

 

DDD와 클린 아키텍처 도입시 구조 

- controller/
  - FriendController.java
- service/
  - FriendService.java
- domain/
  - model/
    - Friend.java
  - port/
    - FriendRepository.java(interface)
- infrastructure/
  - entity/
    - FriendEntity.java
  - repository/
    - FriendRepositoryImpl.java, FriendJpaRepository.java(interface)

 

추가로 헥사고날 아키텍처까지 적용할 계획이였으나, 설계과정에서 헥사고날 아키텍처까지는 현재 프로젝트에 과하다는 생각이 들었습니다. 

자세한 이야기는 다음 포스팅에서 하겠습니다.