Ash with AI (split thread)

,

I didn’t want to derail this thread, which is primarily about AI, and it is really hard for me to talk about Ash without spitting fire and brimstone. And frankly, I’m worried that I might get blacklisted as “that guy”. But since you guys asked, I’d describe it like this: Ash makes some annoying and repetitive things easier and can reduce boilerplate. But it has an extremely steep learning curve, and also makes a lot of very basic things extremely difficult and confusing. And for the latter, there’s usually no way to find the solution without absurd amounts of trial and error, spending hours poring over the docs and eventually giving up and asking in their Slack/Discord. The last one eventually becomes the primary means of getting help because it’s the path of least resistance by far.

The really long list of frustrations we’ve run into includes but definitely is not limited to the below. I may be imprecise or even incorrect on some of these. I’m going off of memory and didn’t fact-check myself aside from the bits I cited. Corrections welcome.

  • Cryptic errors and missing/broken stack traces. A lot of errors don’t even show where exactly your code calls the Ash code that is blowing up, so good luck trying to guess. For developer-friendly error messages, please see how Ecto does it: “X was expected, Y happened instead, this is probably due to one of four reasons…” which actually really helps. Same with Phoenix, which is known for friendly and helpful errors like “couldn’t find the template file the controller is trying to render, the following templates were compiled…” and then you can clearly see what template is missing or why. A lot of Ecto/Phoenix errors actually link directly to their own documentation where relevant.

  • Documentation that is hard to navigate and often seems written for people who already know the framework well. For context, I’ve done a lot of technical writing over my career, so this is a subject that is very dear to me. To put it lightly, Ash docs… need significant work. As a specific example, I came across this Ash.Reactor page the other day via the sidebar. It says “See Ash Reactor Guide for more info”, so I clicked through. Except that one then says “See Getting started with Reactor to understand the core Reactor concepts first”. Hmm, shouldn’t a guide be explaining the core concepts? How many layers does one need to dig through? And the disappointing thing is, neither the “guide” nor the “tutorial” explain why exactly one might want/need to use a Reactor. What is a motivation for using it? What are some realistic, real-world use cases where other parts of Ash fall short? Why does it exist? The guide’s Example section says “An example is worth 1000 words of prose”. No, it’s not. Documentation is written for humans. Code examples are supposed to supplement, not replace, natural language explanations. Please treat documentation as a first-class citizen and expect the same quality from it that you expect from your code. Ideally, have it reviewed by experienced technical writers before it (and the feature it accompanies) go live. Both Ecto and Phoenix have world-class documentation and that is a huge part of what makes them approachable and enjoyable to use.

  • Having to constantly remember the differences between calculations, aggregations, preparations, policies and other Ash-specific concepts, along with all the gotchas and limitations and subtleties of each one in order to maintain a clear mental model. Lots of ways to shoot yourself in the foot with them because the underlying concepts are abstracted away from you, there are overlaps and the docs don’t make clear distinctions where relevant. For example, the Aggregations docs say: “In cases where aggregates don’t suffice, use Calculations, which are intended to be much more flexible.” But calculations can contain inline aggregates, and there appears to be no clear and detailed explanation or examples for when you should declare something as an aggregate on the resource vs. an inline aggregate inside a calculation vs. a standalone aggregate. Similarly, Preparations and expression Calculations both affect reads but through different mechanisms: Preparations modify the query shape (adding filters, sorts, limits), while expression Calculations add computed columns to the result set. Module Calculations without an expression/2 callback are yet another thing entirely: they run in Elixir on each record after it’s been fetched from the database. This is mentioned in passing (“…When calculations require more complex code or can’t be pushed down into the data layer…”), but is super easy to shoot oneself in the foot with, especially for a junior dev. And fundamentally, policies, filter checks, preparations, and expression calculations all ultimately add conditions to a query, but through four different abstraction paths with different DSLs, different mental models, and different documentation pages. When debugging anything remotely complex, you’ll have a lot of difficulty reasoning about it, unless you’re superhuman.

  • The arcane incantations needed to make Ash.Expressions do what you want, and remembering which function, which * function fragment (!!), which * string fragment (!!!) etc. to use when. Not to mention inline aggregates, which come in different flavors. I think that’s like… twenty percent of it? The complexity here alone is astonishing. And I can tell you that over the past year of working with AI, it has never ever gotten moderately complex expressions right (although I think Opus 4.5 came close once). Btw, the Hexdoc page for Expressions is another example of “all of this needs a ton of work”. Just look at it. This page makes me grit my teeth whenever I have to refer to it. Poorly organized bullet points with one-liner explanations for each function, almost no actual usage examples in proper context. For starters, each of the stuff listed under “The following functions are built in…” needs its own dedicated section, or a proper @doc attribute if it is auto-generated.

  • Ash expressions look like Elixir but don’t behave like Elixir. They use SQL-style NULL semantics, where nil values “poison” expressions. For example, x + nil always evaluates to nil, and not nil returns nil. The docs state that true and nil returns nil and even that true or nil returns nil, the latter being stricter than standard SQL, where TRUE OR NULL returns TRUE. This is documented in a single paragraph in the Expressions guide, with no examples of what can go wrong. If you’re writing Ash expressions with Elixir intuitions about truthiness (which you will be, because you’re an Elixir developer and the syntax looks like Elixir) you may get silent wrong results with no error to point you in the right direction.

  • AshJsonApi not supporting all JSON:API features. This has tripped our AI agents several times, to the point where I now have a local copy of the AshJsonApi repo that I keep updated and when the AI struggles I just say “go read the source code to see if this is even possible” and it usually comes back with “OK so it looks like Ash has not implemented this, which means we will need to use a workaround”.

  • Having to generate massive JSON files every time the data layer changes, which leads to bloated PRs and other annoyances. I’m personally really tired of prefacing every PR review request with “hey guys, don’t be discouraged by the fact that this PR adds 744 lines, the code itself is actually only 30 lines!”. This is probably more “domain-driven design” and not Ash specific but I’m not the only one who resents the repo bloat.

  • No framework-level support for database triggers, in the way Ash looks at custom_indexes and automatically includes them in migrations. Maybe this changed recently, or maybe it’s buried somewhere in the docs (I searched for “trigger” and didn’t find anything). There’s a few other critical omissions like this that I’m forgetting at the moment.

