본문 바로가기

Today I Learned

DDD Architecture

아직은 수정 중.. 

 

 

DDD (Domain-Driven Design) 아키텍처를 Spring Boot 애플리케이션에 구성하려면,

여러 개의 모듈로 나누어 도메인, 애플리케이션, 인프라, 사용자 인터페이스(UI) 등을 구조화하는 것이 일반적입니다.

 

 

내가 배우고 있는 곳에서 멘토는 아래처럼 구조를 짰는데 폴더가 많으니까 왠지 더 복잡하게 느껴진다.

아직까지는 익숙하지가 않다.

 

멘토의 예시 코드를 봐보니

호출 흐름

presentation controller -> application service -> domain interface UserRepository

 

1. service

domain.service

application.service

 

서비스가 두 개인데

 

1) domain.service

-> 다른 서버에서 가져온 값을 세팅하는 것

-> 다른서버? 서로 다른 도메인을 섞는?

더보기
@Service
public class UserProductDomainService {

    /***
     * TODO: 도메인 서비스 구현 예제 (비즈니스 로직이 아닙니다! 도메인 로직을 구현해야 합니다)
     * User 엔티티에서 Product 엔티티를 불러올 수 없고,
     * Product 엔티티에서 User 엔티티를 불러올 수 없으니까 아래와 같은 도메인 서비스를 구현합니다.
     */
    public void connectUserProduct(User user, Product product) {
        user.changeToManufacture();
        product.updateManufacture(user.getName());
    }
}

 

2) application.service

-> 기존 디비에서 가져오는 것

-> presentation controller 호출 함

더보기
@Service
public class UserService {
    private final UserRepository userRepository;
    private final ProductRepository orderRepository;

    public UserService(UserRepository userRepository, ProductRepository orderRepository) {
        this.userRepository = userRepository;
        this.orderRepository = orderRepository;
    }

    public UserResponse getUser(Long userId) {
        return userRepository.findById(userId).map(UserResponse::of).orElseThrow();
    }

    @Transactional
    public UserResponse createUser(UserDto request) {
        // User 엔티티에 객체 생성에 대한 책임을 부여합니다.
        User user = User.create(
                request.getName(),
                request.getEmail(),
                request.getPhoneNumber()
        );

        userRepository.save(user);

        // Dirty Checking 으로 User 엔티티의 Id가 추가되어 반환됩니다.
        return UserResponse.of(user);
    }

    @Transactional
    public UserResponse simpleUpdateUser(Long userId, SimpleUpdateUserDto request) {
        return userRepository.findById(userId).map(user -> {
            user.update(request.getName(), request.getEmail());
            return UserResponse.of(user);
        }).orElseThrow();
    }

    @Transactional
    public UserResponse updateUser(Long userId, UpdateUserDto request) {
        return userRepository.findById(userId).map(user -> {
            user.update(request.getName(), request.getEmail(), request.getPhoneNumber());
            return UserResponse.of(user);
        }).orElseThrow();
    }
}

 

 

2. repository

아래 사이트를 참고하여 도메인의 repository와 infra repository의 차이가 뭐지? 뭐가 다르지를 정리하려고 한다

 

나의 방식대로 이해하고 적어보면

도메인 repository: interface MemberRepository 

인프라 repository: interface MemberJpaRepository extends JpaRepository<MemberEntity, Long> 

 

그래서 adapter class 에 

도메인의 interface 구현하는 MemberRepositoryImpl를 생성한다.

인프라의 interface MemberJpaRepository를 주입해버리는 것이다.

 

 

도메인에서는 저장하는 방법만

인프라에서는 실제로 어떻게 저장하는지만 집중한다.


도메인은 어댑터 뒤에 어떤 기술을 사용하던 상관없이 데이터를 조작하는 데에 필요한 인터페이스만을 바라보고 협력 함.

repository의 형태에 의존하지 않으면서 서비스의 구현은 일관성을 가진다.

 

하지만 이것도 문제가 있다.

1. 너무 많은 컨버팅 코드

2. 휴먼 에러

3. 구현 기술의 강력한 기능 사용 불가

 

더보기

https://dev-coco.tistory.com/190

 

Service와 Repository를 완전히 분리하기 (with. DDD)

Intro개발 오픈 톡방에서 "Service와 Repository는 완벽히 분리되어야 한다."의 내용이 화두 되었다.즉, "도메인은 특정 기술(인프라)에 의존하지 않고 순수하게 유지되어야 한다."는 말인데,어떻게 하

dev-coco.tistory.com

 

 

위 블로그에서는 도메인이 인프라를 의존하면 안된다고 한다.

인프라가 도메인을 의존 해야한다고 한다.

 

 

나의 방식대로 이해하고 적어보면

도메인 repository: interface MemberRepository 

인프라 repository: interface MemberJpaRepository extends JpaRepository<MemberEntity, Long> 

 

둘다 인터페이스

 

서로 다른 파일이기에 계속해서 수정해줘야 한다.

그래서 중간에 조율해주는 파일을 추가한다고 한다.

 

 

 

그래서 adapter class 에 

도메인의 interface 구현하는 MemberRepositoryImpl를 생성한다.

인프라의 interface MemberJpaRepository를 주입해버리는 것이다.

 

 

1) domain.repository

interface MemberRepository

//TODO: Interface 계층에 존재하는 JpaRepository 를 분리하기 위한 Interface 구현 예제
@Repository
public interface UserRepository {
    List<User> findAll();

    Optional<User> findById(Long id);

    User save(User user);
}

2) application.repository -> JpaRepository 의존

interface MemberJpaRepository extends JpaRepository<MemberEntity, Long> 

 

//TODO: JpaRepository 를 Interface 계층에 둠
public interface UserRepositoryImpl extends JpaRepository<User, Long>, UserRepository {
}

 

 

 

 

 

그래서 나는 어떻게 할 것인가?

아래에 프로젝트 위키에 적어 놓았다.  아직 수정중.. DDD 구조 조사 & 생각 중

 

 

 

이해하는데 도움이 된 그림

 

아래의 그림은 https://incheol-jung.gitbook.io/docs/q-and-a/architecture/ddd 에서 가져옴