
Model your domain, derive the rest.
Everything old is new again. From my perspective as a Rails developer in the past, who landed on Elixir/Phoenix 10 years ago, Elixir and Phoenix were both born from Rails devs who were tired of Rails magic, keeping just the structure and bare minimum.
I've watched Ash spring up and build steam over these past several years with a bit of dread. From my perspective, it's kind of counter to the philosophy I liked in Elixir. I don't want to "derive" things based on a definition, for the most part. Elixir tends to avoid codegen and macros as much as possible, and José has recommended that if there _are_ macros, they should be a very light layer over calling functions, which should be the primary interface.
All this is said without having tried Ash, so I could be way off base. But I've been mostly ignoring it and wondering if I'll have to actually use it someday, and then be back in Rails-y magic land. I also use Scala at work, and saw how a philosophical split in the community just kind of trashed the language (from my perspective). It used to be a cool, better Java, but then those dang Haskell type astronauts found it and made everything impossible to understand.
Just taking the automatic Authentication, for example: Phoenix settled on a `mix` generator for it, to actually put the code and logic into the app itself. This is surely informed from José's years of experience maintaining Devise. If the best those two could come up with is a code generator, then I'm skeptical whatever Ash does here will be better. It feels like it's just heading right back to Devise land. And that was a successful place, and Rails and all its gems are still around, but that's kind of the "old world" philosophy and again doesn't square with us old Elixir developers who were Rails refugees.
I came to Elixir from PHP/Laravel so I approached Ash with a similar skepticism. I was looking specifically for a set of tools that would allow me to rebuild an ancient and unwieldy ColdFusion SAAS product, as a solo dev, with maximum stability and minimum boilerplate. I landed on Elixir+Ash after about a year of exploring other ecosystems and languages.
If you look under the hood of Ash's macros, they're all just shorthand for creating a particular struct with sensible defaults. It's not actually doing a ton of magic. You can replace them entirely with direct function calls, or probably even manually declare the structs yourself.
If there's magic, it tends to be more in the execution step - like how `Ash.read()` can push authorization logic into the query/db layer. But that's exactly the kind of logic I don't want to have to write myself over and over again.
I have never compared Phoenix's generated auth stuff to Ash's so I can't speak to that. However I can say Ash's generators all use `igniter`, which is part of the Ash project. It allows for package developers to reach into Elixir's AST to make smarter installers/updaters, and is being adopted more broadly throughout the Elixir ecosystem. So in terms of code generation Ash's approach is gaining a lot of traction.
Speaking for myself here, I've really liked working with Ash. The value becomes more apparent the more I use it.
Ash, while magic, is mostly a thin layer over calling functions. Often with a lot of ways to go deep down (let's say, by calling ecto functions yourself).
But I do understand you feeling. E.g. I myself have stayed away from things like AshAuth and Ash Forms for now
This is the beauty of Elixir's macros. For a lot of libraries, they are usually just calls to an underlying function with the same name. Because of Elixir's functional style and immutability, it makes it much easier to read through the magic compared to macros in other languages.
That being said, I don't have any hands-on experience with Ash so I can't speak for its approach to macros. I do think Zach's work in the community with Ash/Igniter is something to be applauded for. I'm currently working in a Python codebase that lacks conventions and would kill for something like Ash that provides consistent design patterns.
We support both Rails and Phoenix at Honeybadger.io (APM/error tracking), and I tend to personally prefer the more declarative/less-magic approach. We're still a Rails monolith primarily but have run Elixir services in production and are fans. We're just getting started on building some observability stuff for Ash, will be interesting to see how it compares.
>> All this is said without having tried Ash, so I could be way off base.
You nailed it. "Magic" is actually a good way to describe it, because much of the developer experience with Ash revolves around figuring out the exact incantation you need to use to make it do what you're trying to do, and the correct intonation and the right amount of reagents.
you can appreciate though how if you are building multiple projects for multiple clients, you'll eventually know all the incantations and it's worth it for your business domain.
> born from Rails devs who were tired of Rails magic
One thing I learned is that we're all different. To me, the magic of Rails is absolutely bonkers, unintuitive, and unmanagable. I also can't work without static types anymore. DHH is apparently my arch nemesis in these regards? Yet I can't definitively say it's because he's inherently flawed.
As many people seem confused, it seems that this framework is a declarative framework (think: expressing a solution like you would in SQL or Prolog), in Elixir, that seems to have the goal of separating out core logic from control flows within web apps, APIs and so on.
Over the course of 15+ years of playing with Rails, I've come to the view of CRUD being a poor choice for most real-world applications and MVC being a useful but often-abused concept. That took me down a bit of a rabbit hole of trying to think about DDD within the constraints of an opinionated framework that thinks CRUD and MVC are the thing that makes you go fast. I, on the other hand, want the thing that makes it easy to extend and maintain. I want actual events to be modeled for all sorts of real-World reasons.
I've been learning Elixir recently because it's something new, the actor model appeals from a concurrency perspective to some of the problems I want to solve, and because Phoenix looks like an interesting and elegant web app framework. However, there was a nag in the back of my head that I'd just end up hitting the same frustrations: I was, and am, ready for that and seeing if anything about Elixir + Phoenix makes life easier than Rails made it.
Ash seems to be a good candidate for part of the puzzle. A declarative framework that brings some packages for useful, often-needed parts of the puzzle (like authentication, authorization, and so on), and it seems to encourage a way of thinking about some key DDD concepts. Resources sound like domains or bounded contexts. Declarative style sounds like it might lend itself to event modeling more easily. Calculated values are a nudge towards "view models".
Never tried, I'm early on my journey, but I think the Ash book will be the first thing I pick up after I've finished the Elixir and Phoenix books I'm reading through. Curious to hear stories from people further down this journey, though!
You sound a lot like me when I first approached Ash a year ago, especially the part about being new to Elixir and worrying I'd just reproduce my prior MVC frustrations (Laravel in my case) in Phoenix.
I've been using Ash for about a year now and it's really hit the spot. Wishing you well on your journey!
I'm planning on trying ash at the weekend when I have time, but right now it's still a little nebulous to me. Have you found a boundary where you end up just being constrained and you're just back to normal phoenix dev?
Not really because Phoenix handles the web layer while Ash handles the domain/application layer. In other words Phoenix provides one interface, of potentially many, through which users could access an application built in Ash.
Which is all to say if there's been something I couldn't figure out in Ash I've tended to put it into a regular Elixir module rather than anything Phoenix-specific.
The only exception I can think of is that AshAuthentication doesn't provide support for API keys out of the box, so I have ended up writing my own little plugs for that.
If you are just getting started I'd strongly recommend working through tutorials and/or the Ash book (it's in beta but pretty solid) rather than just diving into a project. It's a lot when you're first wrapping your head around it.
Appreciate these thoughts. Would you mind sharing what Elixir and Phoenix books you're working through?
I've always been a fan of Model Driven Development (MDD), which is by today's standards an ancient paradigm. You define the core data model (resources), properties (attributes), and actions, and auto-generate the UI/API. It was more popular back when OOP first met Web Development. It seems to have fallen to the wayside with manually constructed "fat controllers" being the dominant way to organize web apps today. I have my own framework in MDD style. For a long time I've told people "I can code at the speed of thought." My apps are 80% the data model, 20% the UI, with the middle layer almost entirely generated. Tools like GraphQL, Supabase, and auto-admins kind of go in the same direction.
This is cool, though you would definitely make changes to make UI look decent, and a lot of changes on top of that to make it something that’s a joy to use. For API though, it should probably just work - but then again, there’s tons of stuff like Django REST Framework which, while also a bit dated, works really well in 95% of cases (and lets you handle the other 5%).
Yet here I am, writing CRUD endpoints by hand in Node.js...
Yep I agree, that "20%" UI is mostly custom view-models to give the right UX. MDD is great for development, but users' brains don't exactly fit the model perfectly, particularly for read views. For write functions however, the views just post almost entirely to the generated API.
Related thought: "Companies ship their org chart"
Can you expand on this?
I've always felt MDD falls short very quickly, because for non-trivial project you often want to show information related to more than one table, and so you have to fallback to writing the controller code manually anyway.
Yep, it can fall down if you force it everywhere. Read views are typically where custom "view models" are needed. For example on my current project with ~30 tables/models (https://humancrm.io), there are only a couple custom user views, and a couple custom write functions to simplify the UI flow. The rest of the API is generated from the data models. I take a pragmatic approach and don't force everything into MDD if there is a clear need.
Your models/resources do not need to have a 1:1 mapping to your data store.