From 59defeecb14215f4caf1c3f537a2e7326144c867 Mon Sep 17 00:00:00 2001 From: SinnoLn Date: Fri, 26 Jun 2026 00:12:49 +0900 Subject: [PATCH 1/2] test: Enforce DTO and entity placement with ArchUnit --- .../architecture/LayerDependencyTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/test/java/chaeso/zip/server/architecture/LayerDependencyTest.java b/src/test/java/chaeso/zip/server/architecture/LayerDependencyTest.java index 7799b74..1966a2d 100644 --- a/src/test/java/chaeso/zip/server/architecture/LayerDependencyTest.java +++ b/src/test/java/chaeso/zip/server/architecture/LayerDependencyTest.java @@ -8,6 +8,7 @@ import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.library.Architectures; +import jakarta.persistence.Entity; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RestController; @@ -54,4 +55,42 @@ class LayerDependencyTest { .that().areAnnotatedWith(Service.class) .should().resideInAPackage("..application..") .as("@Service 는 application 패키지에만 위치해야 한다"); + + @ArchTest + static final ArchRule 엔티티는_도메인_계층에만_위치한다 = classes() + .that().areAnnotatedWith(Entity.class) + .should().resideInAPackage("..domain..") + .as("@Entity 는 domain 패키지에만 위치해야 한다"); + + @ArchTest + static final ArchRule 표현_계층은_엔티티에_직접_의존하지_않는다 = noClasses() + .that().resideInAPackage("..presentation..") + .should().dependOnClassesThat().areAnnotatedWith(Entity.class) + .as("presentation 은 엔티티를 직접 참조하지 말고 application DTO 를 거쳐야 한다"); + + @ArchTest + static final ArchRule 리포지토리는_도메인_계층에_위치한다 = classes() + .that().haveSimpleNameEndingWith("Repository") + .should().resideInAPackage("..domain..") + .as("*Repository 는 domain 패키지에 위치해야 한다"); + + @ArchTest + static final ArchRule 요청_DTO는_표현_계층에_위치한다 = classes() + .that().haveSimpleNameEndingWith("Request") + .and().resideOutsideOfPackage("..common..") + .should().resideInAPackage("..presentation..") + .as("*Request DTO 는 presentation 패키지에 위치해야 한다"); + + @ArchTest + static final ArchRule 커맨드_DTO는_애플리케이션_계층에_위치한다 = classes() + .that().haveSimpleNameEndingWith("Command") + .should().resideInAPackage("..application..") + .as("*Command DTO 는 application 패키지에 위치해야 한다"); + + @ArchTest + static final ArchRule 응답_DTO는_애플리케이션_계층에_위치한다 = classes() + .that().haveSimpleNameEndingWith("Response") + .and().resideOutsideOfPackage("..common..") + .should().resideInAPackage("..application..") + .as("*Response DTO 는 application 패키지에 위치해야 한다 (공통 응답 래퍼는 common 에 둔다)"); } From 7e22c63c97b853c68bc612e6b1d64fa39dffcc86 Mon Sep 17 00:00:00 2001 From: SinnoLn Date: Fri, 26 Jun 2026 00:13:07 +0900 Subject: [PATCH 2/2] build: Centralize dependencies with Gradle version catalog --- build.gradle | 40 +++++++++++++++++++-------------------- gradle/libs.versions.toml | 24 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/build.gradle b/build.gradle index 6a5952b..2610953 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.5.15' - id 'io.spring.dependency-management' version '1.1.7' - id 'org.asciidoctor.jvm.convert' version '4.0.5' + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.asciidoctor) } group = 'chaeso.zip' @@ -24,23 +24,23 @@ ext { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.16' - implementation 'org.flywaydb:flyway-core' - implementation 'org.flywaydb:flyway-database-postgresql' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'org.postgresql:postgresql' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testRuntimeOnly 'com.h2database:h2' - testCompileOnly 'org.projectlombok:lombok' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - testAnnotationProcessor 'org.projectlombok:lombok' + implementation libs.spring.boot.starter.actuator + implementation libs.spring.boot.starter.data.jpa + implementation libs.spring.boot.starter.web + implementation libs.spring.boot.starter.validation + implementation libs.springdoc.openapi.ui + implementation libs.flyway.core + implementation libs.flyway.postgresql + compileOnly libs.lombok + runtimeOnly libs.postgresql + annotationProcessor libs.lombok + testImplementation libs.spring.boot.starter.test + testImplementation libs.spring.restdocs.mockmvc + testImplementation libs.archunit.junit5 + testRuntimeOnly libs.h2 + testCompileOnly libs.lombok + testRuntimeOnly libs.junit.platform.launcher + testAnnotationProcessor libs.lombok } tasks.named('test') { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..c2c8b85 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,24 @@ +[versions] +springdoc = "2.8.16" +archunit = "1.3.0" + +[libraries] +spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" } +spring-boot-starter-data-jpa = { module = "org.springframework.boot:spring-boot-starter-data-jpa" } +spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" } +spring-boot-starter-validation = { module = "org.springframework.boot:spring-boot-starter-validation" } +spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" } +springdoc-openapi-ui = { module = "org.springdoc:springdoc-openapi-starter-webmvc-ui", version.ref = "springdoc" } +flyway-core = { module = "org.flywaydb:flyway-core" } +flyway-postgresql = { module = "org.flywaydb:flyway-database-postgresql" } +lombok = { module = "org.projectlombok:lombok" } +postgresql = { module = "org.postgresql:postgresql" } +spring-restdocs-mockmvc = { module = "org.springframework.restdocs:spring-restdocs-mockmvc" } +archunit-junit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archunit" } +h2 = { module = "com.h2database:h2" } +junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } + +[plugins] +spring-boot = { id = "org.springframework.boot", version = "3.5.15" } +spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.7" } +asciidoctor = { id = "org.asciidoctor.jvm.convert", version = "4.0.5" } \ No newline at end of file