애플리케이션 서비스에서는 영속성 기능을 사용하기 위해서 포트 인터페이스를 호출
영속성 어댑터의 책임
- 입력을 받는다.
- 입력을 데이터베이스 포맷으로 매핑한다.
- 입력을 데이터베이스로 보낸다.
- 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
- 출력을 반환한다.
- 입력 모델은 인터페이스가 정한 도메인 엔티티나 특정 데이터베이스 연산 전용 객체
- 영속성 어댑터는 데이터베이스를 쿼리하거나 변경하는 데 사용할 수 있는 포맷으로 입력 모델을 매핑
- 데이터베이스 응답을 포트에 정의된 출력 모델로 매핑해서 반환
- 출력 모델은 영속성 어댑터가 아니라 애플리케이션 코어에 위치함
포트 인터페이스 나누기
- 일반적으로 특정 엔티티가 필요로 하는 모든 데이터베이스 연산을 하나의 레포지토리 인터페이스에 넣어 두는 것이 일반적인 방법
- 이렇게 하는 경우 각 서비스는 레포지토리 인터페이스의 하나의 메서드만 사용하더라도 하나의 넓은 포트 인터페이스에 의존성을 갖게 됨
- 코드에 불필요한 의존성이 생김
- ISP(Interface Segregation Principle)은 이 문제의 답을 제시
- 클라이언트가 오로지 자신이 필요로 하는 메서드만 알면 되도록 넓은 인터페이스를 특화한 인터페이스로 분리해야 함
- 영속성 포트를 usecase 단위로 분리

- 또한 바운디드 컨택스트 간의 경계를 명확하게 구분하고 싶다면 각 바운디드 컨텍스트가 영속성 어댑터를 하나씩 가지고 있어야 한다.

스프링 JPA 예제
- Domain Entity 클래스 정의
- Domain Entity와 매핑되는 JPA Entity 클래스 정의
- JPA와 관련된 어노테이션(@Entity, @GeneratedValue, @Id, ..) 등이 적용됨
- 스프링 데이터를 사용해 레포지토리 인터페이스 생성
- 영속성 어댑터 구현
- 위에서 정의한 스프링 데이터 레포지토리 인터페이스를 Injection 받음
- outgoing port를 implements
@RequiredArgsConstructor
@PersistenceAdapter
class AccountPersistenceAdapter implements
LoadAccountPort,
UpdateAccountStatePort {
private final SpringDataAccountRepository accountRepository;
private final ActivityRepository activityRepository;
private final AccountMapper accountMapper;
@Override
public Account loadAccount(
AccountId accountId,
LocalDateTime baselineDate) {
AccountJpaEntity account =
accountRepository.findById(accountId.getValue())
.orElseThrow(EntityNotFoundException::new);
List<ActivityJpaEntity> activities =
activityRepository.findByOwnerSince(
accountId.getValue(),
baselineDate);
Long withdrawalBalance = orZero(activityRepository
.getWithdrawalBalanceUntil(
accountId.getValue(),
baselineDate));
Long depositBalance = orZero(activityRepository
.getDepositBalanceUntil(
accountId.getValue(),
baselineDate));
return accountMapper.mapToDomainEntity(
account,
activities,
withdrawalBalance,
depositBalance);
}
private Long orZero(Long value){
return value == null ? 0L : value;
}
@Override
public void updateActivities(Account account) {
for (Activity activity : account.getActivityWindow().getActivities()) {
if (activity.getId() == null) {
activityRepository.save(accountMapper.mapToJpaEntity(activity));
}
}
}
}
- 위의 구조는 도메인 모델과 데이터베이스 모델 간의 양방향 매핑이 존재
데이터베이스 트랜잭션은 어떻게 해야 할까?
- 트랜잭션은 하나의 특정한 유스케이스에 대해서 일어나는 모든 쓰기 작업에 걸쳐 있어야 함
- 영속성 어댑터는 어떤 데이터베이스 연산이 같은 유스케이스에 포함되는지를 알 수 없음
- 따라서 트랜잭션을 열고 닫는 것은, 영속성 어댑터를 호출하는 서비스에 위임해야 함
'Architecture(아키텍처)' 카테고리의 다른 글
| [만들면서 배우는 클린 아키텍처] 08. 경계 간 매핑하기 (1) | 2024.07.20 |
|---|---|
| [만들면서 배우는 클린 아키텍처] 07. 아키텍처 요소 테스트하기 (0) | 2024.07.20 |
| [만들면서 배우는 클린 아키텍처] 05. 웹 어댑터 구현하기 (0) | 2024.07.20 |
| [만들면서 배우는 클린 아키텍처] 04. 유스케이스 구현하기 (0) | 2024.07.20 |
| [만들면서 배우는 클린 아키텍처] 03. 코드 구성하기 (0) | 2024.07.20 |