
structure.applications.appservice.arch-validations.architecture-test.mustache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scaffold-clean-architecture Show documentation
Show all versions of scaffold-clean-architecture Show documentation
Gradle plugin to create a clean application in Java that already works, It follows our best practices!
package {{package}};
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvent;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import com.tngtech.archunit.lang.syntax.elements.MembersShouldConjunction;
import lombok.extern.java.Log;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
{{#hasSpringWeb}}
import org.springframework.web.bind.annotation.RestController;
{{/hasSpringWeb}}
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.stream.Stream;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
// Please do not modify this file
@Log
class ArchitectureTest {
private static JavaClasses allClasses;
private static JavaClasses domainClasses;
private static JavaClasses useCaseClasses;
private static final ConcurrentMap issues = new ConcurrentHashMap<>();
private final Map files = Utils.findFiles();
@BeforeAll
static void importClasses() {
allClasses = new ClassFileImporter().importPackages("{{package}}")
.that(new DescribedPredicate<>("is-not-commons-lib") {
@Override
public boolean test(JavaClass javaClass) {
return !javaClass.getPackageName().contains("co.com.bancolombia.commons.jms");
}
});
domainClasses = new ClassFileImporter().importPackages("{{package}}.usecase", "{{package}}.model");
useCaseClasses = new ClassFileImporter().importPackages("{{package}}.usecase");
}
@AfterAll
static void exportIssues() {
try {
ObjectMapper mapper = new ObjectMapper();
List.of({{{modulePaths}}}).forEach(path -> {
try {
Files.write(Path.of(path, "build/issues.json"), mapper.writeValueAsBytes(issues.getOrDefault(path, new Utils.IssuesReport())));
} catch (IOException e) {
log.log(Level.WARNING, e.getMessage());
}
});
} catch (Exception e) {
log.log(Level.WARNING, e.getMessage());
}
}
{{#reactive}}
@Test
void reactiveFlowsShouldUseAwsAsyncClients() {
ArchRule rule = classes()
.that()
.haveSimpleNameNotEndingWith("Config")
.and(areUsingAnAwsClient())
.should(beAwsAsyncClient())
.allowEmptyShould(true)
.as("Rule_1.5: Reactive flows should use aws async clients");
checkWithWarning(() -> rule.check(allClasses));
}
{{/reactive}}
@Test
void domainClassesShouldNotBeNamedWithTechSuffixes() {
ArchRule rule = Stream.of({{{forbiddenDomainSuffixes}}})
.reduce(classes().should().haveSimpleNameNotEndingWith("Dto"),
(cj, tool) -> cj.andShould().haveSimpleNameNotEndingWith(tool),
(a, b) -> b)
.allowEmptyShould(true)
.as("Rule_2.2: Domain classes should not be named with technology suffixes");
checkWithWarning(() -> rule.check(domainClasses));
}
@Test
void domainClassesShouldNotBeNamedWithToolNames() {
ArchRule rule = Stream.of({{{forbiddenDomainClassNames}}})
.reduce(classes().should().haveSimpleNameNotContaining("rabbit"),
(cj, tool) -> cj.andShould().haveSimpleNameNotContaining(tool),
(a, b) -> b)
.allowEmptyShould(true)
.as("Rule_2.4: Domain classes should not be named with technology names");
checkWithWarning(() -> rule.check(domainClasses));
}
@Test
void useCaseFinalFields() {
ArchRule rule = classes()
.that()
.haveSimpleNameEndingWith("UseCase")
.should()
.haveOnlyFinalFields()
.allowEmptyShould(true)
.as("Rule_2.5: UseCases should only have final attributes to avoid concurrency issues");
rule.check(useCaseClasses);
}
@Test
void domainClassesShouldNotHaveFieldsNamedWithToolNames() {
ArchRule rule = Stream.of({{{forbiddenDomainClassNames}}})
.reduce((MembersShouldConjunction>) fields().should().haveNameNotContaining("rabbit"),
(cj, tool) -> cj.andShould().haveNameNotContaining(tool),
(a, b) -> b)
.allowEmptyShould(true)
.as("Rule_2.7: Domain classes should not have fields named with technology names");
checkWithWarning(() -> rule.check(domainClasses));
}
@Test
void beansShouldOnlyHaveFinalFields() {
ArchRule rule = classes()
.that()
.areNotAnnotatedWith(ConfigurationProperties.class)
.and()
.areAnnotatedWith(Configuration.class)
.or().areAnnotatedWith(Component.class)
.or().areAnnotatedWith(Controller.class)
.or().areAnnotatedWith(Repository.class)
.or().areAnnotatedWith(Service.class)
{{#hasSpringWeb}}
.or().areAnnotatedWith(RestController.class)
{{/hasSpringWeb}}
.should()
.haveOnlyFinalFields()
.allowEmptyShould(true)
.as("Rule_2.7: Beans classes should only have final attributes (injection by constructor) to avoid concurrency issues");
checkWithWarning(() -> rule.check(allClasses));
}
// Utilities
{{#reactive}}
private DescribedPredicate areUsingAnAwsClient() {
return withPredicate("are using an aws client",
input -> input.getDirectDependenciesFromSelf()
.stream()
.anyMatch(dependency -> dependency.getTargetClass()
.getPackage()
.getName()
.contains("software.amazon.awssdk.services")
&& dependency.getTargetClass()
.getSimpleName()
.contains("Client")));
}
private ArchCondition beAwsAsyncClient() {
return withCondition("be aws async client",
(item, events) -> item.getDirectDependenciesFromSelf()
.stream()
.filter(dependency -> dependency.getTargetClass()
.getPackage()
.getName()
.contains("software.amazon.awssdk.services")
&& dependency.getTargetClass()
.getSimpleName()
.contains("Client"))
.filter(dependency -> !dependency.getTargetClass()
.getSimpleName()
.contains("Async"))
.forEach(dependency -> events.add(SimpleConditionEvent.violated(dependency,
ConditionEvent.createMessage(dependency, "Use of sync client " + dependency.getTargetClass().getSimpleName())))));
}
private DescribedPredicate withPredicate(String description, TestPredicate predicate) {
return new DescribedPredicate<>(description) {
@Override
public boolean test(JavaClass input) {
return predicate.test(input);
}
};
}
private ArchCondition withCondition(String description, CheckCondition condition) {
return new ArchCondition<>(description) {
@Override
public void check(JavaClass item, ConditionEvents events) {
condition.check(item, events);
}
};
}
private interface TestPredicate {
boolean test(JavaClass input);
}
private interface CheckCondition {
void check(JavaClass item, ConditionEvents events);
}
{{/reactive}}
private void checkWithWarning(Runnable runnable) {
try {
runnable.run();
} catch (AssertionError e) {
Utils.ArchitectureRule rule = Utils.parseToRule(e.getMessage());
rule.getLocations().forEach(location -> {
Utils.JavaFile file = files.get(location.getClassName());
if (file != null) {
issues.computeIfAbsent(file.getModulePath(), key -> new Utils.IssuesReport())
.add(Utils.Issue.from(rule.getRuleId(), Utils.Issue.Severity.MAJOR, Utils.Issue.Type.CODE_SMELL,
Utils.Issue.Location.from(rule.getDescription() + location.getDescription(),
file.getPath(), Utils.Issue.TextRange.from(
Utils.resolveFinalLocation(file, location))), 5));
}
});
log.log(Level.WARNING, "ARCHITECTURE_RULE_VIOLATED: This will cause a build error in future.\nPlease review our wiki at https://bancolombia.github.io/scaffold-clean-architecture/docs/advanced/arch-unit-analysis", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy