dkulchenko
Just finished the Ash book and excited to dive in, but still trying to wrap my head around incorporating Ash into my mental model of an Elixir/Phoenix app.
I had some more abstract questions:
- How is function composability intended to work with actions? I’m used to functions calling functions calling functions (from Context modules). For example,
Orders.place_ordermight create a customer, look up a payment method, call out to Stripe, do audit logging, clear a cart, send some emails, create shipments/orders/transactions, etc. These functions would live inOrdersor maybe other contexts or modules likeMyApp.Cart, etc. If I define an action that has a lot of different business logic to perform, where semantically should those functions live? In the resource? The domain? The specific Change module for that resource? Or elsewhere in the app? - Related to #1. With a Phoenix app, complex operations that involve multiple schemas often live in the relevant Context as a function that does multiple things. With Ash, it seems like the domain (despite being similar to a context) is not intended to have functions defined in it manually (via def) that do this kind of work. Reviewing Multi-Step Actions — ash v3.29.3, it seems I’d instead need to pick a single resource (of the schemas involved in the transaction) to semantically “own” the action and then use a series of Changes to do the work in the other relevant resources before/after the data model creation, is this understanding correct?
- In a fully Ash-ified app, how do you decide what should be modeled as an Ash resource and what is just a regular Elixir module? Obviously resources backed by the data model would be Ash resources, but what about API clients? Form validations not backed by persistence? Other parts of the app?
- I never built too much with Rails (was more of a Roda/Sequel guy), but I remember the common Rails wisdom eventually becoming “no fat models” and “avoid ActiveRecord callbacks like the plague” (single-responsibility principle, mixing data layer and business logic layer, unexpected bugs/gotchas, side effect spaghetti, etc). Unless I’m misreading, Ash seems to encourage both by design - is this due to a disagreement with the dogma or is the architecture fundamentally different?
Trending in Questions
Hello!
Suppose you are building workflow (order / task / payment) processing system with the following requirements:
Each workflow con...
New
I’m aware of 2 such projects, are there others?
git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
Hi all,
I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
Hi everyone.
My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
Hi,
we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley:
Mutonex gamedesign was finish...
New
Hello
I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New
Other Trending Topics
Hey there,
I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first.
...
New
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself.
My main conc...
New
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly)
AI dream has many faces, but general...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #ai
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #blog-post
- #ecto-query
- #elixirconf-us
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #security
- #hex










Showing Posts 1 to 3- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
zachdaniel
A lot of my feedback and opinions on this front can be hard to explain because they are nuanced. Often times what I see in applications is that, out of a desire to have very strict answers to all of these questions, we introduce a bunch of useless and arbitrary separations and boundaries in our applications that lead to endless complexity and difficult change management down the road.
I think 1 & 2 are basically the same question.
There are various ways to approach this. You can:
This is where people often get into adding dedicated service layers because they want a perfect place for this stuff to live, but IMO thats throwing the baby out with the bathwater, adopting a bunch of architectural complexity just because some portion of actions don’t feel like they belong in any one existing file. Resources are declarative action-containers first-and-foremost, and they don’t have a requirement to model state in addition to behavior.
Composability is done via Ash’s tools for composable logic, which include changes (
Ash.Resource.Change), preparations (Ash.Resource.Preparation), validations (Ash.Resource.Validation), calculations etc.For me, almost everything that is called from “outside” of my domain will go through a domain & resource. Once “inside” (i.e within a resource action, maybe a generic action) then it’s purely tactics. If I just have a one-off thing that calls some HTTP API I’m just going to toss a call to
Reqin and move on with my life. The important abstraction there is the typed interface to my domain logic.Many Ash apps will have thousands of files and not a context in sight, and in fact the emergence of contexts in an Ash app is often a smell, indicating that you’re thinking around what a resource and a domain is is too tightly coupled to the idea of modeling “things”.
Yeah, personally, I like my models fat. Either it should be in the model, or it shouldn’t exist (i.e should be derived). BUT there is nuance here that actually makes my version of “fat models” entirely different from what you might have seen in rails land.
A “model” in rails is first and foremost 2 things:
Fat models in those examples are bad because every place you are looking is in fact conflating state and behavior by its very nature. In Ash, we actually do have separate places for behavior and state. Or at least, we have progressively complex declarative “verbs” via actions, the action types (generic vs crud). Each layer (like
ash_graphqlandash_json_apietc.) are defined in their own section of the resource. The concerns are simultaneously separated and localized. This isn’t an easy thing to explainThe big thing here is that Ash runs in Elixir where there is no mutable state. What this means is that by nature Ash actions can effectively only be “instructions as data for performing some action”. We just don’t have the spooky-action-at-a-distance problem that mutable languages have, and so Ash thrives, and side-steps the foot guns that plague higher level abstractions in other languages.
So, for example:
on its own is nothing but a description of an action. It isn’t actually even “callable” like a method on an active record object.
You can see what the instructions are via
Ash.Resource.Info.action(Resource, :create). You could even define your own resource creator that you call, with a resource and an action and some inputs, using Ash only as a modeling language.Ultimately for me it’s all about letting simple things be simple, but retaining the right to separate when necessary. 85% of the stuff in your app is going to be simple mappings to data layers. Let’s not complicate our entire stack just to handle the 15% where that isn’t the case. Instead, we can just use a generic action. Or make a domain & resource just for that thing, etc.
Not my best explanation ever, but I’ve run out of steam
dkulchenko
This really helped with making things make sense, thanks so much @zachdaniel!
100% agree. My goal here is mostly just to understand how declarative design “intends” to approach this so I don’t actively fight the framework with my ingrained imperative habits
My default inclination is to fill that “run” function with all the steps to place an order (calling out to other actions). Would the more Ash-y approach be a series of hooks inside changes instead? e.g.
By “context” do you mean a module that does broad domain logic (like Phoenix contexts, or a service layer in general)? Is the idea that we can model all of our business logic declaratively, so it all naturally fits into a resource in one way or another, so contexts don’t need to emerge in an Ash app?
Does “outside” mean with respect to that specific domain or to the application in general? i.e. something that has no API exposure of any kind and is purely used internally by your own app (maybe called from actions that are external-facing), is that usually still a resource?
Do you typically still write non-Ash Elixir modules in a production app, just not ones that handle business logic? e.g. a module that’s just a bag of pure functions, or one that converts photos from png to jpeg, for example.
zachdaniel
In general, yes I’d use hooks to compose logic around manipulating records in my app. Then, if complexity or some other need warrants it, I’d upgrade to using a generic action with or without a reactor, depending on the context. The new multi step actions guide provides some guidance on choosing between those.
I mean no explicitly defined service layers. Some folks might disagree with me on this, I’ve seen someone write that Ash is great as long as you define a strict service layer not that long ago. Folks can make up their own mind, but I disagree
This means “outside” the domain, either cross-domain or from something like a Liveview, custom controller, plug, background job etc.
Yes, very often. We still have a need for “regular” code like this often
Its just often utilities and/or stuff backing our actions.