Skip to content

Commit

Permalink
Add archunit tests with default rules (#355)
Browse files Browse the repository at this point in the history
feat(archunit): Add archunit tests with default rules some as warnings.
  • Loading branch information
juancgalvis authored May 30, 2023
1 parent 3329c98 commit ac329d0
Show file tree
Hide file tree
Showing 17 changed files with 402 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ jobs:
mkdir -p ~/.gradle/
echo "${{secrets.SIGNING_KEY_FILE}}" | base64 -d > ~/.gradle/secring.gpg
- name: Publish Libraries
run: ./gradlew publishToSonatype --debug closeAndReleaseSonatypeStagingRepository -Psigning.keyId=${{ secrets.SIGNING_KEY_ID }} -Psigning.password=${{ secrets.SIGNING_KEY_PASSWORD }} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg)
run: ./gradlew publishToSonatype --info closeAndReleaseSonatypeStagingRepository -Psigning.keyId=${{ secrets.SIGNING_KEY_ID }} -Psigning.password=${{ secrets.SIGNING_KEY_PASSWORD }} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg)
env:
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.MAVEN_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.MAVEN_PASSWORD }}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ The **`validateStructure | vs`** Validate that project references aren't violate
gradle vs
```

This validation has another best practices verifications, which you can see on the generated
`ArchitectureTest` file within the unit tests of the `app-service` module.

### Dependency Rules

One important point made by Robert C. Martin on Clean Architecture is the **Dependency Rule**, that can be summarized like
Expand Down
1 change: 1 addition & 0 deletions src/main/java/co/com/bancolombia/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class Constants {
public static final String AWS_BOM_VERSION = "2.19.33";
public static final String COMMONS_JMS_VERSION = "1.0.0";
public static final String GRAPHQL_KICKSTART_VERSION = "15.0.0";
public static final String ARCH_UNIT_VERSION = "1.0.1";
public static final String TOMCAT_EXCLUSION_KOTLIN =
"configurations {\n"
+ "\tall {\n"
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/co/com/bancolombia/PluginClean.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,41 @@
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.testing.Test;
import org.jetbrains.annotations.NotNull;

public class PluginClean implements Plugin<Project> {
private CleanPluginExtension cleanPluginExtension;

public void apply(Project project) {
project.getPluginManager().apply("java");
cleanPluginExtension =
project.getExtensions().create("cleanPlugin", CleanPluginExtension.class);

TaskContainer taskContainer = project.getTasks();
initTasks().forEach(task -> this.appendTask(taskContainer, task));

project.getSubprojects().forEach(this::listenTest);

taskContainer
.getByName("compileJava")
.getDependsOn()
.add(taskContainer.getByName("validateStructure"));
}

private void listenTest(Project project) {
project.getLogger().lifecycle("Injecting test logger");
project
.getTasks()
.withType(Test.class)
.configureEach(
test ->
test.addTestOutputListener(
(testDescriptor, testOutputEvent) -> {
if (!testOutputEvent.getMessage().contains("DEBUG")) {
test.getLogger().lifecycle(testOutputEvent.getMessage().replace('\n', ' '));
}
}));
}

private Stream<TaskModel> initTasks() {
Expand Down
7 changes: 1 addition & 6 deletions src/main/java/co/com/bancolombia/adapters/RestService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import co.com.bancolombia.models.Release;
import co.com.bancolombia.utils.FileUtils;
import co.com.bancolombia.utils.RestConsumer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import org.gradle.api.logging.Logger;
Expand All @@ -30,11 +29,7 @@ public Optional<DependencyRelease> getTheLastDependencyRelease(DependencyRelease
}

private boolean shouldMock() {
try {
return "true".equals(FileUtils.readProperties(".", "simulateRest"));
} catch (IOException ignored) {
return false;
}
return FileUtils.readBooleanProperty("simulateRest");
}

private static class RestOperations implements Operations {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ private void initialize() {
params.put("graphqlKickStartVersion", Constants.GRAPHQL_KICKSTART_VERSION);
params.put("secretsVersion", Constants.SECRETS_VERSION);
params.put("blockHoundVersion", Constants.BLOCK_HOUND_VERSION);
params.put("archUnitVersion", Constants.ARCH_UNIT_VERSION);
params.put("lombok", isEnableLombok());
params.put("metrics", withMetrics());
loadPackage();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package co.com.bancolombia.factory.validations.architecture;

import co.com.bancolombia.Constants;
import co.com.bancolombia.factory.ModuleBuilder;
import co.com.bancolombia.utils.FileUtils;
import java.util.Map;
import java.util.TreeMap;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedDependency;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ArchitectureValidation {

public static void inject(Project project, ModuleBuilder builder) {
if (!FileUtils.readBooleanProperty("skipArchitectureTests")) {
builder.addParam("reactive", builder.isReactive());
project.getAllprojects().stream()
.filter(p -> p.getName().equals("app-service"))
.findFirst()
.ifPresent(appService -> generateArchUnitFiles(project, appService, builder));
}
}

private static void prepareParams(Project project, Project appService, ModuleBuilder builder) {
Map<String, Boolean> deps = new TreeMap<>();
appService.getConfigurations().stream()
.filter(Configuration::isCanBeResolved)
.flatMap(c -> c.getResolvedConfiguration().getFirstLevelModuleDependencies().stream())
.forEach(dependency -> fillDependencyTree(deps, dependency));
boolean hasSpringWeb = deps.containsKey("org.springframework:spring-web");
project.getLogger().debug("hasSpringWeb: {}", hasSpringWeb);
builder.addParam("hasSpringWeb", hasSpringWeb);
}

private static void fillDependencyTree(Map<String, Boolean> deps, ResolvedDependency dependency) {
deps.put(dependency.getModuleGroup() + ":" + dependency.getName(), true);
dependency.getChildren().forEach(dep -> fillDependencyTree(deps, dep));
}

@SneakyThrows
private static void generateArchUnitFiles(
Project project, Project appService, ModuleBuilder builder) {
prepareParams(project, appService, builder);
project
.getLogger()
.lifecycle("Injecting ArchitectureTest in module {}", appService.getProjectDir().getName());
builder.setupFromTemplate("structure/applications/appservice/arch-validations");
builder.appendDependencyToModule(
"app-service",
"testImplementation 'com.tngtech.archunit:archunit:" + Constants.ARCH_UNIT_VERSION + "'");
builder.persist();
}
}
14 changes: 6 additions & 8 deletions src/main/java/co/com/bancolombia/task/ValidateStructureTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static co.com.bancolombia.Constants.APP_SERVICE;

import co.com.bancolombia.exceptions.CleanException;
import co.com.bancolombia.factory.validations.architecture.ArchitectureValidation;
import co.com.bancolombia.task.annotations.CATask;
import co.com.bancolombia.utils.FileUtils;
import co.com.bancolombia.utils.Utils;
Expand All @@ -14,24 +15,20 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.logging.Logger;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;

@CATask(
name = "validateStructure",
shortcut = "vs",
description = "Validate that project references are not violated")
public abstract class ValidateStructureTask extends DefaultTask {
private final Logger logger = getProject().getLogger();
public abstract class ValidateStructureTask extends AbstractCleanArchitectureDefaultTask {
private static final String MODEL_MODULE = "model";
private static final String USE_CASE_MODULE = "usecase";
private static final String REACTOR_CORE = "reactor-core";
Expand All @@ -41,17 +38,18 @@ public abstract class ValidateStructureTask extends DefaultTask {

@Input
@Optional
// @Inject
public abstract Property<String> getWhitelistedDependencies();

@TaskAction
public void validateStructureTask() throws IOException, CleanException {
@Override
public void execute() throws IOException, CleanException {
String packageName =
FileUtils.readProperties(getProject().getProjectDir().getPath(), "package");
logger.lifecycle("Clean Architecture plugin version: {}", Utils.getVersionPlugin());
getModules().forEach(d -> logger.lifecycle("Submodules: " + d.getKey()));
logger.lifecycle("Project Package: {}", packageName);

ArchitectureValidation.inject(getProject(), builder);

if (!validateModelLayer()) {
throw new CleanException("Model module is invalid");
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/co/com/bancolombia/utils/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ public static String readProperties(String projectPath, String variable) throws
}
}

public static boolean readBooleanProperty(String variable) {
try {
return "true".equals(readProperties(".", variable));
} catch (IOException ignored) {
return false;
}
}

public static void setGradleProperty(String projectPath, String variable, String value)
throws IOException {
try (FileInputStream fis = new FileInputStream(projectPath + GRADLE_PROPERTIES)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ArchitectureTest.java
Loading

0 comments on commit ac329d0

Please sign in to comment.