Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Jira
serverONAP JIRA
columnskey,summary,type,created,updated,due,assignee,reporter,priority,status,resolution
serverId425b2b0a-557c-3c0c-b515-579789cceedb
keyCPS-347

We are proposing the introduction of automatic tools to support architecture and design quality during all application life cycle.

...

  • Long term application maintainability and evolution
  • Being able to split and separate application artifacts later on if needed.

...

ArchUnit

Archunit ArchUnit is a library that can be used for this architecture quality purpose. It integrates with Unit Test to verify the application code structure the same way standard Unit Test classes are verifying the application code logic.

It ArchUnit can be used to:

  • Verify application packages and classes dependencies
  • Detect dependency cycles in the application structure
  • Verify annotations, inheritance, naming conventions, ...
  • Compute some software architecture metrics

For more info about what to check: https://www.archunit.org/userguide/html/000_Index.html#_what_to_check

It gives ArchUnit gives flexibility to:

...

Code Block
languagejava
/**
 * Test class responsible for dependencies validations.
 */
@AnalyzeClasses(packages = "org.onap.cps", importOptions = { ImportOption.DoNotIncludeTests.class })
public class DependencyArchitectureTest {

    @ArchTest
    static final ArchRule noCyclesRule =
            slices().matching("org.onap.cps.(**)..").should().beFreeOfCycles();

    @ArchTest
    static final ArchRule noUpperPackageDependencyRule = NO_CLASSES_SHOULD_DEPEND_UPPER_PACKAGES;

}

/**
 * Test class responsible for layered architecture.
 */
@AnalyzeClasses(packages = "org.onap.cps", importOptions = { ImportOption.DoNotIncludeTests.class })
public class LayeredArchitectureTest {

    private static final String A_CONTROLLER_PACKAGE = "org.onap.cps.controller..";
    private static final String A_SERVICE_PACKAGE = "org.onap.cps.service..";
    private static final String A_REPOSITORY_PACKAGE = "org.onap.cps.repository..";

    @ArchTest
    public static final ArchRule layeredArchitectureRule =
            layeredArchitecture()
                    .layer("Controller").definedBy(A_CONTROLLER_PACKAGE)
                    .layer("Service").definedBy(A_SERVICE_PACKAGE)
                    .layer("Repository").definedBy(A_REPOSITORY_PACKAGE)
                    .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
                    .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
                    .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");

    // 'access' catches only violations by real accesses,
    // i.e. accessing a field, calling a method; compare 'dependOn' further down

    @ArchTest
    public static final ArchRule controllerAccessRule =
            classes().that().resideInAPackage(A_CONTROLLER_PACKAGE)
                    .should().onlyBeAccessed().byAnyPackage(A_CONTROLLER_PACKAGE);

    @ArchTest
    public static final ArchRule serviceAccessRule =
            classes().that().resideInAPackage(A_SERVICE_PACKAGE)
                    .should().onlyBeAccessed().byAnyPackage(A_CONTROLLER_PACKAGE, A_SERVICE_PACKAGE);

    @ArchTest
    public static final ArchRule repositoryAccessRule =
            classes().that().resideInAPackage(A_REPOSITORY_PACKAGE)
                    .should().onlyBeAccessed().byAnyPackage(A_SERVICE_PACKAGE, A_REPOSITORY_PACKAGE);

    // 'dependOn' catches a wider variety of violations,
    // e.g. having fields of type, having method parameters of type, extending type ...

    @ArchTest
    static final ArchRule controllerDependencyRule =
            classes().that().resideInAPackage(A_CONTROLLER_PACKAGE)
                    .should().onlyHaveDependentClassesThat()
                    .resideInAPackage(A_CONTROLLER_PACKAGE);

    @ArchTest
    static final ArchRule serviceDependencyRule =
            classes().that().resideInAPackage(A_SERVICE_PACKAGE)
                    .should().onlyHaveDependentClassesThat()
                    .resideInAnyPackage(A_CONTROLLER_PACKAGE, A_SERVICE_PACKAGE);

    @ArchTest
    static final ArchRule repositoryDependencyRule =
            classes().that().resideInAPackage(A_REPOSITORY_PACKAGE)
                    .should().onlyHaveDependentClassesThat()
                    .resideInAnyPackage(A_SERVICE_PACKAGE, A_REPOSITORY_PACKAGE);

}

Finding example:

Code Block
languagetext
[ERROR] Failures:
[ERROR]   Architecture Violation [Priority: MEDIUM] - Rule 'slices matching 'org.onap.cps.(**)..' should be free of cycles' was violated (1 times):
Cycle detected: Slice spi.model -> Slice utils -> Slice spi.model

Dependencies of Slice spi.model
Method <org.onap.cps.spi.model.DataNodeBuilder.addYangContainer(org.onap.cps.spi.model.DataNode, org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode)> calls method <org.onap.cps.utils.YangUtils.buildXpath(org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier$PathArgument)> in (DataNodeBuilder.java:175)

Dependencies of Slice utils
Method <org.onap.cps.utils.DataMapUtils.lambda$containerElementsAsMap$2(org.onap.cps.spi.model.DataNode)> has parameter of type <org.onap.cps.spi.model.DataNode> in (DataMapUtils.java:0)
Method <org.onap.cps.utils.DataMapUtils.lambda$containerElementsAsMap$3(org.onap.cps.spi.model.DataNode)> has parameter of type <org.onap.cps.spi.model.DataNode> in (DataMapUtils.java:0)
Method <org.onap.cps.utils.DataMapUtils.lambda$listElementsAsMap$0(org.onap.cps.spi.model.DataNode)> has parameter of type <org.onap.cps.spi.model.DataNode> in (DataMapUtils.java:0)
Method <org.onap.cps.utils.DataMapUtils.lambda$listElementsAsMap$1(org.onap.cps.spi.model.DataNode)> has parameter of type <org.onap.cps.spi.model.DataNode> in (DataMapUtils.java:0)
Method <org.onap.cps.utils.DataMapUtils.toDataMap(org.onap.cps.spi.model.DataNode)> has parameter of type <org.onap.cps.spi.model.DataNode> in (DataMapUtils.java:0)
Method <org.onap.cps.utils.DataMapUtils.toDataMap(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getLeaves()> in (DataMapUtils.java:48)
Method <org.onap.cps.utils.DataMapUtils.toDataMap(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getChildDataNodes()> in (DataMapUtils.java:49)
Method <org.onap.cps.utils.DataMapUtils.toDataMap(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getChildDataNodes()> in (DataMapUtils.java:50)
Method <org.onap.cps.utils.DataMapUtils.lambda$listElementsAsMap$0(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getXpath()> in (DataMapUtils.java:61)
Method <org.onap.cps.utils.DataMapUtils.lambda$listElementsAsMap$1(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getXpath()> in (DataMapUtils.java:63)
Method <org.onap.cps.utils.DataMapUtils.lambda$containerElementsAsMap$2(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getXpath()> in (DataMapUtils.java:74)
Method <org.onap.cps.utils.DataMapUtils.lambda$containerElementsAsMap$3(org.onap.cps.spi.model.DataNode)> calls method <org.onap.cps.spi.model.DataNode.getXpath()> in (DataMapUtils.java:77)

Sonargraph

Sonargraph is not considered has its free licence is not compatible.