ArchTest.Layers (ArchTest v0.2.0)

Copy Markdown View Source

Layered architecture enforcement.

Define layers top-to-bottom; enforce_direction/1 ensures each layer only depends on layers below it (never upward).

Example

define_layers(
  web:     "MyApp.Web.**",
  context: "MyApp.**",
  repo:    "MyApp.Repo.**"
)
|> enforce_direction()

Onion / Hexagonal Architecture

define_onion(
  domain:      "MyApp.Domain.**",
  application: "MyApp.Application.**",
  adapters:    "MyApp.Adapters.**"
)
|> enforce_onion_rules()

Summary

Functions

Defines an ordered list of architecture layers (top to bottom).

Defines an onion/hexagonal architecture with ordered rings (innermost first).

Enforces that each layer only depends on layers below it in the ordered list.

Enforces onion/hexagonal architecture rules

Sets the OTP application to introspect (default: :all).

Adds a custom rule: the given layer may not depend on listed layers.

Adds a custom rule: the given layer may only depend on listed layers.

Types

layer_name()

@type layer_name() :: atom()

t()

@type t() :: %ArchTest.Layers{
  app: atom() | nil,
  custom_rules: [
    {layer_name(), :may_only_depend_on | :may_not_depend_on, [layer_name()]}
  ],
  layers: [{layer_name(), String.t()}]
}

Functions

define_layers(layer_defs)

@spec define_layers(keyword()) :: t()

Defines an ordered list of architecture layers (top to bottom).

Accepts a keyword list of layer_name: "Pattern" pairs. Order matters: earlier entries are "higher" layers.

define_onion(layer_defs)

@spec define_onion(keyword()) :: t()

Defines an onion/hexagonal architecture with ordered rings (innermost first).

Equivalent to define_layers/1 but with onion semantics applied in enforce_onion_rules/1.

enforce_direction(arch)

@spec enforce_direction(t()) :: :ok

Enforces that each layer only depends on layers below it in the ordered list.

Violations are raised as ExUnit assertion failures.

enforce_onion_rules(arch)

@spec enforce_onion_rules(t()) :: :ok

Enforces onion/hexagonal architecture rules:

  • Inner rings (listed first) must not depend on outer rings
  • Outer rings may depend on inner rings

In other words: dependencies can only point inward.

for_app(arch, app)

@spec for_app(t(), atom()) :: t()

Sets the OTP application to introspect (default: :all).

layer_may_not_depend_on(arch, layer, forbidden_layers)

@spec layer_may_not_depend_on(t(), layer_name(), [layer_name()]) :: t()

Adds a custom rule: the given layer may not depend on listed layers.

layer_may_only_depend_on(arch, layer, allowed_layers)

@spec layer_may_only_depend_on(t(), layer_name(), [layer_name()]) :: t()

Adds a custom rule: the given layer may only depend on listed layers.