Marketing Ash: Why you should use Ash?

This post is an excerpt from our book Domain Modeling with Ash Framework. I’m sharing it here for obviously “Marketing Ash” but also to have healthy discussions around it.

“Marketing is the generous act of helping someone solve a problem. Their problem.”
— Seth Godin

Let’s keep this direct: in this chapter, we want to sell you on Ash. Not because we, as the authors, gain anything from Ash Framework’s success directly, but because we’re developers like you. We’ve used Ash ourselves, and it has significantly improved our productivity. We want to help you solve your problems, and we genuinely believe Ash is a game changer—whether you’re building small apps or large systems.

This chapter addresses common concerns about adopting Ash for your project and demonstrates how it solves real-world challenges, drawing on our experiences. These concerns broadly fall into two categories:

  1. Temporary issues tied to Ash’s small but growing community, which will diminish as the community expands, and
  2. Misconceptions about Ash’s purpose, which will persist until we shift our perspective to understand the complex business logic problems Ash is designed to solve.

Our goal is to convince you that Ash is worth your time.

Community Size Challenges

Some challenges stem from Ash’s relatively small but growing community. These are temporal issues and let these not stop you from using Ash. Doing this causes a vicious feedback loop which is difficult to get out.

  • Inadequate learning resources - fewer tutorials compared to Elixir’s ecosystem.
  • Documentation can be difficult to read - though improvements are ongoing.
  • Fewer experts in Ash - limited support compared to Phoenix or Ecto.

Ash’s creator, Zach Daniel, and core team members like Rebecca Le are active on the Elixir Forum, answering questions daily. We’ve seen bugs addressed within an hour, sometimes with a minor version bump—a level of support we’ve rarely experienced. The documentation is improving, with one book published by the Ash creators and this book as another step towards making Ash more accessible.

Of course, we need more and that’s where we need you to grow Ash’s community and its support. By seeing past the learning curve and experiencing Ash’s benefits, our hope is that you’ll help accelerate its momentum.

Inherent Challenges

Other challenges are tied to Ash’s ambitious goals:

  • New concepts: Resources, actions, and policies require learning.
  • Declarative style: Configuring code feels unfamiliar.
  • Perceived “magic”: It’s not always clear what happens behind the scenes.

If any of the above reasons stop you from adopting Ash, then it’s probable that you are using a wrong lens to see what Ash does. These perceived challenges will never go away from Ash because these are in fact the core strength of Ash as we see below. The solution here is to do the right kind of comparison to understand what benefit Ash gives you and you will be able to automatically acknowledge the worth of spending time to learn Ash. These concerns vanish once you accept to learn Ash and not fight with it with your previous mental constructs on how to build apps.

Why Ash Solves Real Problems

Speaking from our personal experience, we, the authors, have worked on large-scale projects that started as small prototypes. Narendra and Shankar worked on a payment service for Auroville that powers the entire township’s digital transactions. Shankar also worked with startups in synthetic data generation and worked with sports clubs that processed massive volumes of data. All of these projects were created with vanilla Phoenix. We had the pleasure of working on a greenfield project using our favorite framework but the pleasure was short lived due to growing complexity.

In 2024, we started using Ash for our newer projects and after a year now, we could retrospectively feel how much Ash would have relieved the pain in our previous projects. Unfortunately, Ash wasn’t available when we started working on those projects and if we are to restart doing the same projects from scratch, we would do it in Ash. We have spoken to different people in the community, some of whom are startup founders and they don’t immediately see the benefit of Ash but rather see obstacles in using Ash. This is primarily due to the reference point of comparison chosen by them.

The reference point of comparison should not be “how easy or difficult it’s to create a new project” in vanilla Phoenix vs Phoenix with Ash

rather it should be “how easy or difficult it’s to manage this project as it matures with all its complexities” in vanilla Phoenix vs Phoenix with Ash.

Once we fix our point of comparison, we can truly see the real benefit of Ash and also see the necessity of why Ash has to introduce new concepts, bring in declarative style and all the magic it comes with.

