By anders pearson 05 Aug 2023
The conversion happened a while ago, but I haven’t gotten around to mentioning it until now.
Before I share my thoughts on that, I need to mention a bit of history. This site started out more than 20 years ago, running on a Linux box in my dorm room. In the late 90’s I had a personal blog (statically generated by Perl scripts) and a shared message board app for my friends and I to post on and keep in touch. At some point, I merged those and made it more of a multi-user blogging platform. I kind of used it as a testing ground for various ideas I had, like adding a dedicated post type for images/photos, or for links (called “bookmarks” on here and obviously inspired by Delicious), approaches to tags, metadata, versioning, threaded comments, etc. Eventually, when I was pretty much the only one posting, I switched it back to being more of a single-user blog (but leaving all the old content in place, because Cool URIs Don’t Change), turning off comments (because we can’t have nice things) and generally turned off or hid features that no longer seemed important.
It was also my platform for experimenting with new languages, frameworks, application architectures, deployment tools, etc. It’s been Perl backed by MLDBM files and served via CGI, upgraded to mod_perl and PostgreSQL, statically generated and built out of a complex network of SSIs, ported to Python and TurboGears, then ported to Django. It’s run on a Linux box in my dorm room, various early hosting companies, Linode, and DigitalOcean VMs, EC2, a Kubernetes cluster, and Fly.io. Deployed via bash and rsync, Fabric, Jenkins, kubectl and Github actions, etc.
I actually strongly recommend that every developer have some kind of project like this. Something where you understand the domain well but can rewrite in different technologies or styles to get more experience with those technologies or styles than going through simple examples and tutorials. It needs to be small and contained enough that you can rewrite it in maybe a couple weekends or over a few weeks worth of evenings, not months of full time focus. But it needs to be large enough and complicated enough that you’ll run into some real, non-trivial problems. Bonus points for it being public or otherwise used by people other than you. I think this is a great way to evaluate those new languages, technologies, etc. in a much more meaningful way than reading documentation or going through basic tutorials that might not expose you to some of the messier aspects.
After porting this site (and a bunch of other personal apps as well) from Django to Phoenix, I’m certainly not an expert in Phoenix, but I’ve had to figure out all the basics, as well as getting a bit deeper into things to maintain existing URL structures, force Ecto, Phoenix’s “ORM”, into some unnatural contortions to support the database structure that had “evolved” over previous versions of the site, figure out how to do data migrations, automated zero downtime deploys, etc. Lots of things that wouldn’t be included in basic tutorials or that I’d have skipped over if I was just learning for fun because they’re a bit annoying. To actually use a technology in production though, those kinds of real world complexities are something you have to deal with and going through this kind of exercise gives me confidence that Phoenix is adaptable enough for production use.
Phoenix’s headline feature is LiveView which (IMHO) is one of the most exciting developments in web technology in the last decade, and provides a very promising alternative to the overcomplicated SPA mess that’s been bloating our industry. This site doesn’t use it (though I use it on other apps and love it). A blog just doesn’t need anything like that. Just good old dynamic server-side rendering of HTML is plenty (and if I didn’t still like the idea of using this as my test platform, I’d probably have switched to a static site generator long ago).
Even without using LiveView, I’m really happy and impressed with Phoenix.
- Performance is great. I didn’t really have any problems with the performance of the Django version of this app (in the past, even without any kind of caching, it easily handled getting on the frontpage of HN and r/programming), and I didn’t bother benchmarking before/after, but it easily feels at least as fast as the Django version. (and, again, I haven’t yet bothered implementing any kind of caching whatsoever).
- Performance is also great from the perspective of memory usage. I was running the Django version on Fly.io, and while it mostly worked on their 256MB free tier, it would occasionally leak enough memory that it would crash. That happened often enough that I was willing to pay a few bucks a month to bump it to 512MB, where it was perfectly stable again. I’m also running the Phoenix version on Fly.io and it easily runs in a 256MB container and doesn’t seem to ever leak memory or have any stability issues.
Elixir as a language is pretty nice. I’ve been dabbling in Erlang (which Elixir is built on top of) for a long time and I’m a bit of a weirdo in that I think I still actually prefer Erlang syntax to Elixir (an unpopular opinion). I also never really got into Ruby (prefering Python) and Elixir’s syntax is heavily influenced by Ruby. Still, it’s fine. Elixir has made genuine improvements in the usability/ergonomics around strings, macros, modules, and introduced some really wonderful syntax like the pipe operator. Elixir has also dragged the Erlang ecosystem into the world of modern tooling and packaging with
hex. Critically, it’s done all that while keeping all of the best aspects of Erlang.
- Phoenix overall has been proving itself to be a solid web framework. I’ve used a lot of web frameworks over the years and I’ve done a lot with Django. I feel like Phoenix has basically nailed all the basic architecture and features. There’s always a learning curve on a new framework, but I have yet to run into anything that I thought would be significantly easier in Django or some other framework.
- Ecto, the “ORM” part of the stack in Phoenix, while it was definitely the part with the steepest learning curve (and I still feel like I’ve only barely grasped parts of it), is beautiful. I’ve never been an ORM hater (but I’ve used Hibernate, so I can see where the ORM haters are coming from), but Ecto feels like an evolutionary leap. I love Django’s ORM and have defended it in countless online arguments, but going back to it after using Ecto feels like a step backwards. This is one of those things that’s really hard to properly explain; you need to really spend some time with Ecto (and have a lot of experience with competing approaches) to get a feel for why it just feels like a better approach.
- It’s not really relevant in this simple blogging app, but in other apps that I’ve been building with Phoenix that are a bit more complicated, the capabilities of the underlying Erlang/BEAM/OTP system to handle concurrency have let me vastly simplify the overall system. Eg, in a Django app, as soon as you need to do something outside the request/response cycle, you pretty much need to set up Celery, RabbitMQ/Redis, add a bunch of configuration so all the pieces can talk to each other, add a bunch of infrastructure to manage running and deploying and monitoring those other pieces, etc. In a Phoenix app, you just… spawn a process. Maybe push a message into a channel. You just don’t need all the other pieces and life becomes much simpler. (The simplification of converting a React/SPA frontend plus backend REST API to a single, integrated Phoenix LiveView app is even more impressive and liberating).
Of course, not everything is perfect. I have a few things that I’ve been less happy about. Some of them are probably just my personal taste combined with my lack of experience with Elixir/Phoenix, but I think they’re worth mentioning.
- My partner is named Phoenix, so the name of the framework is particularly frustrating for me and makes for some confusing or awkward conversations.
- On a superficial level, I don’t really like the template syntax, which seems roughly inspired by Rails’ ERB, which I also didn’t like.
- It hasn’t been a dealbreaker, but I do miss Django’s approach to reusable apps and the ecosystem that has evolved there.
Related to that, Phoenix takes more of a code generation approach (again, presumably inspired by Rails). In Django, if I wanted to use some third party application/library, I’d add it to my dependencies, wire up a few settings in the project and basically be done; maybe overriding some templates or views if I wanted to customize something. In a Phoenix project, you add it as a dependency, run a
mixcommand that generates a whole directory full of code, then go in there and make any changes you want to that generated code. That’s way more powerful if you want to make a lot of changes/customizations since you have all the code right there and can just change it directly instead of figuring out how to use various hooks or callbacks. But it tends to really quickly bloat a project with code that is now part of your repository. More importantly, it makes upgrades much more difficult. I can’t just pull in a new version of that third party library and do some quick testing to make sure they didn’t change their API. Instead, the code generation step means I’ve effectively forked that library. So an upgrade now involves complicated diffs of hundreds or thousands of lines of code. At some point, I want to spend some time with Ash as it seems like that has most/all of the upsides of Phoenix with more of a declarative/library based approach.
- It’s hard to really complain about this, but Phoenix’s APIs and architecture have been changing rapidly and not always in very backwards-compatible ways. On the plus side, they’ve been making improvements at a rapid pace and it’s clearly a thriving project, which is part of why it’s so exciting. But it’s also meant that a lot of online tutorials and documentation is out of date or misleading. The version of Phoenix described in Programming Phoenix, the book written by Phoenix’s lead developer, is basically unrecognizable now, with different directory structures and templating syntax, and didn’t even have LiveView, the feature that’s probably driving the most new people to the framework. Similarly, I preordered Programming Phoenix LiveView two years ago and its release has been pushed back repeatedly, I assume because LiveView has gone through so many major changes in that time and they don’t want to publish a book that’s already out of date at its first printing. It’s also really exacerbated by the code generation issue. Every time there’s a new version of Phoenix or one of the core libraries, I can’t just wait for the maintainer of a third party library to release a compatible update; I basically have to do it myself since I have all the code in my repo. It’s never been terribly hard, but I’m coming from Django, which while you can criticize for many reasons, has been doing an amazing job of maintaining stable APIs, clear deprecation paths, and backwards compatibility for like 15 years.
Overall, I’m really happy with Elixir and Phoenix and have been happily porting my Python and Django apps to it. I’m excited about the possibilities and expect to be building a lot more in the future. If you’re doing web development in any capacity, I think it’s worth looking into.