-
https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture/
-
의존성을 없애려고하니 계층이 계속 생기는데..?
-
제한을 어떻게 나눌것인가?
-
이것이 도메인에 대한 지식 수준 차이일까?
-
Service/Component/Repository/Controller와 같은 계층으로 나눌수는 없을까?
-
이것으로 나누면 결국 계층형 아키텍처이지 않을까?
-
-
-
익숙하지 않는 '포트-어댑터' 아키텍처를 팀원 모두 학습하는 비용은?
-
도메인별로 git repository 자체를 나누고, msa로 가면서 잘게 나눠진 계층형 아키텍처(처럼 보이는) 구조가 더 빠른 생산성을 가지진 않을까?
-
'포트-어댑터' 아키텍처는 팀에 종속되는 아키텍처 구조가 아니므로 학습하는 것 자체가 개발자 역량에 도움이 되지 않을까
-
설정보다 관례(CoC, convention over configuration) (ref)
-
-
-
헥사고날 아키텍처를 잘못 이해한다면 core를 만들고, 외부 인터페이스만 별도로 만든다고 생각할수도 있을 것 같음
-
아키텍처를 다시보면 도메인에 접근/도메인으로부터 반환되는 모든 것을 별도 객체로 만들어야 함(DB, Web 모두 동일한 추상화단계)
-
-
특정 API에 비지니스는 어디에 둬야하는가? 컨트롤러? 웹용 서비스를 따로?
-
ex) 생성 API는 이미 있을 경우 에러에 기존 리소스를 포함해야 할 경우
-
-
캐시를 두는 이유?
-
로컬 캐시는 왜 둘까?
-
각 비지니스의 연관관계를 끊고자 클라이언트나 서비스를 로컬에서 캐싱할 수도 있다고 봄
-
다만 무분별한 추상화하기 않고, 개발자가 인지하고 캐싱할 수 있도록 분리해야함. 즉 entity, dao, repository 단에서의 캐싱이 아닌 서비스단에 있어야 함.
-
-
대략적인 유스케이스 구조
@startuml component adapter component usecase component "persistence\nadapter" as persistence component "outgoing\nadapter" as outgoing adapter -> usecase: (1) 입력 usecase -> usecase: (2) 비지니스 규칙 검증 usecase --> persistence: (3) 상태 저장 usecase --> outgoing: (3) outgoing ..> usecase: (4) output usecase .> adapter: (5) output 변환 후 반환 @enduml
@startuml hide empty field hide empty method package application.port.in { interface SendMoneyUseCase class SendMoneyCommand } package application.port.out { interface UpdateAccountStatePort } package application.service { class SendMoneyService } package domain { class Account } SendMoneyService -left-|> SendMoneyUseCase SendMoneyUseCase -down-> SendMoneyCommand SendMoneyService -down-> Account SendMoneyService -> UpdateAccountStatePort @enduml
// package application.port.in
// 인커밍 포트 인터페이스
interface SendMoneyUserCase {
fun sendMoney(command: SendMoneyCommand): Boolean
}
// package application.port.in
// 입력 모델(input model)
data class SendMoneyCommand(
val sourceAccountId: AccountId,
val targetAccountId: AccountId,
val money: Money,
) {
init {
require(money > 0)
}
}
class AccountId
data class Money(
val value: BigDecimal,
) {
operator fun compareTo(i: BigDecimal): Int = value.compareTo(i)
operator fun compareTo(i: Int): Int = compareTo(i.toBigDecimal())
}
interface LoadAccountPort // 아웃고잉 포트 인터페이스
class AccountLock
class UpdateAccountStatePort // 영속성 어댑터?
class SendMoneyService(
private val loadAccountPort: LoadAccountPort,
private val accountLock: AccountLock,
private val updateAccountStatePort: UpdateAccountStatePort,
) : SendMoneyUserCase {
override fun sendMoney(command: SendMoneyCommand): Boolean {
TODO("비즈니스 규칙 검증")
TODO("모델 상태 조작")
TODO("출력 값 반환")
}
}