惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
博客园 - 叶小钗
Blog — PlanetScale
Blog — PlanetScale
Microsoft Azure Blog
Microsoft Azure Blog
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
A
About on SuperTechFans
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
Martin Fowler
Martin Fowler
阮一峰的网络日志
阮一峰的网络日志
D
Docker
Jina AI
Jina AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Register - Security
The Register - Security
J
Java Code Geeks
S
SegmentFault 最新的问题
月光博客
月光博客
G
Google Developers Blog
美团技术团队
Last Week in AI
Last Week in AI
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
The Blog of Author Tim Ferriss
腾讯CDC
Recent Announcements
Recent Announcements
Recorded Future
Recorded Future
The Cloudflare Blog
有赞技术团队
有赞技术团队
博客园_首页
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
B
Blog
I
InfoQ
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Fortinet All Blogs
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
MongoDB | Blog
MongoDB | Blog
爱范儿
爱范儿
D
DataBreaches.Net
F
Full Disclosure
M
MIT News - Artificial intelligence
博客园 - 司徒正美
H
Help Net Security

jola.dev

Elixir Cluster 101 | jola.dev How to stop Claude from saying load-bearing | jola.dev CI workflows on Tangled for Elixir | jola.dev Automatically syncing your blog to atproto and standard.site | jola.dev Appreciation for the small web | jola.dev Treating LLMs as programming books Publishing your blog to standard.site in Elixir Generating OG images in Elixir The social contract of writing Highest Random Weight in Elixir bunnyx: a bunny.net Elixir client library Building for the joy of building Running local models on an M4 with 24GB memory How to hit your Claude weekly limit so you can go outside and touch grass Dropping Cloudflare for bunny.net Building a blog with Elixir and Phoenix Stay in the Loop: How I Actually Use Claude Code Ruthless Prioritization: The Path to Delivery Estimates Are More Valuable Than You Think When Software Engineers Think They Need More Focus Time If the Goal is Resiliency, Defensive Programming is Your Enemy The Magic of Daily Pull Requests: Why Smaller is Better Building a Distributed Rate Limiter in Elixir with HashRing Announcing Hex Diff Building Hex Diff Push-based GenStage The Erlang :queue module in Elixir Patterns for managing ETS tables Health checks for Plug and Phoenix The new `Registry.select/2` and what match specs are Elixir String Processing Optimization
Let libraries be libraries | jola.dev
https://jola.dev/about · 2026-07-07 · via jola.dev

My Elixir library pet peeve: application.ex. There are very few reasons for why you would ever need to have a library be its own application, and making it one often encourages anti-pattern library design that leads to less usable code. And yet, I still see a lot of Elixir libraries do this.

What does application.ex do

This is the file that includes the start callback for your application. For most of us, this is how we’re used to working with Elixir. We create our application with mix phx.new or mix new --sup and that’s what pops out. You get a nice little supervision tree and use it to add your GenServers and whatnot, maybe you put in a logger handler in the startup logic, some logs, maybe set up some telemetry. It’s a very intuitive place to put start up logic.

In fact, tons of your dependencies do this. Try it out for yourself. Here’s what I get when I list the running applications that have their own start callback, for this blog, locally.

iex(1)> :application.info()[:running] |> Enum.filter(fn {_app, master} -> is_pid(master) end) |> Enum.map(&elem(&1, 0))

[:jola_dev, :mimic, :image, :req, :sentry, :gettext, :telemetry_poller,

:nimble_pool, :tailwind, :esbuild, :phoenix_live_dashboard, :phoenix_live_view,

:phoenix_live_reload, :phoenix, :bandit, :phoenix_pubsub, :plug, :telemetry,

:plug_crypto, :runtime_tools, :logger, :hex, :inets, :ssl, :mix, :iex, :elixir,

:sasl, :kernel]

While running locally mix actually figures out which dependencies are applications and need to be started for you automatically. The way mix knows whether to treat something like an application is this bit in the mix.exs of the app:

def application do

[

mod: {JolaDev.Application, []},

extra_applications: [:logger, :runtime_tools]

]

end

Ok! Now we've established the difference between what I'm calling a “library” and an “application”, in the Elixir terminology (even though we tend to refer to both things as libraries when we depend on them). In the OTP world, applications that don't have a start callback are known as "library applications". I guess we're not doing ourselves any favors with the terminology.

Anti-pattern library design

The Elixir docs have an incredible section on Anti-patterns that I highly recommend reading. They cover all kinds of anti-patterns, whether they’re related to code, design, processes, or meta-programming. Even if you get nothing else out of this article, I’m happy to just be raising awareness of this great resource.

The section I’m focused on here is actually a little bit hidden away under Using application configuration for libraries. This section starts out covering the topic of using application config, like what you have in config.exs, to let users configure your library. The point it’s making is that if you make your library configurable through app config, since app config is global, each option can only have a single value set per runtime. You can’t have one function use one version and another function use another. This becomes especially bad when we’re talking about transitive dependencies. You want to use one config value for the library, but your dependency that also uses that library wants a different one. Fair enough.

But hidden below, in a sub-header, is the part that I want to rant about: “Additional remarks: Supervision trees”. It lays out the case cleanly, by treating it as a different aspect of the same problem that we just talked through. When a library defines an application.ex with its own supervision tree, then it’s no longer possible to configure that supervision tree. I can’t put it under a sub-tree of my app, I can’t control the supervision strategy. Not to mention that you can no longer run two instances of that tree, and you can’t conditionally start the library. That last one has bitten me a bunch of times.

I’m not going to list out any libraries that have fallen into the trap of running as an application, and become less configurable and more awkward to use because of it, but I’m sure you’ve run into it yourself. There’s no point in criticizing anyone for their work, especially when we’re talking about volunteered open source libraries. I’m only sharing this to help future library designers avoid this common design anti-pattern.

Examples of the better design

The docs mention Nx and DNS Cluster as examples of libraries that offer supervision trees, but let you configure and control them instead of starting them for you in the background. Another great example is Finch, the library that underpins the very popular HTTP client Req. Finch does come with a supervision tree, but in a configurable way where you can define the Finch supervision tree and how it should run. Here’s an example of what adding Finch to your app looks like:

children = [

{Finch, name: MyFinch}

]

You can add multiple Finch “instances”, each one with its own set of configuration options.

children = [

{Finch, name: MyFinch},

{Finch, name: SecondFinch},

{Finch,

name: DifferentOptionsFinch,

pools: %{

:default => [size: 10, count: 2],

"https://hex.pm" => [size: 32, count: 8]

}

}

]

Each one starts its own supervision tree and runs completely independent of the others. That gives you, the user, an incredible level of configurability.

An appeal to the community

I’m not saying a library can’t ever be an application, in fact one of my favorite libraries of all time, Req, runs as an application. It does that to be easier to use and quicker to set up, it starts up a Finch instance for you automatically, but it also supports running separate Finch trees, giving you the best of both worlds. It runs as an application without falling into the usual traps. There are reasons your library might need to start something, but it should be the exception and you can almost always solve it some other way.

So when you’re tinkering on your next library, please consider using Finch and the other examples as references and offering it as a fully configurable drop in supervision tree that the user can add to their own tree, with their own options, under their own supervisor. Let your library be a library.