CI

AshBoundary is a Spark DSL extension for Ash domains. It derives a boundary declaration from the domain DSL. The boundary compiler enforces the declaration on each build.

Every option in the boundary block passes straight through to boundary, unchanged: deps, exports, check, type, dirty_xrefs all mean exactly what they mean on a hand-written use Boundary. AshBoundary computes one thing on top: exports gains the domain module itself and every resource with at least one domain-level define.

Conventions

  • The domain module is public.
  • Each resource that exposes a code interface in the domain is public.
  • Each module named in the boundary block's exports is public.
  • All other modules in the domain's namespace are internal.
  • Referencing another domain requires an explicit boundary dep, subject to whatever check that domain declares.

Installation

Add ash_boundary to the deps in mix.exs:

def deps do
  [
    {:ash_boundary, "~> 0.1.1"}
  ]
end

Add the :boundary compiler to the project configuration:

def project do
  [
    app: :my_app,
    compilers: [:boundary] ++ Mix.compilers(),
    # ...
  ]
end

Usage

Add AshBoundary to the extensions of a domain. Declare dependencies on other domains, and any public module that is not a resource, in a boundary block:

defmodule MyApp.Blog do
  use Ash.Domain, extensions: [AshBoundary]

  boundary do
    deps [MyApp.Accounts]
    exports [MyApp.Blog.PostStatus]
  end

  resources do
    resource MyApp.Blog.Post do
      define :get_post, action: :read
      define :update_post, action: :update
    end

    resource MyApp.Blog.Comment
  end
end

This configuration has these effects:

  • MyApp.Blog.Post is public. All modules can reference it.
  • MyApp.Blog.PostStatus, an Ash.Type.Enum, is public.
  • MyApp.Blog.Comment is internal. Only modules in the MyApp.Blog namespace can reference it.
  • MyApp.Blog can depend on the MyApp.Accounts boundary only.

Every option of the boundary block is listed in the DSL reference.

Examples

Three examples live in examples/, each its own Mix project:

  • 01_exported_vs_internal has two domains. It covers exported and internal resources, and a calculation that reads another domain's data through that domain's exported interface instead of a relationship.
  • 02_phoenix_liveview is a Phoenix application over two domains. Its web layer reaches both through their exported interfaces and cannot call Ash.*.
  • 03_tower stacks thirteen domains over one PostgreSQL database in tiers, from shared infrastructure up to a JSON:API layer that composes the tiers below it. Two Hologram front ends sit above that, a public storefront and a role-gated operations console, each its own boundary.

Clarity

With clarity installed, each domain gains a "Boundary Dependencies" tab holding a diagram of its deps, where clicking a node opens that domain.

The diagram stacks domains in tiers by their distance from the domains that depend on nothing, and leaves out any edge a longer path already implies.

Usage rules

The package ships a usage-rules.md for usage_rules. List :ash_boundary in your project's usage_rules and run mix usage_rules.sync:

def project do
  [
    usage_rules: [:ash_boundary],
    # ...
  ]
end

Documentation

Docs for latest main is published at mbuhot.github.io/ash_boundary. Docs for versioned releases will be available at hexdocs.pm/ash_boundary.

The DSL reference documents every option of the boundary block.

See also the boundary docs.

License

MIT. See LICENSE.