Ash 3.0 Teasers!

Hey folks! I’ll be teasing some interesting bits going into Ash 3.0 while I work on it, and this is post #1!

You can follow along with the changes, if you are interested in the 3.0 branch of Ash. Please do not use this branch :laughing:. If you find issues, I will not help you with them until an actual release candidate is published.

So, on to teaser #1!

Timeline

The current plan is to have a release candidate ready in March, so we are not far off from 3.0!

picosat_elixir Installation Issues

Some of our users (especially those on windows) experience installation issues with picosat_elixir. picosat_elixir is an excellent package, and we suggest that everyone starts there, we have published an alternative, called simple_sat. You can now select one of the two when following the getting started guide, and Ash will use whichever one is present.

We strongly suggest that folks eventually figure out their installation issues with picosat_elixir (our experience is that this is primarily isolated to windows users, and that folks eventually find a workaround). However, we didn’t want that to get in the way of people trying out Ash!

Registries are no more

Registries are no longer necessary, and as such are being removed for 3.0. This simplifies set up overall, and helps avoid a point of confusion that existed in 2.0, as registries were still supported but provided no tangible benefit.

Thats it!

There is tons more already done for 3.0, and more to come, but I’ll talk more about those in upcoming posts.

I hope you’re all as excited as I am!

Teaser #2: Ash 3.0 Teasers! - #9
Teaser #3: Ash 3.0 Teasers! - #23
Teaser #4: Ash 3.0 Teasers! - #28
Teaser #5: Ash 3.0 Teasers! - #30
Teaser #6: Ash 3.0 Teasers! - #36

22 Likes

Thanks @zachdaniel. I will be watching all the teasers. Probably the best teaser #1 would have been the timeline that you have in mind regarding 3.0 release. A specific date is not expected but, 1week/1month/1quarter/1year level of timeline also would make it interesting. :slight_smile:

1 Like

Yay, no more Registries!

Hey @cvkmohan, I think I’ve said it elsewhere but yes I think but it makes sense to put it in the post. I’ll edit the post, but the timeline is for a release candidate to be available in march :slight_smile:

2 Likes

Just to be clear, we do still need a list of resources, but we don’t need them to be in a separate module from your api :slight_smile:

Hey, Zach!

Always excited to see Ash’s progress

I have experienced the picosat_elixir problem so I’m curious if there are tradeoffs between it and the simple_sat alternative

Explaining the function these libraries serve in Ash might be a nice extra

simple_sat is essentially just much slower :slight_smile: it’s not made for production level workloads. It produces the correct results of course, though.

Yeah, I’ve already switched over

1 Like

Hey everyone, time for Teaser #2!

If you missed teaser #1, check out out here: Ash 3.0 Teasers!

This is a big one :slight_smile:

Ash.Api → Ash.Domain

Ash.Api as a name has caused users lots of confusion in the past. It is an overloaded term. Also, while we considered it the “API to a given bounded context” as in, you always interacted with a resource through an API, we’ve ultimately decided to change the conceptual role of what we call an API. With that change, comes a name that is more representative of its function. Shoutout to @lukasender for the suggestion that stuck.

Ash.Domain represents the configuration of a domain, which includes things like “what resources are available to this domain”, as well as other high level configurations. Instead of calling to an API module, we provide one standard interface, but that interface must always be able to determine what Domain it is interacting with, in addition to what Resource it is working with. Keep reading for more on what this looks like.

domain option to use Ash.Resource

When creating a resource, you pass the domain option. For example:

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    domain: MyApp.Accounts
end

This static configuration can be used in various places where you would have previously had to specify an api. For example, you no longer need to specify define_for in the code_interface block.

You will get a warning if you don’t pass the domain option, as well as if the configured domain doesn’t know about the resource.

Which leads us to one of the more significant changes of 3.0:

Using Ash instead of MyApp.MyApi

We will be deprecating the functions that we typically on MyApp.MyApi, in favor of those same functions defined in the Ash module. This allows us to refactor resources and move them from one domain to another without having to hunt down the calling code that interacts with it. It also helps reduce the cognitive overhead of having to remember what Api you’re working with for any given resource in order to call an action on it.

For example:

MyApp.Helpdesk.Ticket
|> Ash.Changeset.for_create(:open, %{title: "halp"})
|> MyApp.Helpdesk.create

MyApp.Helpdesk.count!(MyApp.Helpdesk.Ticket)

MyApp.Helpdesk.Ticket
|> Ash.Query.for_read(:open)
|> MyApp.Helpdesk.read!()

Would become

MyApp.Helpdesk.Ticket
|> Ash.Changeset.for_create(:open, %{title: "halp"})
|> Ash.create

Ash.count!(MyApp.Helpdesk.Ticket)

MyApp.Helpdesk.Ticket
|> Ash.Query.for_read(:open)
|> Ash.read!()

