이번 글에서는 ArchUnit을 사용하여 의존성 제한을 설정하는 방법에 대해 설명하겠습니다. 이를 통해 아키텍처 규칙을 코드 레벨에서 검증할 수 있습니다.
ArchUnit은 자바 프로젝트에서 아키텍처 규칙을 정의하고 검증할 수 있게 해주는 테스트 라이브러리입니다. 프로젝트의 아키텍처를 코드 레벨에서 강제하고, 의존성 사이의 규칙을 검사하여 코드의 일관성과 유지보수성을 높일 수 있습니다.
의존성
관리를 위해 도입했습니다. 하지만, 의존성 관리 이외의 코드 일관성 관리까지 발전해 나갈 예정입니다.먼저 build.gradle.kts
파일에 ArchUnit 의존성을 추가합니다:
testImplementation("com.tngtech.archunit:archunit-junit5:0.23.1")
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)
}
}
adapters should not access domain
:
adapter
패키지는 domain
패키지를 접근하지 않아야 합니다.adapter
와 domain
간의 의존성 순환을 방지합니다.application should not access adapters
:
application
패키지는 adapter
패키지를 접근하지 않아야 합니다.domain should not access adapters, application
:
domain
패키지는 application
및 service
패키지를 접근하지 않아야 합니다.controllers should be annotated with WebAdapter
:
web
패키지 내의 컨트롤러 클래스는 @WebAdapter
애노테이션을 가져야 합니다.adapters should be annotated with PersistenceAdapter
:
persistence
패키지 내의 어댑터 클래스는 @PersistenceAdapter
애노테이션을 가져야 합니다.이와 같이 ArchUnit을 활용하여 프로젝트의 아키텍처 규칙을 코드 수준에서 검증함으로써, 코드의 일관성과 유지보수성을 향상시킬 수 있습니다.
참고로 이 아키텍쳐의 의존성 제한은 일반적인
Hexagonal architecture
보다 더 엄격하게 제한 되어 있습니다.
그 부분은 바로,adapter
에서domain
을 import하지 못하게 하는 부분 인데요. 보통JpaEntity
내부에toDomain()
이라는 함수를 넣어, 즉각적으로 domain entity에 접근하게 하도록 설계를 하는게 보통입니다.만들면서 배우는 클린 아키텍쳐
라는 저서에도 그런식으로 설계가 되어 있습니다. 하지만 의존성 제한을 극도로 엄격하게 하는 제 프로젝트에서는, adapter의 구현은 절대 domain을 알아서는 안된다고 제한 하고 있습니다. 이 부분 참고 부탁드립니다
다음 포스팅에서는 도메인 계층의 고도화를 진행하고 포스팅 해보려고 합니다.