Broadly speaking, every layer of the framework feels somewhere between 50% and 80% finished, docs are maybe 30% there, and that’s only for the official parts. Those parts have a lot of limitations that are either undocumented or only documented obscurely. Escape hatches exist, but it’s up to you to figure out if they should be used, or if Ash actually does have a way to do what you want and you simply missed it (or weren’t smart enough to put the isolated pieces together). So you end up spending a lot of time trying something that seems like it should work, get bizarre errors or confusing performance problems, then ask in Slack, Discord, or GitHub and learn that you should actually be doing it this other way, or that it is not supported and that an escape hatch is actually the way to go.

Bottom line: Ash can be useful, depending on context. I strongly believe however that it should not be the default choice for any team, especially those just starting out. Learning curve is steep and adoption costs are significant. New and more junior hires will have difficulty with it. And perhaps most importantly, it does not feel like writing Elixir, as it has been said elsewhere. If you’re used to enterprise-y DSLs you’ll probably be fine. For everyone else, it’ll be a mixed bag.

I’m sure some Ash users/maintainers will respond by saying some or all of this is incorrect, or that I’m being really harsh and unfair because many people have put tons of effort into it, or that a lot of this is actually working as intended. That’s fine. Ultimately it’s probably good that the framework exists, as it represents probably the most significant and ambitious undertaking in the ecosystem and some people seem to like using it, and those types of initiatives are good to have. All I can say is that I’m simply sharing the collective experience of (and channeling the frustrations of) our team: our staff engineers feel it constantly gets in the way, mid-level engineers dread working with it and our more junior engineers have a hard time becoming productive with it. Maybe we just are not operating at the kind of scale or level of enterprise complexity where Ash really pays off. Or maybe we’re “holding it wrong”, despite most of us having read “The Ash Book” and attended private training(s) as well as Elixir conf sessions. At this point though we’re all super excited to be going back to normal Elixir with Ecto and good ol’ controller and view modules.

8 Likes