How to make this change

Good news! You can make this change before upgrading to 3.0. You can do everything stated above by configuring the api option when calling use Ash.Resource, and switching your calls to the api to call Ash! The difference between 2.0 and 3.0 is that in 3.0 the functions defined on the api module will be deprecated, and api has been renamed to domain.

Teaser #3: Ash 3.0 Teasers! - #23 by zachdaniel

24 Likes

I like the byte size releases. I can read them. Digest them.

One question - How to move to domain from api in Ash 2.0 ? A link / migation guide?

1 Like

There will be a guide for upgrading that will include the steps, but it essentially boils down to a big find and replace for Ash.Api and then api to find variables and options that should be renamed, followed by looking for calls to each api and replacing them with Ash :slight_smile: it will likely be the most inconvenient part of the upgrade for users.

1 Like

There will also be a part of the guide that focuses on things you can do before upgrading that will minimize the impact (like making the MyApi.* - Ash.* transition).

1 Like

We will be deprecating the functions that we typically on MyApp.MyApi, in favor of those same functions defined in the Ash module.

Oh I really like this one too! This will make building generic functionality much easier, eg. the touch_related change I wanted to write - I think I was stuck on how to reliably figure out what API the related resource was in.

3 Likes

I love where 3.0 is heading! One little question. Do you need to configure the domain in the resource, and list all the resources in the domain? Seems like a bit of double handling for the same configuration?

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    domain: MyApp.Accounts
end

defmodule MyApp.Accounts do
  use Ash.Domain
  
  resources do
    resource MyApp.Accounts.User
    resource MyApp.Accounts.UserToken
    # ...
  end
end
1 Like

At the moment you do need to do it in both places, yes. This is because some extensions need to know all resources for a given api, and a static list is the only way to do that at compile time.

You will get a warning if the configuration is wrong though, which should prevent it from being a source of bugs, but it is an unfortunately necessary inconvenience.

1 Like

Are you still accepting suggestions for changes for Ash 3.0?

Here is something that I was wondering:

Sometimes, I have a resource module that starts getting too busy.

For example, I have resources that has the attributes, postgres, graphql, rbac, policies, actions, etc.

It is great that everything is in one place, but at the same time I do wish that for some cases I would be able to extract some code block and move it into a new module.

For example, something like this:

defmodule MyResource do
  use Ash.Resource,
    implementations: [MyResource.Postgres, MyResource.Policies]

  attributes do
    ...
  end

  relationships do
    ...
  end
  
  actions do
    ...
  end
end

defmodule MyResource.Postgres do
  use Ash.Resource.Implementation,
    data_layer: AshPostgres.DataLayer
  
  postgres do
    ...
  end
end

defmodule MyResource.Policies do
  use Ash.Resource.Implementation,
    authorizers: [Ash.Policy.Authorizer]

  policies do
    ...
  end
end

I wonder if something like this would be possible with Ash 3.0 or if there is some technical or architectural problem blocking it.

This is implemented in spark already :slight_smile:

defmodule MyResource do
  use Ash.Resource,
    fragments: [MyResource.Postgres, MyResource.Policies]

  attributes do
    ...
  end

  relationships do
    ...
  end
  
  actions do
    ...
  end
end

defmodule MyResource.Postgres do
  use Spark.Fragment,
    of: Ash.Resource,
    data_layer: AshPostgres.DataLayer
  
  postgres do
    ...
  end
end

defmodule MyResource.Policies do
  use Spark.Fragment,
    of: Ash.Resource,
    authorizers: [Ash.Policy.Authorizer]

  policies do
    ...
  end
end
11 Likes

Always accepting suggestions :smiley: Feel free to share any ideas you have!

Omg Zach, I which I knew that sooner ahahaha that’s amazing!

1 Like

Here is another idea.

First a disclaimer, I didn’t think too deep about this, so I’m not sure how relevant it is, it is just something that I particularly had problems before.

I was wondering if we could have a more “low level” version of Ash.Query in Ash 3.0.

Basically what I mean by that is having something that is closer to what Ecto.Query has, in other words, having a low level API that is closer to raw sql.

I know that we can get an Ecto.Query from an Ash.Query via the data_layer_query function, but after we transform it into an Ecto.Query we can’t go back, that means that some functionality of Ash is lost (for example, support for pagination).

Another thing that bothered me a little bit is the requirement to create a calculation when trying to sort data via another resource field, for example:

defmodule ResourceA do
  attributes do
    attribute :score, :decimal
  end
end

defmodule ResourceB do
  relationships do
    belongs_to :resource_a, ResourceA
  end

  calculations do
    calculate :score, :decimal do
      calculation expr(resource_a.score)
    end
  end
end

It would be great if I could just express directly that I want to sort by ResourceA score attribute in ResourceB without having to create that calculation.