Below, we use these experiences to highlight three major pain points how Ash addresses them.

Problem 1: Onboarding New Developers

In all these projects using vanilla Phoenix, we consistently noticed how difficult it is to onboard new developers later in the lifecycle. Even with decent test coverage and documentation, a lot of the critical knowledge lives only in the heads of long-term contributors. For example, in Auroville’s payment service, new developers struggled to understand custom logic scattered across contexts, or even understand the purpose of a simple attribute named channel_reference_id because it’s not clear what it refers here, slowing their ramp-up. Developers don’t know where to start to understand a huge codebase. Simple things like field names that we defined in our payment service were not meaningful enough for the new developers to understand. We had a spaghetti of inline comments to explain what those did, inventing our own framework.

With Ash, we now have a clear structure to our code. Each Ash resource contains the information needed for new developers to understand what each of these entities do. The describe macro allows us to document any piece of information that we have on our resource module. The declarative style, along with a clean structure in itself is better than reading long pages of documentation or reading through 1000s of test cases.

Problem 2: Managing Code Complexity

As the product features grew, we—the original developers—struggled with managing the rising code complexity. Under delivery pressure, we often compromised best practices in favor of quick hacks or inefficient solutions. Duplication of code to add new features was favored than refactoring existing ones or making them composable. These decisions kept coming back to haunt us because we never got around to fixing them once they were live. For instance, what started as a simple nice policy check using pattern matched function in our controllers grew into an ugly unmanageable chore of managing permissions. Adding and removing permissions was brittle as there were duplicates due to non-composable policy checking functions.

Ash’s declarative resources act as a single source of truth, reducing duplication. Defining an entity once generates migrations, APIs, and validations, keeping complexity manageable. Authorization policy is managed at resource level for all actions in a highly structured way. Being able to filter on attributes, relationships, aggregates and calculations using the same method removes the complexity of managing different join queries for complex operations.

Problem 3: Long-Term Maintenance

From those experiences, we’ve learned that writing a Phoenix app with just plain Ecto is easy in the beginning—but the complexity grows significantly over time. Using Phoenix Contexts to organize your business logic works well initially. We tend to write new functions or modify existing ones as product grows but we need to remember every piece of code written, including any comments and tests, adds to the maintenance burden in long term. Anything that is written must be maintained for long term.

Ash reduces the number of implementation we write. We write less code resulting in less maintenance. We stick to the declarative nature and define what is required and even that is centralized under resources. There is hardly any duplication of what has been already said to Ash. All the implementation details are managed internally by Ash which doesn’t carry the maintenance burden for us.

Phase Phoenix + Contexts Phoenix + Ash
Getting Started Easy setup, minimal learning Steeper learning curve for your very first project
Team Onboarding in early stage of the product Easy early on Easier over time due to conventions
Team Onboarding in later stage of the product Difficult going forward For anyone using Ash, onboarding on new Ash project should be easy at any stage as the framework deals with the complexity internally
Code Growth Becomes fragile and harder to evolve Remains consistent and declarative
Maintenance Manual conventions, more bugs due to undocumented custom implementation Less bugs and easy maintenance due to reliance on Ash’s implementation

Declarative Design and Ash Magic

Declarative coding in Ash means your app feels like a configuration file you write to spell out what you need: “I want a user with an email that’s required and unique.” Instead of coding every step—database queries, validations, API endpoints—you define it once in a resource, and Ash handles the rest. It’s a big shift from traditional apps where you’re used to writing the same logic over and over. With Ash, except for custom validations, actions or policies that are not built in Ash, you’re mostly configuring, not coding from scratch.

This declarative nature is the very strength of Ash. Ecto is also declarative in design like Ash but it requires us to declare our requirements in multiple places even if we have already declared the same requirement elsewhere. For example, in Ecto, the developer would define the schema and then also write manually, once again, the same requirements in the migration file to create the underlying database table with the same set of constraints that is already defined in Ecto Schema. If the developer needs a JSONApi for fetching the Ecto records, then the developer would write again all or subset of the same requirements in the Phoenix layer depending on the business requirements. Ash flips this on its head with resource files acting as the single source of truth.

