Skip to content

Commit e5a7d5f

Browse files
authored
#3: implement component rules C3-C7 (#29)
1 parent 2a7a6f7 commit e5a7d5f

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package com.devonfw.sample.archunit;
2+
3+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
4+
5+
import com.tngtech.archunit.base.DescribedPredicate;
6+
import com.tngtech.archunit.core.domain.Dependency;
7+
import com.tngtech.archunit.core.domain.JavaClass;
8+
import com.tngtech.archunit.core.importer.ImportOption;
9+
import com.tngtech.archunit.junit.AnalyzeClasses;
10+
import com.tngtech.archunit.junit.ArchTest;
11+
import com.tngtech.archunit.lang.ArchCondition;
12+
import com.tngtech.archunit.lang.ArchRule;
13+
import com.tngtech.archunit.lang.ConditionEvents;
14+
import com.tngtech.archunit.lang.SimpleConditionEvent;
15+
16+
@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class)
17+
public class ComponentRuleTest {
18+
19+
private static DescribedPredicate<JavaClass> resideInServiceLayerOfAComponent = new DescribedPredicate<JavaClass>(
20+
"lie inside the service layer of a custom component different from the business architectures default general component") {
21+
@Override
22+
public boolean test(JavaClass input) {
23+
24+
PackageStructure inputPkg = PackageStructure.of(input);
25+
boolean someCustomComponentServiceClass = inputPkg.isLayerService() && !inputPkg.isComponentGeneral()
26+
&& inputPkg.isValid();
27+
return someCustomComponentServiceClass;
28+
}
29+
};
30+
31+
private static DescribedPredicate<JavaClass> resideInLogicLayerOfAComponent = new DescribedPredicate<JavaClass>(
32+
"lie inside the logic layer of a custom component different from the business architectures default general component") {
33+
@Override
34+
public boolean test(JavaClass input) {
35+
36+
PackageStructure inputPkg = PackageStructure.of(input);
37+
boolean someCustomComponentLogicClass = inputPkg.isLayerLogic() && !inputPkg.isComponentGeneral()
38+
&& inputPkg.isValid();
39+
return someCustomComponentLogicClass;
40+
}
41+
};
42+
43+
private static DescribedPredicate<JavaClass> resideInDataaccessLayerOfAComponent = new DescribedPredicate<JavaClass>(
44+
"lie inside the dataaccess layer of a custom component different from the business architectures default general component") {
45+
@Override
46+
public boolean test(JavaClass input) {
47+
48+
PackageStructure inputPkg = PackageStructure.of(input);
49+
boolean someCustomComponentDataaccessClass = inputPkg.isLayerDataAccess() && !inputPkg.isComponentGeneral()
50+
&& inputPkg.isValid();
51+
return someCustomComponentDataaccessClass;
52+
}
53+
};
54+
55+
private static DescribedPredicate<JavaClass> resideInBatchLayerOfAComponent = new DescribedPredicate<JavaClass>(
56+
"lie inside the batch layer of a custom component different from the business architectures default general component") {
57+
@Override
58+
public boolean test(JavaClass input) {
59+
60+
PackageStructure inputPkg = PackageStructure.of(input);
61+
boolean someCustomComponentBatchClass = inputPkg.isLayerBatch() && !inputPkg.isComponentGeneral()
62+
&& inputPkg.isValid();
63+
return someCustomComponentBatchClass;
64+
}
65+
};
66+
67+
private static DescribedPredicate<JavaClass> resideInTheGeneralProjectComponent = new DescribedPredicate<JavaClass>(
68+
"lie inside the business architectures default general component") {
69+
@Override
70+
public boolean test(JavaClass input) {
71+
72+
PackageStructure inputPkg = PackageStructure.of(input);
73+
boolean someGeneralComponentClass = inputPkg.isComponentGeneral() && inputPkg.isValid();
74+
return someGeneralComponentClass;
75+
}
76+
};
77+
78+
public static final ArchCondition<JavaClass> dependOnDiffCustomComponents = new ArchCondition<JavaClass>(
79+
"depend on a different custom component") {
80+
@Override
81+
public void check(JavaClass sourceClass, ConditionEvents events) {
82+
83+
PackageStructure sourcePkg = PackageStructure.of(sourceClass);
84+
85+
// Check for noncompliant dependencies towards other custom components'.
86+
for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) {
87+
88+
JavaClass targetClass = dependency.getTargetClass();
89+
PackageStructure targetPkg = PackageStructure.of(targetClass);
90+
boolean isAllowedDependency = isDependingOnAnotherCustomComponent(sourcePkg, targetPkg);
91+
92+
if (!isAllowedDependency) {
93+
String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg);
94+
events.add(new SimpleConditionEvent(sourceClass, true, message));
95+
}
96+
}
97+
}
98+
};
99+
100+
public static final ArchCondition<JavaClass> dependOnDiffComponentsServiceLayerClasses = new ArchCondition<JavaClass>(
101+
"depend on the service layer of a different custom component") {
102+
@Override
103+
public void check(JavaClass sourceClass, ConditionEvents events) {
104+
105+
PackageStructure sourcePkg = PackageStructure.of(sourceClass);
106+
107+
// Check for noncompliant dependencies towards other components' service layers.
108+
for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) {
109+
110+
JavaClass targetClass = dependency.getTargetClass();
111+
PackageStructure targetPkg = PackageStructure.of(targetClass);
112+
boolean isAllowedDependency = isDependingOnAnotherComponentsServiceLayer(sourcePkg, targetPkg);
113+
114+
if (!isAllowedDependency) {
115+
String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg);
116+
events.add(new SimpleConditionEvent(sourceClass, true, message));
117+
}
118+
}
119+
}
120+
};
121+
122+
public static final ArchCondition<JavaClass> dependOnDiffComponentsLogicLayer = new ArchCondition<JavaClass>(
123+
"depend on the logic layer of a different custom component") {
124+
@Override
125+
public void check(JavaClass sourceClass, ConditionEvents events) {
126+
127+
PackageStructure sourcePkg = PackageStructure.of(sourceClass);
128+
129+
// Check for noncompliant dependencies towards other components' logic layers.
130+
for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) {
131+
132+
JavaClass targetClass = dependency.getTargetClass();
133+
PackageStructure targetPkg = PackageStructure.of(targetClass);
134+
boolean isAllowedDependency = isDependingOnAnotherComponentsLogicLayer(sourcePkg, targetPkg);
135+
136+
if (!isAllowedDependency) {
137+
String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg);
138+
events.add(new SimpleConditionEvent(sourceClass, true, message));
139+
}
140+
}
141+
}
142+
};
143+
144+
public static final ArchCondition<JavaClass> dependOnDiffComponentsDataaccessLayer = new ArchCondition<JavaClass>(
145+
"depend on the dataacces layer of a different custom component") {
146+
@Override
147+
public void check(JavaClass sourceClass, ConditionEvents events) {
148+
149+
PackageStructure sourcePkg = PackageStructure.of(sourceClass);
150+
151+
// Check for noncompliant dependencies towards other components' dataaccess
152+
// layers.
153+
for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) {
154+
155+
JavaClass targetClass = dependency.getTargetClass();
156+
PackageStructure targetPkg = PackageStructure.of(targetClass);
157+
boolean isAllowedDependency = isDependingOnAnotherComponentsDataaccessLayer(sourcePkg, targetPkg);
158+
159+
if (!isAllowedDependency) {
160+
String message = composeViolationMessage(sourceClass, targetClass, sourcePkg, targetPkg);
161+
events.add(new SimpleConditionEvent(sourceClass, true, message));
162+
}
163+
}
164+
}
165+
};
166+
167+
/**
168+
* verifying that the service layer of one component does not depend on the service layer of another component.
169+
*/
170+
@ArchTest
171+
public static final ArchRule noComponentsServiceLayerDependsOnTheServiceLayerOfAnotherComponent = noClasses()
172+
.that(resideInServiceLayerOfAComponent).should(dependOnDiffComponentsServiceLayerClasses)
173+
.as("Code from service layer of a component shall not depend on service layer of a different component.")
174+
.allowEmptyShould(true);
175+
176+
/**
177+
* verifying that the service layer of one component does not depend on the logic layer of another component.
178+
*/
179+
@ArchTest
180+
public static final ArchRule noComponentsServiceLayerDependsOnTheLogicLayerOfAnotherComponent = noClasses()
181+
.that(resideInServiceLayerOfAComponent).should(dependOnDiffComponentsLogicLayer)
182+
.as("Code from service layer of a component shall not depend on logic layer of a different component.")
183+
.allowEmptyShould(true);
184+
185+
/**
186+
* verifying that the logic layer of a component may not depend on the dataaccess layer of another component.
187+
*/
188+
@ArchTest
189+
public static final ArchRule noComponentsLogicLayerDependsOnTheDataaccessLayerOfAnotherComponent = noClasses()
190+
.that(resideInLogicLayerOfAComponent).should(dependOnDiffComponentsDataaccessLayer)
191+
.as("Code from logic layer of a component shall not depend on dataaccess layer of a different component.")
192+
.allowEmptyShould(true);
193+
194+
// medium severity
195+
/**
196+
* verifying that the dataaccess layer of one component does not depend on the dataaccess layer of another component.
197+
*/
198+
@ArchTest
199+
public static final ArchRule noComponentsDataaccessLayerDependsOnTheDataaccessLayerOfAnotherComponent = noClasses()
200+
.that(resideInDataaccessLayerOfAComponent).should(dependOnDiffComponentsDataaccessLayer)
201+
.as("Code from dataaccess layer shall not depend on dataaccess layer of a different component.")
202+
.allowEmptyShould(true);
203+
204+
/**
205+
* verifying that the batch layer of a component may not depend on the logic layer of another component.
206+
*/
207+
@ArchTest
208+
public static final ArchRule noComponentsBatchLayerDependsOnTheLogicLayerOfAnotherComponent = noClasses()
209+
.that(resideInBatchLayerOfAComponent).should(dependOnDiffComponentsLogicLayer)
210+
.as("Code from batch layer of a component shall not depend on logic layer of a different component.")
211+
.allowEmptyShould(true);
212+
213+
/**
214+
* verifying that the business architectures default general component does not depend on any other component.
215+
*/
216+
@ArchTest
217+
public static ArchRule theDefaultProjectComponentDoesNotDependOnAnyOtherComponent = noClasses()
218+
.that(resideInTheGeneralProjectComponent).should(dependOnDiffCustomComponents)
219+
.as("Code from the business architecture general component must not depend on any other component.")
220+
.allowEmptyShould(true);
221+
222+
private static boolean isDependingOnAnotherCustomComponent(PackageStructure sourcePkg, PackageStructure targetPkg) {
223+
224+
boolean isAllowed = true;
225+
if (isDifferentCustomComponent(sourcePkg, targetPkg)) {
226+
isAllowed = false;
227+
}
228+
return isAllowed;
229+
}
230+
231+
private static boolean isDependingOnAnotherComponentsDataaccessLayer(PackageStructure sourcePkg,
232+
PackageStructure targetPkg) {
233+
234+
boolean isAllowed = true;
235+
if (isDifferentCustomComponent(sourcePkg, targetPkg) && targetPkg.isLayerDataAccess()) {
236+
isAllowed = false;
237+
}
238+
return isAllowed;
239+
}
240+
241+
private static boolean isDependingOnAnotherComponentsLogicLayer(PackageStructure sourcePkg,
242+
PackageStructure targetPkg) {
243+
244+
boolean isAllowed = true;
245+
if (isDifferentCustomComponent(sourcePkg, targetPkg) && targetPkg.isLayerLogic()) {
246+
isAllowed = false;
247+
}
248+
return isAllowed;
249+
}
250+
251+
private static boolean isDependingOnAnotherComponentsServiceLayer(PackageStructure sourcePkg,
252+
PackageStructure targetPkg) {
253+
254+
boolean isAllowed = true;
255+
if (isDifferentCustomComponent(sourcePkg, targetPkg) && targetPkg.isLayerService()) {
256+
isAllowed = false;
257+
}
258+
return isAllowed;
259+
}
260+
261+
/**
262+
* Check whether the given PackageStructures do not share the same component name and if the target package is not the
263+
* default component.
264+
*
265+
* @param sourcePkg
266+
* @param targetPkg
267+
* @return Return {@code true} if the given {@code targetPkg} is not the default {@link PackageStructure} "general"
268+
* component and both of the parameters do not belong to the same component. Otherwise, return {@code false}.
269+
*/
270+
private static boolean isDifferentCustomComponent(PackageStructure sourcePkg, PackageStructure targetPkg) {
271+
272+
return !sourcePkg.hasSameComponent(targetPkg) && !targetPkg.isComponentGeneral() && targetPkg.isValid();
273+
}
274+
275+
private static String composeViolationMessage(JavaClass sourceClass, JavaClass targetClass,
276+
PackageStructure sourcePkg, PackageStructure targetPkg) {
277+
278+
String violationMessage = String.format(
279+
"'%s.%s' is dependend on '%s.%s'. Violated in: (%s). Dependency towards (%s)", sourcePkg.getComponent(),
280+
sourcePkg.getLayer(), targetPkg.getComponent(), targetPkg.getLayer(), sourceClass.getDescription(),
281+
targetClass.getDescription());
282+
return violationMessage;
283+
}
284+
}

src/test/java/com/devonfw/sample/archunit/PackageStructure.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class PackageStructure {
3838
/** The {@link #getScope() scope} {@value} */
3939
public static final String SCOPE_IMPLEMENTATION = "impl";
4040

41+
public static final String DEFAULT_COMPONENT = "general";
42+
4143
private final String packageName;
4244

4345
private final boolean valid;
@@ -152,6 +154,14 @@ public boolean hasSameComponent(PackageStructure otherPkg) {
152154
return getComponent().equals(otherPkg.getComponent());
153155
}
154156

157+
/**
158+
* @return {@code true} if this is the default architecture component.
159+
*/
160+
public boolean isComponentGeneral() {
161+
162+
return getComponent().equals(DEFAULT_COMPONENT);
163+
}
164+
155165
/**
156166
* @return the name of the layer. Will be the empty {@link String} if not {@link #isValid() valid}.
157167
*/

0 commit comments

Comments
 (0)