일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- web
- 개발서적
- FP
- 계층형아키텍처
- 유지보수
- template
- 조영호
- Java
- DDD
- 만들면서배우는클린아키텍처
- Thymeleaf
- Kotlin
- 개발방법론
- Boot Legacy 차이점
- TDD
- 클린아키텍처
- 책스터디
- 아키텍처
- 이펙티브코틀린
- 코틀린
- 도메인 주도 개발 시작하기
- 스터디
- 함수형프로그래밍
- GrokkingFunctionalProgramming
- 테스트
- 추상화 설계
- 테스트주도개발
- 객체지향의사실과오해
- Spring
- 헥사고날아키텍처
- Today
- Total
김동형수 개발기
만들면서 배우는 클린 아키텍처 - 06 정리 본문
영속성 어댑터 구현하기
전통적인 계층형 아키텍처는 영속성에 의존하게 되면서 '데이터베이스 주도 설계'가 되는데, 이러한 의존성을 역전시키기 위해 영속성 계층을 어플리케이션 계층의 플러그인으로 만드는 방법을 살펴본다.
의존성 역전
애플리케이션 서비스에서 영속성 기능을 사용하기 위해서 인터페이스를 호출하고 실제 데이터베이스와 통신할 책임을 가진 클래스는 영속성 어댑터 의해 구현된다.
포트는 애플리케이션 서비스와 영속성 계층간 의존성을 없애기 위한 간접 계층이다.
영속성 계층 코드를 변경하는 중 버그가 생기면 애플리케이션 코어가 제 기능을 하지 못한다. 하지만 중간계층인 포트가 기대하는 역할을 한다면 영속성 계층 코드에 버그가 생기더라도 애플리케이션 코어에는 영향이 없을 것이다.
영속성 어댑터의 책임
- 입력을 받는다.
- 입력을 데이터베이스 포멧으로 매핑한다.
- 입력을 데이터베이스로 보낸다.
- 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
- 출력을 반환한다.
- 포트 인터페이스를 통해 입력을 받는다. 입력 모델은 인터페이스가 지정한 도메인 엔티티나 특정 데이터베이스 연산 전용 객체가 된다.
- 데이터베이스 쿼리하거나 변경하는데 사용할 수 있는 포맷으로 입력 모델을 매핑한다. 일반적으로 자바에서 사용하는 JPA 엔티티로 매핑할 것이다.
- ORM 프레임워크, 데이터베이스와 통신하기 위한 어떤 기술, SQL 구문에 매핑, 파일로 직렬화한 뒤 데이터 읽기 등 방법에 구애받지 않는다.
- 입력모델이 애플리케이션 코어에 있기 때문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다.
- 데이터베이스에 쿼리를 날리고 쿼리 결과를 받아온다.
- 데이터베이스 응답 포트에 정의된 출력 모델로 매핑해서 반환한다.
포트 인터페이스 나누기
특정 엔티티가 필요하는 모든 데이터베이스 연산을 하나의 리포지토리 인터페이스에 넣어두는게 일반적인 방법이다.
하나의 리포지토리 인터페이스를 사용하게 되면 하나의 메서드를 사용하더라도 포트 인터페이스 '넓은' 의존성을 갖게 된다. 불필요한 의존성이 생긴다는 뜻이다.
기존에는 엔티티 1개에 대응하는 1개의 리포지토리 인터페이스를 생성했었다.
서비스 클래스 입장에서 몰라도 되는 메서드의 의존성이 생기므로 '넓은' 의존성이 있는 코드를 양산해내고 있는 것 같다. 반성한다.
불필요한 의존성은 코드파악과 테스트를 어렵게 만든다.
예시로 여러개의 메서드가 있는 인터페이스를 모킹할 때 하나의 메서드만 모킹하고 나머지 메서드는 실제 사용되는 구현체의 코드를 복사했다고 가정한다. 그럼 이때 모킹된 구현체에 기대하는 내용이 호출하는 메서드마다 다를 수 있어서 테스트에 혼란을 줄 수 있다.
에러가 발생한다고 하면 해당 메서드에 찾아가서 모킹 여부를 확인하고 안되어있다면 모킹이 이뤄져야 하므로 많은 시간이 소요될 것 같다.
유지보수하는 시간이 길어진다? -> 공수 증가 -> 업무 효율 감소!!!
로버트 C. 마틴은 이런 표현을 했다.
필요없는 화물을 운반하는 무언가에 의존하고 있으면 예상하지 못했던 문제가 생길 수 있다.
인터페이스 분리 원칙 : 클라이언트가 오로지 자신이 필요로 하는 메서드만 알면 되도록 넓은 인터페이스를 특화된 인터페이스로 분리해야하는 원칙
인터페이스 분리 원칙을 적용한다면 그림 6.2가 6.3으로 변한다.
하나의 데이터베이스 호출에 하나의 포트의존성을 갖는다. 보통 포트 인터페이스에 하나의 메서드만 존재한다. 포트의 이름으로 포트의 역할을 잘 표현하고 있다. 모킹할때도 인터페이스에 하나의 메서드만 존재하니 고민할 필요가 없을 것이다.
코드의 응집성을 고려한다면 1 인터페이스 1 메소드를 적용하지 못할 수 있다.
영속성 어댑터 나누기
1. 애그리거트당 하나의 영속성 어댑터
애그리거트 : 불변식을 만족해서 하나의 단위로 취급될 수 있는 연관 객체의 모음
그림 상으로는 애플리케이션 서비스 당 1개의 어댑터로 보인다.
2. 바운디드 컨텍스트 영속성 어댑터
'바운디드 컨텍스트'는 같은 경계선상에 영속성 어댑터를 1개 (혹은 1개 이상) 가지고 있다.
스프링 데이터 JPA 예제
Account 클래스는 getter, setter만 가진 간단한 데이터 클래스가 아니며 최대한 불변성을 유지하려 한다.
모든 변경은 메서드를 통해서 일어나므로 유효하지 않은 인스턴스가 생성될 순 없다.
Account의 JPA Entity 클래스
Activity의 JPA Entity 클래스
계좌 아이디가 ID로 되어있지만, 차후에는 변경될 수 있다.
@ManyToOne, @OneToMany를 사용해서 관계를 맺을 수 있지만 쿼리에 사이드 이펙트가 생길 수 있기 때문에 제외가 되었다.
기본적인 CRUD와 커스텀 쿼리를 제공하는 리포지토리 인터페이스 생성를 위해 스프링 데이터를 사용한다.
스프링 부트에선 위 리포지토리들을 자동으로 찾고, 스프링 데이터는 데이터베이스와 통신하는 위의 인터페이스들의 구현체를 제공한다.
영속성 어댑터는 애플리케이션에 필요한 LoadAccountPort와 UpdateAccountStatePort라는 2개의 포트를 구현했다.
데이터베이스에서 계좌를 가져오기 위한 AccountRepository, 계좌의 활동내역을 조회하기 위한 ActivityRepository
유효한 도메인을 생성하기 위해서는 ActivityWindow와 활동직전 잔고가 필요하다.
이 모든 데이터를 Account 도메인 엔티티에 매핑하고 반환한다.
Account의 ActivityWindow에서 Activity목록을 순회하며 ID가 있는지 확인한다. 없다면 새로운 활동이라 판단해서 ActivityRepository를 사용해서 저장해야한다.
앞에 설명한 시나리오에서는 양방향 매핑이 존재한다. 매핑없이 엔티티만으로 그대로 저장하면 할 수 있지만 '매핑하지 않기' 전략도 있다.
@ManyToOne 관계를 설정하면 성능 측면에서 이득이 있지만, 예제는 항상 데이터의 일부만 가져오기를 바라기 때문에 '매핑하지 않기' 전략을 사용하는 것 같다.
'풍부한 도메인 모델'을 생성한다면 도메인 모델과 영속성 모델을 매핑하는 것이 좋다.
데이터베이스 트랜잭션은 어떻게 해야 할까?
트랜잭션은 하나의 특정한 유스케이스에 대해서 일어나는 모든 쓰기 작업에 걸쳐 있어야한다. 그래야 중간에 예외가 발생하더라도 작업 전체가 롤백이 된다.
영속성 계층에서는 어떤 유스케이스인지 알지 못하므로
도메인 계층이 영속성 계층보다 상위이므로 영속성 계층에서 도메인 계층에 대한 정보를 알지 못한다.
이 책임은 영속성 어댑터 호출을 관장하는 서비스에 위임해야한다.
@Transactional 어노테이션을 사용하지 않으려면 AspectJ 같은 도구를 사용해서 AOP로 트랜젝션 경계를 코드에 위빙 할 수 있다.
위빙 : AOP에서 일반적으로 사용하는 용어로, 런타임이나 컴파일 타임에 관점을 코드에 연결하는 것
유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?
- 플러그인 처럼 동작하는 영속성 어댑터를 만들면 도메인 코드가 영속성과 분리되어 풍부한 도메인 모델을 만들 수 있다.
- 포트 인터페이스를 분리하면 포트마다 다른 방식으로 구현할 수 있는 유현함이 생긴다. 특정 ORM에 종속되지 않는 영속성 기술을 사용할 수도 있다.
- 포트 작성 규칙만 잘 지켜진다면, 영속성 계층 전체를 교체해도 애플리케이션에 영향이 가지 않을 수있다.
'책 스터디 > [완료] 만들면서 배우는 클린아키텍처' 카테고리의 다른 글
만들면서 배우는 클린 아키텍처 - 08 정리 (0) | 2022.04.15 |
---|---|
만들면서 배우는 클린아키텍처 - 07 정리 (0) | 2022.04.14 |
만들면서 배우는 클린 아키텍처 - 05 정리 (0) | 2022.04.12 |
만들면서 배우는 클린 아키텍처 - 04 정리 (0) | 2022.04.11 |
만들면서 배우는 클린 아키텍처 - 03 정리 (0) | 2022.04.08 |