Define a resource like this:

defmodule User do
  use Ash.Resource
  attributes do
    uuid :id
    string :email, allow_nil?: false
  end
  actions do
    create :register do
      accept [:email]
    end
  end
end

This resource says: “Users have an email, it’s required, and you can create them.” From that alone, Ash generates the database migration with the right columns and constraints—no extra work. Later, if you want a JSON API, Ash’s JSON API extension (a separate add-on) reads the same resource and spins up endpoints automatically. If you want GraphQL API, no problem. Just plugin the AshGraphQL library and it will work seamlessly using the same configuration already defined in the resource file. No controllers, no duplicate code. One definition, reused everywhere.

While this ability of Ash of doing everything automatically feels like magic and might make us feel not having control of how things are done, in reality, it’s simply an efficient way of reusing the data and managing a single source of truth. In this sense, Ash framework’s slogan “Model your domain and derive the rest” truly conveys how Ash works internally.

Hopefully we have sold you Ash! Now, let’s dive into the practical chapters to learn Ash hands-on. We can’t wait to see how you will use Ash to make your code cleaner and your work easier.

This is an excerpt from our book Domain Modeling with Ash Framework and you can buy it to learn 150+ hands-on lessons, exercises, and real-world examples. You can read reviews of the book here: Ash Framework Build Fast, Model Right (self-published) - #30 by zachdaniel

10 Likes

Ash sounds very interesting, with great principles, but I wonder what the DX is like? For example in this example:

What the LSP and IDE support like for this DSL?
Is the code checked at compile- or runtime?
What are the error messages like?
How would I found out the allowed vocabulary inside of the actions block?

The LSP support for Ash is crazy good. I think it has something to do with how Spark (which powers the Ash DSLs) makes use of elixir_sense. (I would love to hear more about the nitty-gritty of how this is made possible.)

The LSP support is one of the things that stood out the most to me when I first used Ash, a while back. I was blown away by how much information the language server was presenting to me, and it suggests that there is a lot of room to enhance LSP integrations on my own projects. It makes me want to make a project with Spark, but unfortunately, there is not enough free time to go around.

For reference, I was (and still am) using ElixirLS as my LSP, and I cannot speak to the quality of the Ash LSP integration for Lexical/NextLS.

EDIT: Fixed broken links

3 Likes

I’ve used Ash for about a year now in a team that is split 70-30 on it, with the majority not liking it. Unfortunately, the minority has more sway so we’ve stuck with it.

I had a shower thought the other day and realized why I’m not productive in Ash. I wanted to share it in the hopes that it leads to a healthy discussion, as the OP invited. To me Ash has strong “enterprise Java stack” vibes: it turns the Elixir experience on its head, into a metadata‑driven, resource‑centric, inversion‑of‑control style of development, much like Spring/JPA/Hibernate in Java where you configure cross‑cutting concerns (auth, validation, persistence, APIs, etc.) in a big DSL and let the framework orchestrate implicit pipelines. There are benefits (you get a lot out of the box), but the indirection, ceremony, and entity‑first modeling feel a lot closer to how things are done in big Java shops these days than to idiomatic Elixir’s small, explicit functions and pattern‑matching pipelines. For example:

@Entity @Table(name="tickets")
public class Ticket {
  @Id @GeneratedValue private UUID id;
  @Column(nullable=false) private String subject;
  @Enumerated(EnumType.STRING) private Status status = Status.OPEN;

  @ManyToOne private User reporter;
  @OneToMany(mappedBy="ticket") private List<Comment> comments;

  @CreationTimestamp private Instant createdAt;
  @UpdateTimestamp private Instant updatedAt;
}

public interface TicketRepository extends JpaRepository<Ticket, UUID> {
  List<Ticket> findByStatus(Status status);
}

The Ash parallel to this is resource declarations (attributes, relationships, actions, etc.) and the framework builds pipelines and APIs for you:

