ash/documentation/topics/resources/domains.md

140 lines
3.7 KiB
Markdown
Raw Normal View History

# Domains
Domains serve three primary purposes:
1. They group related resources together, providing organization and structure to your project.
2. They allow you to define a centralized [code interfaces](/documentation/topics/resources/code-interfaces.md)
3. They allow you to configure certain cross-cutting concerns of those resources in a single place.
If you are familiar with a [Phoenix Context](https://hexdocs.pm/phoenix/contexts.html), you can think of a domain as the Ash equivalent.
## Grouping Resources
In an `Ash.Domain`, you will typically see something like this:
```elixir
defmodule MyApp.Tweets do
use Ash.Domain
resources do
resource MyApp.Tweets.Tweet
resource MyApp.Tweets.Comment
end
end
```
With 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:
```elixir
use AshGraphql, domains: [MyApp.Tweets]
```
## Centralized [Code Interface](/documentation/topics/resources/code-interfaces.md)
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.
```elixir
defmodule MyApp.Tweets do
use Ash.Domain
resources do
resource MyApp.Tweets.Tweet do
# define a function called `tweet` that uses
# the `:create` action on MyApp.Tweets.Tweet
define :tweet, :create, args: [:text]
end
resource MyApp.Tweets.Comment do
# define a function called `comment` that uses
# the `:create` action on MyApp.Tweets.Comment
define :comment, :create, args: [:tweet_id, :text]
end
end
end
```
With these definitions, we can now do things like this:
```elixir
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 `d:Ash.Domain` for more.
For example:
```elixir
defmodule MyApp.Tweets do
use Ash.Domain
resources do
resource MyApp.Tweets.Tweet
resource MyApp.Tweets.Comment
end
execution do
# raise the default timeout for all actions in this domain from 30s to 60s
timeout :timer.seconds(60)
end
authorization do
# disable using the authorize?: false flag when calling actions
authorize :always
end
end
```
### Extensions
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:
```elixir
defmodule MyApp.Tweets do
use Ash.Domain,
extensions: [AshGraphql.Domain]
graphql do
# skip authorization for these resources
authorize? false
end
resources do
resource MyApp.Tweets.Tweet
resource MyApp.Tweets.Comment
end
end
```
### Policies
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:
```elixir
defmodule MyApp.Tweets do
use Ash.Domain,
extensions: [Ash.Policy.Authorizer]
resources do
resource MyApp.Tweets.Tweet
resource MyApp.Tweets.Comment
end
policies do
# add a bypass up front to allow administrators to do whatever they want
bypass actor_attribute_equals(:is_admin, true) do
authorize_if always()
end
# forbid all access from disabled users
policy actor_attribute_equals(:disabled, true) do
forbid_if always()
end
end
end
```