Domains serve three primary purposes:
- They group related resources together, providing organization and structure to your project.
- They allow you to define a centralized code interface
- They allow you to configure certain cross-cutting concerns of those resources in a single place.
If you are familiar with a Phoenix Context, you can think of a domain as the Ash equivalent.
Application Configuration (:ash_domains)
Ash expects you to list your domain modules in your application config:
config:my_app,:ash_domains,[MyApp.Tweets,MyApp.Billing]This configuration is used for:
- Mix tasks and tooling that need to load all domains (e.g. diagrams, livebooks, policy charts)
- Compile-time validation that domains and resources are registered (the warnings shown by
use Ash.Domainanduse Ash.Resource)
If you see warnings about missing domains or resources, it usually means this list is incomplete. You can add your domain modules here to resolve those warnings, or disable the validations if you prefer to manage it manually.
Grouping Resources
In an Ash.Domain, you will typically see something like this:
defmoduleMyApp.TweetsdouseAsh.DomainresourcesdoresourceMyApp.Tweets.TweetresourceMyApp.Tweets.CommentendendWith this definition, you can do things like placing all of these resources into a GraphQL Api with AshGraphql. You'd see a line like this:
useAshGraphql,domains:[MyApp.Tweets]Centralized Code Interface
Working with our domain & resources in code can be done the long form way, by building changesets/queries/action inputs and calling the relevant function in Ash. However, we generally want to expose a well defined code API for working with our resources. This makes our code much clearer, and gives us nice things like auto complete and inline documentation.
defmoduleMyApp.TweetsdouseAsh.DomainresourcesdoresourceMyApp.Tweets.Tweetdo# define a function called `tweet` that uses# the `:create` action on MyApp.Tweets.Tweetdefine:tweet,action::create,args:[:text]endresourceMyApp.Tweets.Commentdo# define a function called `comment` that uses# the `:create` action on MyApp.Tweets.Commentdefine:comment,action::create,args:[:tweet_id,:text]endendendWith these definitions, we can now do things like this:
tweet=MyApp.Tweets.tweet!("My first tweet!",actor:user1)comment=MyApp.Tweets.comment!(tweet.id,"What a cool tweet!",actor:user2)Configuring Cross-cutting Concerns
Built in configuration
Ash.Domain comes with a number of built-in configuration options. See Ash.Domain for more.
For example:
defmoduleMyApp.TweetsdouseAsh.DomainresourcesdoresourceMyApp.Tweets.TweetresourceMyApp.Tweets.Commentendexecutiondo# raise the default timeout for all actions in this domain from 30s to 60stimeout:timer.seconds(60)endauthorizationdo# disable using the authorize?: false flag when calling actionsauthorize:alwaysendendExtensions
Extensions will often come with "domain extensions" to allow you to configure the behavior of all resources within a domain, as it pertains to that extension. For example:
defmoduleMyApp.TweetsdouseAsh.Domain,extensions:[AshGraphql.Domain]graphqldo# skip authorization for these resourcesauthorize?falseendresourcesdoresourceMyApp.Tweets.TweetresourceMyApp.Tweets.CommentendendPolicies
You can also use Ash.Policy.Authorizer on your domains. This allows you to add policies that apply to all actions using this domain. For example:
defmoduleMyApp.TweetsdouseAsh.Domain,extensions:[Ash.Policy.Authorizer]resourcesdoresourceMyApp.Tweets.TweetresourceMyApp.Tweets.Commentendpoliciesdo# add a bypass up front to allow administrators to do whatever they wantbypassactor_attribute_equals(:is_admin,true)doauthorize_ifalways()end# forbid all access from disabled userspolicyactor_attribute_equals(:disabled,true)doforbid_ifalways()endendend
