Skip to content

Commit

Permalink
Add "onion architecture" builder
Browse files Browse the repository at this point in the history
  • Loading branch information
spanierm42 committed May 5, 2019
1 parent 024c5d2 commit 1aaab34
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 1 deletion.
104 changes: 104 additions & 0 deletions archunit/src/main/java/com/tngtech/archunit/library/Architectures.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.tngtech.archunit.library;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
Expand All @@ -26,6 +27,7 @@

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.Optional;
Expand Down Expand Up @@ -289,4 +291,106 @@ public String toString() {
}
}
}

public static OnionArchitecture onionArchitecture() {
return new OnionArchitecture();
}

public static final class OnionArchitecture implements ArchRule {
private static final String DOMAIN_MODEL_LAYER = "domain model";
private static final String DOMAIN_SERVICE_LAYER = "domain service";
private static final String APPLICATION_LAYER = "application";
private static final String ADAPTER_LAYER = "adapter";

private final Set<String> domainModelPackageIdentifiers = new LinkedHashSet<>();
private final Set<String> domainServicePackageIdentifiers = new LinkedHashSet<>();
private final Set<String> applicationPackageIdentifiers = new LinkedHashSet<>();
private final Map<String, Set<String>> adapterPackageIdentifiers = new LinkedHashMap<>();

private LayeredArchitecture layeredArchitectureDelegate = layeredArchitecture();

public OnionArchitecture domainModel(String... packageIdentifiers) {
domainModelPackageIdentifiers.addAll(asList(packageIdentifiers));
updateLayersDelegate();
return this;
}

public OnionArchitecture domainService(String... packageIdentifiers) {
domainServicePackageIdentifiers.addAll(asList(packageIdentifiers));
updateLayersDelegate();
return this;
}

public OnionArchitecture application(String... packageIdentifiers) {
applicationPackageIdentifiers.addAll(asList(packageIdentifiers));
updateLayersDelegate();
return this;
}

public OnionArchitecture adapter(String name, String... packageIdentifiers) {
adapterPackageIdentifiers.put(name, new LinkedHashSet<>(asList(packageIdentifiers)));
updateLayersDelegate();
return this;
}

private void updateLayersDelegate() {
layeredArchitectureDelegate = layeredArchitecture()
.layer(DOMAIN_MODEL_LAYER).definedBy(domainModelPackageIdentifiers.toArray(new String[] {}))
.layer(DOMAIN_SERVICE_LAYER).definedBy(domainServicePackageIdentifiers.toArray(new String[] {}))
.layer(APPLICATION_LAYER).definedBy(applicationPackageIdentifiers.toArray(new String[] {}))
.layer(ADAPTER_LAYER).definedBy(union(adapterPackageIdentifiers.values()).toArray(new String[] {}));
for (Map.Entry<String, Set<String>> adapter : adapterPackageIdentifiers.entrySet()) {
layeredArchitectureDelegate = layeredArchitectureDelegate
.layer(getAdapterLayer(adapter.getKey())).definedBy(adapter.getValue().toArray(new String[] {}));
}
layeredArchitectureDelegate = layeredArchitectureDelegate
.whereLayer(DOMAIN_MODEL_LAYER).mayOnlyBeAccessedByLayers(DOMAIN_SERVICE_LAYER, APPLICATION_LAYER, ADAPTER_LAYER)
.whereLayer(DOMAIN_SERVICE_LAYER).mayOnlyBeAccessedByLayers(APPLICATION_LAYER, ADAPTER_LAYER)
.whereLayer(APPLICATION_LAYER).mayOnlyBeAccessedByLayers(ADAPTER_LAYER)
.whereLayer(ADAPTER_LAYER).mayNotBeAccessedByAnyLayer();
for (Map.Entry<String, Set<String>> adapter : adapterPackageIdentifiers.entrySet()) {
String adapterLayer = getAdapterLayer(adapter.getKey());
layeredArchitectureDelegate = layeredArchitectureDelegate
.layer(adapterLayer).definedBy(adapter.getValue().toArray(new String[] {}))
.whereLayer(adapterLayer).mayNotBeAccessedByAnyLayer();
}
}

private Set<String> union(Collection<Set<String>> sets) {
Set<String> result = new LinkedHashSet<>();
for (Set<String> set: sets){
result = Sets.union(result, set);
}
return result;
}

private String getAdapterLayer(String name) {
return String.format("%s %s", name, ADAPTER_LAYER);
}

@Override
public void check(JavaClasses classes) {
layeredArchitectureDelegate.check(classes);
}

@Override
public ArchRule because(String reason) {
return layeredArchitectureDelegate.because(reason);
}

@Override
public ArchRule as(String newDescription) {
return layeredArchitectureDelegate.as(newDescription);
}

@Override
public EvaluationResult evaluate(JavaClasses classes) {
return layeredArchitectureDelegate.evaluate(classes);
}

@Override
public String getDescription() {
return layeredArchitectureDelegate.getDescription();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.library.Architectures.LayeredArchitecture;
import com.tngtech.archunit.library.Architectures.OnionArchitecture;
import com.tngtech.archunit.library.testclasses.first.any.pkg.FirstAnyPkgClass;
import com.tngtech.archunit.library.testclasses.first.three.any.FirstThreeAnyClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli.CliAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence.PersistenceAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest.RestAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.application.ApplicationLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.model.DomainModelLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.service.DomainServiceLayerClass;
import com.tngtech.archunit.library.testclasses.second.three.any.SecondThreeAnyClass;
import com.tngtech.archunit.library.testclasses.some.pkg.SomePkgClass;
import com.tngtech.archunit.library.testclasses.some.pkg.sub.SomePkgSubClass;
Expand All @@ -30,6 +37,7 @@

import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
import static com.tngtech.archunit.library.Architectures.onionArchitecture;
import static java.lang.System.lineSeparator;
import static java.util.regex.Pattern.quote;
import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -146,6 +154,66 @@ public static Object[][] toIgnore() {
"rule with ignore specified as predicates"));
}

