Best practices for organizing domains and resources

Hey guys, I was wondering if there is some “best practices” or how do you guys tend to organize domains and resources in Ash.

Here is how I do it right now:

project
|___ domain 1
|    |___ resource A
|    |___ resource B
|___ domain 2
     |___ resource A (Shares same table from resource A in domain 1 but possibly with some different attributes and actions)
     |___ resource C

So, all resources needs to be used in one, and only one, domain, if the resource needs to be used in another domain, a new resource is created inside the scope of that new domain and the attributes needed for it are copied over.

Advantages:

  • IMO this makes things easier to understand since I have a guarantee that each resource in my system can only affect one domain;
  • It is easier to navigate through code since I know all resources for that specific domain resides inside that domain directory.

Disadvantages:

  • If you have a resource that needs to be used in multiple domains, it creates a bunch of duplicated code;
  • When changing a resource attribute (and other stuff too), I always need to make sure that the change is done in all resources from other domains that share the same table.

Here are some other organization alternatives I though about:

  1. Have a “common” domain directory to move resources that are commonly used in multiple domains.
    That would probably lower code duplication, specially for a resource like user which is probably shared between a lot of domains. But, at the same time, it seems like a place where a dev would use to put anything inside just because it is “easier”, making the thing a mess in the end.
  2. Have directories with resources that don’t have a domain associated to it. This one is similar to (1), but instead of having a “generic” common directory and put everything there, we would have directories that state the resources inside “business” clearly. For example, there could be an account directory with an user resource inside, and then I can have an auth domain that will share that account/user resource with other domains.
  3. Break some resources between Spark parts and move the shared parts to a “common” place. For example, let’s say I have an user resource that needs to be shared by multiple domains. For each domain, the actions for that resource changes, but the attributes are the same. In that case, I could move the attributes part of that resource to a common place and create a new resource in each domain for the user and just “import” the attributes for it.

Anyway, these are just some of ideas I was thinking of. How do you normally organize these in your code? And what are the advantages and issues you found with your strategy?

Most often, I begin by just allowing resources to live in only one domain, and then I relate across domains, i.e

defmodule MyApp.Tweets.Tweet do
  ...

  relationships do
    belongs_to :user, MyApp.Accounts.User
  end
end

I only typically refactor into separate resources-per-domain when a resource gets too large or clearly has multiple purposes.

1 Like

Guys, can you help by sharing examples of implementing a resource-per-domain pattern?

I’m new to Ash and Elixir. I’m trying to write my first service using them.

I managed to find a single code sample from ash_example, but it is deprecated. I love this example of Customer and Representative implementation.

defmodule Helpdesk.Tickets.Customer do
  use Ash.Resource
  ...
    postgres do
    table "users"
    repo Helpdesk.Repo
  end
  ...
  attributes do
    uuid_primary_key :id

    attribute :first_name, :string
    attribute :last_name, :string
    attribute :representative, :boolean
  end

  relationships do
    has_many :reported_tickets, Helpdesk.Tickets.Ticket do
      destination_field :reporter_id
    end
  end
end

But I have a question: do Customer and User use the same table user or are they in different schemas’ tables? If they are using the same table, how should it be implemented to use different tables so the Customer stays consistent relative to the User?

This is ultimately a design decision, not something Ash enforces one way or another.

Having two resources share the same table is perfectly fine :slight_smile:

I would avoid doing things in such a way that they need to stay consistent relative to each other, so if I wanted Customer to live in its own table, I’d do something like this:

defmodule Helpdesk.Tickets.Customer do
  use Ash.Resource
  ...
    postgres do
    table "customer"
    repo Helpdesk.Repo
  end
  ...
  attributes do
    uuid_primary_key :id
  end

  calculations do
    calculate :first_name, :string, expr(user.first_name)
    calculate :last_name, :string, expr(user.last_name)
  end

  relationships do
    belongs_to :user, User
    has_many :reported_tickets, Helpdesk.Tickets.Ticket do
      destination_field :reporter_id
    end
  end
end

That way the source of truth for user-related things stays the user resource.

Which approach you go with is entirely up to you, but those are the two main strategies (composing via relationships, and sharing a table) for that kind of thing.

1 Like

Thanks for the answer. I’ll definitely try the approach with the shared table, as I like it better.

1 Like