๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

ArchUnit์œผ๋กœ ์•„ํ‚คํ…์ฒ˜ ๊ฒ€์‚ฌํ•˜๊ธฐ

by ์•ˆ์ฃผํ˜• 2023. 10. 28.

๊ฐœ์š”

ArchUnit์€ Java ์ฝ”๋“œ์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ฒ€์‚ฌํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ„๊ฒฐํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ArchUnit์€ Java์˜ ๊ธฐ๋ณธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ, ์ฃผ์–ด์ง„ Java ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๋ชจ๋“  ํด๋ž˜์Šค์˜ ๊ตฌ์กฐ๋ฅผ ํ•ด์„ํ•จ์œผ๋กœ์จ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด ์ค๋‹ˆ๋‹ค.

  • ํŒจํ‚ค์ง€ ๋ฐ ํด๋ž˜์Šค ์˜์กด์„ฑ ๊ฒ€์‚ฌ: ํŒจํ‚ค์ง€์™€ ํด๋ž˜์Šค ๊ฐ„์˜ ์˜์กด ๊ด€๊ณ„๋ฅผ ๋ถ„์„ํ•˜๊ณ , ๊ฒฉ๋ฆฌ๋œ ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•˜๋Š”์ง€ ํ™•์ธ
  • ์ƒ์† ๊ด€๊ณ„ ๋ฐ ์ˆœํ™˜ ์ฐธ์กฐ ๊ฒ€์‚ฌ: ํด๋ž˜์Šค ๊ฐ„์˜ ์ƒ์† ๊ตฌ์กฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ์—†๋Š”์ง€ ๊ฒ€์‚ฌ
  • ๋ ˆ์ด์–ด ์•„ํ‚คํ…์ฒ˜ ๊ฒ€์‚ฌ: ๋ ˆ์ด์–ด ๊ฐ„์˜ ์˜์กด์„ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ, ๋ช…ํ™•ํ•˜๊ณ  ๊ฒฌ๊ณ ํ•œ ๋ ˆ์ด์–ด ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•˜๋Š”์ง€ ํ™•์ธ
  • ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜ ๊ฒ€์‚ฌ: ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ ์ฝ”๋”ฉ ๊ทœ์น™์„ ๊ฒ€์‚ฌํ•˜์—ฌ, ์ผ๊ด€๋œ ์ฝ”๋”ฉ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•˜๋Š”์ง€ ํ™•์ธ

์ „์ฒด ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

 

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

ArchUnit์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ณต์žกํ•œ ์ธํ”„๋ผ ์„ค์ •์ด๋‚˜ ์ƒˆ๋กœ์šด ์–ธ์–ด ํ•™์Šต์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์•„๋ž˜์˜ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

dependencies {

    //archunit
    testImplementation 'com.tngtech.archunit:archunit:1.1.0'
}

 

 

์ฝ”๋“œ

ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์™€ ์ดˆ๊ธฐ ์„ค์ •

class ArchitectureTest {
    JavaClasses javaClasses;

    @BeforeEach
    public void beforeEach() {
        javaClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) // ํ…Œ์ŠคํŠธ ํŒจํ‚ค์ง€๋Š” ์ œ์™ธ
                .importPackages("com.example.ArchUnit");
    }

    // ์ดํ•˜ ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ๋“ค...
}

ClassFileImporter๋Š” ArchUnit์„ ์‚ฌ์šฉํ•˜์—ฌ Java ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ClassFileImporter๋Š” ๋‹ค์–‘ํ•œ ์ฒด์ด๋‹ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ๊ฐ€์ ธ์˜ฌ ํด๋ž˜์Šค์˜ ๋ฒ”์œ„์™€ ์กฐ๊ฑด์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉ๋œ withImportOption๋ฉ”์„œ๋“œ๋Š” ๊ฐ€์ ธ์˜ฌ ํด๋ž˜์Šค๋“ค์— ๋Œ€ํ•œ ํ•„ํ„ฐ๋ง ์˜ต์…˜์„ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

