Skip to content

Commit

Permalink
support OnionArchitecture.ignoreDependency #320
Browse files Browse the repository at this point in the history
add `ignoreDependency(..)` methods to `onionArchitecture()`
  • Loading branch information
codecholeric authored Feb 29, 2020
2 parents 84a6a6c + 7599565 commit 472cc18
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 66 deletions.
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Contributions are very welcome. The following will provide some helpful guidelin

## How to build the project

ArchUnit requires at least JDK 9 to build. The following is just an example input/output from a
Unix command line. Windows users should use `gradlew.bat` instead.
ArchUnit requires at least JDK 11 to build.
The following is just an example input/output from a Unix command line.
Windows users should use `gradlew.bat` instead.

```
$ cd /path/to/git/clone/of/ArchUnit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.tngtech.archunit.exampletest.junit4;

import com.tngtech.archunit.example.onionarchitecture.domain.model.OrderItem;
import com.tngtech.archunit.example.onionarchitecture.domain.service.OrderQuantity;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchUnitRunner;
Expand All @@ -22,4 +24,15 @@ public class OnionArchitectureTest {
.adapter("cli", "..adapter.cli..")
.adapter("persistence", "..adapter.persistence..")
.adapter("rest", "..adapter.rest..");

@ArchTest
static final ArchRule onion_architecture_is_respected_with_exception = onionArchitecture()
.domainModels("..domain.model..")
.domainServices("..domain.service..")
.applicationServices("..application..")
.adapter("cli", "..adapter.cli..")
.adapter("persistence", "..adapter.persistence..")
.adapter("rest", "..adapter.rest..")

.ignoreDependency(OrderItem.class, OrderQuantity.class);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.tngtech.archunit.exampletest.junit5;

import com.tngtech.archunit.example.onionarchitecture.domain.model.OrderItem;
import com.tngtech.archunit.example.onionarchitecture.domain.service.OrderQuantity;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTag;
import com.tngtech.archunit.junit.ArchTest;
Expand All @@ -19,4 +21,15 @@ public class OnionArchitectureTest {
.adapter("cli", "..adapter.cli..")
.adapter("persistence", "..adapter.persistence..")
.adapter("rest", "..adapter.rest..");

@ArchTest
static final ArchRule onion_architecture_is_respected_with_exception = onionArchitecture()
.domainModels("..domain.model..")
.domainServices("..domain.service..")
.applicationServices("..application..")
.adapter("cli", "..adapter.cli..")
.adapter("persistence", "..adapter.persistence..")
.adapter("rest", "..adapter.rest..")

.ignoreDependency(OrderItem.class, OrderQuantity.class);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.example.onionarchitecture.domain.model.OrderItem;
import com.tngtech.archunit.example.onionarchitecture.domain.service.OrderQuantity;
import org.junit.Test;
import org.junit.experimental.categories.Category;

Expand All @@ -22,4 +24,19 @@ public void onion_architecture_is_respected() {
.adapter("rest", "..adapter.rest..")
.check(classes);
}

@Test
public void onion_architecture_is_respected_with_exception() {
onionArchitecture()
.domainModels("..domain.model..")
.domainServices("..domain.service..")
.applicationServices("..application..")
.adapter("cli", "..adapter.cli..")
.adapter("persistence", "..adapter.persistence..")
.adapter("rest", "..adapter.rest..")

.ignoreDependency(OrderItem.class, OrderQuantity.class)

.check(classes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -823,54 +823,62 @@ Stream<DynamicTest> LayeredArchitectureTest() {

@TestFactory
Stream<DynamicTest> OnionArchitectureTest() {
BiConsumer<String, ExpectedTestFailures> addExpectedCommonFailure =
(memberName, expectedTestFailures) ->
expectedTestFailures
.ofRule(memberName, "Onion architecture consisting of" + lineSeparator() +
"domain models ('..domain.model..')" + lineSeparator() +
"domain services ('..domain.service..')" + lineSeparator() +
"application services ('..application..')" + lineSeparator() +
"adapter 'cli' ('..adapter.cli..')" + lineSeparator() +
"adapter 'persistence' ('..adapter.persistence..')" + lineSeparator() +
"adapter 'rest' ('..adapter.rest..')")

.by(constructor(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class).withParameter(ProductId.class))
.by(constructor(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class).withParameter(ProductName.class))
.by(constructor(ShoppingCart.class).withParameter(ShoppingCartId.class))
.by(constructor(ShoppingService.class).withParameter(ProductRepository.class))
.by(constructor(ShoppingService.class).withParameter(ShoppingCartRepository.class))

.by(field(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class, "id").ofType(ProductId.class))
.by(field(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class, "name").ofType(ProductName.class))
.by(field(ShoppingCart.class, "id").ofType(ShoppingCartId.class))
.by(field(ShoppingService.class, "productRepository").ofType(ProductRepository.class))
.by(field(ShoppingService.class, "shoppingCartRepository").ofType(ShoppingCartRepository.class))

.by(callFromMethod(AdministrationCLI.class, "handle", String[].class, AdministrationPort.class)
.toMethod(ProductRepository.class, "getTotalCount")
.inLine(17).asDependency())
.by(callFromMethod(ShoppingController.class, "addToShoppingCart", UUID.class, UUID.class, int.class)
.toConstructor(ProductId.class, UUID.class)
.inLine(20).asDependency())
.by(callFromMethod(ShoppingController.class, "addToShoppingCart", UUID.class, UUID.class, int.class)
.toConstructor(ShoppingCartId.class, UUID.class)
.inLine(20).asDependency())
.by(method(ShoppingService.class, "addToShoppingCart").withParameter(ProductId.class))
.by(method(ShoppingService.class, "addToShoppingCart").withParameter(ShoppingCartId.class))
.by(callFromMethod(ShoppingService.class, "addToShoppingCart", ShoppingCartId.class, ProductId.class, OrderQuantity.class)
.toMethod(ShoppingCartRepository.class, "read", ShoppingCartId.class)
.inLine(21).asDependency())
.by(callFromMethod(ShoppingService.class, "addToShoppingCart", ShoppingCartId.class, ProductId.class, OrderQuantity.class)
.toMethod(ProductRepository.class, "read", ProductId.class)
.inLine(22).asDependency())
.by(callFromMethod(ShoppingService.class, "addToShoppingCart", ShoppingCartId.class, ProductId.class, OrderQuantity.class)
.toMethod(ShoppingCartRepository.class, "save", ShoppingCart.class)
.inLine(25).asDependency());

ExpectedTestFailures expectedTestFailures = ExpectedTestFailures
.forTests(
com.tngtech.archunit.exampletest.OnionArchitectureTest.class,
com.tngtech.archunit.exampletest.junit4.OnionArchitectureTest.class,
com.tngtech.archunit.exampletest.junit5.OnionArchitectureTest.class)

.ofRule("Onion architecture consisting of" + lineSeparator() +
"domain models ('..domain.model..')" + lineSeparator() +
"domain services ('..domain.service..')" + lineSeparator() +
"application services ('..application..')" + lineSeparator() +
"adapter 'cli' ('..adapter.cli..')" + lineSeparator() +
"adapter 'persistence' ('..adapter.persistence..')" + lineSeparator() +
"adapter 'rest' ('..adapter.rest..')")
com.tngtech.archunit.exampletest.junit5.OnionArchitectureTest.class);

addExpectedCommonFailure.accept("onion_architecture_is_respected", expectedTestFailures);
expectedTestFailures = expectedTestFailures
.by(constructor(OrderItem.class).withParameter(OrderQuantity.class))
.by(constructor(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class).withParameter(ProductId.class))
.by(constructor(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class).withParameter(ProductName.class))
.by(constructor(ShoppingCart.class).withParameter(ShoppingCartId.class))
.by(constructor(ShoppingService.class).withParameter(ProductRepository.class))
.by(constructor(ShoppingService.class).withParameter(ShoppingCartRepository.class))

.by(field(OrderItem.class, "quantity").ofType(OrderQuantity.class))
.by(field(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class, "id").ofType(ProductId.class))
.by(field(com.tngtech.archunit.example.onionarchitecture.domain.model.Product.class, "name").ofType(ProductName.class))
.by(field(ShoppingCart.class, "id").ofType(ShoppingCartId.class))
.by(field(ShoppingService.class, "productRepository").ofType(ProductRepository.class))
.by(field(ShoppingService.class, "shoppingCartRepository").ofType(ShoppingCartRepository.class))

.by(callFromMethod(AdministrationCLI.class, "handle", String[].class, AdministrationPort.class)
.toMethod(ProductRepository.class, "getTotalCount")
.inLine(17).asDependency())
.by(callFromMethod(ShoppingController.class, "addToShoppingCart", UUID.class, UUID.class, int.class)
.toConstructor(ProductId.class, UUID.class)
.inLine(20).asDependency())
.by(callFromMethod(ShoppingController.class, "addToShoppingCart", UUID.class, UUID.class, int.class)
.toConstructor(ShoppingCartId.class, UUID.class)
.inLine(20).asDependency())
.by(method(ShoppingService.class, "addToShoppingCart").withParameter(ProductId.class))
.by(method(ShoppingService.class, "addToShoppingCart").withParameter(ShoppingCartId.class))
.by(callFromMethod(ShoppingService.class, "addToShoppingCart", ShoppingCartId.class, ProductId.class, OrderQuantity.class)
.toMethod(ShoppingCartRepository.class, "read", ShoppingCartId.class)
.inLine(21).asDependency())
.by(callFromMethod(ShoppingService.class, "addToShoppingCart", ShoppingCartId.class, ProductId.class, OrderQuantity.class)
.toMethod(ProductRepository.class, "read", ProductId.class)
.inLine(22).asDependency())
.by(callFromMethod(ShoppingService.class, "addToShoppingCart", ShoppingCartId.class, ProductId.class, OrderQuantity.class)
.toMethod(ShoppingCartRepository.class, "save", ShoppingCart.class)
.inLine(25).asDependency());
.by(field(OrderItem.class, "quantity").ofType(OrderQuantity.class));

addExpectedCommonFailure.accept("onion_architecture_is_respected_with_exception", expectedTestFailures);

return expectedTestFailures.toDynamicTests();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ public static final class OnionArchitecture implements ArchRule {
private String[] applicationPackageIdentifiers = new String[0];
private Map<String, String[]> adapterPackageIdentifiers = new LinkedHashMap<>();
private boolean optionalLayers = false;
private List<IgnoredDependency> ignoredDependencies = new ArrayList<>();

private OnionArchitecture() {
overriddenDescription = Optional.absent();
Expand Down Expand Up @@ -481,6 +482,22 @@ public OnionArchitecture withOptionalLayers(boolean optionalLayers) {
return this;
}

@PublicAPI(usage = ACCESS)
public OnionArchitecture ignoreDependency(Class<?> origin, Class<?> target) {
return ignoreDependency(equivalentTo(origin), equivalentTo(target));
}

@PublicAPI(usage = ACCESS)
public OnionArchitecture ignoreDependency(String origin, String target) {
return ignoreDependency(name(origin), name(target));
}

@PublicAPI(usage = ACCESS)
public OnionArchitecture ignoreDependency(DescribedPredicate<? super JavaClass> origin, DescribedPredicate<? super JavaClass> target) {
this.ignoredDependencies.add(new IgnoredDependency(origin, target));
return this;
}

private LayeredArchitecture layeredArchitectureDelegate() {
LayeredArchitecture layeredArchitectureDelegate = layeredArchitecture()
.layer(DOMAIN_MODEL_LAYER).definedBy(domainModelPackageIdentifiers)
Expand All @@ -498,6 +515,9 @@ private LayeredArchitecture layeredArchitectureDelegate() {
.layer(adapterLayer).definedBy(adapter.getValue())
.whereLayer(adapterLayer).mayNotBeAccessedByAnyLayer();
}
for (IgnoredDependency ignoredDependency : this.ignoredDependencies) {
layeredArchitectureDelegate = ignoredDependency.ignoreFor(layeredArchitectureDelegate);
}
return layeredArchitectureDelegate.as(getDescription());
}

Expand Down Expand Up @@ -555,5 +575,19 @@ public String getDescription() {
}
return Joiner.on(lineSeparator()).join(lines);
}

private static class IgnoredDependency {
private final DescribedPredicate<? super JavaClass> origin;
private final DescribedPredicate<? super JavaClass> target;

IgnoredDependency(DescribedPredicate<? super JavaClass> origin, DescribedPredicate<? super JavaClass> target) {
this.origin = origin;
this.target = target;
}

LayeredArchitecture ignoreFor(LayeredArchitecture layeredArchitecture) {
return layeredArchitecture.ignoreDependency(origin, target);
}
}
}
}
Loading

0 comments on commit 472cc18

Please sign in to comment.