이 글은 헥사고날 아키텍쳐의 이해를 높이려는 목표로 작성했다.
이전 헥사고날 포스트
이전 헥사고날 포스트2
헥사고날 아키텍처(Hexagonal Architecture)는 포트와 어댑터 아키텍처(Ports and Adapters Architecture)라고도 불리며, 애플리케이션의 핵심 비즈니스 로직을 외부 의존성으로부터 격리하는 아키텍처 패턴입니다.
1. 기술적 부채 감소
- 외부 의존성 변경이 용이
- 비즈니스 로직 보호
2. 테스트 용이성
- 도메인 로직 단독 테스트 가능
- 외부 시스템 모킹 용이
3. 유지보수성 향상
- 관심사 분리
- 코드 재사용성 증가
기술 독립성
테스트 용이성
유연성과 확장성
초기 개발 비용
학습 곡선
application/
├── auth/
├── image/
├── keyword/
├── novel/
└── user/세부 역할
port/in: 외부에서 애플리케이션을 사용하기 위한 인터페이스 정의
CreateNovelUseCase, SocialLoginUsecase)port/out: 애플리케이션이 외부 시스템과 통신하기 위한 인터페이스
NovelPort, AIOutputPort, ImageManagementPortservice: 실제 비즈니스 로직 구현
domain/
├── common/
├── novel/
├── reaction/
└── user/
세부 역할
model: 핵심 비즈니스 엔티티 정의
Novel, User, NovelContentenums: 도메인 관련 열거형 정의
Genre, Status, Stepexception: 도메인 특화 예외 정의
infra/
├── client/
├── persistence/
└── security/
세부 역할
client: 외부 서비스 통합
persistence: 데이터 영속성 처리
security: 보안 관련 구성
bootstrap/
├── auth/
├── common/
├── image/
├── keyword/
├── novel/
└── user/
세부 역할
api: API 스펙 정의
controller: HTTP 요청 처리
dto: 데이터 전송 객체 정의
common: 공통 설정 및 유틸리티
각 레이어의 의존성 방향:
Bootstrap → Application → Domain ← Infrastructure
// domain/novel/model/Novel.kt
data class Novel (
val id: String = DomainId.generate().id,
val genre: String,
val status: String = Status.ONGOING.stringStatus,
// ...
) {
// 개선 필요: 비즈니스 로직이 부족함
// 추가 필요한 예시:
fun canAddContent(): Boolean
fun validateContent(content: String)
fun changeStatus(newStatus: Status)
}
잘된 점
개선 필요
@Service
class NovelService(
private val novelPort: NovelPort,
private val novelContentPort: NovelContentPort,
// ...
) : CreateNovelUseCase, RetrieveNovelUseCae {
@Transactional
override fun createNovelWithContent(userId: String, command: CreateNovelUseCase.NovelWithContentCommand): CreateNovelUseCase.Response {
// ...
}
}
잘된 점
개선 필요
@Component
class NovelAdapter(
private val novelRepository: NovelRepository
) : NovelPort {
override fun retrieveById(novelId: String): Novel? {
// 개선 필요: Optional 처리
return novelRepository.findById(novelId).get().toDomain();
}
}
잘된 점
개선 필요
@RestController
class NovelController(
private val createNovelUseCase: CreateNovelUseCase,
private val retrieveNovelUseCae: RetrieveNovelUseCae
) : NovelApi {
override fun createNovel(userId: String, request: CreateNovelRequest): CreateNovelResponse {
// ...
}
}
잘된 점
개선 필요
data class Novel(
// 현재
val status: String = Status.ONGOING.stringStatus,
// 개선
val status: Status = Status.ONGOING,
// 비즈니스 로직 추가
fun validateNovelCreation() {
require(title.length in 1..100) { "제목은 1-100자 사이여야 합니다" }
// ...
}
)
interface NovelQueryService {
fun findNovelsByWriter(writerId: String): List<NovelReadModel>
}
interface NovelCommandService {
fun createNovel(command: CreateNovelCommand): NovelId
}
data class NovelCreatedEvent(
val novelId: String,
val writerId: String,
val timestamp: LocalDateTime
)
interface DomainEventPublisher {
fun publish(event: DomainEvent)
}
포트와 어댑터 패턴
모듈화
성능 최적화
현재 구현은 헥사고날 아키텍처의 기본 원칙을 따르고 있으나, 도메인 주도 설계의 핵심 개념들을 더 적극적으로 도입할 필요가 있다고 느껴졌다. 특히 도메인 모델의 강화와 CQRS 패턴 도입을 통해 더 견고한 아키텍처로 발전시킬 수 있을 것 같다.
이러한 개선을 통해 비즈니스 로직의 응집도를 높이고, 시스템의 확장성과 유지보수성을 더욱 향상시킬 예정이다.