Skip to content
메모장
Go back

Port/Adapter 인터페이스, 외부와의 연결

개요

회원탈퇴 기능을 만들면서 인프라 컴포넌트 두 개를 거의 동시에 설계했다.

둘 다 “infra에 있는 기술적 컴포넌트”라는 점은 같은데 결과가 반대다. 이 글은 그 판단 기준을 정리한다.


1. 인터페이스 생략 - TokenEncryptor

TokenEncryptor(KMS 암복호화)를 쓰는 곳은 두 곳이다.

AppleTokenCapturingAuthorizedClientRepository (infra/configuration)
  -> tokenEncryptor.encrypt(refreshToken)

AppleUnlinkAdapter (infra/external)
  -> tokenEncryptor.decrypt(credential.encryptedRefreshToken)

둘 다 같은 infra 레이어다.

DDD 레이어드 아키텍처에서 인터페이스(포트)가 필요한 근본 이유는 “app이 infra의 구체적인 구현을 몰라야 한다”는 방향성 때문인데, 여기는 애초에 app이 끼어들 자리가 없다.

infra가 infra를 직접 참조하는 건 레이어 위반이 아니다.

// infra/external/KmsEncryptor.kt — 인터페이스 없음
@Component
@ConditionalOnProperty(name = ["aws.kms.key-id"])
class KmsEncryptor(
    private val kmsClient: KmsClient,
    private val kmsProperties: KmsProperties,
) {
    fun encrypt(plainText: String): String { ... }
    fun decrypt(cipherText: String): String { ... }
}

2. 인터페이스가 생성 - SocialUnlinkRunnerPort

반대로 소셜 unlink를 비동기로 실행하는 컴포넌트는 app 레이어(WithdrawService)가 직접 호출한다.

@Service
class WithdrawService(
    private val unlinkRunner: SocialUnlinkRunnerPort,   // 인터페이스
    ...
) : WithdrawUseCase {
    override fun execute(command: WithdrawUser) {
        ...
        unlinkRunner.unlinkAsync(user.provider, user.providerId)
        ...
    }
}

WithdrawService가 구현체(SocialUnlinkRunnerAdapter, 내부적으로 @Async + SocialUnlinkResolver + WebClient 어댑터들을 조합)를 직접 알게 되면, app 레이어가 infra의 실행 방식(스레드풀, 어떤 provider 어댑터를 쓰는지)에 종속된다.

그래서 여기는 명확하게 app/port에 인터페이스를 두고, infra 구현체는 그 뒤에 숨겼다.

// app/port/SocialUnlinkRunnerPort.kt
interface SocialUnlinkRunnerPort {
    fun unlinkAsync(provider: OAuthProvider, providerId: String)
}

// infra/external/SocialUnlinkRunnerAdapter.kt
@Component
class SocialUnlinkRunnerAdapter(
    private val resolver: SocialUnlinkResolver,
) : SocialUnlinkRunnerPort {
    @Async
    override fun unlinkAsync(provider: OAuthProvider, providerId: String) { ... }
}

판단 기준

컴포넌트호출하는 쪽인터페이스 필요 여부
TokenEncryptor/KmsEncryptorinfra ← infra불필요 (레이어 경계 없음, 테스트 편의만 남음)
SocialUnlinkRunnerPortapp ← infra필요 (app이 infra 구현을 몰라야 함)
SocialUnlinkResolver (수정 전)app ← infra필요했는데 놓쳤던 사례

기준은 결국 하나다


Share this post:

Previous Post
FK 없는 스키마 설계 — 데이터 무결성을 애플리케이션에 넘긴 이유
Next Post
@Transactional 함정