(3) - Hexagonal 아키텍처 기반 설계 - (2)

S-J LEE·2024년 6월 4일
0

OD

목록 보기
3/5

의존성 제한을 통한 아키텍처 규칙 강화

이번 글에서는 ArchUnit을 사용하여 의존성 제한을 설정하는 방법에 대해 설명하겠습니다. 이를 통해 아키텍처 규칙을 코드 레벨에서 검증할 수 있습니다.


ArchUnit 이란?

ArchUnit은 자바 프로젝트에서 아키텍처 규칙을 정의하고 검증할 수 있게 해주는 테스트 라이브러리입니다. 프로젝트의 아키텍처를 코드 레벨에서 강제하고, 의존성 사이의 규칙을 검사하여 코드의 일관성과 유지보수성을 높일 수 있습니다.

ArchUnit의 주요 기능

  1. 아키텍처 규칙 정의: 클래스 간의 의존성, 패키지 구조, 클래스 명명 규칙 등 다양한 아키텍처 규칙을 정의할 수 있습니다.
  2. 테스트를 통한 검증: 정의된 규칙을 테스트 코드에서 검증하여 아키텍처가 지켜지고 있는지 확인할 수 있습니다.
  3. 자동화된 검증: CI/CD 파이프라인에 ArchUnit 테스트를 포함시켜 지속적으로 아키텍처 규칙을 검증할 수 있습니다.

왜 ArchUnit을 사용하는가?

  1. 유지보수성 향상: 명확한 아키텍처 규칙을 정의하고 이를 검증함으로써, 코드의 일관성을 유지할 수 있습니다.
  2. 의존성 관리: 의존성 사이의 규칙을 검사하여 의존성 순환이나 불필요한 의존성을 방지할 수 있습니다.
  3. 팀 협업 강화: 팀원들이 공통된 아키텍처 규칙을 따르도록 하여 협업을 원활하게 할 수 있습니다.

이 중 저는 의존성 관리를 위해 도입했습니다. 하지만, 의존성 관리 이외의 코드 일관성 관리까지 발전해 나갈 예정입니다.


예제 코드

프로젝트에 ArchUnit 추가하기

먼저 build.gradle.kts 파일에 ArchUnit 의존성을 추가합니다:

testImplementation("com.tngtech.archunit:archunit-junit5:0.23.1")

ArchUnit 테스트 코드 작성

package com.example.stock

import com.example.stock.common.PersistenceAdapter
import com.example.stock.common.WebAdapter
import com.tngtech.archunit.core.domain.JavaClasses
import com.tngtech.archunit.core.importer.ClassFileImporter
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses
import org.junit.jupiter.api.Test

class ArchUnitTest {

    private val importedClasses: JavaClasses = ClassFileImporter().importPackages("com.example.stock")

    @Test
    fun `adapters should not access domain`() {
        val rule = noClasses()
            .that().resideInAnyPackage("..adapter..")
            .should().accessClassesThat().resideInAnyPackage("..domain..")
        rule.check(importedClasses)
    }

    @Test
    fun `application should not access adapters`() {
        val rule = noClasses()
            .that().resideInAnyPackage("..application..")
            .should().accessClassesThat().resideInAnyPackage("..adapter..")
        rule.check(importedClasses)
    }

    @Test
    fun `domain should not access adapters, application`() {
        val rule = noClasses()
            .that().resideInAnyPackage("..domain..")
            .should().accessClassesThat().resideInAnyPackage("..application..", "..service..")
        rule.check(importedClasses)
    }

    @Test
    fun `controllers should be annotated with WebAdapter`() {
        val rule = classes()
            .that().resideInAPackage("..adapter..in..web..")
            .and().haveSimpleNameEndingWith("Controller")
            .should().beAnnotatedWith(WebAdapter::class.java)
        rule.check(importedClasses)
    }

    @Test
    fun `adapters should be annotated with PersistenceAdapter`() {
        val rule = classes()
            .that().resideInAPackage("..adapter..out..persistence..")
            .and().haveSimpleNameEndingWith("Adapter")
            .should().beAnnotatedWith(PersistenceAdapter::class.java)
        rule.check(importedClasses)
    }
}

설명

  1. adapters should not access domain:

    • adapter 패키지는 domain 패키지를 접근하지 않아야 합니다.
    • 이를 통해 adapterdomain 간의 의존성 순환을 방지합니다.
  2. application should not access adapters:

    • application 패키지는 adapter 패키지를 접근하지 않아야 합니다.
    • 이를 통해 비즈니스 로직이 외부 기술에 의존하지 않도록 합니다.
  3. domain should not access adapters, application:

    • domain 패키지는 applicationservice 패키지를 접근하지 않아야 합니다.
    • 도메인 모델이 비즈니스 로직이나 외부 시스템에 의존하지 않도록 합니다.
  4. controllers should be annotated with WebAdapter:

    • web 패키지 내의 컨트롤러 클래스는 @WebAdapter 애노테이션을 가져야 합니다.
    • 이를 통해 특정 규칙을 강제하고, 코드의 일관성을 유지합니다.
  5. adapters should be annotated with PersistenceAdapter:

    • persistence 패키지 내의 어댑터 클래스는 @PersistenceAdapter 애노테이션을 가져야 합니다.
    • 이를 통해 데이터베이스와 상호작용하는 어댑터의 명확한 식별이 가능합니다.

이와 같이 ArchUnit을 활용하여 프로젝트의 아키텍처 규칙을 코드 수준에서 검증함으로써, 코드의 일관성과 유지보수성을 향상시킬 수 있습니다.


특이사항

참고로 이 아키텍쳐의 의존성 제한은 일반적인 Hexagonal architecture 보다 더 엄격하게 제한 되어 있습니다.
그 부분은 바로, adapter에서 domain을 import하지 못하게 하는 부분 인데요. 보통 JpaEntity 내부에 toDomain() 이라는 함수를 넣어, 즉각적으로 domain entity에 접근하게 하도록 설계를 하는게 보통입니다. 만들면서 배우는 클린 아키텍쳐라는 저서에도 그런식으로 설계가 되어 있습니다. 하지만 의존성 제한을 극도로 엄격하게 하는 제 프로젝트에서는, adapter의 구현은 절대 domain을 알아서는 안된다고 제한 하고 있습니다. 이 부분 참고 부탁드립니다


다음 포스팅에서는 도메인 계층의 고도화를 진행하고 포스팅 해보려고 합니다.

profile
MSA 와 관련된 기반 기술에 관심이 많습니다.

0개의 댓글