Trending in Blog Posts
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
New
Hey folks,
I just published a post about Hologram’s funding and where the project goes next - the short version:
Curiosum as Main Spons...
New
A while back, I had to process millions of database updates in a legacy system that was already hitting its 64 GB RAM limit, so scaling t...
New
I recently figured out how to the the Rust hotpath profiling crate running in an elixir benchmark script (for profiling NIFs). I had some...
New
At the heart of every Phoenix application is the often “invisible” HTTP server layer.
For over a decade Cowboy has served the community ...
New
The Phoenix framework is notorious for its long term stability and dependability. Unlike most comparable projects, the Phoenix team activ...
New
I’ve published Part 3 of my Elixir distributed systems learning series.
This part explores process monitoring using the low-level primit...
New
Other Trending Topics
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
There are three potential reasons for members of this forum to have a look at https://vutuv.de
You are tired or annoyed of LinkedIn.
Yo...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #iex
- #elixirconf-us
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
tubedude
Thanks! finally I understood the critics I’ve been receiving in my library!
mudasobwa
Sometimes we want to take control over the supervision tree, sometimes we just expect the library to do its work once added to
mix. Sometimes the configuration is an anti-pattern, in 99% of cases it’s not, specifically in modern environment when it’s all containerized. Also, starting an application is necessary if it needs to track the main application startup.I finally came up to a compromise. I declare the application in my libraries, empty-handed by default. Then I got an option to have a config and I use it to start the supervision tree within the application.
martosaur
100% with you in this uphill battle. Unfortunately, the DX of letting the app run its own supervision tree is just so good and
Reqis indeed a good example. Same goes for accessing application configuration: it’s just so much easier to say “put an api key into you configconfig :foo_client, api_key: <my api key>and you’re ready to go!”Asd
I agree with the post for the most part, but I would push this idea further: just don’t use applications, let users start their own trees. To simplify this, provide a single entry-point so that starting your dependency is a single line of code.
I can see comments here and I’ve heard lots of them in the past saying “its better DX”, but it is actually not. If you think about it, the offer of applications is as follows: You get global state by default (which is not very easy to disable) and in return you don’t have to write a single line of code which adds the process in the supervision tree. Global state is bad, it creates problems, and fix for this is just one line of code
And DX is actually worse. Consider Ecto. I can’t just connect to different databases in runtime, because database connections pool is tied to the Repo module (the one which calls
use Ecto.Repoin it), and Repo module is required because Ecto uses global application state in order to set up pools of connections. That’s why you dont start the{Ecto.Repo, connection_opts}in the supervision tree, and you start{MyApp.Repo, ....}. That leads to funny problems like you now have to write a separate module if you want a separate pool of connections for, for example, read-only replica or different prefix or isolated test database. A huge set of problems caused by thisApplicationapproach is fixed in many ways by many libraries and approaches, from sandboxes with ownership,Ecto.Multiwhich acceptrepoin a callback to libraries liketriplexwhich handle multi-tenancy.And it is true even for smaller libraries. In the early days of Elixir, some libraries were requiring their own configuration formats, they’d have
Applicationimplemented just in order to parse some.envfile, pull out the variables and populate the application env (or application configuration as it is called in the post above). LibraryNumberis a good example. It doesn’t have the Application, but it uses application env where it could just use options.However there is only one thing which applications do good: topological sorting of the dependencies. If I have a dependency
Awhich uses dependencyBand there is another dependencyCwhich uses dependencyA, the application logic will perform topological sort to make sure that dependencies are always started in the orderB, A, C.But this thing is extremely easy to implement and libraries like
Parentby Sasa Juric showed that it is possible and fairly easy, definitely not a rocket science.GrammAcc
I agree.
I love how OTP primitives make complex state management explicit and easy to follow without dictating how the project needs to be structured. Dependencies starting their own supervision trees could be more convenient, but as soon as I need to debug something or want to implement a usage pattern the library designer didn’t foresee, things could get a lot less convenient. I can understand the desire to make a library super easy to use for the “common” cases, but that usually comes at the expense of making it more cumbersome to use in other scenarios. And most real-world problems seem to fall into the “other” category in my experience.
I also prefer it when I can put config into the initial state in the child spec as opposed to having a 10k line
config.exsfile. It keeps the process orchestration and configuration in one place. And if I did want to have the dependency’s config in theconfig.exs, I could still add a config key for it myself and just callApplication.fetch_env!/2in the child spec.hauleth
That is not true. You can 100% connect to different DBs using the same module. It is not common practice, because that is not the common problem, but it is fully supported and documented.
It is not fully ergonomic, I agree, but at the same time that is not a common situation when you need such thing.
About the topic, I mostly agree with @jola. There are however grey areas sometimes. If you use
Applicationjust for env, then yeah, it is dumb, but there are situations when there is no point in having multiple instances of the processes provided by the library. Like in my systemd library, where there is no point in having multiple instances, and moving that to the user-side to manage processes for this would only make it worse wrt. UX.Another thing is that sometimes the process running in the background should be transparent to the user. Like in my aww library. Some may argue, that cache there is a wrong approach and user should cache on their own. But at the same time, when you are using (most of the time indirectly) Erlang’s DNS resolver do you think that there is a cache for A and AAAA responses? Because there is one.
Another thing that uses their own supervision tree, but people do not think about it at all, is Telemetry. Yeah, really, there is supervision tree. To be fair, it would be impossible to implement without that tree (at least if we want to use ETS, after you fire
:telemetry.persist()it is not needed anymore).So I would say that it is a complex topic, but when in doubt - do not spawn your own supervision tree, you ain’t gonna need one probably.
Asd
Yeah, I’ve missed that, it was implemented at around 3.3.0 and became stable (as in without “It may be removed in future releases” note) at around 3.8.0. That’s why I never used it, apparently. But now I know, thanks!
Which is a good point, but people don’t use it not because “it is not a common situation”, but because it is hard to use and it is a bad DX. Situation, however, is very common, and it is testing when I want to check how multiple processes use the database. If I had the manual startup approach with explicit pool, I’d have something like this
This way I dont have to write any testing code, because pool propagation is already there. I don’t have to rewrite the code in order to make it work with a different database. Plus, I can now see which functions work with database, because I pass the pool around explicitly.
And also, at any moment, I can make this explicit pool passing approach into implicit one, like Ecto does today, by introducing one wrapper module which would take around 30 lines of code.
LostKobrakai
I do agree that that direction is more natural, but you can also do it the other way round. Wrap the repo functions with calls doing
put_dynamic_repo. You can already start an ecto repo with a different name, which includes the connection pool. I do all that in a project of mine, where I deal with catalogs provided as sqlite dbs. I agree that the apis and ectos process tree are not optimal due to legacy reasons, but ecto added all the pieces needed to deal with dynamically run repos as well.cevado
A few years ago I wrote an article on that(and I just discovered that my abandoned blog is down bc the domain expired
, i’ll send the link here if i’m able to get the old domain set or if i setup a new domain), I was way less polite and ranted a lot about the subject.
Supervision trees are not for DX, they’re for resiliency, DX is your libarary with good defaults so just a plain copy/paste of your docs resolve 90% of the needs someone have.
It feels really terrible when a library hijacks the power to define how your app starts and recovers, and supervision tree is about that, not about ease of use.
Again, needing just one instance of a supervision tree is not the issue, it’s aboult controlling how it gonna behave when it fails. If you just need one instance, make your childspec use a name scoped by your library, or allow it to optionally receive the name and default to getting the name from the application env that the user can set, the dev using it decides where it sits in their supervision tree, it doesn’t lose usability, it doesn’t hijack the control over what should bring the app down or not.
Not to the quoted people here, but people writing libraries need to remember once an application that your app depends doesn’t start it blocks the entire application of starting, I’ve had a lot of issues just because one particular library couldn’t start and it was impossible to start a new instance of the app no matter how I configured their library, I’d need to remove them of my dependencies to make it work.
For writing applications instead of libraries my rule of thumb is: can you assure there is no scenario where using your application as dependency will block the main app of starting? If you can’t assure that, create a worker and allow people to set their supervision tree appropriately to the place your library have in their systems.
Oliver
Isn’t this just to a good degree a crutch for configurability and shared state problems?
I personally am lucky enough to only use true library dependencies, not being part of the elixir web dev world but the other… 5?.. percent or whatever, so these stacks and ecosystems people typically use do not impact me.
But then I create my own application stack and run into the same problems, how to efficiently share state and configuration around if needed, and often reinventing the wheel, I guess. Part of it is that there are so many options… do I want a full-fledged DB, a key-value in the application, or with ETS, or do I want to optimize for read-only values… what are my guarantees, my performance… ease of use vs. my own requirements. I guess some of this could benefit from a unified layer allowing you to switch between options and their guarantees. Maybe somebody already did that.
If an external application added to this mess, I would not be happy. That’s why I personally am somewhat biased against dependencies that are apps, not saying they have no place, but I’d rather have control over certain aspects of my system. I do badly enough on my own writing distributed systems…