The dendritic pattern (in response to comments in ‘How to define the default package for a NixOS module in a Nix flake?’)

Ouch. Frustrating to see my friends bashing my dendritic pattern.

Just to help everyone be on the same page, the dendritic pattern has been decoupled from flakes and flake-parts. Read the latest definition of the pattern in its homepage, just make sure we’re having an informed conversation.

Both of these takes “not really a thing” and “basically of no value” from prominent and skilled contributors are irresponsible. Do some research. See how many users have written blog posts and documentation about the dendritic pattern because they’ve found it useful. Sure; I know that popularity is not a proof that a pattern is actually more valuable than another pattern. But, hey, if you’re going to recommend against a pattern, why don’t you suggest a different one? Go ahead; write a blog post about how silly dendritic is and how much better we can do, instead. I’d be the first to read it.

3 Likes

I’m sorry, but having read the repository my opinion does not change. I understood the pattern from the examples I’d seen before, just extracting the nested module eval from flake-parts does not change that I consider it unnecessary. This can be done by extracting relevant config attributes. The existence of this framework just invites new users to overcomplicate their configurations, I see this daily.

But this is very unrelated to the question at hand.

11 Likes

Dawn, I apologize for what it’s worth, but these many threads and blog posts about the pattern are precisely the reason I allow myself this rather hostile language: I suspect them to be a part of our documentation problem and spreading confusion. Say, are drv-parts, finix, or NixOS “dendritic”?

I appreciate you.

drv-parts (dream2nix), finix and NixOS most likely do not comply with the dendritic pattern, intentionally or otherwise, because in the dendritic pattern there is a single top-level configuration and one or more lower-level configurations.

Why?

1 Like

I think it would be cool to see a Rosetta Stone like repo that could depict the config diff between this and dendritic - with equivalent outputs, providing contrast for the uninitiated.

Perhaps something realistic and representative of a modest config with multiple hosts and user variants?

For what it’s worth, as a developer, I’ve found the import via namespace pattern in dendritic more manageable for separation of concerns and familiar from a PLT standpoint than what a initially started with before ever adopting flakes (for other reasons).

7 Likes

I can kinda see it if you really know what you’re doing, and actually have that worst-case-complexity scenario where you want to maintain three different module systems and insist on grouping “shared” configuration between the three in single files.

In that case it does let you replace the config extraction boilerplate with slightly nicer boilerplate and additional eval overhead, which is arguably worthwhile. It’s an extreme edge case, but maybe that’s really what you have.


I’ll probably eventually sit down and write a more extensive piece on this, but I think it’s more productive to first focus on writing appropriately hand-holdy guides on NixOS configuration without the complexity of frameworks.

I’d focus that on the typical single-user, multiple machine use case, which I am very confident does not benefit from the framework at all, since the home-manager NixOS module already does the same thing using the NixOS module system.

IME because the only viable docs currently focus on employing flakes, this dendritic framework, or other “prebuilt” configs like snowfall, complete newbies end up approaching NixOS configuration with an entirely wrong mindset and completely overengineer everything, without understanding what they’re doing.

And I do believe even more experienced users end up employing this framework before they fully grok the module system, and then believe that it is responsible for an architectural improvement it’s not remotely necessary for.


This is also coming from a developer’s point of view, with at this point close to a decade of nix experience, mostly focused on NixOS. I appreciate that my opinion is influenced by that; some things that are second nature to me are probably easily overlooked. I agree with @SergeK that most frameworks are just substitutes for our poor documentation story, written by people who stumble upon a complicated solution to problems they didn’t realize could be solved differently because it isn’t documented anywhere.

6 Likes

I have some ideas about dendritic patterns, but they may have nothing to do with the views of SergeK and TLATER. I’m not good at English, so I’m using a translator, with no intention to offend. I’m a complete beginner in nix, and most of these ideas are based on intuition, so corrections to any mistakes are welcome.

I think the problem with the dendritic pattern is that we used too many custom functions that are hard to modify.The dependencies of modules, the parameters passed to modules, and so on, these functions are all implemented by nix functions written by the dendritic pattern framework itself. This feels more like the style of flake-utils rather than nixpkgs/flake-parts. It completely ignores the options and type features of the module system, and this approach makes it impossible for users to modify the behavior of the framework. If you want an extra feature, you either have to modify the framework code or go through 200% of the effort to migrate to another framework.

When I try to implement a dendritic pattern outside of flakes, I often have the illusion that I am still doing something similar to inputs.self.*, but through config. It doesn’t feel like writing nix in a NixOS-like module system. I cannot simply affect another module through config. I had to create several specialized module variants.