defmodule Helpdesk.Tickets.Ticket do
  use Ash.Resource, data_layer: AshPostgres.DataLayer

  postgres do
    table "tickets"
    repo Helpdesk.Repo
  end

  attributes do
    uuid_primary_key :id
    attribute :subject, :string, allow_nil?: false
    attribute :status, :atom, default: :open
    create_timestamp :inserted_at
    update_timestamp :updated_at
  end

  relationships do
    belongs_to :reporter, Helpdesk.Accounts.User
    has_many :comments, Helpdesk.Tickets.Comment
  end

  actions do
    defaults [:create, :read, :update, :destroy]
    read :open_tickets, filter: expr(status == :open)
    create :assign do
      argument :assignee_id, :uuid
      change set_attribute(:assignee_id, arg(:assignee_id))
    end
  end

  policies do
    policy action_type(:read) do
      authorize_if relation_attribute_equals(:reporter, :id, actor(:id))
    end
  end
end

The domain modeling is object/resource-centric in both cases. In Java land, you have entities, repositories and services. In Ash, the resource is the center of gravity with CRUD actions and framework-generated code interfaces, and business logic often lives in changes and preparations attached to the resource. In this sense, Ash mirrors the modeling of, say, JPA, more than Elixir’s typical “write plain functions over data” approach.

Another similarity is configuration over convention. In Java you have a sea of annotations/properties, which toggle cross-cutting concerns (transactions, caching, security, events, etc.). In Ash, you have DSL blocks (policies, notifiers, identities, etc.) which you define declaratively. So you spend a lot of time configuring features (e.g. setting public? true on each and every attribute, relationship, etc. that needs to be accessed from the outside) rather than writing small, composable functions like in Elixir and wiring them up.

The “magic” has already been mentioned. For us it has been a big negative because it makes debugging significantly slower when things go sideways. In terms of Java, it reminds me of proxies/AOP/interceptors injecting behavior with hidden control flows. In Ash your request pipelines run through validations, changes, authorizers, notifiers… a LOT happens implicitly, which results in significant loss of control, as well as framework internals leaking into stack traces and sometimes even appearing as cryptic top-level error messages.

Testing patterns are also pretty similar in that they heavily favor integration tests over unit tests. In Spring/Hibernate for instance, you often test through the framework to exercise proxies and interceptors. In Ash, since logic is scattered around changes, policies and actions, people often lean on integration-style tests that execute the entire pipeline, and assertions are made on the results and side effects. If a small part of the pipeline breaks and tests start to fail, it takes a long time to trace it to the source.

Obviously the comparison is not apples to apples, and there are differences as well. Ash is still Elixir, so it’s functional and uses macros, rather than runtime reflection, and you can sometimes (not always) drop down to plain Elixir or even Ecto when needed. And it’s still a lot less boilerplate compared to virtually any Java codebase. But I think the similarities are remarkable considering we’re talking about frameworks/stacks in two very different languages!

To conclude, I want to emphasize that none of this means Ash is objectively bad. In fact I think it’s an impressive achievement and a good addition to the Elixir ecosystem. I just think that it’s not for every team or codebase, and the productivity claims are not universally applicable - and I’m generally wary of how heavily it is being marketed. With that in mind, I think it could be interesting to hear from the Ash team in terms of when they would not recommend Ash. To put it another way, if you were paid a million dollars to adopt a steelman approach and discourage people from using Ash, how would you go about it?

2 Likes

There are a few particular cases where I wouldn’t recommend Ash.

You only need something small

Ash has a learning curve that takes time to get through, and if you’re only going to build something lightweight and never use the things you’ve learned again on another project, its the equivalent of learning a programming language for a one-off project. Using what you’re already comfortable with is important.

You already know how to do what you want without it

Ultimately, I think Ash brings a lot more to the table than most people realize. It makes a lot of really hard problems easy, and the main goal has never been to just make CRUD stuff easier. That is just a side effect. But at the end of the day if you have training on a particular stack, and you know how to accomplish what you’re setting out to accomplish, then why use something new? You have to be able to clearly articulate something about Ash that will yield a positive result on your project before you can justify adding a new tool to the stack.

