ArchUnit Practice: Architecture Testing of Onion Architecture

//Execution environment
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1

Layer dependencies

image.png

https://dzone.com/articles/onion-architecture-is-interesting

Java project package structure

image.png

Architecture test implementation

package com.example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.library.Architectures.onionArchitecture;

class ArchitectureTest {

    //Class to be inspected
    private static final JavaClasses CLASSES =
            new ClassFileImporter()
                    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                    .importPackages("com.example");

    @Test
void Architectural testing of onion architecture() {
        onionArchitecture()
            // domain.Define model package as domain model layer
            .domainModels("com.example.domain.model..")
            // domain.Define service package as domain service layer
            .domainServices("com.example.domain.service..")
            //Define application package as application service layer
            .applicationServices("com.example.application..")

            //Define infrastructure package as infrastructure adapter
            .adapter("infra", "com.example.infrastructure..")
            //Define presentation package as user interface adapter
            .adapter("ui", "com.example.presentation..")

            .check(CLASSES);
    }
}

Architecture test execution example

Test failure example ① (domain service → dependency on application service)

An example of a test failure assuming that an architecture violation was detected in which the Service class of the inner domain service layer depends on the UseCase class of the outer application service layer.

$ ./gradlew clean check

> Task :test FAILED

ArchitectureTest >Architecture testing of onion architecture() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Onion architecture consisting of
    domain models ('com.example.domain.model..')
    domain services ('com.example.domain.service..')
    application services ('com.example.application..')
    adapter 'infra' ('com.example.infrastructure..')
    adapter 'ui' ('com.example.presentation..')' was violated (2 times):
    Constructor <com.example.domain.service.employee.EmployeeRegisterService.<init>(com.example.application.HogeUseCase)> has parameter of type <com.example.application.HogeUseCase> in (EmployeeRegisterService.java:0)
    Field <com.example.domain.service.employee.EmployeeRegisterService.hogeUseCase> has type <com.example.application.HogeUseCase> in (EmployeeRegisterService.java:0)
        at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94)
        at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:82)
        at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:267)
        at com.tngtech.archunit.library.Architectures$OnionArchitecture.check(Architectures.java:538)
        at com.example.ArchitectureTest.Architecture testing of onion architecture(ArchitectureTest.java:560)

1 test completed, 1 failed

Test failure example (2) (User interface adapter → Dependence on infrastructure adapter)

An example of a test failure assuming an architecture violation that the Controller class of the user interface adapter depends on the Repository (Impl) class of the infrastructure adapter.

$ ./gradlew clean check

> Task :test FAILED

ArchitectureTest >Architecture testing of onion architecture() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Onion architecture consisting of
    domain models ('com.example.domain.model..')
    domain services ('com.example.domain.service..')
    application services ('com.example.application..')
    adapter 'infra' ('com.example.infrastructure..')
    adapter 'ui' ('com.example.presentation..')' was violated (2 times):
    Constructor <com.example.presentation.employee.EmployeeController.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeController.java:0)
    Field <com.example.presentation.employee.EmployeeController.employeeRepository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeController.java:0)
        at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94)
        at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:82)
        at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:267)
        at com.tngtech.archunit.library.Architectures$OnionArchitecture.check(Architectures.java:538)
        at com.example.ArchitectureTest.Architecture testing of onion architecture(ArchitectureTest.java:560)

1 test completed, 1 failed

Recommended Posts

ArchUnit Practice: Architecture Testing of Onion Architecture
ArchUnit Practice: Clean Architecture Architecture Testing
CI the architecture of Java / Kotlin applications with ArchUnit
[Java] Practice of exception handling [Exception]
Practice of binary search method
[Practice! 】 Execution of SQL statement
Unit test architecture using ArchUnit
Overall architecture of Tomcat (Catalina)
Practice of linear search method
ArchUnit Practice: Enforce visibility of limited-use methods into package private or private