ClassFileImporter์˜ ์ฃผ์š” ๋ฉ”์„œ๋“œ

  • withImportOption(ImportOption option): ๊ฐ€์ ธ์˜ฌ ํด๋ž˜์Šค์— ๋Œ€ํ•œ ์˜ต์…˜์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ์ œ์™ธํ•˜๊ฑฐ๋‚˜ ํŠน์ • ํŒจํ„ด์— ๋งž๋Š” ํด๋ž˜์Šค๋งŒ ํฌํ•จ์‹œํ‚ค๋Š” ๋“ฑ์˜ ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • importPackages(String... packages): ์ง€์ •๋œ ํŒจํ‚ค์ง€ ๋‚ด์˜ ํด๋ž˜์Šค๋“ค์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํŒจํ‚ค์ง€๋ฅผ ๋™์‹œ์— ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • importClasses(Class... classes): ํŠน์ • ํด๋ž˜์Šค๋“ค์„ ์ง์ ‘ ์ง€์ •ํ•˜์—ฌ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • importUrl(URL url): URL์„ ํ†ตํ•ด ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์›น ๊ธฐ๋ฐ˜ ๋ฆฌ์†Œ์Šค๋‚˜ JAR ํŒŒ์ผ ๋“ฑ์—์„œ ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • importJar(File jarFile): JAR ํŒŒ์ผ๋กœ๋ถ€ํ„ฐ ํด๋ž˜์Šค๋“ค์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

ImportOption์˜ ์ฃผ์š” ์˜ต์…˜๋“ค

  • DO_NOT_INCLUDE_TESTS: ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ฒ€์‚ฌํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • DO_NOT_INCLUDE_ARCHIVES: ์•„์นด์ด๋ธŒ(์˜ˆ: JAR ํŒŒ์ผ) ๋‚ด์˜ ํด๋ž˜์Šค๋ฅผ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค.
  • DO_NOT_INCLUDE_JARS: JAR ํŒŒ์ผ ๋‚ด์˜ ํด๋ž˜์Šค๋ฅผ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค.
  • Predefined: ์‚ฌ์ „์— ์ •์˜๋œ ์—ฌ๋Ÿฌ ์˜ต์…˜๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, DO_NOT_INCLUDE_TESTS๋Š” Predefined ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ๋Š” ์˜ต์…˜๋“ค์ด ๋” ๋งŽ์€๋ฐ, ์˜คํ”ˆ ์†Œ์Šค๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

