[만들면서 배우는 클린 아키텍처] 6. 영속성 어댑터 구현하기

2022. 2. 1. 19:11· Book
목차
  1. 의존성 역전
  2. 영속성 어댑터의 책임
  3. 포트 인터페이스 나누기
  4. 영속성 어댑터 나누기
  5. 스프링 데이터 JPA 예제
  6. 출처
728x90

6. 영속성 어댑터 구현하기

의존성 역전

image

코어의 서비스가 영속성 어댑터에 접근하기 위해 포트를 사용한다. 애플리케이션에 의해 호출될 뿐, 애플리케이션을 호출하지 않는다. 여기서 포트는 사실상 애플리케이션 서비스와 영속성 코드 사이의 간접적인 계증이다. 자연스럽게 런타임에도 의존성을 애플리케이션 코어에서 영속성 어댑터로 향한다.

영속성 어댑터의 책임

  1. 입력을 받는다.
  2. 입력을 데이터베이스 포맷으로 매핑한다.
  3. 입력을 데이터베이스로 보낸다.
  4. 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
  5. 출력을 반환한다.

핵심은 영속성 어댑터의 입력 모델이 영속성 어댑터 내부에 있는 것이 아니라 애플리케이션 코어에 있기 떄문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다는 것이다.

포트 인터페이스 나누기

하나의 아웃고잉 포트 인터페이스에 모든 데이터베이스 연산을 모아두면 모든 서비스가 실제로는 필요하지 않은 메서드에 의존하게 된다. 인터페이스 분리 원칙(Interface Segregation Principle, ISP)은 이 문제의 답을 제시한다. 이 원칙은 클라이언트가 오로지 자신이 필요로 하는 메서드만 알면 되도록 넓은 인터페이스를 특화된 인터페이스로 분리해야 한다고 설명한다. 인터페이스 분리 원칙을 적용하면 불필요한 의존성을 제거하고 기존 의존성을 눈에 더 잘 띄게 만들 수 있다.

영속성 어댑터 나누기

하나의 애그리거트당 하나의 영속성 어댑터를 만들어서 여러 개의 영속성 어댑터를 만들수 수도 있다. 도메인 코드는 영속성 포트에 의해 정의된 명시를 어떤 클래스가 충족시키는지에 관심이 없다. 모든 포트가 구현돼 있기만 한다면 영속성 계층에서 하고 싶은 어떤 작업이든 해도 된다. 애그리거트당 하나의 영속성 어댑터 접근 방식 또한 나중에 여러 개의 바운디드 컨텍스트의 영속성 요구사항을 분리하기 위한 좋은 토대가 된다. 바운디드 컨텍스트 간의 경계를 명확하게 구분하고 싶다면 각 바운디드 컨텍스트가 영속성 어댑터(들)을 하나씩 가지고 있어야 한다.

스프링 데이터 JPA 예제

package com.book.cleanarchitecture.buckpal.account.adapter.out.persistence;

import javax.persistence.*;

@Entity
@Table(name = "accounts")
class AccountJpaEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    protected AccountJpaEntity() {
    }

    public Long getId() {
        return id;
    }
}
package com.book.cleanarchitecture.buckpal.account.adapter.out.persistence;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "activities")
public class ActivityJpaEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private LocalDateTime timestamp;

    @Column
    private Long ownerAccountId;

    @Column
    private Long sourceAccountId;

    @Column
    private Long targetAccountId;

    @Column
    private Long amount;

    protected ActivityJpaEntity() {
    }

    public ActivityJpaEntity(Long id, LocalDateTime timestamp, Long ownerAccountId, Long sourceAccountId, Long targetAccountId, Long amount) {
        this.id = id;
        this.timestamp = timestamp;
        this.ownerAccountId = ownerAccountId;
        this.sourceAccountId = sourceAccountId;
        this.targetAccountId = targetAccountId;
        this.amount = amount;
    }

    public Long getId() {
        return id;
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }

    public Long getOwnerAccountId() {
        return ownerAccountId;
    }

    public Long getSourceAccountId() {
        return sourceAccountId;
    }

    public Long getTargetAccountId() {
        return targetAccountId;
    }

    public Long getAmount() {
        return amount;
    }
}
package com.book.cleanarchitecture.buckpal.account.adapter.out.persistence;

import org.springframework.data.jpa.repository.JpaRepository;

interface SpringDataAccountRepository extends JpaRepository<AccountJpaEntity, Long> {
}
package com.book.cleanarchitecture.buckpal.account.adapter.out.persistence;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;
import java.util.List;

interface ActivityRepository extends JpaRepository<ActivityJpaEntity, Long> {

    @Query("SELECT activites FROM ActivityJpaEntity activites " +
            "WHERE activites.ownerAccountId = :ownerAccountId " +
            "AND activites.timestamp >= :since")
    List<ActivityJpaEntity> findByOwnerSince(@Param("ownerAccountId") Long ownerAccountId, @Param("since") LocalDateTime since);

    @Query("SELECT SUM(activites.amount) FROM ActivityJpaEntity activites " +
            "WHERE activites.targetAccountId = :accountId " +
            "AND activites.ownerAccountId = :accountId " +
            "AND activites.timestamp < :until")
    Long getDepositBalanceUntil(@Param("accountId") Long accountId, @Param("until") LocalDateTime until);

    @Query("SELECT SUM(activites.amount) FROM ActivityJpaEntity activites " +
            "WHERE activites.sourceAccountId = :accountId " +
            "AND activites.ownerAccountId = :accountId " +
            "AND activites.timestamp < :until")
    Long getWithdrawalBalanceUntil(@Param("accountId") Long accountId, @Param("until") LocalDateTime until);
}
package com.book.cleanarchitecture.buckpal.account.adapter.out.persistence;

