Ever since I read a book on what Erlang/OTP and Elixir do, I question why we put up with kubernetes/openshift and microservices at all. Even phoenix seems to do what SPA/SSR frameworks want to push atm. Does this stack really put everything under the sun to shame?
> Does this stack really put everything under the sun to shame?
Yes. I'm a .NET guy by trade and I've poked around Elixir/Phoenix a bit. I wish this stack (and its concepts) would gain some popularity in the enterprise so we could get rid of microservices and SPAs entirely. 99% of companies don't need Microservices, they solve problems they don't have at costs they shouldn't have to pay (velocity, complexity, etc.) SPAs could go away for most LOB apps with Phoenix Live, Blazor (.NET), or even just classic server-side web apps + Htmx.
Just upvoting this isn't enough. I work in a large .NET shop. I only wish there was just the option of building admin or LOB apps with any of the three you listed above.
Sure, there's some consistency in the tech stack, but I do not need a full-blown TypeScript/React SPA any time I just need a page with a form.
Not everything of course but for the sort of network-based soft low latency uses web apps are built around yes pretty much. Even within its domain it has some weaknesses but their contours and mitigations are well understood. OTP is venerable and proven.
If you're ever looking at microservices as possibly a good idea you should definitely consider "can I just do this in erlang." Usually the answer is "yes, but no, because of company politics" and those hurt a lot once you know what you're giving up.
I've read a few and sat through a few video courses. The book that made the OTP part really click and appreciate why and how was Elixir in Action [0]. There is a new edition in MEAP at the moment too.
k8s is meant for frameworks and runtimes, like Rails, that don’t have any real concepts of processes or clustering.
I think of Elixir as a way to get full control of the stack in one runtime. It has process management and clustering built right in, which is kind of redundant with everything k8s offers. It gives you so much control over so many things with one runtime and language.
Contrast that to a runtime like Ruby/Rails where it’s “run a server process and don’t attempt anything outside of that” — k8s can actually be helpful for process management, clustering, and service discovery because doing so in Ruby would be a huge effort that requires cobbling the Ruby, Linux, and k8s things together that you just don’t have to do in Elixir.
- A phoenix application basically can be seen as a "microlith": monolithic codebase (even with umbrella setups!) and during runtime there a lots of independent processes that scale over clusters of VMs, where partial failure doesn't neccessarily affect the rest. So no truly independent deployments of microservices, but most advantages of the pattern, without HTTP-API overheads or similar, since sending messages between processes is a natural thing to do anyways.
- Liveview extends that by connecting a client with the VM via websocket, where there is only a minor difference between receiving a message from another server process or from the client (handle_info vs handle_event). The data/state lives completely in the users' backend process, so much less cognitive overhead about getting data into the client, you actually don't even think about this topic. Also no JS blobs that get bigger with increasing amounts of pages/components or clever splitting + latency compensation of prefetching, and the SSR performance is spectacular, and also free (see the complexity of ReactServerComponents) and no thinking about the whole topic while doing LV.
- under demanding pressure, the VM behaves optimal for a web app actually. Every LV is its own process on the server, every "thing" ultimately lives in some process, and its all supervised by other dedicated processes that do automatic restarts/... in case of failure, sometimes called "self-healing". Erlang was the original system actually achieving the nine-nies of uptime. Garbage collection is also on a per-process level, not a global stop-the-world thing, with short-lived processes cleaned right away without the local GC kicking in. And the VM itself does aggressive preemptive scheduling. This translates into: quick http responses stay quick even under load, but long-running jobs run even longer when there is pressure - exactly what you want: not blocking visitors' experience with crunching jobs.
- the database story is exceptionally great, you have schemas and separate functions to do stuff with data (validations/...), not a coupled thing like ActiveRecord models - I can have different changesets/rules per usecase on the same schema easily. Can also be used for casting JSON responses, and supports embedding document-like structures (with schema/...) into postgres natively.
- many problems you would reach out for external services to solve have native built-in equivalents. IE you don't need Redis, since there is the built-in (D)ETS. which also is objectively better than redis for caching needs, since there is no serialization overhead between service boundaries, and native language structs are used for keys/values, not just strings, and it can be queried/matched with full power. When you know your full Elixir toolbox, your app becomes much simpler on the highlevel (architecture/infrastructure) and more convenient on the lower (code) levels.
- Everything[2] is packaged up (i believe) in an industry-leading level of polish and quality, from exhaustive docs, awesome testing tools built-in (ExUnit for liveviews is dramatically better to use than something like cypress and getting app configs right for testing modes and you have the fullstack integration testing level by default easily.
- the data processing/ML story is generally great and is quickly catching up to python levels, and the unique capabilities of the VM itself unlock tools like Broadway to build data pipelines to feed into things like ML models. Speaking of, some algorithms like NN have great native libs now, there is a adhoc integration with huggingface (bumblebee), and last but not least some things like evolutionary algorithms are great to design with OTP constructs.
- the language itself is as powerful as a lisp in practice, the only noticable exception being its not homoiconic so you need quote/unqote for macros and AST stuff, but also there are less brackets. But everything you see is a macro ultimately, even defmacro is a macro. create your own DSLs with ease if needed.
---
as a downside, deployment is more complex when you want to leverage the distributed nature of some native features, but you don't need to in general, you can also deploy it like any other ruby/django app iE with a dockerfile and loadbalance it traditionally. Also there is a learning curve in general, since all this stuff _looks_ alien for many devs not used to it, and there are features that don't exist at all in more mainstream languages.
---
[1] except for raw compute tasks, when even Nx doesn't cut it, but then there is rustler for native extensions which works reasonable well!
[2] except for liveview itself - the docs are there but cluttered over many places, and not really within phoenix itself but as an external lib, and there has been lots of changes over time.