If I have an impermanence module, then it would be difficult for me to directly use it in other modules in this way:

config = {
  out.imports = [ ./nixos-iwd.nix ];
  hardware.impermanence = {
    dirs = [ "/var/lib/iwd" ];
  };
};

I often have to create multiple variant modules like iwd-impermanence, or handle it manually.

Wow, this is exactly what I wanted to say!

I recently did something similar in my configuration by using specialized evalModules and a function similar to extendModules.

EDIT:
(Some probably unrelated nonsense)
Perhaps the original discussion was not completely unrelated to the dendritic pattern, and there is a reason for mentioning it. I remember Vic said, ‘The dendritic pattern is for the convenience of sharing configurations.’ So this is still a DI problem. The problem with nix is that it is really free. No one can guess where the entry point of a project is before reading the documentation.

This is what nixers has been doing all along, but it is not user-friendly at all! Flakes might be a solution. But in my opinion, flakes are a ‘compromise’ solution. It builds an ordered ‘virtual file system’ in the nix data structure, but essentially it is still a compromise with the unordered reality of the project’s actual file structure.

I guess flakes were intentionally designed to be simple, because we always need to check what a flake actually provides through nix flake show.

The dendritic pattern was originally built on top of the flakes foundation, right? But after studying the nixpkgs module system, I feel that the dendritic pattern might have made a flawed system abstraction: it treats the module system as an untyped attribute set framework, thereby losing both the declarative dependency handling and AOP advantages of the nixpkgs module system, while also failing to achieve the simple evaluability of flakes. It is an intermediary layer that is, in the worst sense, ‘unsupported on both ends.’

BTW, I have always felt a ghost of Guix wandering around these discussions. Guix is not like this. Scheme has a clear module system that relies on the existence of real file structures. Therefore, they never need to discuss the issue of entry points.

My opinion is that it’s just nicer to have a modules/niri/default.nix than a pkgs/patched-niri/package.nix(? unsure the recommended way to put a pkgs/ dir in your personal config) for building niri with a Smithay patch to work around a Firefox issue, three modules/{nixos,system-manager,home-manager}/niri.nix files for actual install and setup, and putting the niri-flake input directly in flake.nix. Now your niri stuff is split across 5 different files, not very good for Locality of Behaviour! (not an HTMX fan, but I do like the principle) I do think frameworks like den are overly complicated, but I think the core idea of keeping different module systems in the same place is good.

I, like nixpkgs, see little benefit in combining NixOS modules with a package in the same file.

I use lib.packagesFromDirectoryRecursive in an overlay. But I’m moving everything over to my nixpkgs fork anyway.

Still, if you’re just patching an existing package, you can just set the .package option, and you don’t need a separate file for that, even without this dendritic nonsense.

2 Likes

But even if you do, you can easily achieve that without sticking another module system on top of it all, even without the .package option, or overrides, with the exact same ergonomics, by using the module system you’re already in.

I’m almost certain this is just a documentation issue + maybe a bit of lack of creativity in interpreting the documentation that does exist. I think a non-code “framework” that just tells you how to do this would be plenty.

1 Like

Maybe if it was just a NixOS module, but I have 3 module systems.

Maybe if i only had to set it in one place, but again I have 3 module systems I need. (maybe I could get away with only 2)

Well, again, I don’t have a module system I’m already in, except I do, and it’s called flake-parts. And I happen to LIKE the module system.

I think the notable thing here might be adding nix-darwin to the mix, but that is the only scenario in which I would consider this sane, and I’m still fairly certain it could be done without the additional eval overhead. This also sets aside the sanity of mixing NixOS and nix-darwin modules; once you only have either and not both of those, you can use only the home-manager integration to make the dendritic concept pointless.

Even if I accept that aspect, I stand by my point that the majority of users should not touch this kind of setup. You’re free to overengineer as much as you want, but the marketing and hype around this are harmful to the new user story.

(edited to be less passive-agressive. sorry!)

, not nix-darwin, is the third non-NixOS/HM module system. I use it to get my niri session files symlinked into the right location on my Pop!_OS laptop, among other things. Ideally it would have 100% NixOS module compatibility so I could just use my NixOS modules, but ¯\_(ツ)_/¯.

IMHO the dendritic pattern is no more harmful to the new user story than, say, React/NextJS/Svelte/SvelteKit are harmful to the new web developer story. Just like in that case, I do promote understanding the base technologies, but also using frameworks if you find them more comfortable.

I also use multiple module systems (NixOS, nix-darwin, and hjem as a submodule within the prior two) :slight_smile: I’ve no need to add another one to wrap around the others - the module system is well-known to be inefficient and lacking in some features, and besides, flake-parts in this scenario adds more complexity than it takes away.