@Test
public void gathers_all_onion_architecture_violations() {
OnionArchitecture architecture = onionArchitecture()
.domainModel(absolute("onionarchitecture.domain.model"))
.domainService(absolute("onionarchitecture.domain.service"))
.application(absolute("onionarchitecture.application"))
.adapter("cli", absolute("onionarchitecture.adapter.cli"))
.adapter("persistence", absolute("onionarchitecture.adapter.persistence"))
.adapter("rest", absolute("onionarchitecture.adapter.rest"));
JavaClasses classes = new ClassFileImporter().importPackages(getClass().getPackage().getName() + ".testclasses.onionarchitecture");

EvaluationResult result = architecture.evaluate(classes);

ImmutableSet<String> expectedRegexes = ImmutableSet.of(
expectedAccessViolationPattern(DomainModelLayerClass.class, "call", DomainServiceLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainModelLayerClass.class, "call", ApplicationLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainModelLayerClass.class, "call", CliAdapterLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainModelLayerClass.class, "call", PersistenceAdapterLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainModelLayerClass.class, "call", RestAdapterLayerClass.class, "callMe"),
fieldTypePattern(DomainModelLayerClass.class, "domainServiceLayerClass", DomainServiceLayerClass.class),
fieldTypePattern(DomainModelLayerClass.class, "applicationLayerClass", ApplicationLayerClass.class),
fieldTypePattern(DomainModelLayerClass.class, "cliAdapterLayerClass", CliAdapterLayerClass.class),
fieldTypePattern(DomainModelLayerClass.class, "persistenceAdapterLayerClass", PersistenceAdapterLayerClass.class),
fieldTypePattern(DomainModelLayerClass.class, "restAdapterLayerClass", RestAdapterLayerClass.class),

expectedAccessViolationPattern(DomainServiceLayerClass.class, "call", ApplicationLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainServiceLayerClass.class, "call", CliAdapterLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainServiceLayerClass.class, "call", PersistenceAdapterLayerClass.class, "callMe"),
expectedAccessViolationPattern(DomainServiceLayerClass.class, "call", RestAdapterLayerClass.class, "callMe"),
fieldTypePattern(DomainServiceLayerClass.class, "applicationLayerClass", ApplicationLayerClass.class),
fieldTypePattern(DomainServiceLayerClass.class, "cliAdapterLayerClass", CliAdapterLayerClass.class),
fieldTypePattern(DomainServiceLayerClass.class, "persistenceAdapterLayerClass", PersistenceAdapterLayerClass.class),
fieldTypePattern(DomainServiceLayerClass.class, "restAdapterLayerClass", RestAdapterLayerClass.class),

expectedAccessViolationPattern(ApplicationLayerClass.class, "call", CliAdapterLayerClass.class, "callMe"),
expectedAccessViolationPattern(ApplicationLayerClass.class, "call", PersistenceAdapterLayerClass.class, "callMe"),
expectedAccessViolationPattern(ApplicationLayerClass.class, "call", RestAdapterLayerClass.class, "callMe"),
fieldTypePattern(ApplicationLayerClass.class, "cliAdapterLayerClass", ApplicationLayerClass.class),
fieldTypePattern(ApplicationLayerClass.class, "persistenceAdapterLayerClass", ApplicationLayerClass.class),
fieldTypePattern(ApplicationLayerClass.class, "restAdapterLayerClass", ApplicationLayerClass.class),

expectedAccessViolationPattern(CliAdapterLayerClass.class, "call", PersistenceAdapterLayerClass.class, "callMe"),
fieldTypePattern(CliAdapterLayerClass.class, "persistenceAdapterLayerClass", PersistenceAdapterLayerClass.class),
expectedAccessViolationPattern(CliAdapterLayerClass.class, "call", RestAdapterLayerClass.class, "callMe"),
fieldTypePattern(CliAdapterLayerClass.class, "restAdapterLayerClass", RestAdapterLayerClass.class),

expectedAccessViolationPattern(PersistenceAdapterLayerClass.class, "call", CliAdapterLayerClass.class, "callMe"),
fieldTypePattern(PersistenceAdapterLayerClass.class, "cliAdapterLayerClass", CliAdapterLayerClass.class),
expectedAccessViolationPattern(PersistenceAdapterLayerClass.class, "call", RestAdapterLayerClass.class, "callMe"),
fieldTypePattern(PersistenceAdapterLayerClass.class, "restAdapterLayerClass", RestAdapterLayerClass.class),

expectedAccessViolationPattern(RestAdapterLayerClass.class, "call", CliAdapterLayerClass.class, "callMe"),
fieldTypePattern(RestAdapterLayerClass.class, "cliAdapterLayerClass", CliAdapterLayerClass.class),
expectedAccessViolationPattern(RestAdapterLayerClass.class, "call", PersistenceAdapterLayerClass.class, "callMe"),
fieldTypePattern(RestAdapterLayerClass.class, "persistenceAdapterLayerClass", PersistenceAdapterLayerClass.class)
);
assertPatternMatches(result.getFailureReport().getDetails(), expectedRegexes);
assertThat(result.getFailureReport().getDetails().size()).isEqualTo(expectedRegexes.size());
}

