Taikai¶
1. Introduction¶
Taikai is an automated architecture testing tool for Java projects designed to maintain clean and consistent architecture. It enforces predefined and custom architectural constraints, ensuring code quality, maintainability, and adherence to best practices.
2. Getting Started¶
To use Taikai, include it as a dependency in your Maven pom.xml
:
<dependency>
<groupId>com.enofex</groupId>
<artifactId>taikai</artifactId>
<version>${taikai.version}</version>
<scope>test</scope>
</dependency>
Ensure to configure ${taikai.version}
to the latest stable version compatible with your project's ArchUnit version.
3. Usage¶
3.1 Setting the Namespace¶
The namespace
setting specifies the base package of your project. Taikai will analyze all classes within this namespace. The default mode is WITHOUT_TESTS
, which excludes test classes from the import check.
3.2 Setting the JavaClasses¶
You can configure classes
as well. This allows you to specify specific Java classes to analyze. Note that setting both namespace
and classes
simultaneously is not supported and will result in an IllegalArgumentException
.
JavaClasses classes = new ClassFileImporter()
.importClasses(ClassToCheck.class)
Taikai.builder()
.classes(classes)
.build()
.check();
3.3 Enforcing Rules on Empty Sets¶
The failOnEmpty
setting determines whether the build should fail if no classes match a given rule. This is useful to ensure that your rules are applied consistently and to avoid false positives. The default is false
.
3.4 Excluding Classes Globally¶
You can globally exclude specific classes from all rule checks by using the excludeClasses
methods in the builder. This ensures that the specified classes are not checked by any rule.
Taikai.builder()
.namespace("com.company.project")
.excludeClasses("com.company.project.foo.ClassToExclude", "com.company.project.bar.ClassToExclude")
.build()
.check();
3.5 Modifying an Existing Configuration¶
The toBuilder
method allows you to create a new Builder instance from an existing Taikai configuration. This is useful if you need to modify an existing configuration.
Taikai taikai = Taikai.builder()
.namespace("com.company.project")
.excludeClasses("com.company.project.SomeClassToExclude")
.failOnEmpty(true)
.java(java -> java
.fieldsShouldNotBePublic())
.build();
// Modify the existing configuration
Taikai modifiedTaikai = taikai.toBuilder()
.namespace("com.company.newproject")
.excludeClasses("com.company.project.AnotherClassToExclude")
.failOnEmpty(false)
.java(java -> java
.classesShouldImplementHashCodeAndEquals()
.finalClassesShouldNotHaveProtectedMembers())
.build();
// Perform the check with the modified configuration
modifiedTaikai.check();
4. Rules Overview¶
Taikai's architecture rules cover a wide range of categories to enforce best practices and maintain consistency.
Java Rules¶
The default mode is WITHOUT_TESTS
, which excludes test classes from the import check.
Category | Method Name | Rule Description |
---|---|---|
General | classesShouldImplementHashCodeAndEquals | Classes should implement hashCode and equals together. |
General | classesShouldResideInPackage | Classes matching specific naming patterns should reside in a specified package. |
General | classesShouldResideInPackage | Classes should reside in a specified package. (e.g., com.company.project.. ). |
General | classesAnnotatedWithShouldResideInPackage | Classes annotated with a specific annotation should reside in a specified package. |
General | classesShouldResideOutsidePackage | Classes matching specific naming patterns should reside outside a specified package. |
General | classesShouldBeAnnotatedWith | Classes matching specific naming patterns should be annotated with a specified annotation. |
General | classesShouldBeAnnotatedWithAll | Classes annotated with a specific annotation should be annotated with a specified annotations. |
General | classesShouldNotBeAnnotatedWith | Classes matching specific naming patterns should not be annotated with a specified annotation. |
General | classesShouldBeAssignableTo | Classes matching specific naming patterns should be assignable to a certain type. |
General | classesShouldImplement | Classes matching specific naming patterns should implement to a interface. |
General | fieldsShouldHaveModifiers | Fields matching specific naming patterns should have specified modifiers. |
General | fieldsShouldNotBePublic | Fields should not be public , except constants. |
General | methodsShouldNotDeclareGenericExceptions | Methods should not declare generic exceptions, like Exception or RuntimeException . |
General | methodsShouldNotDeclareException | Methods with names matching a specified pattern should not declare a specified exception type. |
General | methodsShouldBeAnnotatedWith | Methods matching specific naming patterns should be annotated with a specified annotation. |
General | methodsShouldBeAnnotatedWithAll | Methods annotated with a specific annotation should be annotated with a specified annotations. |
General | noUsageOf | Disallow usage of specific classes. |
General | noUsageOfDeprecatedAPIs | No usage of deprecated APIs annotated with @Deprecated . |
General | noUsageOfSystemOutOrErr | Disallow usage of System.out or System.err . |
General | utilityClassesShouldBeFinalAndHavePrivateConstructor | Utility classes with only static methods (except main ) should be final and have a private constructor. |
General | finalClassesShouldNotHaveProtectedMembers | Classes declared as final should not contain any protected members. |
General | serialVersionUIDShouldBeStaticFinalLong | Fields named serialVersionUID should be declared as static final long . |
Imports | shouldHaveNoCycles | No cyclic dependencies in imports. |
Imports | shouldNotImport | Disallow specific imports (e.g., ..shaded.. ). |
Naming | packagesShouldMatchDefault | Packages should match ^[a-z_]+(\.[a-z_][a-z0-9_]*)*$ naming patterns. |
Naming | packagesShouldMatch | Packages should match specific naming patterns. |
Naming | classesShouldNotMatch | Classes should not match specific naming patterns (e.g., .*Impl ). |
Naming | classesAnnotatedWithShouldMatch | Classes annotated with a specific annotation should match specific naming patterns. |
Naming | methodsShouldNotMatch | Methods should not match specific naming patterns. |
Naming | methodsAnnotatedWithShouldMatch | Methods annotated with a specific annotation should match specific naming patterns. |
Naming | fieldsShouldNotMatch | Fields should not match specific naming patterns. |
Naming | fieldsShouldMatch | Fields should match specific naming patterns for specific classes. |
Naming | fieldsAnnotatedWithShouldMatch | Fields annotated with a specific annotation should match specific naming patterns. |
Naming | constantsShouldFollowConventions | Constants should follow naming conventions, except serialVersionUID . |
Naming | interfacesShouldNotHavePrefixI | Interfaces should not have the prefix I . |
Logging Rules¶
The default mode is WITHOUT_TESTS
, which checks only test classes.
Category | Method Name | Rule Description |
---|---|---|
General | loggersShouldFollowConventions | Ensure that specified loggers follow specific naming patterns and have the required modifiers. |
General | classesShouldUseLogger | Ensure that classes matching a given regex have a field of a specified logger type. |
Test Rules¶
The default mode is ONLY_TESTS
, which checks only test classes.
Category | Method Name | Rule Description |
---|---|---|
JUnit 5 | classesShouldBePackagePrivate | Ensure that classes whose names match a specific naming pattern are declared as package-private. |
JUnit 5 | classesShouldNotBeAnnotatedWithDisabled | Ensure classes are not annotated with @Disabled . |
JUnit 5 | methodsShouldBePackagePrivate | Ensure that test methods annotated with @Test or @ParameterizedTest are package-private. |
JUnit 5 | methodsShouldNotBeAnnotatedWithDisabled | Ensure methods are not annotated with @Disabled . |
JUnit 5 | methodsShouldBeAnnotatedWithDisplayName | Ensure that test methods annotated with @Test or @ParameterizedTest are annotated with @DisplayName . |
JUnit 5 | methodsShouldMatch | Ensure that test methods annotated with @Test or @ParameterizedTest have names matching a specific regex pattern. |
JUnit 5 | methodsShouldNotDeclareExceptions | Ensure that test methods annotated with @Test or @ParameterizedTest do not declare any thrown exceptions. |
JUnit 5 | methodsShouldContainAssertionsOrVerifications | Ensure that test methods annotated with @Test or @ParameterizedTest contain at least one assertion or verification. |
Spring Rules¶
The default mode is WITHOUT_TESTS
, which excludes test classes from the import check.
Category | Method Name | Rule Description |
---|---|---|
General | noAutowiredFields | Fields should not be annotated with @Autowired , prefer constructor injection. |
Boot | springBootApplicationShouldBeIn | Ensure @SpringBootApplication is in the default package. |
Properties | namesShouldEndWithProperties | Properties annotated with @ConfigurationProperties should end with Properties . |
Properties | namesShouldMatch | Properties annotated with @ConfigurationProperties should match a regex pattern. |
Properties | shouldBeAnnotatedWithValidated | Properties annotated with @ConfigurationProperties should be annotated with @Validated . |
Properties | shouldBeAnnotatedWithConfigurationProperties | Properties ending with Properties should be annotated with @ConfigurationProperties . |
Configurations | namesShouldEndWithConfiguration | Configurations annotated with @Configuration should end with Configuration . |
Configurations | namesShouldMatch | Configurations annotated with @Configuration should match a regex pattern. |
Controllers | namesShouldEndWithController | Controllers annotated with @Controller or @RestController should end with Controller . |
Controllers | namesShouldMatch | Controllers annotated with @Controller or @RestController should match a regex pattern. |
Controllers | shouldBeAnnotatedWithController | Controllers ending with Controller should be annotated with @Controller . |
Controllers | shouldBeAnnotatedWithRestController | Controllers ending with Controller should be annotated with @RestController . |
Controllers | shouldBeAnnotatedWithValidated | Controllers annotated with @Controller or @RestController or match a regex pattern, containing methods having a parameter annotated with @PathVariable or @RequestParam and a validation annotation (e.g., @Min , @NotNull , etc.), must also be annotated with @Validated . |
Controllers | shouldBePackagePrivate | Controllers annotated with @Controller or @RestController should be package-private. |
Controllers | shouldNotDependOnOtherControllers | Controllers annotated with @Controller or @RestController should not depend on other controllers annotated with @Controller or @RestController . |
Repositories | namesShouldEndWithRepository | Repositories annotated with @Repository should end with Repository . |
Repositories | namesShouldMatch | Repositories annotated with @Repository should match a regex pattern. |
Repositories | shouldBeAnnotatedWithRepository | Repositories ending with Repository should be annotated with @Repository . |
Repositories | shouldNotDependOnServices | Repositories annotated with @Repository should not depend on service classes annotated with @Service . |
Services | namesShouldEndWithService | Services annotated with @Service should end with Service . |
Services | namesShouldMatch | Services annotated with @Service should match a regex pattern. |
Services | shouldBeAnnotatedWithService | Services ending with Service should be annotated with @Service . |
Services | shouldNotDependOnControllers | Services annotated with @Service should not depend on controllers annotated with @Controller or @RestController . |
5. Java Rules¶
Java configuration involves defining constraints related to Java language features, coding standards, and architectural patterns.
- No Usage of Deprecated APIs: Ensure that deprecated APIs annotated with
@Deprecated
not used in the codebase.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.noUsageOfDeprecatedAPIs())
.build()
.check();
- Classes Should Implement
hashCode
andequals
together: Ensure that classes override thehashCode
andequals
methods together.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldImplementHashCodeAndEquals())
.build()
.check();
- Classes Should Reside in Specified Package: Ensure that classes matching a specific regex pattern reside in the specified package.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldResideInPackage(".*Utils", "com.company.project.utils"))
.build()
.check();
- Classes Should Reside in Specified Package: Ensure that classes reside in the specified package.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldResideInPackage("com.company.project.."))
.build()
.check();
- Classes Annotated with Specified Annotation Should Reside in Specified Package: Ensure that classes annotated with a specific annotation reside in the specified package.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesAnnotatedWithShouldResideInPackage(PublicApi.class, "com.company.project.api")
.classesAnnotatedWithShouldResideInPackage("com.company.project.PublicApi", "com.company.project.api"))
.build()
.check();
- Classes Should Reside outside Specified Package: Ensure that classes matching a specific regex pattern reside outside the specified package.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldResideOutsidePackage(".*Dto", "com.company.project.domain"))
.build()
.check();
- Classes Should Be Annotated with Specified Annotation: Ensure that classes matching a specific regex pattern are annotated with the specified annotation.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldBeAnnotatedWith(".*Api", PublicApi.class)
.classesShouldBeAnnotatedWith(".*Api", "com.company.project.PublicApi"))
.build()
.check();
- Classes Annotated with a Specified Annotation Should Be Annotated with Specified Annotations: Ensure that classes annotated with a specific annotations should be annotated with the specified annotations.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldBeAnnotatedWithAll(RestController.class, List.of(RequestMapping.class))
.classesShouldBeAnnotatedWithAll("org.springframework.web.bind.annotation.RestController", List.of("org.springframework.web.bind.annotation.RequestMapping"))
.build()
.check();
- Classes Should Not Be Annotated with Specified Annotation: Ensure that classes matching a specific regex pattern are not annotated with the specified annotation.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldNotBeAnnotatedWith(".*Internal", PublicApi.class)
.classesShouldNotBeAnnotatedWith(".*Internal", "com.company.project.PublicApi"))
.build()
.check();
- Classes Should Be Assignable to a Specified Type: Ensure that classes matching a specific regex pattern assignable to a certain type.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldBeAssignableTo(".*Repository", BaseRepository.class)
.classesShouldBeAssignableTo(".*Repository", "com.company.project.BaseRepository"))
.build()
.check();
- Classes Should Implement a Specified Interface: Ensure that classes matching a specific regex pattern implement a certain interface.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.classesShouldImplement(".*Repository", CrudRepository.class)
.classesShouldImplement(".*Repository", "org.springframework.data.repository.CrudRepository"))
.build()
.check();
- Methods Should Not Throw Generic Exception: Ensure that methods do not throw generic exceptions like
Exception
andRuntimeException
and use specific exception types instead.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.methodsShouldNotDeclareGenericExceptions())
.build()
.check();
- Methods Should Not Declare Specific Exception: Ensure that methods with names matching a specified pattern do not declare a specified exception type.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.methodsShouldNotDeclareException("should*", SpecificException.class)
.methodsShouldNotDeclareException("should*", "com.company.project.SpecificException"))
.build()
.check();
- Methods Should Be Annotated with Specified Annotation: Ensure that methods matching a specific regex pattern are annotated with the specified annotation.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.methodsShouldBeAnnotatedWith(".*Api", PublicApi.class)
.methodsShouldBeAnnotatedWith(".*Api", "com.company.project.PublicApi"))
.build()
.check();
- Methods Annotated with a Specified Annotation Should Be Annotated with Specified Annotations: Ensure that methods annotated with a specific annotations should be annotated with the specified annotations.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.methodsShouldBeAnnotatedWithAll(Modifying.class, List.of(Transactional.class, Query.class))
.methodsShouldBeAnnotatedWithAll("org.springframework.data.jpa.repository.Modifying", List.of("org.springframework.transaction.annotation.Transactional", "org.springframework.data.jpa.repository.Query"))
.build()
.check();
- Utility Classes Should Be Final and Have Private Constructor: Ensure that utility classes with only
static
methods exceptmain
should be declared asfinal
and haveprivate
constructors to prevent instantiation.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.utilityClassesShouldBeFinalAndHavePrivateConstructor())
.build()
.check();
- Fields Should Have Modifiers: Ensure that fields matching a specific naming pattern have the required modifiers.
Taikai.builder()
.namespace("com.enofex.taikai")
.java(java -> java
.fieldsShouldHaveModifiers("^[A-Z][A-Z0-9_]*$", List.of(STATIC, FINAL)))
.build()
.check();
- Fields Should Not Be Public: Ensure that no fields in your Java classes are declared as
public
, except constants.
Taikai.builder()
.namespace("com.enofex.taikai")
.java(java -> java
.fieldsShouldNotBePublic())
.build()
.check();
- Ensure
serialVersionUID
isstatic final long
: Ensure that fields namedserialVersionUID
are declared asstatic final long
.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.serialVersionUIDShouldBeStaticFinalLong())
.build()
.check();
- Imports Configuration: Ensure that there are no cyclic dependencies in imports and disallow specific imports.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.imports(imports -> imports
.shouldHaveNoCycles()
.shouldNotImport("..shaded..")
.shouldNotImport("..lombok..")
.shouldNotImport(".*Service", "com.company.project.SpecificException")))
.build()
.check();
- Naming Configuration: Define naming conventions for packages, classes, methods, fields, constants and interfaces.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.naming(naming -> naming
.packagesShouldMatchDefault()
.packagesShouldMatch("regex")
.classesShouldNotMatch(".*Impl")
.classesAnnotatedWithShouldMatch(Annotation.class, "coolClass")
.classesAnnotatedWithShouldMatch("com.company.project.Annotation", "coolClass")
.methodsShouldNotMatch("coolMethod")
.methodsAnnotatedWithShouldMatch(Annotation.class, "coolMethods")
.methodsAnnotatedWithShouldMatch("com.company.project.Annotation", "coolMethods")
.fieldsShouldNotMatch("coolField")
.fieldsShouldMatch(Annotation.class, "coolField")
.fieldsShouldMatch("com.company.project.Annotation", "coolField")
.fieldsAnnotatedWithShouldMatch(Annotation.class, "coolField")
.fieldsAnnotatedWithShouldMatch("com.company.project.Annotation", "coolField")
.constantsShouldFollowConventions()
.interfacesShouldNotHavePrefixI())))
.build()
.check();
- No Usage of Specific Classes: Ensure that certain classes are not used in your codebase.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.noUsageOf(UnwantedClass.class)
.noUsageOf(UnwantedClass.class, "in.specific.package")
.noUsageOf("com.example.UnwantedClass")
.noUsageOf("com.example.UnwantedClass", "in.specific.package"))
.build()
.check();
- No Usage of
System.out
orSystem.err
: Enforce disallowing the use ofSystem.out
andSystem.err
for logging, encouraging the use of proper logging frameworks instead.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.noUsageOfSystemOutOrErr())
.build()
.check();
- Ensure Final Classes Do Not Have Protected Members: Ensures that classes declared as
final
do not contain anyprotected
members. Sincefinal
classes cannot be subclassed, havingprotected
members is unnecessary.
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.finalClassesShouldNotHaveProtectedMembers())
.build()
.check();
6. Logging Rules¶
Logging configuration involves specifying constraints related to logging frameworks and practices.
- Ensure Logger Field Conforms to Standards: Ensure that classes use a logger field of the specified type, with the correct name and optionally required modifiers.
Taikai.builder()
.namespace("com.company.project")
.logging(logging -> logging
.loggersShouldFollowConventions(org.slf4j.Logger.class, "logger")
.loggersShouldFollowConventions("org.slf4j.Logger", "logger")
.loggersShouldFollowConventions(org.slf4j.Logger.class, "logger", List.of(PRIVATE, FINAL))
.loggersShouldFollowConventions("org.slf4j.Logger", "logger", List.of(PRIVATE, FINAL)))
.build()
.check();
- Ensure Classes Use Specified Logger: Ensure that classes matching a given regex have a field of a specified logger type.
Taikai.builder()
.namespace("com.company.project")
.logging(logging -> logging
.classesShouldUseLogger(org.slf4j.Logger.class, ".*Service")
.classesShouldUseLogger("org.slf4j.Logger", ".*Service"))
.build()
.check();
7. Test Rules¶
Test configuration involves specifying constraints related to testing frameworks and practices.
- JUnit 5 Configuration: Ensure that JUnit 5 test classes and methods are not annotated with
@Disabled
.
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.classesShouldNotBeAnnotatedWithDisabled()
.methodsShouldNotBeAnnotatedWithDisabled()))
.build()
.check();
- Ensure Test Methods are Package-Private: Ensure that JUnit 5 test methods annotated with
@Test
or@ParameterizedTest
are package-private.
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldBePackagePrivate()))
.build()
.check();
- Ensure Test Methods are Annotated with
@DisplayName
: Ensure that JUnit 5 test methods annotated with@Test
or@ParameterizedTest
are also annotated with@DisplayName
to provide descriptive test names.
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldBeAnnotatedWithDisplayName()))
.build()
.check();
- Ensure Test Methods Follow Naming Convention: Ensure that JUnit 5 test methods annotated with
@Test
or@ParameterizedTest
have names matching a specific regex pattern.
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldMatch("regex")))
.build()
.check();
- Ensure Test Methods Do Not Declare Thrown Exceptions: Ensure that JUnit 5 test methods annotated with
@Test
or@ParameterizedTest
do not declare any thrown exceptions.
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldNotDeclareExceptions()))
.build()
.check();
- Ensure Classes with Matching Names are Package-Private: Ensure that classes whose names match a specified regex pattern are declared as package-private.
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.classesShouldBePackagePrivate(".*Test")))
.build()
.check();
-
Ensure Test Methods Contain Assertions or Verifications: : Ensure that test methods annotated with
@Test
or@ParameterizedTest
contain at least one assertion or verification.- JUnit 5: Ensure the use of assertions from
org.junit.jupiter.api.Assertions
. - Mockito: Ensure the use of verification methods from
org.mockito.Mockito
likeverify
,inOrder
, orcapture
. - Hamcrest: Ensure the use of assertions from
org.hamcrest.MatcherAssert
. - AssertJ: Ensure the use of assertions from
org.assertj.core.api.Assertions
. - Truth: Ensure the use of assertions from
com.google.common.truth.Truth
. - Cucumber: Ensure the use of assertions from
io.cucumber.java.en.Then
orio.cucumber.java.en.Given
. - Spring MockMvc: Ensure the use of assertions from
org.springframework.test.web.servlet.MockMvc
likeandExpect
orandDo
. - ArchUnit: Ensure the use of the
check
method fromcom.tngtech.archunit.lang.ArchRule
. - Taikai: Ensure the use of the
check
method fromcom.enofex.taikai.Taikai
.
- JUnit 5: Ensure the use of assertions from
Taikai.builder()
.namespace("com.company.project")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldContainAssertionsOrVerifications()))
.build()
.check();
8. Spring Rules¶
Spring configuration involves defining constraints specific to Spring Framework usage.
- No Autowired Fields Configuration: Ensure that fields are not annotated with
@Autowired
and constructor injection is preferred.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.noAutowiredFields())
.build()
.check();
- Spring Boot Configuration: Ensure that the main application class annotated with
@SpringBootApplication
is located in the default package.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.boot(boot -> boot
.springBootApplicationShouldBeIn("com.company.project")))
.build()
.check();
- Properties Configuration: Ensure that configuration property classes end with
Properties
or match a specific regex pattern, are annotated with@ConfigurationProperties
or annotated with@Validated
.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.properties(properties -> properties
.shouldBeAnnotatedWithConfigurationProperties()
.namesShouldEndWithProperties()
.namesShouldMatch("regex")
.shouldBeAnnotatedWithValidated()))
.build()
.check();
- Configurations Configuration: Ensure that configuration classes end with
Configuration
or match a specific regex pattern.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.configurations(configuration -> configuration
.namesShouldEndWithConfiguration()
.namesShouldMatch("regex")))
.build()
.check();
- Controllers Configuration: Ensure that controller classes end with
Controller
or match a specific regex pattern, are annotated with@RestController
, do not depend on other controllers, has set@Validated
correctly, or are package-private.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.controllers(controllers -> controllers
.shouldBeAnnotatedWithRestController()
.namesShouldEndWithController()
.shouldBeAnnotatedWithValidated()
.namesShouldMatch("regex")
.shouldNotDependOnOtherControllers()
.shouldBePackagePrivate()))
.build()
.check();
- Services Configuration: Ensure that service classes end with
Service
or match a specific regex pattern and are annotated with@Service
and do not depend on other controllers.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.services(services -> services
.shouldBeAnnotatedWithService()
.shouldNotDependOnControllers()
.namesShouldMatch("regex")
.namesShouldEndWithService()))
.build()
.check();
- Repositories Configuration: Ensure that repository classes end with
Repository
or match a specific regex pattern and are annotated with@Repository
and not depend on classes annotated with@Service
.
Taikai.builder()
.namespace("com.company.project")
.spring(spring -> spring
.repositories(repositories -> repositories
.shouldNotDependOnServices()
.shouldBeAnnotatedWithRepository()
.namesShouldMatch("regex")
.namesShouldEndWithRepository()))
.build()
.check();
9. Customization¶
Custom Configuration for Import Rules¶
For every rule, you have the flexibility to add a custom configuration. This allows you to specify the namespace, import options, and exclude specific classes from the checks.
The Configuration
class offers various static methods to create custom configurations: - Configuration.of(String namespace)
to set a custom namespace. - Configuration.of(Namespace.IMPORT namespaceImport)
to specify import options such as WITHOUT_TESTS
, WITH_TESTS
, or ONLY_TESTS
. - Configuration.of(String namespace, Namespace.IMPORT namespaceImport)
to set both namespace and import options. - Configuration.of(JavaClasses javaClasses)
to directly provide a set of Java classes. - Configuration.of(Collection<String> excludedClasses)
to exclude specific classes from the checks. - Additional overloaded methods to combine these options in various ways.
If a namespaceImport
is not explicitly provided, it defaults to Namespace.IMPORT.WITHOUT_TESTS
.
Here's an example of how you can use these methods to create a custom configuration:
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.imports(imports -> imports
.shouldNotImport("..shaded..", Configuration.of("com.company.project.different", Namespace.IMPORT.WITHOUT_TESTS))
.shouldNotImport("..lombok..", Configuration.of(Namespace.IMPORT.ONLY_TESTS))))
.build()
.check();
In this example, the import rule is configured to apply to classes within the specified namespace, excluding test classes.
Adding Custom ArchUnit Rules¶
In addition to the predefined rules provided by Taikai, you can also add custom ArchUnit rules to tailor the architecture testing to your specific project requirements. Here's how you can integrate custom rules into your Taikai configuration:
Taikai.builder()
.namespace("com.company.project")
.addRule(TaikaiRule.of(...)) // Add custom ArchUnit rule here
.build()
.check();
addRule()
method and providing a custom ArchUnit rule, you can extend Taikai's capabilities to enforce additional architectural constraints that are not covered by the predefined rules. This flexibility allows you to adapt Taikai to suit the unique architectural needs of your Java project. 10. Examples¶
Below are some examples demonstrating the usage of Taikai to define and enforce architectural rules in Java projects, including Spring-specific configurations:
class ArchitectureTest {
@Test
void shouldFulfilConstrains() {
Taikai.builder()
.namespace("com.company.project")
.java(java -> java
.noUsageOfDeprecatedAPIs()
.classesShouldImplementHashCodeAndEquals()
.methodsShouldNotDeclareGenericExceptions()
.utilityClassesShouldBeFinalAndHavePrivateConstructor())
.test(test -> test
.junit5(junit5 -> junit5
.classesShouldNotBeAnnotatedWithDisabled()
.methodsShouldNotBeAnnotatedWithDisabled()))
.logging(logging -> logging
.loggersShouldFollowConventions(Logger.class, "logger", List.of(PRIVATE, FINAL)))
.spring(spring -> spring
.noAutowiredFields()
.boot(boot -> boot
.shouldBeAnnotatedWithSpringBootApplication())
.configurations(configuration -> configuration
.namesShouldEndWithConfiguration()
.namesShouldMatch("regex"))
.controllers(controllers -> controllers
.shouldBeAnnotatedWithRestController()
.namesShouldEndWithController()
.namesShouldMatch("regex")
.shouldNotDependOnOtherControllers()
.shouldBePackagePrivate()))
.services(services -> services
.namesShouldEndWithService()
.shouldBeAnnotatedWithService())
.repositories(repositories -> repositories
.namesShouldEndWithRepository()
.shouldBeAnnotatedWithRepository())
.build()
.check();
}
}