I’m not asking you to defend your choice to use flake-parts, nor am I asking you to stop using it. What I am saying is it’s completely unnecessary to recommend to new users; you can group the package with the module(s) (on the filesystem), and some modules can be written as a “common” module for NixOS, nix-darwin, and system-manager if you’re concerned about DRY.

The feeling that “flake-parts makes things organised” is usually due to not having any organisational system beforehand, and f-p being the only one they encountered.

That’s just wrong lmao, people copypaste in every ecosystem and nix’s is no exception. But the module system is infamous for being confusing to new users and having multiple levels of it is harming their understanding of what “level” of config they’re setting (we already see this with NixOS and home-manager) and increases their reliance on external tools.

PS: passive aggressive whining is toxic, stop.

2 Likes

I guess that’s true. Maybe my opinions are biased by the fact that I never found the module system confusing (maybe the implementation of it, but not the idea of declaring options and merging them, and being able to use functions to control the merge behavior, as well as having multiple systems of them). I’ll see myself out of this conversation as I already laid out my 2 cents. Sorry for the “passive aggressive whining”, usually I spend more time thinking about what I wrote before hitting reply than I did there. :heart:

2 Likes

Hello! I’m working on something that is too early to share quite yet, but wanted to throw my hat into the ring as a fairly early adopter of the dendritic pattern. I think the model is great, and somewhat mirrors what other frameworks like snow-fall lib attempted to do – just differently. Config guarding everything felt really icky and I had modules that I wanted available that had upstream includes… but there was no way to config guard an include.

Dendritic and flake-parts solved this for me initially, but I didn’t personally like the flat ‘workstation’ aspect, I wanted finite granular feature modules for re-use and composite role modules. Flake-parts alone doesn’t solve the diamond import pattern problem, so I wound up building my own resolution/de-duplication methods. My personal pattern is still ‘denritic’ in nature, though.

I think there’s a conflation of the pattern as a conceptual idea and the implementation patterns of it in practice, which is where new users are easily confused. This is a design opportunity space, as well as an education/documentation opportunity.

4 Likes

Over the course of the last two years I transitioned from being a beginner nix
user to an intermediate one.

From that perspective (which is valuable - ignoring it is exactly how we lose
the “new user” story we claim to protect), it feels like this discussion is
reaching a familiar “event horizon” in architectural discussions, where
technical merits get mixed up with the social psychology of the community
behind those technicalities

The dendritic pattern seems to offer a genuine solution for “fleet-level”
management, whilst raising concerns about eval time and being complicated for
new users.

Beyond those points, though, I feel like this discussion is tinged by a factor
of pride and ego.

Those latter two often haunt open-source frameworks. It’s the “Grass is
Greener” syndrome: early adopters justify their switch with enthusiastic
devotion, whilst those that are masters of the common standard perceive
(whether consciously or unconsciously) a threat to their expertise.

There’s real difficulty in validating an alternative path, don’t get me wrong:
acknowledging a new, “complex” pattern as better feels like taking on an
“invisible tax.” It’s much easier to attack a pattern as “over-engineered” than
to admit, “This is a cool evolution, but I don’t have the time or the desire to
support that level of complexity.” It’s a shared friction: the maintainer’s
problem, but also the innovator’s responsibility."

Maybe the way forward is not to decide which road to pick, but to merge them.
Yes the dendritic pattern is useful for power users. Yes, it shouldn’t be
marketed as the “standard” or “correct” way.

The grass might genuinely be greener on the other side for… someone. But that
doesn’t mean the “old lawn” is dead - it just serves a different purpose. Can
we accept that this pattern is “sane over-engineering” for those who need it,
without it being a threat to the simplicity Nix needs to survive?

2 Likes

I didn’t try to convince advanced users on what they should do in their own configs, the topic is about advice for new users.

2 Likes

Reminder: you said “passive aggressive whining is toxic, stop.” Let’s not take this to places that it doesn’t have to go, please.

Now, I apologize, but I’ll unfortunately have to ask you to repeat yourself once again: what part of my reply missed any of the previous points that you had talked about? I ask this because I want to learn where I made a mistake, where I was near-sighted. What caused you the need to repeat yourself?

In addition, what part of my reply is suspicious? How can I be of service to clear that up for you?

unsure the recommended way to put a pkgs/ dir in your personal config

you could try generating a overlay with auto override for preexisting pkgs, auto callPackage for your custom ones, and auto scope creation/override similar to packagesFromDirectoryRecursive

and consume the overlay

2 Likes