Every last millisecond is mission critical

Ash isn’t slow, but if you’re doing something like day trading where every cycle and millisecond will break the bank, then you need to hand optimize and tune the relevant code. You can easily have one part of your app be in Ash and one part not be in Ash, so in many cases this might mean some parts of your application are specially hand-optimized and others use Ash etc.

You don’t have the time to learn a new paradigm

Even if Ash might help with your project, you have to ask yourself if you have the time to acquire said help. I could tell you its not that hard to learn Ash, but everyone’s journey is different, and a lot of it is driven by externalities out of anyone’s control, i.e “which part do you need first” and “how much up-front learning time to you have”. So you have to decide if you’re willing to risk spending an unknown amount of time getting the upsides we claim that you’ll get (assuming you believe those upsides).

You don’t have interest in engaging with the community

The number one factor for success that I’ve seen with Ash, and in fact any piece of modern technology, is engagement with the community. The Ash docs have come a long way over the last year, and we will continue to improve them over time, but still, there is no replacement for a good old fashioned conversation. I can’t tell you how many times I’ve worked with teams or developers who have struggled with some particular concept, or were convinced “surely this can’t be how you do X”, for like…actual years. When just the right explanation of a core concept of the framework laid out an entire new set of options to them. So if you have zero interest in asking questions from folks (not that I’m guaranteeing answers, or that you’ll get an answer you like), then it may not be your best bet. With that said, I know of plenty of people who have worked with Ash for a long time and shipped to prod without ever asking questions, :person_shrugging: YMMV I guess.


At the end of the day, I think it’s also important to skate to where the puck will be, not where it is, but that isn’t everyone’s cup of tea. While we’re well past the “ready for prime time” stage, we’ll be improving it for many years to come, and we shouldn’t pretend like it is all peaches and cream. But everyone’s perspective is wildly different on this stuff to the point that it sometimes make my head spin. I’ve been told “Ash actually clicked really quickly for me” and “It took me 2 years to finally like using Ash”. I’ve been told “The Ash docs are horrendous and I can never find anything I want” and I’ve been told “wow, you guys have tons of useful docs”. No idea at this point :laughing:

With all that said, comparing Ash’s data-structure driven approach with something like Java’s mutable object oriented approach only holds at a very surface level. It takes a mindset shift, but in the same way that there are new difficulties in debugging and testing declarative code, there are also new powers in debugging and testing declarative code. I’d suggest that we still have a long way to go to provide more tooling along those lines, and to surface the options and tools that exist that are unique to how we build things with Ash.

I’d love to set out some examples of those pros and cons but this post is already long enough and I just don’t really have the time :sweat_smile: But I will leave you with one particular bad/good dichotomy that definitely exists with Ash, which is that you often don’t know what you don’t know, and we need to increase the discoverability of the tools that we provide.

For example, unit testing a well designed Ash application actually isn’t very difficult, and the framework effectively shows you many places where you can unit test.

For example:

create :create do
  change DoSomeChange
  change DoSomeOtherChange
end

You can unit test those changes via calling their callbacks, i.e

test "DoSomeChange sets an attribute" do
  changeset = Ash.Changeset.new(Resource)
  new_changeset = DoSomeChange.change(changeset, [], %{})
  assert Ash.Changeset.changing?(new_changeset, :field)
end

And you can test specifically “just the policies” with things like:

assert Ash.can?({Ticket, :create}, user)

or unit test just resolving particular calculations, without having their dependent data:

assert Ash.calculate!(MyApp.User, :age, refs: %{birth_date: ~D[1990-01-01]} == 35

There are tons of useful tools like this all over the framework that provide things that would actually be quite difficult to do in many applications (not saying all of the above are hard). Oftentimes we see folks not really see the value until the go back to doing things the old way too. I’ve seen even the most hardest skeptics come back to the table. Not because its perfect, but because they are different and preferable tradeoffs.

3 Likes