archunit is a powerful and flexible library for enforcing architectural rules in your Go projects. It helps you maintain a clean and robust architecture by providing a fluent and declarative API for defining and validating architectural constraints. With archunit, you can ensure your code adheres to your intended design, preventing unwanted dependencies and maintaining modularity.
-
Declarative, Fluent API:
archunitprovides a fluent and declarative API that allows you to define architectural rules in a clear, readable, and chainable way. This makes your architecture tests easy to understand and maintain. -
Functional Approach: The library promotes a functional style by treating rules as first-class citizens. You can define, combine, and pass rules as functions, leading to more modular and reusable architecture tests.
-
Generic Support:
archunitleverages Go generics to provide type-safe and reusable selections and rules. This reduces boilerplate code, improves type safety, and makes your architecture tests more robust. -
Rich Pre-defined Rules: Get started quickly with a comprehensive set of pre-defined rules for common Go best practices. These rules cover a wide range of checks, from naming conventions and package structure to dependency management and API design.
-
Code as Promotion: AI-Guided Development:
archunitintroduces a "Code as Promotion" paradigm, where your architectural rules act as a direct guide for AI code generation.- AI-Friendly Code Style: The declarative and readable rules serve as a machine-readable design specification. This guides AI tools to generate code that is always aligned with your intended architecture.
- Native AI Feedback Loop: When a rule is violated, the assertion output is structured as a clear and actionable prompt. This "promotion" can be fed directly back to the AI, enabling it to learn from its mistakes and automatically correct the code, creating a powerful and efficient development feedback loop.
To install archunit, use go get:
go get github.com/kcmvp/archunitarchunit makes it easy to get started. Here's a simple example of how to check for common Go best practices and enforce a basic layering rule.
Create a test file (e.g., architecture_test.go) in your project's root directory:
package main_test
import (
"testing"
"github.com/kcmvp/archunit"
)
func TestArchitecture(t *testing.T) {
// Define your architectural layers
domainLayer := archunit.ArchLayer("Domain", "github.com/your-project/domain/...")
appLayer := archunit.ArchLayer("Application", "github.com/your-project/application/...")
// Initialize ArchUnit with your layers
arch := archunit.ArchUnit(domainLayer, appLayer)
// Define and validate your rules
err := arch.Validate(
// Use a pre-defined set of best practice rules
archunit.BestPractices(3, "config"),
// Define a custom rule: the domain layer should not depend on the application layer
archunit.Layers("Domain").ShouldNotRefer(archunit.Layers("Application")),
)
if err != nil {
t.Fatal(err)
}
}archunit is built on a simple and powerful mental model. You define your architecture, select parts of it, and then apply rules to those selections. This can be broken down into three core concepts: ArchObject, Selection, and Rule.
graph LR
subgraph "Architecture Objects"
direction TB
%% subgraph "Architecture Objects"
A1(Layer);
subgraph code_unit ["Code Unit"]
direction TB
B1(Package);
B2(Type);
B3(Function);
B4(Variable);
B5(File);
end
A1 --> B1;
A1 --> B2;
A1 --> B3;
A1 --> B4;
A1 --> B5;
%% end
subgraph abstract_points ["Abstract Pointcuts"]
D1(Referable);
D2(Exportable);
end
A1 --> D1;
B1 --> D1;
B2 --> D1;
B2 --> D2;
B3 --> D2;
B4 --> D2;
style code_unit stroke:cyan,stroke-dasharray: 5 5
style abstract_points stroke:cyan,stroke-dasharray: 5 5
style A1 stroke:cyan,stroke-dasharray: 5 5
end
subgraph "How it Works"
direction LR
S1(Selection);
R1(Rule);
V1(Validate);
abstract_points -->|Match & Selection| S1;
code_unit -->|Match & Selection| S1;
A1 -->|Match & Selection| S1;
S1 --> R1;
R1 --> V1;
end
linkStyle 11 stroke:lightcoral,stroke-dasharray: 5 5
linkStyle 12 stroke:lightcoral,stroke-dasharray: 5 5
linkStyle 13 stroke:lightcoral,stroke-dasharray: 5 5
An ArchObject is the fundamental building block of your architecture. It represents a specific element within your codebase. archunit parses your code and models it into six concrete ArchObject types: Layer, Package, Type, Function, Variable, and File.
These objects are then categorized by Pointcut Interfaces, which define their architectural properties. For example:
Referable: Implemented byLayer,Package, andType. This interface is a pointcut for applying dependency rules.Exportable: Implemented byType,Function, andVariable. This is a pointcut for applying visibility rules.
This design allows for a powerful, type-safe rule system. A rule that checks dependencies can only be applied to objects that are Referable.
A Selection is the process of choosing which ArchObjects to apply a rule to. You start by selecting a broad category (e.g., Packages(...)) and then filter it using composable Matchers (e.g., WithName(...), Not(...)). This allows you to create precise, composite selections that target specific parts of your architecture.
A Rule defines the specific constraint you want to enforce on your selection. After selecting your objects, you chain a rule method to define the relationship. Because the selection is typed by the pointcut interfaces, only valid rules will be available. For example, you can only apply ShouldNotRefer(...) to a selection of Referable objects.
By combining Selections and Rules, you create a clear, declarative, and enforceable architectural test:
// 1. Selection: All packages with the suffix "service"
// (This selection is 'Referable')
// 2. Rule: Should not refer to any package in the "repository" layer.
Packages(HaveNameSuffix[Package]("service")).
ShouldNotRefer(Layers("Repository"))archunit comes with a set of pre-defined rules for common Go best practices, available through the BestPractices function. These include checks for:
These rules are applied to the entire project and are bundled together in the BestPractices function.
AtMostOneInitFuncPerPackage: Ensures that each package has at most oneinitfunction.ConfigurationFilesShouldBeInFolder: Checks that all configuration files are in a specified folder.ConstantsAndVariablesShouldBeGrouped: Enforces thatconstandvardeclarations are grouped.ConstantsShouldBeConsolidated: Ensures all constants in a package are in a single file.ContextShouldBeFirstParam: Checks thatcontext.Contextis the first parameter in functions.ContextKeysShouldBePrivateType: Enforces that context keys are not built-in types.ErrorShouldBeLastReturn: Ensures thaterroris the last return value in functions.NoPublicReAssignableVariables: Prevents exported variables that can be reassigned.NoUnusedPublicDeclarations: Checks for public declarations that are not used outside their package.PackageNamedAsFolder: Enforces that a package's name matches its folder's name.PackagesShouldNotExceedDepth: Checks that package depth does not exceed a maximum.TestDataShouldBeInTestDataFolder: Ensures that test data is located in atestdatafolder.VariablesShouldBeUsedInDefiningFile: Checks that variables are used in the file where they are defined.VariablesAndConstantsShouldUseMixedCaps: Enforces theMixedCapsnaming convention.
These rules are applied to specific selections of architectural objects.
ShouldNotRefer: Asserts that the selected objects do not refer to forbidden objects.ShouldOnlyRefer: Asserts that the selected objects only refer to allowed objects.ShouldNotBeReferredBy: Asserts that the selected objects are not referred to by forbidden objects.ShouldOnlyBeReferredBy: Asserts that the selected objects are only referred to by allowed objects.
ShouldBeExported: Asserts that the selected objects are exported.ShouldNotBeExported: Asserts that the selected objects are not exported.ShouldResideInPackages: Asserts that the selected objects reside in a package matching a given pattern.ShouldResideInLayers: Asserts that the selected objects reside in one of the given layers.
NameShould: Asserts that the names of the selected objects match a given predicate.NameShouldNot: Asserts that the names of the selected objects do not match a given predicate.
Contributions are welcome! Please feel free to submit a pull request or open an issue.
archunit is licensed under the MIT License.