ํด๋ž˜์Šค ์ด๋ฆ„ ๊ฒ€์‚ฌ

    @Test
    @DisplayName("Controller ํŒจํ‚ค์ง€ ๋‚ด์˜ ํด๋ž˜์Šค์˜ ์ด๋ฆ„์€ 'Controller'๋กœ ๋๋‚˜์•ผ ํ•œ๋‹ค.")
    void controllersShouldBeNamedCorrectly() {
        ArchRule rule = ArchRuleDefinition.classes()
                .that().resideInAPackage("..controller..")
                .should().haveSimpleNameEndingWith("Controller")
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

    @Test
    @DisplayName("์„œ๋น„์Šค ํŒจํ‚ค์ง€ ๋‚ด์˜ ํด๋ž˜์Šค์˜ ์ด๋ฆ„์€ 'Service'๋กœ ๋๋‚˜์•ผ ํ•œ๋‹ค.")
    void servicesShouldBeNamedCorrectly() {
        ArchRule rule = ArchRuleDefinition.classes()
                .that().resideInAPackage("..service..")
                .should().haveSimpleNameEndingWith("Service")
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

    @Test
    @DisplayName("๋ฆฌํฌ์ง€ํ† ๋ฆฌ ํŒจํ‚ค์ง€ ๋‚ด์˜ ํด๋ž˜์Šค์˜ ์ด๋ฆ„์€ 'Repository'๋กœ ๋๋‚˜์•ผ ํ•œ๋‹ค.")
    void repositoriesShouldBeNamedCorrectly() {
        ArchRule rule = ArchRuleDefinition.classes()
                .that().resideInAPackage("..repository..")
                .should().haveSimpleNameEndingWith("Repository")
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

์œ„ ์„ธ ๊ฐœ์˜ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋Š” ArchUnit์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ํด๋ž˜์Šค ์œ ํ˜•์˜ ์ด๋ฆ„์ด ์ผ์ •ํ•œ ํŒจํ„ด์„ ๋”ฐ๋ฅด๋Š”์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ณผ์ •์„ ๊ฑฐ์นฉ๋‹ˆ๋‹ค.

  1. ArchRuleDefinition.classes()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ทœ์น™์„ ์ •์˜ํ•œ๋‹ค. ์ด๋Š” ํด๋ž˜์Šค ์œ ํ˜•์— ๋Œ€ํ•œ ๊ทœ์น™์„ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•œ ์‹œ์ž‘์ ์ด๋‹ค.
  2. .that().resideInAPacakage("..controller..")์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ํŒจํ‚ค์ง€ ๋‚ด์˜ ํด๋ž˜์Šค๋ฅผ ๋Œ€์ƒ์„ ์ง€์ •ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ "..controller.."์™€ ๊ฐ™์€ ๋ฌธ์ž์—ด์€ ํŒจํ‚ค์ง€ ๊ฒฝ๋กœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ์ด ๊ฒฝ๋กœ๋Š” ํŒจํ‚ค์ง€ ์ด๋ฆ„์˜ ์ผ๋ถ€๋ฅผ ํฌํ•จํ•˜๊ฑฐ๋‚˜ ์ „์ฒด๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค.
  3. .should().havaSimpleNameEndingWith("Controller")์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ž˜์Šค ์ด๋ฆ„์ด ํŠน์ • ๋ฌธ์ž์—ด๋กœ ๋๋‚˜์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์„ ์„ค์ •ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ๊ฐ "Controller", "Service", "Repository"๋กœ ๋๋‚˜๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง„ ํด๋ž˜์Šค๋งŒ ํ†ต๊ณผ์‹œํ‚จ๋‹ค.
  4. .allowEmptyShould(true)๋Š” ํ•ด๋‹น ๊ทœ์น™์— ๋Œ€ํ•ด ๊ฒ€์‚ฌํ•  ๋Œ€์ƒ์ด ์—†์„ ๊ฒฝ์šฐ(์˜ˆ: ํŠน์ • ํŒจํ„ด์„ ๋”ฐ๋ฅด๋Š” ํด๋ž˜์Šค๊ฐ€ ์—†๊ฑฐ๋‚˜, ํŒจํ‚ค์ง€๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ) ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
  5. rule.check(javaClasses)๋Š” ์ •์˜๋œ ๊ทœ์น™์„ javaClasses์ปฌ๋ ‰์…˜์— ๋Œ€ํ•ด ์‹คํ–‰ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ javaClasses๋Š” ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์ด ๋˜๋Š” ํด๋ž˜์Šค๋“ค์˜ ์ง‘ํ•ฉ์ด๋‹ค.

์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๋ฒ ์ด์Šค ๋‚ด์—์„œ ์ผ๊ด€๋œ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์„ ์œ ์ง€ํ•˜๊ณ , ๊ฐ ํŒจํ‚ค์ง€(๊ณ„์ธต)์˜ ํด๋ž˜์Šค๋“ค์ด ๊ทธ๋“ค์˜ ์—ญํ• ์— ๋งž๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ปจํŠธ๋กค๋Ÿฌ์˜ ์˜์กด์„ฑ ๊ฒ€์‚ฌ

    @Test
    @DisplayName("์ปจํŠธ๋กค๋Ÿฌ๋Š” ์„œ๋น„์Šค ํด๋ž˜์Šค์—๋งŒ ์˜์กดํ•ด์•ผ ํ•œ๋‹ค.")
    void controllersShouldOnlyDependOnServices() {
        ArchRule rule = ArchRuleDefinition.classes()
                .that().resideInAPackage("..controller..")
                .should().onlyDependOnClassesThat()
                .resideInAnyPackage("..service..", "java..", "..controller..")
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

์ด ํ…Œ์ŠคํŠธ๋Š” ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค๊ฐ€ ์„œ๋น„์Šค ํด๋ž˜์Šค, ์ž๋ฐ” ๊ธฐ๋ณธ ํด๋ž˜์Šค, ๋˜๋Š” ๋‹ค๋ฅธ ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์—๋งŒ ์˜์กดํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์„œ๋น„์Šค ๊ณ„์ธต์—๋งŒ ์˜์กดํ•˜๊ณ , ๋‹ค๋ฅธ ๊ณ„์ธต(์˜ˆ: ๋ฆฌํฌ์ง€ํ† ๋ฆฌ, ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค ๋“ฑ)์— ์ง์ ‘์ ์œผ๋กœ ์˜์กดํ•˜์ง€ ์•Š๋„๋ก ํ•จ์œผ๋กœ์จ, ์ฝ”๋“œ์˜ ๋ชจ๋“ˆ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ•„๋“œ ์ธ์ ์…˜ ๊ฒ€์‚ฌ

    @Test
    @DisplayName("๋ชจ๋“  ํด๋ž˜์Šค๋Š” ํ•„๋“œ ์ธ์ ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.")
    void allClassesShouldNotUseFieldInjection() {
        ArchRule rule = ArchRuleDefinition.fields()
                .that().areDeclaredInClassesThat().resideInAPackage("..")
                .should().notBeAnnotatedWith(Autowired.class)
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

์œ„ ํ…Œ์ŠคํŠธ๋Š” ๋ชจ๋“  ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ํด๋ž˜์Šค์— ๋Œ€ํ•ด @Autowired๋ฅผ ํ†ตํ•œ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

 

์ˆœํ™˜ ์˜์กด์„ฑ ๊ฒ€์‚ฌ

    @Test
    @DisplayName("ํด๋ž˜์Šค๋“ค์€ ์ˆœํ™˜ ์˜์กด์„ฑ์„ ๊ฐ€์ง€๋ฉด ์•ˆ ๋œ๋‹ค.")
    void noCyclicDependencies() {
        ArchRule rule = SlicesRuleDefinition.slices()
                .matching("com.example.ArchUnit.(*)..")
                .should().beFreeOfCycles()
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ํด๋ž˜์Šค๋“ค ์‚ฌ์ด์— ์ˆœํ™˜ ์˜์กด์„ฑ์ด ์กด์žฌํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

SlicesRuleDefinition.slices()๋Š” ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ํด๋ž˜์Šค๋ฅผ ์—ฌ๋Ÿฌ ์Šฌ๋ผ์ด์Šค๋กœ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, matching("com.example.ArchUnit.(*)..")๋ถ€๋ถ„์€ com.example.ArchUnitํŒจํ‚ค์ง€ ์•„๋ž˜์˜ ๋ชจ๋“  ํด๋ž˜์Šค๋ฅผ ๋Œ€์ƒ์œผ๋กœ ๊ทœ์น™์„ ์ ์šฉํ•˜๊ฒ ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ `(*)`๋Š” ํ•˜์œ„ ํŒจํ‚ค์ง€๋ฅผ ํฌํ•จํ•˜๋Š” ์™€์ผ๋“œ์นด๋“œ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

should().beFreeOfCycles()๋ถ€๋ถ„์€ ์ด ์Šฌ๋ผ์ด์Šค๋“ค ์‚ฌ์ด์— ์ˆœํ™˜ ์˜์กด์„ฑ์ด ์—†์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ˆœํ™˜ ์˜์กด์„ฑ์ด ๋ฐœ๊ฒฌ๋˜๋ฉด, ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

 

ํด๋ž˜์Šค ์ ‘๊ทผ ๊ฒ€์‚ฌ

    @Test
    @DisplayName("์„œ๋น„์Šค ํด๋ž˜์Šค๋Š” ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์— ์ ‘๊ทผํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.")
    void servicesShouldNotAccessControllers() {
        ArchRule rule = ArchRuleDefinition.classes()
                .that().resideInAPackage("..service..")
                .should().onlyAccessClassesThat()
                .resideOutsideOfPackage("..controller..")
                .allowEmptyShould(true);  // ๋นˆ ๊ฒฐ๊ณผ ํ—ˆ์šฉ

        rule.check(javaClasses);
    }

์œ„ ํ…Œ์ŠคํŠธ๋Š” ์„œ๋น„์Šค ํด๋ž˜์Šค๋“ค์ด ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์— ์ ‘๊ทผํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ณ„์ธตํ™”๋œ ์•„ํ‚คํ…์ฒ˜์—์„œ ์„œ๋น„์Šค ๋ ˆ์ด์–ด๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ์ด์–ด์— ์ข…์†๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜๋ฉฐ ์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค์–‘ํ•˜๊ฒŒ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

.should().onlyAccessClassesThat().resideOutsideOfPackage("..controller..")๋ถ€๋ถ„์€ ์„œ๋น„์Šค ํด๋ž˜์Šค๋“ค์ด "controller" ํŒจํ‚ค์ง€ ์™ธ๋ถ€์˜ ํด๋ž˜์Šค๋“ค์—๋งŒ ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์„œ๋น„์Šค ํด๋ž˜์Šค๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์— ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด, ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

 

 

ํ…Œ์ŠคํŠธ ๊ด€๋ จ ๊ฐ™์ด ๋ณด๋ฉด ์ข‹์€ ๊ธ€

๋Œ“๊ธ€