@Test
@UseDataProvider("toIgnore")
public void ignores_specified_violations(RuleWithIgnore layeredArchitectureWithIgnore) {
Expand Down Expand Up @@ -235,4 +303,4 @@ public String toString() {
return description;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli;

import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence.PersistenceAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest.RestAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.application.ApplicationLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.model.DomainModelLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.service.DomainServiceLayerClass;

public class CliAdapterLayerClass {
private DomainModelLayerClass domainModelLayerClass;
private DomainServiceLayerClass domainServiceLayerClass;
private ApplicationLayerClass applicationLayerClass;
private CliAdapterLayerClass cliAdapterLayerClass;
private PersistenceAdapterLayerClass persistenceAdapterLayerClass;
private RestAdapterLayerClass restAdapterLayerClass;

private void call() {
domainModelLayerClass.callMe();
domainServiceLayerClass.callMe();
applicationLayerClass.callMe();
cliAdapterLayerClass.callMe();
persistenceAdapterLayerClass.callMe();
restAdapterLayerClass.callMe();
}

public void callMe() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence;

import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli.CliAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest.RestAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.application.ApplicationLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.model.DomainModelLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.service.DomainServiceLayerClass;

public class PersistenceAdapterLayerClass {
private DomainModelLayerClass domainModelLayerClass;
private DomainServiceLayerClass domainServiceLayerClass;
private ApplicationLayerClass applicationLayerClass;
private CliAdapterLayerClass cliAdapterLayerClass;
private PersistenceAdapterLayerClass persistenceAdapterLayerClass;
private RestAdapterLayerClass restAdapterLayerClass;

private void call() {
domainModelLayerClass.callMe();
domainServiceLayerClass.callMe();
applicationLayerClass.callMe();
cliAdapterLayerClass.callMe();
persistenceAdapterLayerClass.callMe();
restAdapterLayerClass.callMe();
}

public void callMe() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest;

import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli.CliAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence.PersistenceAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.application.ApplicationLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.model.DomainModelLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.service.DomainServiceLayerClass;

public class RestAdapterLayerClass {
private DomainModelLayerClass domainModelLayerClass;
private DomainServiceLayerClass domainServiceLayerClass;
private ApplicationLayerClass applicationLayerClass;
private CliAdapterLayerClass cliAdapterLayerClass;
private PersistenceAdapterLayerClass persistenceAdapterLayerClass;
private RestAdapterLayerClass restAdapterLayerClass;

private void call() {
domainModelLayerClass.callMe();
domainServiceLayerClass.callMe();
applicationLayerClass.callMe();
cliAdapterLayerClass.callMe();
persistenceAdapterLayerClass.callMe();
restAdapterLayerClass.callMe();
}

public void callMe() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.tngtech.archunit.library.testclasses.onionarchitecture.application;

import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli.CliAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence.PersistenceAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest.RestAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.model.DomainModelLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.service.DomainServiceLayerClass;

public class ApplicationLayerClass {
private DomainModelLayerClass domainModelLayerClass;
private DomainServiceLayerClass domainServiceLayerClass;
private ApplicationLayerClass applicationLayerClass;
private CliAdapterLayerClass cliAdapterLayerClass;
private PersistenceAdapterLayerClass persistenceAdapterLayerClass;
private RestAdapterLayerClass restAdapterLayerClass;

private void call() {
domainModelLayerClass.callMe();
domainServiceLayerClass.callMe();
applicationLayerClass.callMe();
cliAdapterLayerClass.callMe();
persistenceAdapterLayerClass.callMe();
restAdapterLayerClass.callMe();
}

public void callMe() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.tngtech.archunit.library.testclasses.onionarchitecture.domain.model;

import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli.CliAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence.PersistenceAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest.RestAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.application.ApplicationLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.domain.service.DomainServiceLayerClass;

public class DomainModelLayerClass {
private DomainModelLayerClass domainModelLayerClass;
private DomainServiceLayerClass domainServiceLayerClass;
private ApplicationLayerClass applicationLayerClass;
private CliAdapterLayerClass cliAdapterLayerClass;
private PersistenceAdapterLayerClass persistenceAdapterLayerClass;
private RestAdapterLayerClass restAdapterLayerClass;

private void call() {
domainModelLayerClass.callMe();
domainServiceLayerClass.callMe();
applicationLayerClass.callMe();
cliAdapterLayerClass.callMe();
persistenceAdapterLayerClass.callMe();
restAdapterLayerClass.callMe();
}

public void callMe() {
}
}
Loading

0 comments on commit 1aaab34

Please sign in to comment.