From 350da3ebab7a9a20338de7ee585f0b668ffb83f2 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 21 Feb 2023 10:59:40 +0100 Subject: [PATCH 01/18] Added first draft for C2: Business architecture definition of dependencies test. --- ...omponentRuleC2ComponentDependencyTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java new file mode 100644 index 0000000..3f6a3aa --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java @@ -0,0 +1,63 @@ +package com.devonfw.sample.archunit; + +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.library.dependencies.Slice; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.importer.ImportOption; +/** + * This is an exemplary component dependency test. It is assumed that the business architecture defines two components: + * + * { + * "architecture": { + * "components": [ + * {"name":"task","dependencies":["componentb"]}, + * {"name":"componentb","dependencies":[]} + * ] + * } + *} + * Component task is allowed to have dependencies towards componentb but not vice versa. + */ +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) +public class ComponentRuleC2ComponentDependencyTest { + + private static DescribedPredicate containDescription(String descriptionPart) { + return new DescribedPredicate("contain description '%s'", descriptionPart) { + @Override + public boolean test(Slice input) { + return input.getDescription().contains(descriptionPart); + } + }; + } + + // No components should depend on each other except the general component which can be used by any other component. + static final ArchRule components_should_only_use_their_own_slice_with_custom_ignore = + slices().matching("..archunit.(*)..").namingSlices("Component $1") + .as("Components").should().notDependOnEachOther() + // default business component general holds code which doesn't belong to a specific component + .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")); + + // Components: componentb and task should not depend on each other (bi-directional). + static final ArchRule specific_components_should_only_use_their_own_slice = + slices().matching("..archunit.(*)..").namingSlices("Component $1") + .that(containDescription("Component task")) + .or(containDescription("Component componentb")) + .as("Component Task or componentb").should().notDependOnEachOther(); + + // componentb should not depend on the task component but not vice versa (unidirectional). + // TODO: How to modify the output message ('should not depend on each other' does not fit with my prior message and my intended use case) + @ArchTest + static final ArchRule component_task_may_depend_on_componentb = + slices().matching("..archunit.(*)..").namingSlices("Component $1") + .that(containDescription("Component task")) + .or(containDescription("Component componentb")) + .as("Task is allowed to depend on componentb but not vice versa.").should().notDependOnEachOther() + .ignoreDependency(nameContaining("task"), nameContaining("componentb")); +} From 599738913c7a61ec6d43e3402a511031c2c7f9b4 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 21 Feb 2023 11:51:41 +0100 Subject: [PATCH 02/18] Added first draft for C3: Components service layers should not depend on each other. --- ...eC3LayerService2Service4ComponentTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java new file mode 100644 index 0000000..ca29c35 --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java @@ -0,0 +1,20 @@ +package com.devonfw.sample.archunit; + +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +import com.tngtech.archunit.core.importer.ImportOption; + +/** + * verifying that the service layer of one component does not depend on the service layer of + * another component. + */ +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) +public class ComponentRuleC3LayerService2Service4ComponentTest { + @ArchTest + static final ArchRule no_service_layer_depends_on_service_layer_of_another_component = + slices().matching("..archunit.(*).service..").namingSlices("$1 service").should().notDependOnEachOther(); +} From 2a1ac45cd53c6469b213a9bb4c90d2f3098e5a13 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Wed, 22 Feb 2023 10:28:09 +0100 Subject: [PATCH 03/18] Added first C4 draft. NOT READY! --- ...uleC4LayerService2Logic4ComponentTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java new file mode 100644 index 0000000..6acc9b3 --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java @@ -0,0 +1,67 @@ +package com.devonfw.sample.archunit; + +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.library.dependencies.SliceAssignment; +import com.tngtech.archunit.library.dependencies.SliceIdentifier; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; +/** + * verifying that the service layer of a component may not depend on the + * logic layer of another component. + */ +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) +public class ComponentRuleC4LayerService2Logic4ComponentTest { + + static final String PROJECT_NAME = "com.devonfw.sample.archunit"; + static final String DEFAULT_COMPONENT = PROJECT_NAME + ".general"; + @ArchTest + static final ArchRule no_dependencies_from_componentb_to_any_other_component = + slices().assignedFrom(inComponentServiceOrLogicLayer()) + .namingSlices("$1") + .should().notDependOnEachOther(); + + @ArchTest + static final ArchRule no_cycles_by_method_calls_between_slices = + slices().matching("..archunit.(*).(service)..").namingSlices("$2 of $1").should().notDependOnEachOther(); + + private static SliceAssignment inComponentServiceOrLogicLayer() { + return new SliceAssignment() { + @Override + public String getDescription() { + return "the service layer of one component and a different components logic layer"; + } + + @Override + public SliceIdentifier getIdentifierOf(JavaClass javaClass) { + String sourcePackageName = javaClass.getPackageName(); + // All components except the default component. + if(sourcePackageName.contains(PROJECT_NAME) && !sourcePackageName.contains(DEFAULT_COMPONENT)){ + for (JavaAccess access : javaClass.getAccessesFromSelf()) { + String targetPackageName = access.getTargetOwner().getName(); + if (sourcePackageName.contains(".service") && !targetPackageName.contains(".logic")) { + String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".service", ""); + if(!targetPackageName.contains(componentName)){ + return SliceIdentifier.of(componentName); + } + } + if(!sourcePackageName.contains("service") && targetPackageName.contains(".logic")){ + String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".logic", ""); + if(!targetPackageName.contains(componentName)) { + return SliceIdentifier.of(componentName); + } + + } + } + } + + return SliceIdentifier.ignore(); + } + }; + } +} From 845c1307a4625c4845452543eddbd2c43c6b8388 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Mon, 27 Feb 2023 12:30:48 +0100 Subject: [PATCH 04/18] Added new draft for C4Layer2Logic4ComponentTest --- ...uleC4LayerService2Logic4ComponentTest.java | 145 +++++++++++++----- 1 file changed, 103 insertions(+), 42 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java index 6acc9b3..9d9d37e 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java @@ -2,15 +2,18 @@ import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.library.dependencies.SliceAssignment; -import com.tngtech.archunit.library.dependencies.SliceIdentifier; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; -import com.tngtech.archunit.core.domain.JavaAccess; + +import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.importer.ImportOption; + /** * verifying that the service layer of a component may not depend on the * logic layer of another component. @@ -19,49 +22,107 @@ public class ComponentRuleC4LayerService2Logic4ComponentTest { static final String PROJECT_NAME = "com.devonfw.sample.archunit"; - static final String DEFAULT_COMPONENT = PROJECT_NAME + ".general"; - @ArchTest - static final ArchRule no_dependencies_from_componentb_to_any_other_component = - slices().assignedFrom(inComponentServiceOrLogicLayer()) - .namingSlices("$1") - .should().notDependOnEachOther(); + static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; + static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - @ArchTest - static final ArchRule no_cycles_by_method_calls_between_slices = - slices().matching("..archunit.(*).(service)..").namingSlices("$2 of $1").should().notDependOnEachOther(); + static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition ("have dependencies, towards another components logic layer (Rule-C4).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + String sourceClassName = sourceClass.getFullName(); + String sourceClassLayer = getClassLayer(sourceClass); + String sourceClassComponent = getComponentNameOfClass(sourceClass); - private static SliceAssignment inComponentServiceOrLogicLayer() { - return new SliceAssignment() { - @Override - public String getDescription() { - return "the service layer of one component and a different components logic layer"; - } + // All project components service layer except the default component. + if(sourceClassLayer.equals("service") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassName = targetClass.getFullName(); + String targetClassComponent = getComponentNameOfClass(targetClass); + String targetClassLayer = getClassLayer(targetClass); + boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); - @Override - public SliceIdentifier getIdentifierOf(JavaClass javaClass) { - String sourcePackageName = javaClass.getPackageName(); - // All components except the default component. - if(sourcePackageName.contains(PROJECT_NAME) && !sourcePackageName.contains(DEFAULT_COMPONENT)){ - for (JavaAccess access : javaClass.getAccessesFromSelf()) { - String targetPackageName = access.getTargetOwner().getName(); - if (sourcePackageName.contains(".service") && !targetPackageName.contains(".logic")) { - String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".service", ""); - if(!targetPackageName.contains(componentName)){ - return SliceIdentifier.of(componentName); - } - } - if(!sourcePackageName.contains("service") && targetPackageName.contains(".logic")){ - String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".logic", ""); - if(!targetPackageName.contains(componentName)) { - return SliceIdentifier.of(componentName); - } - - } + // WARNING: Dependency of a components service layer towards another components layer other than logic wont be registered as a violation. + // (Other rules cover these violations.) + if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { + String message = String.format("Code from service layer of a component shall not depend on logic layer of a different component. ('%s.%s' is dependend on '%s.%s'. Dependency to (%s). Violated in: (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); + events.add(new SimpleConditionEvent(sourceClass, true, message)); } } - - return SliceIdentifier.ignore(); } - }; + } + }; + + @ArchTest + static final ArchRule no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = + noClasses() + .should(haveNonCompliantComponentDependencies) + .allowEmptyShould(true); + + /** + * Dependency of a components service layer towards the same components logic or common layer is allowed. + */ + private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { + boolean isAllowed = false; + String targetClassName = targetClass.getFullName(); + + // Components service layer can depend on their own lower layers: logic and common. + if(classesAreInSameComponent(sourceClass, targetClass) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { + isAllowed = true; + } + // Components may always depend on the default business component. + if(targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Returns the name of the layer of a given class, if the class does lie in a projects component. + * Otherwise returns a blank string "". + */ + private static String getClassLayer(JavaClass clazz) { + String classLayerName = ""; + String className = clazz.getFullName(); + if(className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); + int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; + int endOfLayerName = classComponentAndLayerName.length(); + classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); + } + return classLayerName; + } + + /** + * Returns the name of the component of a given class, if the class does lie in a projects component. + * Otherwise returns a blank string "". + */ + private static String getComponentNameOfClass(JavaClass clazz) { + String classComponentName = ""; + String className = clazz.getFullName(); + if(className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); + int endOfComponentName = classComponentAndLayerName.indexOf("."); + classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); + } + return classComponentName; + } + + /** + * Returns true, if both given classes lie in the same projects component. + * In case at least one of the given classes does not lie in any of the project components false will be returned. + * @param sourceClass JavaClass A to check against another JavaClass B. + * @param targetClass JavaClass B to check against another JavaClass A. + */ + private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { + String sourceComponent = getComponentNameOfClass(sourceClass); + String targetComponent = getComponentNameOfClass(targetClass); + if( targetComponent == "" || sourceComponent =="") + { + return false; + } + return sourceComponent.equals(targetComponent) ? true : false; } + } From f1942b626ec1af9802904084410640469c01e74f Mon Sep 17 00:00:00 2001 From: Nicolas van Bellen Date: Mon, 27 Feb 2023 12:34:41 +0100 Subject: [PATCH 05/18] First commits of rule C5 - C7 --- .../ComponentRuleC7LayerBatch2Logic4ComponentTest.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java new file mode 100644 index 0000000..347c6cf --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java @@ -0,0 +1,5 @@ +package com.devonfw.sample.archunit; + +public class ComponentRuleC7LayerBatch2Logic4ComponentTest { + +} From 6bb5fb1975e6fdc075e2414aae6c4a2337602ec8 Mon Sep 17 00:00:00 2001 From: Nicolas van Bellen Date: Mon, 27 Feb 2023 12:36:02 +0100 Subject: [PATCH 06/18] First commits for rules C5 - C7 --- ...C5LayerLogic2Dataaccess4ComponentTest.java | 68 +++++++++++++++++++ ...erDataaccess2Dataaccess4ComponentTest.java | 27 ++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java new file mode 100644 index 0000000..4228f6d --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java @@ -0,0 +1,68 @@ +package com.devonfw.sample.archunit; + +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.library.dependencies.SliceAssignment; +import com.tngtech.archunit.library.dependencies.SliceIdentifier; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; +/** + * verifying that the service layer of a component may not depend on the + * logic layer of another component. + */ +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) +public class ComponentRuleC5LayerLogic2Dataaccess4ComponentTest { + + static final String PROJECT_NAME = "com.devonfw.sample.archunit"; + static final String DEFAULT_COMPONENT = PROJECT_NAME + ".general"; + @ArchTest + static final ArchRule no_dependencies_from_componentb_to_any_other_component = + slices().assignedFrom(inComponentServiceOrLogicLayer()) + .namingSlices("$1") + .should().notDependOnEachOther(); + + @ArchTest + static final ArchRule no_cycles_by_method_calls_between_slices = + slices().matching("..archunit.(*).(service)..").namingSlices("$2 of $1").should().notDependOnEachOther(); + + private static SliceAssignment inComponentServiceOrLogicLayer() { + return new SliceAssignment() { + @Override + public String getDescription() { + return "the service layer of one component and a different components logic layer"; + } + + @Override + public SliceIdentifier getIdentifierOf(JavaClass javaClass) { + String sourcePackageName = javaClass.getPackageName(); + // All components except the default component. + if(sourcePackageName.contains(PROJECT_NAME) && !sourcePackageName.contains(DEFAULT_COMPONENT)){ + for (JavaAccess access : javaClass.getAccessesFromSelf()) { + String targetPackageName = access.getTargetOwner().getName(); + if (sourcePackageName.contains(".service") && !targetPackageName.contains(".logic")) { + String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".service", ""); + if(!targetPackageName.contains(componentName)){ + return SliceIdentifier.of(componentName); + } + } + if(!sourcePackageName.contains("service") && targetPackageName.contains(".logic")){ + String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".logic", ""); + if(!targetPackageName.contains(componentName)) { + return SliceIdentifier.of(componentName); + } + + } + } + } + + return SliceIdentifier.ignore(); + } + }; + } +} + diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java new file mode 100644 index 0000000..6db9c4a --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java @@ -0,0 +1,27 @@ +package com.devonfw.sample.archunit; + +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +import com.tngtech.archunit.core.importer.ImportOption; + +/** + * verifying that the dataaccess layer of one component does not depend on the dataaccess layer of + * another component. + */ + +// medium severity + +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) +public class ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest { + @ArchTest + static final ArchRule no_dataaccess_layer_depends_on_dataaccess_layer_of_another_component = + slices() + .matching("..archunit.(*).dataaccess..") + .namingSlices("$1 dataaccess") + .should() + .notDependOnEachOther(); +} \ No newline at end of file From 796411d0b94443516c91c95210e0dc7a2509ec7d Mon Sep 17 00:00:00 2001 From: Nicolas van Bellen Date: Mon, 27 Feb 2023 13:21:52 +0100 Subject: [PATCH 07/18] almost final version of rules C5 - C7 --- ...C5LayerLogic2Dataaccess4ComponentTest.java | 150 ++++++++++++------ ...tRuleC7LayerBatch2Logic4ComponentTest.java | 124 +++++++++++++++ 2 files changed, 229 insertions(+), 45 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java index 4228f6d..2fe68d1 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java @@ -2,67 +2,127 @@ import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.library.dependencies.SliceAssignment; -import com.tngtech.archunit.library.dependencies.SliceIdentifier; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; -import com.tngtech.archunit.core.domain.JavaAccess; + +import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.importer.ImportOption; + /** - * verifying that the service layer of a component may not depend on the - * logic layer of another component. + * verifying that the logic layer of a component may not depend on the + * dataaccess layer of another component. */ @AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) public class ComponentRuleC5LayerLogic2Dataaccess4ComponentTest { static final String PROJECT_NAME = "com.devonfw.sample.archunit"; - static final String DEFAULT_COMPONENT = PROJECT_NAME + ".general"; - @ArchTest - static final ArchRule no_dependencies_from_componentb_to_any_other_component = - slices().assignedFrom(inComponentServiceOrLogicLayer()) - .namingSlices("$1") - .should().notDependOnEachOther(); + static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; + static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - @ArchTest - static final ArchRule no_cycles_by_method_calls_between_slices = - slices().matching("..archunit.(*).(service)..").namingSlices("$2 of $1").should().notDependOnEachOther(); + static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition ("have dependencies, towards another components dataaccess layer (Rule-C5).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + String sourceClassName = sourceClass.getFullName(); + String sourceClassLayer = getClassLayer(sourceClass); + String sourceClassComponent = getComponentNameOfClass(sourceClass); - private static SliceAssignment inComponentServiceOrLogicLayer() { - return new SliceAssignment() { - @Override - public String getDescription() { - return "the service layer of one component and a different components logic layer"; - } + // All project components logic layer except the default component. + if(sourceClassLayer.equals("logic") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassName = targetClass.getFullName(); + String targetClassComponent = getComponentNameOfClass(targetClass); + String targetClassLayer = getClassLayer(targetClass); + boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); - @Override - public SliceIdentifier getIdentifierOf(JavaClass javaClass) { - String sourcePackageName = javaClass.getPackageName(); - // All components except the default component. - if(sourcePackageName.contains(PROJECT_NAME) && !sourcePackageName.contains(DEFAULT_COMPONENT)){ - for (JavaAccess access : javaClass.getAccessesFromSelf()) { - String targetPackageName = access.getTargetOwner().getName(); - if (sourcePackageName.contains(".service") && !targetPackageName.contains(".logic")) { - String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".service", ""); - if(!targetPackageName.contains(componentName)){ - return SliceIdentifier.of(componentName); - } - } - if(!sourcePackageName.contains("service") && targetPackageName.contains(".logic")){ - String componentName = javaClass.getPackageName().substring(PROJECT_NAME.length() + 1, sourcePackageName.length()).replace(".logic", ""); - if(!targetPackageName.contains(componentName)) { - return SliceIdentifier.of(componentName); - } - - } + // WARNING: Dependency of a components logic layer towards another components layer other than logic wont be registered as a violation. + // (Other rules cover these violations.) + if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { + String message = String.format("Code from logic layer of a component shall not depend on dataaccess layer of a different component. ('%s.%s' is dependend on '%s.%s'. Dependency to (%s). Violated in: (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); + events.add(new SimpleConditionEvent(sourceClass, true, message)); } } - - return SliceIdentifier.ignore(); } - }; + } + }; + + @ArchTest + static final ArchRule no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = + noClasses() + .should(haveNonCompliantComponentDependencies) + .allowEmptyShould(true); + + /** + * Dependency of a components logic layer towards the same components dataaccess or common layer is allowed. + */ + private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { + boolean isAllowed = false; + String targetClassName = targetClass.getFullName(); + + // Components logic layer can depend on their own lower layers: dataaccess and common. + if(classesAreInSameComponent(sourceClass, targetClass) && (targetClassName.contains("dataaccess") || targetClassName.contains("common"))) { + isAllowed = true; + } + // Components may always depend on the default business component. + if(targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) && (targetClassName.contains("dataaccess") || targetClassName.contains("common"))) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Returns the name of the layer of a given class, if the class does lie in a projects component. + * Otherwise returns a blank string "". + */ + private static String getClassLayer(JavaClass clazz) { + String classLayerName = ""; + String className = clazz.getFullName(); + if(className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); + int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; + int endOfLayerName = classComponentAndLayerName.length(); + classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); + } + return classLayerName; + } + + /** + * Returns the name of the component of a given class, if the class does lie in a projects component. + * Otherwise returns a blank string "". + */ + private static String getComponentNameOfClass(JavaClass clazz) { + String classComponentName = ""; + String className = clazz.getFullName(); + if(className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); + int endOfComponentName = classComponentAndLayerName.indexOf("."); + classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); + } + return classComponentName; + } + + /** + * Returns true, if both given classes lie in the same projects component. + * In case at least one of the given classes does not lie in any of the project components false will be returned. + * @param sourceClass JavaClass A to check against another JavaClass B. + * @param targetClass JavaClass B to check against another JavaClass A. + */ + private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { + String sourceComponent = getComponentNameOfClass(sourceClass); + String targetComponent = getComponentNameOfClass(targetClass); + if( targetComponent == "" || sourceComponent =="") + { + return false; + } + return sourceComponent.equals(targetComponent) ? true : false; } -} +} diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java index 347c6cf..b3d6cdf 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java @@ -1,5 +1,129 @@ package com.devonfw.sample.archunit; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + + +import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; + +/** + * verifying that the batch layer of a component may not depend on the + * logic layer of another component. + */ +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) //adjust packages? public class ComponentRuleC7LayerBatch2Logic4ComponentTest { + + static final String PROJECT_NAME = "com.devonfw.sample.archunit"; + static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; + static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; + + static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition ("have dependencies, towards another components logic layer (Rule-C7).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + String sourceClassName = sourceClass.getFullName(); + String sourceClassLayer = getClassLayer(sourceClass); + String sourceClassComponent = getComponentNameOfClass(sourceClass); + + // All project components batch layer except the default component. + if(sourceClassLayer.equals("batch") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassName = targetClass.getFullName(); + String targetClassComponent = getComponentNameOfClass(targetClass); + String targetClassLayer = getClassLayer(targetClass); + boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); + + // WARNING: Dependency of a components batch layer towards another components layer other than logic wont be registered as a violation. + // (Other rules cover these violations.) + if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("batch") && !dependencyAllowed) { + String message = String.format("Code from batch layer of a component shall not depend on logic layer of a different component. ('%s.%s' is dependend on '%s.%s'. Dependency to (%s). Violated in: (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } + } + } + } + }; + @ArchTest + static final ArchRule no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = + noClasses() + .should(haveNonCompliantComponentDependencies) + .allowEmptyShould(true); + + /** + * Dependency of a components batch layer towards the same components logic or common layer is allowed. + */ + private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { + boolean isAllowed = false; + String targetClassName = targetClass.getFullName(); + + // Components batch layer can depend on their own lower layers: logic and common. + if(classesAreInSameComponent(sourceClass, targetClass) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { + isAllowed = true; + } + // Components may always depend on the default business component. + if(targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Returns the name of the layer of a given class, if the class does lie in a projects component. + * Otherwise returns a blank string "". + */ + private static String getClassLayer(JavaClass clazz) { + String classLayerName = ""; + String className = clazz.getFullName(); + if(className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); + int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; + int endOfLayerName = classComponentAndLayerName.length(); + classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); + } + return classLayerName; + } + + /** + * Returns the name of the component of a given class, if the class does lie in a projects component. + * Otherwise returns a blank string "". + */ + private static String getComponentNameOfClass(JavaClass clazz) { + String classComponentName = ""; + String className = clazz.getFullName(); + if(className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); + int endOfComponentName = classComponentAndLayerName.indexOf("."); + classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); + } + return classComponentName; + } + + /** + * Returns true, if both given classes lie in the same projects component. + * In case at least one of the given classes does not lie in any of the project components false will be returned. + * @param sourceClass JavaClass A to check against another JavaClass B. + * @param targetClass JavaClass B to check against another JavaClass A. + */ + private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { + String sourceComponent = getComponentNameOfClass(sourceClass); + String targetComponent = getComponentNameOfClass(targetClass); + if( targetComponent == "" || sourceComponent =="") + { + return false; + } + return sourceComponent.equals(targetComponent) ? true : false; + } + } + From 1245dcb4f44ff4816b273272bd33f5f31d5065b8 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 28 Feb 2023 08:59:19 +0100 Subject: [PATCH 08/18] Refactored C4 Rule. --- .../ComponentRuleC4LayerService2Logic4ComponentTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java index 9d9d37e..2acacb1 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java @@ -9,7 +9,6 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.importer.ImportOption; @@ -32,7 +31,7 @@ public void check(JavaClass sourceClass, ConditionEvents events) { String sourceClassLayer = getClassLayer(sourceClass); String sourceClassComponent = getComponentNameOfClass(sourceClass); - // All project components service layer except the default component. + // Check all project components service layers except the default component for noncompliant dependencies towards other components logic layer. if(sourceClassLayer.equals("service") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { JavaClass targetClass = dependency.getTargetClass(); @@ -44,7 +43,9 @@ public void check(JavaClass sourceClass, ConditionEvents events) { // WARNING: Dependency of a components service layer towards another components layer other than logic wont be registered as a violation. // (Other rules cover these violations.) if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { - String message = String.format("Code from service layer of a component shall not depend on logic layer of a different component. ('%s.%s' is dependend on '%s.%s'. Dependency to (%s). Violated in: (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", + sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, sourceClassName, targetClass.getDescription()); events.add(new SimpleConditionEvent(sourceClass, true, message)); } } @@ -56,6 +57,7 @@ public void check(JavaClass sourceClass, ConditionEvents events) { static final ArchRule no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() .should(haveNonCompliantComponentDependencies) + .as("Code from service layer of a component shall not depend on logic layer of a different component.") .allowEmptyShould(true); /** From c756ba71637ee5c80cf8bcb1b087993b4d5cf6cd Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 28 Feb 2023 09:35:35 +0100 Subject: [PATCH 09/18] Refactored C3: Added a more concise error message. --- .../ComponentRuleC3LayerService2Service4ComponentTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java index ca29c35..bcaf8a3 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java @@ -16,5 +16,8 @@ public class ComponentRuleC3LayerService2Service4ComponentTest { @ArchTest static final ArchRule no_service_layer_depends_on_service_layer_of_another_component = - slices().matching("..archunit.(*).service..").namingSlices("$1 service").should().notDependOnEachOther(); + slices() + .matching("..archunit.(*).service..") + .namingSlices("$1 service").should().notDependOnEachOther() + .as("Code from service layer shall not depend on service layer of a different component"); } From 604f310b3a3ef377b707ac15bebdc60dbd5d886e Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 28 Feb 2023 10:10:16 +0100 Subject: [PATCH 10/18] Refactored C2 --- .../archunit/ComponentRuleC2ComponentDependencyTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java index 3f6a3aa..c2bf266 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java @@ -52,12 +52,12 @@ public boolean test(Slice input) { .as("Component Task or componentb").should().notDependOnEachOther(); // componentb should not depend on the task component but not vice versa (unidirectional). - // TODO: How to modify the output message ('should not depend on each other' does not fit with my prior message and my intended use case) @ArchTest static final ArchRule component_task_may_depend_on_componentb = slices().matching("..archunit.(*)..").namingSlices("Component $1") .that(containDescription("Component task")) .or(containDescription("Component componentb")) - .as("Task is allowed to depend on componentb but not vice versa.").should().notDependOnEachOther() + .should().notDependOnEachOther() + .as("Component task is allowed to depend on component componentb but not vice versa.") .ignoreDependency(nameContaining("task"), nameContaining("componentb")); } From e3cb0326369fa442568cb1034fabf4dfd07d9ec0 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Fri, 3 Mar 2023 11:18:33 +0100 Subject: [PATCH 11/18] Refactoring C4 Rule + Delete C2 Rule Deleted C2 rule since it is obsolete. It will be covered by #21. Refactoring by automatic code-formatting of devonfw-ide. --- ...omponentRuleC2ComponentDependencyTest.java | 63 ----- ...uleC4LayerService2Logic4ComponentTest.java | 225 +++++++++--------- 2 files changed, 119 insertions(+), 169 deletions(-) delete mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java deleted file mode 100644 index c2bf266..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC2ComponentDependencyTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.devonfw.sample.archunit; - -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.library.dependencies.Slice; - -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; -import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; - -import com.tngtech.archunit.base.DescribedPredicate; -import com.tngtech.archunit.core.importer.ImportOption; -/** - * This is an exemplary component dependency test. It is assumed that the business architecture defines two components: - * - * { - * "architecture": { - * "components": [ - * {"name":"task","dependencies":["componentb"]}, - * {"name":"componentb","dependencies":[]} - * ] - * } - *} - * Component task is allowed to have dependencies towards componentb but not vice versa. - */ -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ComponentRuleC2ComponentDependencyTest { - - private static DescribedPredicate containDescription(String descriptionPart) { - return new DescribedPredicate("contain description '%s'", descriptionPart) { - @Override - public boolean test(Slice input) { - return input.getDescription().contains(descriptionPart); - } - }; - } - - // No components should depend on each other except the general component which can be used by any other component. - static final ArchRule components_should_only_use_their_own_slice_with_custom_ignore = - slices().matching("..archunit.(*)..").namingSlices("Component $1") - .as("Components").should().notDependOnEachOther() - // default business component general holds code which doesn't belong to a specific component - .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")); - - // Components: componentb and task should not depend on each other (bi-directional). - static final ArchRule specific_components_should_only_use_their_own_slice = - slices().matching("..archunit.(*)..").namingSlices("Component $1") - .that(containDescription("Component task")) - .or(containDescription("Component componentb")) - .as("Component Task or componentb").should().notDependOnEachOther(); - - // componentb should not depend on the task component but not vice versa (unidirectional). - @ArchTest - static final ArchRule component_task_may_depend_on_componentb = - slices().matching("..archunit.(*)..").namingSlices("Component $1") - .that(containDescription("Component task")) - .or(containDescription("Component componentb")) - .should().notDependOnEachOther() - .as("Component task is allowed to depend on component componentb but not vice versa.") - .ignoreDependency(nameContaining("task"), nameContaining("componentb")); -} diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java index 2acacb1..2d86c2e 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java @@ -1,5 +1,10 @@ package com.devonfw.sample.archunit; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchCondition; @@ -7,124 +12,132 @@ import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; - /** - * verifying that the service layer of a component may not depend on the - * logic layer of another component. + * verifying that the service layer of a component may not depend on the logic layer of another component. */ @AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) public class ComponentRuleC4LayerService2Logic4ComponentTest { - static final String PROJECT_NAME = "com.devonfw.sample.archunit"; - static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; - static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - - static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition ("have dependencies, towards another components logic layer (Rule-C4).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - String sourceClassName = sourceClass.getFullName(); - String sourceClassLayer = getClassLayer(sourceClass); - String sourceClassComponent = getComponentNameOfClass(sourceClass); - - // Check all project components service layers except the default component for noncompliant dependencies towards other components logic layer. - if(sourceClassLayer.equals("service") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassName = targetClass.getFullName(); - String targetClassComponent = getComponentNameOfClass(targetClass); - String targetClassLayer = getClassLayer(targetClass); - boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); - - // WARNING: Dependency of a components service layer towards another components layer other than logic wont be registered as a violation. - // (Other rules cover these violations.) - if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", - sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, sourceClassName, targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - @ArchTest - static final ArchRule no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = - noClasses() - .should(haveNonCompliantComponentDependencies) - .as("Code from service layer of a component shall not depend on logic layer of a different component.") - .allowEmptyShould(true); - - /** - * Dependency of a components service layer towards the same components logic or common layer is allowed. - */ - private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { - boolean isAllowed = false; - String targetClassName = targetClass.getFullName(); - - // Components service layer can depend on their own lower layers: logic and common. - if(classesAreInSameComponent(sourceClass, targetClass) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { - isAllowed = true; - } - // Components may always depend on the default business component. - if(targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { - isAllowed = true; + static final String PROJECT_NAME = "com.devonfw.sample.archunit"; + + static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; + + static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; + + static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition( + "have dependencies, towards another components logic layer (Rule-C4).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + + String sourceClassName = sourceClass.getFullName(); + String sourceClassLayer = getClassLayer(sourceClass); + String sourceClassComponent = getComponentNameOfClass(sourceClass); + + // Check all project components' service layers except the default component for noncompliant dependencies towards + // other components' logic layers. + if (sourceClassLayer.equals("service") && !sourceClassComponent.equals("") + && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassName = targetClass.getFullName(); + String targetClassComponent = getComponentNameOfClass(targetClass); + String targetClassLayer = getClassLayer(targetClass); + boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); + + // WARNING: Dependency of a components service layer towards another components layer other than logic wont be + // registered as a violation. + // (Other rules cover these violations.) + if (targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, + sourceClassLayer, targetClassComponent, targetClassLayer, sourceClassName, + targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } } - return isAllowed; + } } + }; - /** - * Returns the name of the layer of a given class, if the class does lie in a projects component. - * Otherwise returns a blank string "". - */ - private static String getClassLayer(JavaClass clazz) { - String classLayerName = ""; - String className = clazz.getFullName(); - if(className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); - int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; - int endOfLayerName = classComponentAndLayerName.length(); - classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); - } - return classLayerName; + @ArchTest + static final ArchRule no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() + .should(haveNonCompliantComponentDependencies) + .as("Code from service layer of a component shall not depend on logic layer of a different component.") + .allowEmptyShould(true); + + /** + * Dependency of a components service layer towards the same components logic or common layer is allowed. + */ + private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { + + boolean isAllowed = false; + String targetClassName = targetClass.getFullName(); + + // Components service layer can depend on their own lower layers: logic and common. + if (classesAreInSameComponent(sourceClass, targetClass) + && (targetClassName.contains("logic") || targetClassName.contains("common"))) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) + && (targetClassName.contains("logic") || targetClassName.contains("common"))) { + isAllowed = true; } + return isAllowed; + } - /** - * Returns the name of the component of a given class, if the class does lie in a projects component. - * Otherwise returns a blank string "". - */ - private static String getComponentNameOfClass(JavaClass clazz) { - String classComponentName = ""; - String className = clazz.getFullName(); - if(className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); - int endOfComponentName = classComponentAndLayerName.indexOf("."); - classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); - } - return classComponentName; + /** + * Returns the name of the layer of a given class, if the class does lie in a projects component. Otherwise returns a + * blank string "". + */ + private static String getClassLayer(JavaClass clazz) { + + String classLayerName = ""; + String className = clazz.getFullName(); + if (className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, + classPackageName.length()); + int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; + int endOfLayerName = classComponentAndLayerName.length(); + classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); } + return classLayerName; + } - /** - * Returns true, if both given classes lie in the same projects component. - * In case at least one of the given classes does not lie in any of the project components false will be returned. - * @param sourceClass JavaClass A to check against another JavaClass B. - * @param targetClass JavaClass B to check against another JavaClass A. - */ - private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { - String sourceComponent = getComponentNameOfClass(sourceClass); - String targetComponent = getComponentNameOfClass(targetClass); - if( targetComponent == "" || sourceComponent =="") - { - return false; - } - return sourceComponent.equals(targetComponent) ? true : false; + /** + * Returns the name of the component of a given class, if the class does lie in a projects component. Otherwise + * returns a blank string "". + */ + private static String getComponentNameOfClass(JavaClass clazz) { + + String classComponentName = ""; + String className = clazz.getFullName(); + if (className.startsWith(PROJECT_NAME)) { + String classPackageName = clazz.getPackageName(); + String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, + classPackageName.length()); + int endOfComponentName = classComponentAndLayerName.indexOf("."); + classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); + } + return classComponentName; + } + + /** + * Returns true, if both given classes lie in the same projects component. In case at least one of the given classes + * does not lie in any of the project components false will be returned. + * + * @param sourceClass JavaClass A to check against another JavaClass B. + * @param targetClass JavaClass B to check against another JavaClass A. + */ + private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { + + String sourceComponent = getComponentNameOfClass(sourceClass); + String targetComponent = getComponentNameOfClass(targetClass); + if (targetComponent == "" || sourceComponent == "") { + return false; } + return sourceComponent.equals(targetComponent) ? true : false; + } } From 8706f9bac80b27a926b9c900853186a8584f2270 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Mon, 6 Mar 2023 08:18:36 +0100 Subject: [PATCH 12/18] Update ComponentRuleC4LayerService2Logic4ComponentTest.java Minor refactoring. Changed ArchCondition to final. Renamed boolean dependencyAllowed to isAllowedDependency. --- ...ntRuleC4LayerService2Logic4ComponentTest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java index 2d86c2e..58e3ede 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java @@ -24,7 +24,7 @@ public class ComponentRuleC4LayerService2Logic4ComponentTest { static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition( + static final ArchCondition haveNonCompliantComponentDependencies = new ArchCondition( "have dependencies, towards another components logic layer (Rule-C4).") { @Override public void check(JavaClass sourceClass, ConditionEvents events) { @@ -42,12 +42,12 @@ public void check(JavaClass sourceClass, ConditionEvents events) { String targetClassName = targetClass.getFullName(); String targetClassComponent = getComponentNameOfClass(targetClass); String targetClassLayer = getClassLayer(targetClass); - boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); + boolean isAllowedDependency = isAllowedDependency(sourceClass, targetClass); // WARNING: Dependency of a components service layer towards another components layer other than logic wont be // registered as a violation. // (Other rules cover these violations.) - if (targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { + if (targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !isAllowedDependency) { String message = String.format( "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, sourceClassName, @@ -66,7 +66,11 @@ public void check(JavaClass sourceClass, ConditionEvents events) { .allowEmptyShould(true); /** - * Dependency of a components service layer towards the same components logic or common layer is allowed. + * Dependency of a components service layer towards the same components logic or common layer is allowed. In addition + * a dependency towards the projects default component is allowed too. + * + * @param sourceClass Source JavaClass to check if dependencies from itself towards targetClass are allowed. + * @param targetClass Target JavaClass to check if dependencies from sourceClass towards it are allowed. */ private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { @@ -89,6 +93,8 @@ private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targ /** * Returns the name of the layer of a given class, if the class does lie in a projects component. Otherwise returns a * blank string "". + * + * @param clazz JavaClass to get the layer name off. */ private static String getClassLayer(JavaClass clazz) { @@ -108,6 +114,8 @@ private static String getClassLayer(JavaClass clazz) { /** * Returns the name of the component of a given class, if the class does lie in a projects component. Otherwise * returns a blank string "". + * + * @param clazz JavaClass to get the component name off. */ private static String getComponentNameOfClass(JavaClass clazz) { From 95e5e6cb30c129221f1c1c93e4e64c81c386c2e3 Mon Sep 17 00:00:00 2001 From: Nicolas van Bellen Date: Mon, 6 Mar 2023 09:15:17 +0100 Subject: [PATCH 13/18] Output changes --- .../ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java | 6 ++++-- ...onentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java | 3 ++- .../ComponentRuleC7LayerBatch2Logic4ComponentTest.java | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java index 2fe68d1..4b3cfa2 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java @@ -43,8 +43,9 @@ public void check(JavaClass sourceClass, ConditionEvents events) { // WARNING: Dependency of a components logic layer towards another components layer other than logic wont be registered as a violation. // (Other rules cover these violations.) - if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !dependencyAllowed) { - String message = String.format("Code from logic layer of a component shall not depend on dataaccess layer of a different component. ('%s.%s' is dependend on '%s.%s'. Dependency to (%s). Violated in: (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); + if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("dataaccess") && !dependencyAllowed) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); events.add(new SimpleConditionEvent(sourceClass, true, message)); } } @@ -56,6 +57,7 @@ public void check(JavaClass sourceClass, ConditionEvents events) { static final ArchRule no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = noClasses() .should(haveNonCompliantComponentDependencies) + .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") .allowEmptyShould(true); /** diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java index 6db9c4a..d577ea7 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java @@ -23,5 +23,6 @@ public class ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest { .matching("..archunit.(*).dataaccess..") .namingSlices("$1 dataaccess") .should() - .notDependOnEachOther(); + .notDependOnEachOther() + .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component"); } \ No newline at end of file diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java index b3d6cdf..12ad122 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java @@ -44,7 +44,8 @@ public void check(JavaClass sourceClass, ConditionEvents events) { // WARNING: Dependency of a components batch layer towards another components layer other than logic wont be registered as a violation. // (Other rules cover these violations.) if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("batch") && !dependencyAllowed) { - String message = String.format("Code from batch layer of a component shall not depend on logic layer of a different component. ('%s.%s' is dependend on '%s.%s'. Dependency to (%s). Violated in: (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); events.add(new SimpleConditionEvent(sourceClass, true, message)); } } @@ -56,6 +57,7 @@ public void check(JavaClass sourceClass, ConditionEvents events) { static final ArchRule no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = noClasses() .should(haveNonCompliantComponentDependencies) + .as("Code from batch layer of a component shall not depend on logic layer of a different component.") .allowEmptyShould(true); /** From 2ad2e67bcc33121bea79489f36a2334ddd17f9ff Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Mon, 6 Mar 2023 10:20:11 +0100 Subject: [PATCH 14/18] Refactored C3 and C6 HOTFIX general component use. Co-Authored-By: NicolasVanBellen <74297820+NicolasVanBellen@users.noreply.github.com> --- .../ComponentRuleC3LayerService2Service4ComponentTest.java | 3 +++ ...omponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java index bcaf8a3..e6cdca7 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java @@ -5,6 +5,8 @@ import com.tngtech.archunit.lang.ArchRule; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; import com.tngtech.archunit.core.importer.ImportOption; @@ -19,5 +21,6 @@ public class ComponentRuleC3LayerService2Service4ComponentTest { slices() .matching("..archunit.(*).service..") .namingSlices("$1 service").should().notDependOnEachOther() + .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) .as("Code from service layer shall not depend on service layer of a different component"); } diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java index d577ea7..16ddf80 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java @@ -5,6 +5,8 @@ import com.tngtech.archunit.lang.ArchRule; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; import com.tngtech.archunit.core.importer.ImportOption; @@ -24,5 +26,6 @@ public class ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest { .namingSlices("$1 dataaccess") .should() .notDependOnEachOther() + .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component"); } \ No newline at end of file From 9d3b6adfd6cfb0d0dfcc7459f19b944d9197eb6d Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Mon, 13 Mar 2023 16:35:40 +0100 Subject: [PATCH 15/18] Merged all component rules into one file. --- ...eC3LayerService2Service4ComponentTest.java | 26 -- ...uleC4LayerService2Logic4ComponentTest.java | 151 ---------- ...C5LayerLogic2Dataaccess4ComponentTest.java | 130 --------- ...erDataaccess2Dataaccess4ComponentTest.java | 31 --- ...tRuleC7LayerBatch2Logic4ComponentTest.java | 131 --------- .../sample/archunit/ComponentRuleTest.java | 263 ++++++++++++++++++ 6 files changed, 263 insertions(+), 469 deletions(-) delete mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java delete mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java delete mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java delete mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java delete mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java create mode 100644 src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java deleted file mode 100644 index e6cdca7..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC3LayerService2Service4ComponentTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.devonfw.sample.archunit; - -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchRule; - -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; -import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; - -import com.tngtech.archunit.core.importer.ImportOption; - -/** - * verifying that the service layer of one component does not depend on the service layer of - * another component. - */ -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ComponentRuleC3LayerService2Service4ComponentTest { - @ArchTest - static final ArchRule no_service_layer_depends_on_service_layer_of_another_component = - slices() - .matching("..archunit.(*).service..") - .namingSlices("$1 service").should().notDependOnEachOther() - .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) - .as("Code from service layer shall not depend on service layer of a different component"); -} diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java deleted file mode 100644 index 58e3ede..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC4LayerService2Logic4ComponentTest.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.devonfw.sample.archunit; - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -/** - * verifying that the service layer of a component may not depend on the logic layer of another component. - */ -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ComponentRuleC4LayerService2Logic4ComponentTest { - - static final String PROJECT_NAME = "com.devonfw.sample.archunit"; - - static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; - - static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - - static final ArchCondition haveNonCompliantComponentDependencies = new ArchCondition( - "have dependencies, towards another components logic layer (Rule-C4).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - - String sourceClassName = sourceClass.getFullName(); - String sourceClassLayer = getClassLayer(sourceClass); - String sourceClassComponent = getComponentNameOfClass(sourceClass); - - // Check all project components' service layers except the default component for noncompliant dependencies towards - // other components' logic layers. - if (sourceClassLayer.equals("service") && !sourceClassComponent.equals("") - && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassName = targetClass.getFullName(); - String targetClassComponent = getComponentNameOfClass(targetClass); - String targetClassLayer = getClassLayer(targetClass); - boolean isAllowedDependency = isAllowedDependency(sourceClass, targetClass); - - // WARNING: Dependency of a components service layer towards another components layer other than logic wont be - // registered as a violation. - // (Other rules cover these violations.) - if (targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("logic") && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, - sourceClassLayer, targetClassComponent, targetClassLayer, sourceClassName, - targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - @ArchTest - static final ArchRule no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() - .should(haveNonCompliantComponentDependencies) - .as("Code from service layer of a component shall not depend on logic layer of a different component.") - .allowEmptyShould(true); - - /** - * Dependency of a components service layer towards the same components logic or common layer is allowed. In addition - * a dependency towards the projects default component is allowed too. - * - * @param sourceClass Source JavaClass to check if dependencies from itself towards targetClass are allowed. - * @param targetClass Target JavaClass to check if dependencies from sourceClass towards it are allowed. - */ - private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { - - boolean isAllowed = false; - String targetClassName = targetClass.getFullName(); - - // Components service layer can depend on their own lower layers: logic and common. - if (classesAreInSameComponent(sourceClass, targetClass) - && (targetClassName.contains("logic") || targetClassName.contains("common"))) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) - && (targetClassName.contains("logic") || targetClassName.contains("common"))) { - isAllowed = true; - } - return isAllowed; - } - - /** - * Returns the name of the layer of a given class, if the class does lie in a projects component. Otherwise returns a - * blank string "". - * - * @param clazz JavaClass to get the layer name off. - */ - private static String getClassLayer(JavaClass clazz) { - - String classLayerName = ""; - String className = clazz.getFullName(); - if (className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, - classPackageName.length()); - int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; - int endOfLayerName = classComponentAndLayerName.length(); - classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); - } - return classLayerName; - } - - /** - * Returns the name of the component of a given class, if the class does lie in a projects component. Otherwise - * returns a blank string "". - * - * @param clazz JavaClass to get the component name off. - */ - private static String getComponentNameOfClass(JavaClass clazz) { - - String classComponentName = ""; - String className = clazz.getFullName(); - if (className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, - classPackageName.length()); - int endOfComponentName = classComponentAndLayerName.indexOf("."); - classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); - } - return classComponentName; - } - - /** - * Returns true, if both given classes lie in the same projects component. In case at least one of the given classes - * does not lie in any of the project components false will be returned. - * - * @param sourceClass JavaClass A to check against another JavaClass B. - * @param targetClass JavaClass B to check against another JavaClass A. - */ - private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { - - String sourceComponent = getComponentNameOfClass(sourceClass); - String targetComponent = getComponentNameOfClass(targetClass); - if (targetComponent == "" || sourceComponent == "") { - return false; - } - return sourceComponent.equals(targetComponent) ? true : false; - } - -} diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java deleted file mode 100644 index 4b3cfa2..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC5LayerLogic2Dataaccess4ComponentTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.devonfw.sample.archunit; - -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; - -/** - * verifying that the logic layer of a component may not depend on the - * dataaccess layer of another component. - */ -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ComponentRuleC5LayerLogic2Dataaccess4ComponentTest { - - static final String PROJECT_NAME = "com.devonfw.sample.archunit"; - static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; - static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - - static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition ("have dependencies, towards another components dataaccess layer (Rule-C5).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - String sourceClassName = sourceClass.getFullName(); - String sourceClassLayer = getClassLayer(sourceClass); - String sourceClassComponent = getComponentNameOfClass(sourceClass); - - // All project components logic layer except the default component. - if(sourceClassLayer.equals("logic") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassName = targetClass.getFullName(); - String targetClassComponent = getComponentNameOfClass(targetClass); - String targetClassLayer = getClassLayer(targetClass); - boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); - - // WARNING: Dependency of a components logic layer towards another components layer other than logic wont be registered as a violation. - // (Other rules cover these violations.) - if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("dataaccess") && !dependencyAllowed) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - @ArchTest - static final ArchRule no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = - noClasses() - .should(haveNonCompliantComponentDependencies) - .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") - .allowEmptyShould(true); - - /** - * Dependency of a components logic layer towards the same components dataaccess or common layer is allowed. - */ - private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { - boolean isAllowed = false; - String targetClassName = targetClass.getFullName(); - - // Components logic layer can depend on their own lower layers: dataaccess and common. - if(classesAreInSameComponent(sourceClass, targetClass) && (targetClassName.contains("dataaccess") || targetClassName.contains("common"))) { - isAllowed = true; - } - // Components may always depend on the default business component. - if(targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) && (targetClassName.contains("dataaccess") || targetClassName.contains("common"))) { - isAllowed = true; - } - return isAllowed; - } - - /** - * Returns the name of the layer of a given class, if the class does lie in a projects component. - * Otherwise returns a blank string "". - */ - private static String getClassLayer(JavaClass clazz) { - String classLayerName = ""; - String className = clazz.getFullName(); - if(className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); - int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; - int endOfLayerName = classComponentAndLayerName.length(); - classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); - } - return classLayerName; - } - - /** - * Returns the name of the component of a given class, if the class does lie in a projects component. - * Otherwise returns a blank string "". - */ - private static String getComponentNameOfClass(JavaClass clazz) { - String classComponentName = ""; - String className = clazz.getFullName(); - if(className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); - int endOfComponentName = classComponentAndLayerName.indexOf("."); - classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); - } - return classComponentName; - } - - /** - * Returns true, if both given classes lie in the same projects component. - * In case at least one of the given classes does not lie in any of the project components false will be returned. - * @param sourceClass JavaClass A to check against another JavaClass B. - * @param targetClass JavaClass B to check against another JavaClass A. - */ - private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { - String sourceComponent = getComponentNameOfClass(sourceClass); - String targetComponent = getComponentNameOfClass(targetClass); - if( targetComponent == "" || sourceComponent =="") - { - return false; - } - return sourceComponent.equals(targetComponent) ? true : false; - } - -} diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java deleted file mode 100644 index 16ddf80..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.devonfw.sample.archunit; - -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchRule; - -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; -import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; - -import com.tngtech.archunit.core.importer.ImportOption; - -/** - * verifying that the dataaccess layer of one component does not depend on the dataaccess layer of - * another component. - */ - -// medium severity - -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ComponentRuleC6LayerDataaccess2Dataaccess4ComponentTest { - @ArchTest - static final ArchRule no_dataaccess_layer_depends_on_dataaccess_layer_of_another_component = - slices() - .matching("..archunit.(*).dataaccess..") - .namingSlices("$1 dataaccess") - .should() - .notDependOnEachOther() - .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) - .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component"); -} \ No newline at end of file diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java deleted file mode 100644 index 12ad122..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleC7LayerBatch2Logic4ComponentTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.devonfw.sample.archunit; - -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; - -/** - * verifying that the batch layer of a component may not depend on the - * logic layer of another component. - */ -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) //adjust packages? -public class ComponentRuleC7LayerBatch2Logic4ComponentTest { - - static final String PROJECT_NAME = "com.devonfw.sample.archunit"; - static final String DEFAULT_COMPONENT_SIMPLE_NAME = "general"; - static final String DEFAULT_COMPONENT_FULL_NAME = PROJECT_NAME + "." + DEFAULT_COMPONENT_SIMPLE_NAME; - - static ArchCondition haveNonCompliantComponentDependencies = new ArchCondition ("have dependencies, towards another components logic layer (Rule-C7).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - String sourceClassName = sourceClass.getFullName(); - String sourceClassLayer = getClassLayer(sourceClass); - String sourceClassComponent = getComponentNameOfClass(sourceClass); - - // All project components batch layer except the default component. - if(sourceClassLayer.equals("batch") && !sourceClassComponent.equals("") && !sourceClassComponent.equals(DEFAULT_COMPONENT_SIMPLE_NAME)){ - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassName = targetClass.getFullName(); - String targetClassComponent = getComponentNameOfClass(targetClass); - String targetClassLayer = getClassLayer(targetClass); - boolean dependencyAllowed = isAllowedDependency(sourceClass, targetClass); - - // WARNING: Dependency of a components batch layer towards another components layer other than logic wont be registered as a violation. - // (Other rules cover these violations.) - if(targetClassName.startsWith(PROJECT_NAME) && targetClassLayer.equals("batch") && !dependencyAllowed) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, targetClass.getDescription(), sourceClassName); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - @ArchTest - static final ArchRule no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = - noClasses() - .should(haveNonCompliantComponentDependencies) - .as("Code from batch layer of a component shall not depend on logic layer of a different component.") - .allowEmptyShould(true); - - /** - * Dependency of a components batch layer towards the same components logic or common layer is allowed. - */ - private static boolean isAllowedDependency(JavaClass sourceClass, JavaClass targetClass) { - boolean isAllowed = false; - String targetClassName = targetClass.getFullName(); - - // Components batch layer can depend on their own lower layers: logic and common. - if(classesAreInSameComponent(sourceClass, targetClass) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { - isAllowed = true; - } - // Components may always depend on the default business component. - if(targetClassName.startsWith(DEFAULT_COMPONENT_FULL_NAME) && (targetClassName.contains("logic") || targetClassName.contains("common"))) { - isAllowed = true; - } - return isAllowed; - } - - /** - * Returns the name of the layer of a given class, if the class does lie in a projects component. - * Otherwise returns a blank string "". - */ - private static String getClassLayer(JavaClass clazz) { - String classLayerName = ""; - String className = clazz.getFullName(); - if(className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); - int beginOfLayerName = classComponentAndLayerName.indexOf(".") + 1; - int endOfLayerName = classComponentAndLayerName.length(); - classLayerName = classComponentAndLayerName.substring(beginOfLayerName, endOfLayerName); - } - return classLayerName; - } - - /** - * Returns the name of the component of a given class, if the class does lie in a projects component. - * Otherwise returns a blank string "". - */ - private static String getComponentNameOfClass(JavaClass clazz) { - String classComponentName = ""; - String className = clazz.getFullName(); - if(className.startsWith(PROJECT_NAME)) { - String classPackageName = clazz.getPackageName(); - String classComponentAndLayerName = classPackageName.substring(PROJECT_NAME.length() + 1, classPackageName.length()); - int endOfComponentName = classComponentAndLayerName.indexOf("."); - classComponentName = classComponentAndLayerName.substring(0, endOfComponentName); - } - return classComponentName; - } - - /** - * Returns true, if both given classes lie in the same projects component. - * In case at least one of the given classes does not lie in any of the project components false will be returned. - * @param sourceClass JavaClass A to check against another JavaClass B. - * @param targetClass JavaClass B to check against another JavaClass A. - */ - private static boolean classesAreInSameComponent(JavaClass sourceClass, JavaClass targetClass) { - String sourceComponent = getComponentNameOfClass(sourceClass); - String targetComponent = getComponentNameOfClass(targetClass); - if( targetComponent == "" || sourceComponent =="") - { - return false; - } - return sourceComponent.equals(targetComponent) ? true : false; - } - -} - diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java new file mode 100644 index 0000000..f81cbdc --- /dev/null +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java @@ -0,0 +1,263 @@ +package com.devonfw.sample.archunit; + +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; + +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; + +@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) +public class ComponentRuleTest { + + /** + * verifying that the service layer of one component does not depend on the + * service layer of + * another component. + */ + @ArchTest + public static final ArchRule C3_no_service_layer_depends_on_service_layer_of_another_component = slices() + .matching("..archunit.(*).service..") + .namingSlices("$1 service").should().notDependOnEachOther() + .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) + .as("Code from service layer shall not depend on service layer of a different component"); + + public static final ArchCondition haveComponentServiceLayerDependingOnDiffComponentsLogicLayer = new ArchCondition( + "have dependencies, towards another components logic layer (Rule-C4).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + + String sourceClassPackageName = sourceClass.getPackageName(); + PackageStructure sourcePkg = PackageStructure.of(sourceClassPackageName); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + // Check all project components' service layers except the default component for + // noncompliant dependencies towards + // other components' logic layers. + if (sourcePkg.isLayerService() && !sourceClassComponent.equals("") + && !sourceClassComponent.equals("general")) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassPackageName = targetClass.getPackageName(); + PackageStructure targetPkg = PackageStructure.of(targetClassPackageName); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentServiceLayerCompliant(sourceClass, targetClass); + + // WARNING: Dependency of a components service layer towards another components + // layer other than logic wont be + // registered as a violation. + // (Other rules cover these violations.) + if (targetPkg.isLayerLogic() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", + sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, + sourceClass.getDescription(), targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } + } + } + } + }; + + /** + * verifying that the service layer of one component does not depend on the + * logic layer of + * another component. + */ + @ArchTest + public static final ArchRule C4_no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() + .should(haveComponentServiceLayerDependingOnDiffComponentsLogicLayer) + .as("Code from service layer of a component shall not depend on logic layer of a different component.") + .allowEmptyShould(true); + + public static ArchCondition haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( + "have dependencies, towards another components dataaccess layer (Rule-C5).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + String sourceClassPackageName = sourceClass.getPackageName(); + PackageStructure sourcePkg = PackageStructure.of(sourceClassPackageName); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // All project components logic layer except the default component. + if (sourceClassLayer.equals("logic") && !sourceClassComponent.equals("") + && !sourceClassComponent.equals("general")) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassPackageName = targetClass.getPackageName(); + PackageStructure targetPkg = PackageStructure.of(targetClassPackageName); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentLogicLayerCompliant(sourceClass, targetClass); + + // WARNING: Dependency of a components logic layer towards another components + // layer other than logic wont be registered as a violation. + // (Other rules cover these violations.) + if (targetPkg.isLayerDataAccess() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", + sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, + sourceClass.getDescription(), targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } + } + } + } + }; + + /** + * verifying that the logic layer of a component may not depend on the + * dataaccess layer of another component. + */ + @ArchTest + public static final ArchRule C5_no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = noClasses() + .should(haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer) + .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") + .allowEmptyShould(true); + + /** + * verifying that the dataaccess layer of one component does not depend on the + * dataaccess layer of + * another component. + */ + + // medium severity + @ArchTest + public static final ArchRule C6_no_dataaccess_layer_depends_on_dataaccess_layer_of_another_component = slices() + .matching("..archunit.(*).dataaccess..") + .namingSlices("$1 dataaccess") + .should() + .notDependOnEachOther() + .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) + .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component"); + + public static ArchCondition haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( + "have dependencies, towards another components logic layer (Rule-C7).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + String sourceClassPackageName = sourceClass.getPackageName(); + PackageStructure sourcePkg = PackageStructure.of(sourceClassPackageName); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // All project components batch layer except the default component. + if (sourcePkg.isLayerBatch() && !sourceClassComponent.equals("") + && !sourceClassComponent.equals("general")) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + String targetClassPackageName = targetClass.getPackageName(); + PackageStructure targetPkg = PackageStructure.of(targetClassPackageName); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentBatchLayerCompliant(sourceClass, targetClass); + + // WARNING: Dependency of a components batch layer towards another components + // layer other than logic wont be registered as a violation. + // (Other rules cover these violations.) + if (targetPkg.isLayerBatch() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", + sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, + sourceClass.getDescription(), targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } + } + } + } + }; + + /** + * verifying that the batch layer of a component may not depend on the + * logic layer of another component. + */ + @ArchTest + public static final ArchRule C7_no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = noClasses() + .should(haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer) + .as("Code from batch layer of a component shall not depend on logic layer of a different component.") + .allowEmptyShould(true); + + /** + * Dependency of a components batch layer towards the same components logic or + * common layer is allowed. + */ + public static boolean isComponentBatchLayerCompliant(JavaClass sourceClass, JavaClass targetClass) { + boolean isAllowed = false; + PackageStructure sourcePkg = PackageStructure.of(sourceClass.getPackageName()); + PackageStructure targetPkg = PackageStructure.of(targetClass.getPackageName()); + // Components batch layer can depend on their own lower layers: logic and + // common. + if (sourcePkg.hasSameComponent(targetPkg) + && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.getComponent().equals("general") + && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Dependency of a components logic layer towards the same components dataaccess + * or common layer is allowed. + */ + public static boolean isComponentLogicLayerCompliant(JavaClass sourceClass, JavaClass targetClass) { + boolean isAllowed = false; + PackageStructure sourcePkg = PackageStructure.of(sourceClass.getPackageName()); + PackageStructure targetPkg = PackageStructure.of(targetClass.getPackageName()); + // Components logic layer can depend on their own lower layers: dataaccess and + // common. + if (sourcePkg.hasSameComponent(targetPkg) + && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.getComponent().equals("general") + && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Dependency of a components service layer towards the same components logic or + * common layer is allowed. In addition + * a dependency towards the projects default component is allowed too. + * + * @param sourceClass Source JavaClass to check if dependencies from itself + * towards targetClass are allowed. + * @param targetClass Target JavaClass to check if dependencies from sourceClass + * towards it are allowed. + */ + public static boolean isComponentServiceLayerCompliant(JavaClass sourceClass, JavaClass targetClass) { + + boolean isAllowed = false; + PackageStructure sourcePkg = PackageStructure.of(sourceClass.getPackageName()); + PackageStructure targetPkg = PackageStructure.of(targetClass.getPackageName()); + // Components service layer can depend on their own lower layers: logic and + // common. + if (sourcePkg.hasSameComponent(targetPkg) + && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.getComponent().equals("general") + && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + +} From e9fb52716f29968693f17c426e001da20a91b3af Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Mon, 13 Mar 2023 19:08:21 +0100 Subject: [PATCH 16/18] Update ComponentRuleTest.java @NicolasVanBellen Adjusted component rules C3-C7 to use the newly introduced PackageStructure type. Co-Authored-By: NicolasVanBellen <74297820+NicolasVanBellen@users.noreply.github.com> --- .../java/com/devonfw/sample/archunit/ComponentRuleTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java index f81cbdc..b66118c 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java @@ -91,7 +91,7 @@ public void check(JavaClass sourceClass, ConditionEvents events) { String sourceClassComponent = sourcePkg.getComponent(); // All project components logic layer except the default component. - if (sourceClassLayer.equals("logic") && !sourceClassComponent.equals("") + if (sourcePkg.isLayerLogic() && !sourceClassComponent.equals("") && !sourceClassComponent.equals("general")) { for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { JavaClass targetClass = dependency.getTargetClass(); From ee6983045a9bc74ab3106c82889260939bcf38b7 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 14 Mar 2023 13:52:29 +0100 Subject: [PATCH 17/18] Reworked Component Rules Updated PackageStructure.java: - Added a projects architecture DEFAULT_COMPONENT field. - Added a isComponentGeneral() getter. Updated ComponentRuleTest.java: - Reimplemented C3 and C6 using a custom ArchCondition + PackageStructure - Resolved all review annotation of @hohwille Co-Authored-By: NicolasVanBellen <74297820+NicolasVanBellen@users.noreply.github.com> --- .../sample/archunit/ComponentRuleTest.java | 541 ++++++++++-------- .../sample/archunit/PackageStructure.java | 10 + 2 files changed, 313 insertions(+), 238 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java index b66118c..6eea1b0 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java @@ -1,11 +1,5 @@ package com.devonfw.sample.archunit; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import com.tngtech.archunit.core.domain.Dependency; @@ -13,251 +7,322 @@ import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; - -import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; @AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) public class ComponentRuleTest { - /** - * verifying that the service layer of one component does not depend on the - * service layer of - * another component. - */ - @ArchTest - public static final ArchRule C3_no_service_layer_depends_on_service_layer_of_another_component = slices() - .matching("..archunit.(*).service..") - .namingSlices("$1 service").should().notDependOnEachOther() - .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) - .as("Code from service layer shall not depend on service layer of a different component"); - - public static final ArchCondition haveComponentServiceLayerDependingOnDiffComponentsLogicLayer = new ArchCondition( - "have dependencies, towards another components logic layer (Rule-C4).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - - String sourceClassPackageName = sourceClass.getPackageName(); - PackageStructure sourcePkg = PackageStructure.of(sourceClassPackageName); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - // Check all project components' service layers except the default component for - // noncompliant dependencies towards - // other components' logic layers. - if (sourcePkg.isLayerService() && !sourceClassComponent.equals("") - && !sourceClassComponent.equals("general")) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassPackageName = targetClass.getPackageName(); - PackageStructure targetPkg = PackageStructure.of(targetClassPackageName); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentServiceLayerCompliant(sourceClass, targetClass); - - // WARNING: Dependency of a components service layer towards another components - // layer other than logic wont be - // registered as a violation. - // (Other rules cover these violations.) - if (targetPkg.isLayerLogic() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", - sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, - sourceClass.getDescription(), targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - /** - * verifying that the service layer of one component does not depend on the - * logic layer of - * another component. - */ - @ArchTest - public static final ArchRule C4_no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() - .should(haveComponentServiceLayerDependingOnDiffComponentsLogicLayer) - .as("Code from service layer of a component shall not depend on logic layer of a different component.") - .allowEmptyShould(true); - - public static ArchCondition haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( - "have dependencies, towards another components dataaccess layer (Rule-C5).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - String sourceClassPackageName = sourceClass.getPackageName(); - PackageStructure sourcePkg = PackageStructure.of(sourceClassPackageName); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // All project components logic layer except the default component. - if (sourcePkg.isLayerLogic() && !sourceClassComponent.equals("") - && !sourceClassComponent.equals("general")) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassPackageName = targetClass.getPackageName(); - PackageStructure targetPkg = PackageStructure.of(targetClassPackageName); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentLogicLayerCompliant(sourceClass, targetClass); - - // WARNING: Dependency of a components logic layer towards another components - // layer other than logic wont be registered as a violation. - // (Other rules cover these violations.) - if (targetPkg.isLayerDataAccess() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", - sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, - sourceClass.getDescription(), targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - /** - * verifying that the logic layer of a component may not depend on the - * dataaccess layer of another component. - */ - @ArchTest - public static final ArchRule C5_no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = noClasses() - .should(haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer) - .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") - .allowEmptyShould(true); - - /** - * verifying that the dataaccess layer of one component does not depend on the - * dataaccess layer of - * another component. - */ - - // medium severity - @ArchTest - public static final ArchRule C6_no_dataaccess_layer_depends_on_dataaccess_layer_of_another_component = slices() - .matching("..archunit.(*).dataaccess..") - .namingSlices("$1 dataaccess") - .should() - .notDependOnEachOther() - .ignoreDependency(alwaysTrue(), nameMatching(".*general.*")) - .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component"); - - public static ArchCondition haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( - "have dependencies, towards another components logic layer (Rule-C7).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - String sourceClassPackageName = sourceClass.getPackageName(); - PackageStructure sourcePkg = PackageStructure.of(sourceClassPackageName); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // All project components batch layer except the default component. - if (sourcePkg.isLayerBatch() && !sourceClassComponent.equals("") - && !sourceClassComponent.equals("general")) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - JavaClass targetClass = dependency.getTargetClass(); - String targetClassPackageName = targetClass.getPackageName(); - PackageStructure targetPkg = PackageStructure.of(targetClassPackageName); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentBatchLayerCompliant(sourceClass, targetClass); - - // WARNING: Dependency of a components batch layer towards another components - // layer other than logic wont be registered as a violation. - // (Other rules cover these violations.) - if (targetPkg.isLayerBatch() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", - sourceClassComponent, sourceClassLayer, targetClassComponent, targetClassLayer, - sourceClass.getDescription(), targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; - - /** - * verifying that the batch layer of a component may not depend on the - * logic layer of another component. - */ - @ArchTest - public static final ArchRule C7_no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = noClasses() - .should(haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer) - .as("Code from batch layer of a component shall not depend on logic layer of a different component.") - .allowEmptyShould(true); - - /** - * Dependency of a components batch layer towards the same components logic or - * common layer is allowed. - */ - public static boolean isComponentBatchLayerCompliant(JavaClass sourceClass, JavaClass targetClass) { - boolean isAllowed = false; - PackageStructure sourcePkg = PackageStructure.of(sourceClass.getPackageName()); - PackageStructure targetPkg = PackageStructure.of(targetClass.getPackageName()); - // Components batch layer can depend on their own lower layers: logic and - // common. - if (sourcePkg.hasSameComponent(targetPkg) - && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetPkg.getComponent().equals("general") - && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; + public static final ArchCondition haveComponentServiceLayerDependingOnDiffComponentsServiceLayer = new ArchCondition( + "have dependencies, towards another components service layer (Rule-C3).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + + PackageStructure sourcePkg = PackageStructure.of(sourceClass); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // Check all project components' service layers except the default component for noncompliant dependencies towards + // other components' service layers. + if (sourcePkg.isLayerService() && !sourcePkg.isComponentGeneral()) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentServiceLayerCompliantTowardsComponenetsServiceLayer(sourcePkg, + targetPkg); + + if (targetPkg.isLayerService() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, + sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), + targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } } - return isAllowed; + } } + }; + + /** + * verifying that the service layer of one component does not depend on the service layer of another component. + */ + @ArchTest + public static final ArchRule C3_no_service_layer_depends_on_service_layer_of_another_component = noClasses() + .should(haveComponentServiceLayerDependingOnDiffComponentsServiceLayer) + .as("Code from service layer of a component shall not depend on service layer of a different component.") + .allowEmptyShould(true); + + public static final ArchCondition haveComponentServiceLayerDependingOnDiffComponentsLogicLayer = new ArchCondition( + "have dependencies, towards another components logic layer (Rule-C4).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { - /** - * Dependency of a components logic layer towards the same components dataaccess - * or common layer is allowed. - */ - public static boolean isComponentLogicLayerCompliant(JavaClass sourceClass, JavaClass targetClass) { - boolean isAllowed = false; - PackageStructure sourcePkg = PackageStructure.of(sourceClass.getPackageName()); - PackageStructure targetPkg = PackageStructure.of(targetClass.getPackageName()); - // Components logic layer can depend on their own lower layers: dataaccess and - // common. - if (sourcePkg.hasSameComponent(targetPkg) - && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { - isAllowed = true; + PackageStructure sourcePkg = PackageStructure.of(sourceClass); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // Check all project components' service layers except the default component for noncompliant dependencies towards + // other components' logic layers. + if (sourcePkg.isLayerService() && !sourcePkg.isComponentGeneral()) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentServiceLayerCompliantTowardsComponentsLogicLayer(sourcePkg, + targetPkg); + + if (targetPkg.isLayerLogic() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, + sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), + targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } } - // Components may always depend on the default business component. - if (targetPkg.getComponent().equals("general") - && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { - isAllowed = true; + } + } + }; + + /** + * verifying that the service layer of one component does not depend on the logic layer of another component. + */ + @ArchTest + public static final ArchRule C4_no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() + .should(haveComponentServiceLayerDependingOnDiffComponentsLogicLayer) + .as("Code from service layer of a component shall not depend on logic layer of a different component.") + .allowEmptyShould(true); + + public static final ArchCondition haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( + "have dependencies, towards another components dataaccess layer (Rule-C5).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + + PackageStructure sourcePkg = PackageStructure.of(sourceClass); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // Check all project components' logic layers except the default component for noncompliant dependencies towards + // other components' dataaccess layers. + if (sourcePkg.isLayerLogic() && !sourcePkg.isComponentGeneral()) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentLogicLayerCompliant(sourcePkg, targetPkg); + + if (targetPkg.isLayerDataAccess() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, + sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), + targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } } - return isAllowed; + } } + }; + + /** + * verifying that the logic layer of a component may not depend on the dataaccess layer of another component. + */ + @ArchTest + public static final ArchRule C5_no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = noClasses() + .should(haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer) + .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") + .allowEmptyShould(true); + + public static final ArchCondition haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( + "have dependencies, towards another dataaccess layer (Rule-C6).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { - /** - * Dependency of a components service layer towards the same components logic or - * common layer is allowed. In addition - * a dependency towards the projects default component is allowed too. - * - * @param sourceClass Source JavaClass to check if dependencies from itself - * towards targetClass are allowed. - * @param targetClass Target JavaClass to check if dependencies from sourceClass - * towards it are allowed. - */ - public static boolean isComponentServiceLayerCompliant(JavaClass sourceClass, JavaClass targetClass) { - - boolean isAllowed = false; - PackageStructure sourcePkg = PackageStructure.of(sourceClass.getPackageName()); - PackageStructure targetPkg = PackageStructure.of(targetClass.getPackageName()); - // Components service layer can depend on their own lower layers: logic and - // common. - if (sourcePkg.hasSameComponent(targetPkg) - && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; + PackageStructure sourcePkg = PackageStructure.of(sourceClass); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // Check all project components' dataaccess layers except the default component for noncompliant dependencies + // towards + // other components' dataaccess layers. + if (sourcePkg.isLayerDataAccess() && !sourcePkg.isComponentGeneral()) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentDataaccessLayerCompliant(sourcePkg, targetPkg); + + if (targetPkg.isLayerDataAccess() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, + sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), + targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } } - // Components may always depend on the default business component. - if (targetPkg.getComponent().equals("general") - && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; + } + } + }; + + // medium severity + /** + * verifying that the dataaccess layer of one component does not depend on the dataaccess layer of another component. + */ + @ArchTest + public static final ArchRule C6_no_dataaccess_layer_depends_on_dataaccess_layer_of_another_component = noClasses() + .should(haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer) + .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component") + .allowEmptyShould(true); + + public static final ArchCondition haveComponentBatchLayerDependingOnDiffComponentsLogicLayer = new ArchCondition( + "have dependencies, towards another components logic layer (Rule-C7).") { + @Override + public void check(JavaClass sourceClass, ConditionEvents events) { + + PackageStructure sourcePkg = PackageStructure.of(sourceClass); + String sourceClassLayer = sourcePkg.getLayer(); + String sourceClassComponent = sourcePkg.getComponent(); + + // Check all project components' batch layers except the default component for noncompliant dependencies + // towards + // other components' logic layers. + if (sourcePkg.isLayerBatch() && !sourcePkg.isComponentGeneral()) { + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + String targetClassComponent = targetPkg.getComponent(); + String targetClassLayer = targetPkg.getLayer(); + boolean isAllowedDependency = isComponentBatchLayerCompliant(sourcePkg, targetPkg); + + if (targetPkg.isLayerLogic() && !isAllowedDependency) { + String message = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, + sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), + targetClass.getDescription()); + events.add(new SimpleConditionEvent(sourceClass, true, message)); + } } - return isAllowed; + } + } + }; + + /** + * verifying that the batch layer of a component may not depend on the logic layer of another component. + */ + @ArchTest + public static final ArchRule C7_no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = noClasses() + .should(haveComponentBatchLayerDependingOnDiffComponentsLogicLayer) + .as("Code from batch layer of a component shall not depend on logic layer of a different component.") + .allowEmptyShould(true); + + /** + * Dependency of a components batch layer towards the same components logic or common layer is allowed. In addition a + * dependency towards the projects default component is allowed too. + */ + private static boolean isComponentBatchLayerCompliant(PackageStructure sourcePkg, PackageStructure targetPkg) { + + boolean isAllowed = false; + // Components batch layer can depend on their own lower layers: logic and + // common. + if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.isComponentGeneral() && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Dependency of a components dataaccess layer towards the same components common layer is allowed. In addition a + * dependency towards the projects default component is allowed too. + */ + private static boolean isComponentDataaccessLayerCompliant(PackageStructure sourcePkg, PackageStructure targetPkg) { + + boolean isAllowed = false; + // Components dataaccess layer can depend on their own dataaccess and common layer. + if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.isComponentGeneral() && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Dependency of a components logic layer towards the same components dataaccess or common layer is allowed. In + * addition a dependency towards the projects default component is allowed too. + */ + private static boolean isComponentLogicLayerCompliant(PackageStructure sourcePkg, PackageStructure targetPkg) { + + boolean isAllowed = false; + // Components logic layer can depend on their own lower layers: dataaccess and + // common. + if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.isComponentGeneral() && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Dependency of a components service layer towards the same components logic or common layer is allowed. In addition + * a dependency towards the projects default component is allowed too. + * + * @param sourceClass Source JavaClass to check if dependencies from itself towards targetClass are allowed. + * @param targetClass Target JavaClass to check if dependencies from sourceClass towards it are allowed. + */ + private static boolean isComponentServiceLayerCompliantTowardsComponentsLogicLayer(PackageStructure sourcePkg, + PackageStructure targetPkg) { + + boolean isAllowed = false; + // Components service layer can depend on their own lower layers: logic and + // common. + if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.isComponentGeneral() && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + return isAllowed; + } + + /** + * Dependency of a components service layer towards the same components logic and common layer is allowed. In addition + * a dependency towards the projects default component is allowed too. + */ + private static boolean isComponentServiceLayerCompliantTowardsComponenetsServiceLayer(PackageStructure sourcePkg, + PackageStructure targetPkg) { + + boolean isAllowed = false; + // Components service layer can depend on their own logic and common layer. + if (sourcePkg.hasSameComponent(targetPkg) + && (targetPkg.isLayerService() || targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; + } + // Components may always depend on the default business component. + if (targetPkg.isComponentGeneral() + && (targetPkg.isLayerService() || targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { + isAllowed = true; } + return isAllowed; + } } diff --git a/src/test/java/com/devonfw/sample/archunit/PackageStructure.java b/src/test/java/com/devonfw/sample/archunit/PackageStructure.java index 1e224c7..fb67df6 100644 --- a/src/test/java/com/devonfw/sample/archunit/PackageStructure.java +++ b/src/test/java/com/devonfw/sample/archunit/PackageStructure.java @@ -38,6 +38,8 @@ public class PackageStructure { /** The {@link #getScope() scope} {@value} */ public static final String SCOPE_IMPLEMENTATION = "impl"; + public static final String DEFAULT_COMPONENT = "general"; + private final String packageName; private final boolean valid; @@ -152,6 +154,14 @@ public boolean hasSameComponent(PackageStructure otherPkg) { return getComponent().equals(otherPkg.getComponent()); } + /** + * @return {@code true} if this is the default architecture component. + */ + public boolean isComponentGeneral() { + + return getComponent().equals(DEFAULT_COMPONENT); + } + /** * @return the name of the layer. Will be the empty {@link String} if not {@link #isValid() valid}. */ From 60c4b38534a814c735a0d28ad66436eac09e885b Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 21 Mar 2023 00:42:35 +0100 Subject: [PATCH 18/18] Update ComponentRuleTest.java Reworked component rules according to recent review. - added DescribedPredicates - added composeViolationMessage method - added test for general component --- .../sample/archunit/ComponentRuleTest.java | 420 ++++++++---------- 1 file changed, 188 insertions(+), 232 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java index 6eea1b0..4c752aa 100644 --- a/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ComponentRuleTest.java @@ -2,6 +2,7 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.importer.ImportOption; @@ -15,314 +16,269 @@ @AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) public class ComponentRuleTest { - public static final ArchCondition haveComponentServiceLayerDependingOnDiffComponentsServiceLayer = new ArchCondition( - "have dependencies, towards another components service layer (Rule-C3).") { + private static DescribedPredicate resideInServiceLayerOfAComponent = new DescribedPredicate( + "lie inside the service layer of a custom component different from the business architectures default general component") { + @Override + public boolean test(JavaClass input) { + + PackageStructure inputPkg = PackageStructure.of(input); + boolean someCustomComponentServiceClass = inputPkg.isLayerService() && !inputPkg.isComponentGeneral() + && inputPkg.isValid(); + return someCustomComponentServiceClass; + } + }; + + private static DescribedPredicate resideInLogicLayerOfAComponent = new DescribedPredicate( + "lie inside the logic layer of a custom component different from the business architectures default general component") { + @Override + public boolean test(JavaClass input) { + + PackageStructure inputPkg = PackageStructure.of(input); + boolean someCustomComponentLogicClass = inputPkg.isLayerLogic() && !inputPkg.isComponentGeneral() + && inputPkg.isValid(); + return someCustomComponentLogicClass; + } + }; + + private static DescribedPredicate resideInDataaccessLayerOfAComponent = new DescribedPredicate( + "lie inside the dataaccess layer of a custom component different from the business architectures default general component") { + @Override + public boolean test(JavaClass input) { + + PackageStructure inputPkg = PackageStructure.of(input); + boolean someCustomComponentDataaccessClass = inputPkg.isLayerDataAccess() && !inputPkg.isComponentGeneral() + && inputPkg.isValid(); + return someCustomComponentDataaccessClass; + } + }; + + private static DescribedPredicate resideInBatchLayerOfAComponent = new DescribedPredicate( + "lie inside the batch layer of a custom component different from the business architectures default general component") { + @Override + public boolean test(JavaClass input) { + + PackageStructure inputPkg = PackageStructure.of(input); + boolean someCustomComponentBatchClass = inputPkg.isLayerBatch() && !inputPkg.isComponentGeneral() + && inputPkg.isValid(); + return someCustomComponentBatchClass; + } + }; + + private static DescribedPredicate resideInTheGeneralProjectComponent = new DescribedPredicate( + "lie inside the business architectures default general component") { + @Override + public boolean test(JavaClass input) { + + PackageStructure inputPkg = PackageStructure.of(input); + boolean someGeneralComponentClass = inputPkg.isComponentGeneral() && inputPkg.isValid(); + return someGeneralComponentClass; + } + }; + + public static final ArchCondition dependOnDiffCustomComponents = new ArchCondition( + "depend on a different custom component") { @Override public void check(JavaClass sourceClass, ConditionEvents events) { PackageStructure sourcePkg = PackageStructure.of(sourceClass); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // Check all project components' service layers except the default component for noncompliant dependencies towards - // other components' service layers. - if (sourcePkg.isLayerService() && !sourcePkg.isComponentGeneral()) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - - JavaClass targetClass = dependency.getTargetClass(); - PackageStructure targetPkg = PackageStructure.of(targetClass); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentServiceLayerCompliantTowardsComponenetsServiceLayer(sourcePkg, - targetPkg); - - if (targetPkg.isLayerService() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, - sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), - targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } + + // Check for noncompliant dependencies towards other custom components'. + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + boolean isAllowedDependency = isDependingOnAnotherCustomComponent(sourcePkg, targetPkg); + + if (!isAllowedDependency) { + String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg); + events.add(new SimpleConditionEvent(sourceClass, true, message)); } } } }; - /** - * verifying that the service layer of one component does not depend on the service layer of another component. - */ - @ArchTest - public static final ArchRule C3_no_service_layer_depends_on_service_layer_of_another_component = noClasses() - .should(haveComponentServiceLayerDependingOnDiffComponentsServiceLayer) - .as("Code from service layer of a component shall not depend on service layer of a different component.") - .allowEmptyShould(true); - - public static final ArchCondition haveComponentServiceLayerDependingOnDiffComponentsLogicLayer = new ArchCondition( - "have dependencies, towards another components logic layer (Rule-C4).") { + public static final ArchCondition dependOnDiffComponentsServiceLayerClasses = new ArchCondition( + "depend on the service layer of a different custom component") { @Override public void check(JavaClass sourceClass, ConditionEvents events) { PackageStructure sourcePkg = PackageStructure.of(sourceClass); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // Check all project components' service layers except the default component for noncompliant dependencies towards - // other components' logic layers. - if (sourcePkg.isLayerService() && !sourcePkg.isComponentGeneral()) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - - JavaClass targetClass = dependency.getTargetClass(); - PackageStructure targetPkg = PackageStructure.of(targetClass); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentServiceLayerCompliantTowardsComponentsLogicLayer(sourcePkg, - targetPkg); - - if (targetPkg.isLayerLogic() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, - sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), - targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } + + // Check for noncompliant dependencies towards other components' service layers. + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + boolean isAllowedDependency = isDependingOnAnotherComponentsServiceLayer(sourcePkg, targetPkg); + + if (!isAllowedDependency) { + String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg); + events.add(new SimpleConditionEvent(sourceClass, true, message)); } } } }; - /** - * verifying that the service layer of one component does not depend on the logic layer of another component. - */ - @ArchTest - public static final ArchRule C4_no_dependencies_from_a_components_service_layer_to_anothers_component_logic_layer = noClasses() - .should(haveComponentServiceLayerDependingOnDiffComponentsLogicLayer) - .as("Code from service layer of a component shall not depend on logic layer of a different component.") - .allowEmptyShould(true); - - public static final ArchCondition haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( - "have dependencies, towards another components dataaccess layer (Rule-C5).") { + public static final ArchCondition dependOnDiffComponentsLogicLayer = new ArchCondition( + "depend on the logic layer of a different custom component") { @Override public void check(JavaClass sourceClass, ConditionEvents events) { PackageStructure sourcePkg = PackageStructure.of(sourceClass); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // Check all project components' logic layers except the default component for noncompliant dependencies towards - // other components' dataaccess layers. - if (sourcePkg.isLayerLogic() && !sourcePkg.isComponentGeneral()) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - - JavaClass targetClass = dependency.getTargetClass(); - PackageStructure targetPkg = PackageStructure.of(targetClass); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentLogicLayerCompliant(sourcePkg, targetPkg); - - if (targetPkg.isLayerDataAccess() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, - sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), - targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } + + // Check for noncompliant dependencies towards other components' logic layers. + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + boolean isAllowedDependency = isDependingOnAnotherComponentsLogicLayer(sourcePkg, targetPkg); + + if (!isAllowedDependency) { + String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg); + events.add(new SimpleConditionEvent(sourceClass, true, message)); } } } }; - /** - * verifying that the logic layer of a component may not depend on the dataaccess layer of another component. - */ - @ArchTest - public static final ArchRule C5_no_dependencies_from_a_components_logic_layer_to_anothers_component_dataaccess_layer = noClasses() - .should(haveComponentLogicLayerDependingOnDiffComponentsDataaccessLayer) - .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") - .allowEmptyShould(true); - - public static final ArchCondition haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer = new ArchCondition( - "have dependencies, towards another dataaccess layer (Rule-C6).") { + public static final ArchCondition dependOnDiffComponentsDataaccessLayer = new ArchCondition( + "depend on the dataacces layer of a different custom component") { @Override public void check(JavaClass sourceClass, ConditionEvents events) { PackageStructure sourcePkg = PackageStructure.of(sourceClass); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // Check all project components' dataaccess layers except the default component for noncompliant dependencies - // towards - // other components' dataaccess layers. - if (sourcePkg.isLayerDataAccess() && !sourcePkg.isComponentGeneral()) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - - JavaClass targetClass = dependency.getTargetClass(); - PackageStructure targetPkg = PackageStructure.of(targetClass); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentDataaccessLayerCompliant(sourcePkg, targetPkg); - - if (targetPkg.isLayerDataAccess() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, - sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), - targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } + + // Check for noncompliant dependencies towards other components' dataaccess + // layers. + for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { + + JavaClass targetClass = dependency.getTargetClass(); + PackageStructure targetPkg = PackageStructure.of(targetClass); + boolean isAllowedDependency = isDependingOnAnotherComponentsDataaccessLayer(sourcePkg, targetPkg); + + if (!isAllowedDependency) { + String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg); + events.add(new SimpleConditionEvent(sourceClass, true, message)); } } } }; - // medium severity /** - * verifying that the dataaccess layer of one component does not depend on the dataaccess layer of another component. + * verifying that the service layer of one component does not depend on the service layer of another component. */ @ArchTest - public static final ArchRule C6_no_dataaccess_layer_depends_on_dataaccess_layer_of_another_component = noClasses() - .should(haveComponentDataaccessLayerDependingOnDiffComponentsDataaccessLayer) - .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component") + public static final ArchRule noComponentsServiceLayerDependsOnTheServiceLayerOfAnotherComponent = noClasses() + .that(resideInServiceLayerOfAComponent).should(dependOnDiffComponentsServiceLayerClasses) + .as("Code from service layer of a component shall not depend on service layer of a different component.") .allowEmptyShould(true); - public static final ArchCondition haveComponentBatchLayerDependingOnDiffComponentsLogicLayer = new ArchCondition( - "have dependencies, towards another components logic layer (Rule-C7).") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { + /** + * verifying that the service layer of one component does not depend on the logic layer of another component. + */ + @ArchTest + public static final ArchRule noComponentsServiceLayerDependsOnTheLogicLayerOfAnotherComponent = noClasses() + .that(resideInServiceLayerOfAComponent).should(dependOnDiffComponentsLogicLayer) + .as("Code from service layer of a component shall not depend on logic layer of a different component.") + .allowEmptyShould(true); - PackageStructure sourcePkg = PackageStructure.of(sourceClass); - String sourceClassLayer = sourcePkg.getLayer(); - String sourceClassComponent = sourcePkg.getComponent(); - - // Check all project components' batch layers except the default component for noncompliant dependencies - // towards - // other components' logic layers. - if (sourcePkg.isLayerBatch() && !sourcePkg.isComponentGeneral()) { - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - - JavaClass targetClass = dependency.getTargetClass(); - PackageStructure targetPkg = PackageStructure.of(targetClass); - String targetClassComponent = targetPkg.getComponent(); - String targetClassLayer = targetPkg.getLayer(); - boolean isAllowedDependency = isComponentBatchLayerCompliant(sourcePkg, targetPkg); - - if (targetPkg.isLayerLogic() && !isAllowedDependency) { - String message = String.format( - "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourceClassComponent, - sourceClassLayer, targetClassComponent, targetClassLayer, sourceClass.getDescription(), - targetClass.getDescription()); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - } - }; + /** + * verifying that the logic layer of a component may not depend on the dataaccess layer of another component. + */ + @ArchTest + public static final ArchRule noComponentsLogicLayerDependsOnTheDataaccessLayerOfAnotherComponent = noClasses() + .that(resideInLogicLayerOfAComponent).should(dependOnDiffComponentsDataaccessLayer) + .as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.") + .allowEmptyShould(true); + + // medium severity + /** + * verifying that the dataaccess layer of one component does not depend on the dataaccess layer of another component. + */ + @ArchTest + public static final ArchRule noComponentsDataaccessLayerDependsOnTheDataaccessLayerOfAnotherComponent = noClasses() + .that(resideInDataaccessLayerOfAComponent).should(dependOnDiffComponentsDataaccessLayer) + .as("Code from dataaccess layer shall not depend on dataaccess layer of a different component.") + .allowEmptyShould(true); /** * verifying that the batch layer of a component may not depend on the logic layer of another component. */ @ArchTest - public static final ArchRule C7_no_dependencies_from_a_components_batch_layer_to_anothers_component_logic_layer = noClasses() - .should(haveComponentBatchLayerDependingOnDiffComponentsLogicLayer) + public static final ArchRule noComponentsBatchLayerDependsOnTheLogicLayerOfAnotherComponent = noClasses() + .that(resideInBatchLayerOfAComponent).should(dependOnDiffComponentsLogicLayer) .as("Code from batch layer of a component shall not depend on logic layer of a different component.") .allowEmptyShould(true); /** - * Dependency of a components batch layer towards the same components logic or common layer is allowed. In addition a - * dependency towards the projects default component is allowed too. + * verifying that the business architectures default general component does not depend on any other component. */ - private static boolean isComponentBatchLayerCompliant(PackageStructure sourcePkg, PackageStructure targetPkg) { + @ArchTest + public static ArchRule theDefaultProjectComponentDoesNotDependOnAnyOtherComponent = noClasses() + .that(resideInTheGeneralProjectComponent).should(dependOnDiffCustomComponents) + .as("Code from the business architecture general component must not depend on any other component.") + .allowEmptyShould(true); - boolean isAllowed = false; - // Components batch layer can depend on their own lower layers: logic and - // common. - if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetPkg.isComponentGeneral() && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; + private static boolean isDependingOnAnotherCustomComponent(PackageStructure sourcePkg, PackageStructure targetPkg) { + + boolean isAllowed = true; + if (isDifferentCustomComponent(sourcePkg, targetPkg)) { + isAllowed = false; } return isAllowed; } - /** - * Dependency of a components dataaccess layer towards the same components common layer is allowed. In addition a - * dependency towards the projects default component is allowed too. - */ - private static boolean isComponentDataaccessLayerCompliant(PackageStructure sourcePkg, PackageStructure targetPkg) { + private static boolean isDependingOnAnotherComponentsDataaccessLayer(PackageStructure sourcePkg, + PackageStructure targetPkg) { - boolean isAllowed = false; - // Components dataaccess layer can depend on their own dataaccess and common layer. - if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetPkg.isComponentGeneral() && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { - isAllowed = true; + boolean isAllowed = true; + if (isDifferentCustomComponent(sourcePkg, targetPkg) && targetPkg.isLayerDataAccess()) { + isAllowed = false; } return isAllowed; } - /** - * Dependency of a components logic layer towards the same components dataaccess or common layer is allowed. In - * addition a dependency towards the projects default component is allowed too. - */ - private static boolean isComponentLogicLayerCompliant(PackageStructure sourcePkg, PackageStructure targetPkg) { + private static boolean isDependingOnAnotherComponentsLogicLayer(PackageStructure sourcePkg, + PackageStructure targetPkg) { - boolean isAllowed = false; - // Components logic layer can depend on their own lower layers: dataaccess and - // common. - if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetPkg.isComponentGeneral() && (targetPkg.isLayerDataAccess() || targetPkg.isLayerCommon())) { - isAllowed = true; + boolean isAllowed = true; + if (isDifferentCustomComponent(sourcePkg, targetPkg) && targetPkg.isLayerLogic()) { + isAllowed = false; } return isAllowed; } - /** - * Dependency of a components service layer towards the same components logic or common layer is allowed. In addition - * a dependency towards the projects default component is allowed too. - * - * @param sourceClass Source JavaClass to check if dependencies from itself towards targetClass are allowed. - * @param targetClass Target JavaClass to check if dependencies from sourceClass towards it are allowed. - */ - private static boolean isComponentServiceLayerCompliantTowardsComponentsLogicLayer(PackageStructure sourcePkg, + private static boolean isDependingOnAnotherComponentsServiceLayer(PackageStructure sourcePkg, PackageStructure targetPkg) { - boolean isAllowed = false; - // Components service layer can depend on their own lower layers: logic and - // common. - if (sourcePkg.hasSameComponent(targetPkg) && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetPkg.isComponentGeneral() && (targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; + boolean isAllowed = true; + if (isDifferentCustomComponent(sourcePkg, targetPkg) && targetPkg.isLayerService()) { + isAllowed = false; } return isAllowed; } /** - * Dependency of a components service layer towards the same components logic and common layer is allowed. In addition - * a dependency towards the projects default component is allowed too. + * Check whether the given PackageStructures do not share the same component name and if the target package is not the + * default component. + * + * @param sourcePkg + * @param targetPkg + * @return Return {@code true} if the given {@code targetPkg} is not the default {@link PackageStructure} "general" + * component and both of the parameters do not belong to the same component. Otherwise, return {@code false}. */ - private static boolean isComponentServiceLayerCompliantTowardsComponenetsServiceLayer(PackageStructure sourcePkg, - PackageStructure targetPkg) { + private static boolean isDifferentCustomComponent(PackageStructure sourcePkg, PackageStructure targetPkg) { - boolean isAllowed = false; - // Components service layer can depend on their own logic and common layer. - if (sourcePkg.hasSameComponent(targetPkg) - && (targetPkg.isLayerService() || targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - // Components may always depend on the default business component. - if (targetPkg.isComponentGeneral() - && (targetPkg.isLayerService() || targetPkg.isLayerLogic() || targetPkg.isLayerCommon())) { - isAllowed = true; - } - return isAllowed; + return !sourcePkg.hasSameComponent(targetPkg) && !targetPkg.isComponentGeneral() && targetPkg.isValid(); } + private static String composeViolationMessage(JavaClass sourceClass, JavaClass targetClass, + PackageStructure sourcePkg, PackageStructure targetPkg) { + + String violationMessage = String.format( + "'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourcePkg.getComponent(), + sourcePkg.getLayer(), targetPkg.getComponent(), targetPkg.getLayer(), sourceClass.getDescription(), + targetClass.getDescription()); + return violationMessage; + } }