import com.book.cleanarchitecture.buckpal.account.application.port.out.LoadAccountPort;
import com.book.cleanarchitecture.buckpal.account.application.port.out.UpdateAccountStatePort;
import com.book.cleanarchitecture.buckpal.account.domain.Account;
import com.book.cleanarchitecture.buckpal.account.domain.Activity;
import com.book.cleanarchitecture.buckpal.account.domain.vo.AccountId;
import com.book.cleanarchitecture.buckpal.shared.PersistenceAdapter;

import javax.persistence.EntityNotFoundException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

@PersistenceAdapter
public class AccountPersistenceAdapter implements LoadAccountPort, UpdateAccountStatePort {

    private final SpringDataAccountRepository accountRepository;
    private final ActivityRepository activityRepository;
    private final AccountMapper accountMapper;

    public AccountPersistenceAdapter(SpringDataAccountRepository accountRepository, ActivityRepository activityRepository, AccountMapper accountMapper) {
        this.accountRepository = accountRepository;
        this.activityRepository = activityRepository;
        this.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);
    }

    @Override
    public void updateActivities(Account account) {
        List<Activity> activities = account.getActivities();

        activities.stream()
                .filter(activity -> Objects.isNull(activity.getId()))
                .forEach(activity -> activityRepository.save(accountMapper.mapToJpaEntity(activity)));
    }

    private Long orZero(Long value) {
        return value == null ? 0L : value;
    }
}

출처

  • 만들면서 배우는 클린 아키텍처 - 자바 코드로 구현하는 클린 웹 애플리케이션
728x90
728x90
저작자표시 비영리

'Book' 카테고리의 다른 글

[만들면서 배우는 클린 아키텍처] 8. 경계 간 매핑하기  (0) 2022.02.02
[만들면서 배우는 클린 아키텍처] 7. 아키텍처 요소 테스트하기  (0) 2022.02.01
[만들면서 배우는 클린아키텍처] 5. 웹 어댑터 구현하기  (0) 2022.01.30
[만들면서 배우는 클린 아키텍처] 4. 유스케이스 구현하기  (2) 2022.01.30
[만들면서 배우는 클린 아키텍처] 3. 코드 구성하기  (0) 2022.01.29
  1. 의존성 역전
  2. 영속성 어댑터의 책임
  3. 포트 인터페이스 나누기
  4. 영속성 어댑터 나누기
  5. 스프링 데이터 JPA 예제
  6. 출처
'Book' 카테고리의 다른 글
  • [만들면서 배우는 클린 아키텍처] 8. 경계 간 매핑하기
  • [만들면서 배우는 클린 아키텍처] 7. 아키텍처 요소 테스트하기
  • [만들면서 배우는 클린아키텍처] 5. 웹 어댑터 구현하기
  • [만들면서 배우는 클린 아키텍처] 4. 유스케이스 구현하기
Seyun(Marco)
Seyun(Marco)
개발, PO, PM, 기획과 관련된 서비스 관련 업무에 관심이 있습니다. 또한 성장,교육과 관련된 주제에 대해서도 관심이 많습니다. 티타임을 좋아하는 사람으로써, 혹시 티타임에 관심이 있으신 분은 ksy90101@gmail.com로 메일 보내주시면 감사하겠습니다 :) 과제를 제공해주고 피드백을 주는 서비스를 오픈하였습니다. 관심있으신 분들은 https://jobskill.notion.site/ 해당 사이트를 참고해주세요.
Enthusiastically, Steady, Slowly개발, PO, PM, 기획과 관련된 서비스 관련 업무에 관심이 있습니다. 또한 성장,교육과 관련된 주제에 대해서도 관심이 많습니다. 티타임을 좋아하는 사람으로써, 혹시 티타임에 관심이 있으신 분은 ksy90101@gmail.com로 메일 보내주시면 감사하겠습니다 :) 과제를 제공해주고 피드백을 주는 서비스를 오픈하였습니다. 관심있으신 분들은 https://jobskill.notion.site/ 해당 사이트를 참고해주세요.
250x250
Seyun(Marco)
Enthusiastically, Steady, Slowly
Seyun(Marco)
전체
오늘
어제
  • 분류 전체보기 (258)
    • Common (31)
      • ComputerScience (10)
      • Web (9)
      • Git (1)
      • 세미나 (8)
      • Testing (3)
    • Backend (129)
      • Java (35)
      • Kotlin (3)
      • Spring (26)
      • Ruby (11)
      • RubyOnRails (17)
      • Python (1)
      • Django (3)
      • Infra (3)
      • DataBase (9)
      • SQL (21)
    • Frontend (30)
      • HTML&CSS (1)
      • JavaScript (16)
      • TypeScript (1)
      • Vue (5)
      • Nuxt.js (3)
      • React (4)
    • Book (30)
      • 이펙티브 자바 3판 (9)
    • Lecture (26)
      • [인프런] Vue.js 시작하기 - Age of .. (14)
      • [Tacademy] JPA 프로그래밍 기본기 다지.. (8)
    • 회고 (7)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • RESTful
  • Nuxt3
  • SCSS
  • JavaScriprt
  • 멱등성
  • IBM Plex Sans
  • System.out.printlin
  • JAR index
  • java
  • 우아콘
  • locale
  • REST API
  • system.out.println
  • MVC
  • rubyonrails
  • Finalization
  • 개발문화
  • ruby
  • scanner
  • JUnit

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
Seyun(Marco)
[만들면서 배우는 클린 아키텍처] 6. 영속성 어댑터 구현하기
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.