byu

byu

Given a very very simple twist on the canonical Ash Example, I just have a Ticket resource with only an id and name attributes backed by a postgres DB.

In the iex -S mix console, while playing around some more, and exploring a bit…

I execute the following code to create tickets; I see a series of INSERTs where each insert is in its own database transaction.

for i <- 0..5 do
  Helpdesk.Support.Ticket
    |> Ash.Changeset.for_create(:create, %{name: "Issue #{i}"})
    |> Helpdesk.Support.create!()
end

So, in a very loose analogy to DDD, the Ash Resource is akin to an Aggregate Root, where the Ash action also has the job to enforce the bounded context’s business rules of the Aggregate (or Resource).

And not to get sucked into the DDD arguments of whether or not the transactional boundaries must match consistency boundaries (the aggregate root), say I still want a set of action/changes to all succeed or not…

How is that achieved in Ash? How can I get a series of resources acted upon within the same DB transaction?

Like if I wanted something similar to a “DDD Service” that takes two resources and updates them conforming to a business rule, then persisting then in a “Unit of Work” ? – Loose analogies still.

For example, say I have two chess players (each a Resource/Aggregate instance) and I want to update their ranking after a game… I want to increase the winner’s points and decrease the loser’s points, while staying consistent?

Or am I thinking about this all wrong?

Thanks

Showing Posts 1 to 8

zachdaniel

zachdaniel

Creator of Ash

:wave: so you have a few options. Transactionality is built into Ash action. So using your chess game example, you might have something like this:

# on ChessGame

update :finish_game do
  change set_attribute(:complete)
  change fn changeset, _ -> 
    Ash.Changeset.after_action(changeset, fn changeset, game -> 
      # fetch players and modify their score
      # whatever happens here is transactional
    end)
  end
end

By default things that happen in hooks will be in the same transaction. You can also start your own transactions using Ash.DataLayer.transaction(resource, fn -> end)

If your resources are in the same data layer then this works exactly as you’d expect transactions to work. If you have resources across multiple data layers, a transaction for both will be opened. This makes it technically possible to achieve inconsistency if one transactions loses and the other one doesn’t due to a network error (for example). However, it makes a good effort at cross-data-layer consistency. There are usually some things to keep in mind if you are using cross data layer actions but I won’t go into that here because I don’t think that is the main thing in question :slight_smile:

byu

byu OP

Thanks Zach,

Repeating so I understand… two options that you have suggested:

  1. Using a “wrapping” resource, which would drive subsequent actions.
    • In this Chess example, it does make more sense that “ending the game” is the fundamental business change that is taking place… the player points is just a side effect action.
  2. Use Ash.DataLayer.transaction(resource, fn -> end) , which would be outside of an Ash Resource action.
    • With the caveat/warning that it’s not actually 2-phase-commit across two different Repositories, rather just attempted transactions per repository.
    • But with the practice of only applying multi-action Ash Context API’s “bounded context”-- also assuming that each Ash Context API is strictly tied to a single Repository, we are safe…

There are usually some things to keep in mind if you are using cross data layer actions but I won’t go into that here because I don’t think that is the main thing in question

With that, I’m hearing that eventual consistency techniques would be required if we were to cross such data layer boundaries. And yes, I’m not wanting to go down a complicated rabbit hole of a multi-bounded-context distributed system (ala multiple project deployments)… Just looking at the scope of keeping things, hopefully,
straight forward simple: single domain, single bounded context, and single data layer. :slight_smile:

Thanks! I’ll try those options.

zachdaniel

zachdaniel

Creator of Ash

You can use generic actions to use Ash.DataLayer.transaction to do arbitrary stuff in any resource.

action :name, :return_type do
  argument :foo, :string, ...
  run fn input, _ -> 
    # input.arguments.foo

    Ash.DataLayer.transaction([set, of, resources], fn -> 
       ..... do your work
    end)
  end
end

…or you can use the handy transaction? true option for generic actions

action :name, :return_type do
  transaction? true
  argument :foo, :string, ...
  run fn input, _ -> 
    # input.arguments.foo

  end
end
byu

byu OP

Ok. Looks like I just need to get my hands dirty and try things to find what’s going to work out best for my situation.

Thanks for showing these options.

zachdaniel

zachdaniel

Creator of Ash

My pleasure! Feel free to keep the questions coming :slight_smile:

brunoripa

brunoripa

Hey @zachdaniel , hi ! One question about something I already bumped into some months ago but then things changed and I had no chance to look into this.

I have an UserInvitation resource that defines an :approve action:

update :approve do
  description "Approve an invitation."

  change set_attribute(:status, :approved)

  change fn changeset, _ctx ->
    Ash.Changeset.after_action(changeset, fn cs, invitation ->
      case App.Accounts.create_user(invitation.email) do
        {:ok, _} ->
          {:ok, cs}

        {:error, _} ->
          {:error, "Failed to create user"}
      end
    end)

    changeset
  end
end

The user case is: an user requires an invitation and approves it; at this point an user must be created and the invitation must be set to status: :approved. There is also a notification which sends and email outside the transaction, of course.

If I try to compile the code I see the warning about the need to set required_atomic? to true. I fix the code with such suggestion, and the after_action function simply is not executed.

What am I missing ? Thanks !

zachdaniel

zachdaniel

Creator of Ash

You’re not returning the changeset that has the after action hook on it :slight_smile: you’re returning the original changeset.

brunoripa

brunoripa

Ok, right. It works now. I was somehow confused by the two different resources involved. Thanks

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews