Dendritic Pattern

repository·master·Indexed 20 days ago

https://github.com/mightyiam/dendritic

A Nixpkgs module system usage pattern for Nix configurations (NixOS, home-manager, nix-darwin). It treats every Nix file as a module of a single top-level configuration, utilizing the deferredModule type for lower-level module storage and value merging to avoid specialArgs pass-thru and enable option anti-patterns.

Tokens
839
Snippets
1
Records
4
Agent score
19%

What's inside dendritic

  1. What is the Dendritic Pattern?

    master

    The Dendritic Pattern is a Nixpkgs module system usage pattern designed to provide a practical and expressive architecture for Nix configurations (NixOS, home-manager, nix-darwin, etc.).

    In this pattern, every Nix file (except for entry points like default.nix or flake.nix) is treated as a module of a single top-level configuration. This top-level configuration facilitates the declaration and evaluation of lower-level configurations.

    Key characteristics:

    • Single Feature Focus: Each top-level module implements a single feature across all configurations it applies to.
    • Path-based Naming: The file path serves to name the feature.
    • Module Storage: Lower-level modules (NixOS, home-manager, etc.) are stored as option values within the top-level configuration.
    • Value Merging: Uses the deferredModule type to allow multiple lower-level module values to merge under a single name.
  2. Avoid the `specialArgs` pass-thru anti-pattern

    master

    In non-dendritic patterns, developers often use specialArgs (in lib.evalModules) or extraSpecialArgs (in home-manager) to pass values from a higher-level evaluation into a nested lower-level evaluation. This creates complex dependencies.

    In the Dendritic Pattern, you avoid this by making every file a top-level module. Because every file is part of the same top-level configuration:

    1. Any file can add values to the top-level config.
    2. Any file can read from the top-level config.

    This makes sharing values between files trivial and removes the need for manual argument injection through nested layers.

  3. Avoid the `enable` option anti-pattern

    master

    In NixOS, many modules from Nixpkgs are imported by default and require an enable option (created via lib.mkEnableOption) to actually turn the feature on.

    In the Dendritic Pattern, this is considered an anti-pattern. Instead, importing a module should enable the feature it provides. This simplifies the configuration logic and reduces the need for repetitive boolean checks.

  4. How to implement lower-level module storage using `deferredModule`

    master

    To follow the dendritic pattern, you should not rely solely on existing options (like flake.parts' flake.modules) to store your lower-level modules. Instead, declare your own options using lib.mkOption with the lib.types.deferredModule type. This allows you to model your infrastructure explicitly.

    1. Declare the option

    In a base module, define the option that will hold your lower-level modules:

    # modules/nixos/base.nix
    { lib, ... }: {
      options.nixos.base = lib.mkOption {
        type = lib.types.deferredModule;
      };
    }

    2. Use the option

    In another module, you can then assign a module to that option or merge it with others:

    # modules/nixos/pc.nix
    { config, lib, ... }: {
      options.nixos.pc = lib.mkOption {
        type = lib.types.deferredModule;
      };
      config.nixos.pc = config.nixos.base;
    }
    # modules/nixos/base.nix
    {lib, ...}: {
      options.nixos.base = lib.mkOption {
        type = lib.types.deferredModule;
      };
    }
    
    # modules/nixos/pc.nix
    {config, lib, ...}: {
      options.nixos.pc = lib.mkOption {
        type = lib.types.deferredModule;
      };
      config.nixos.pc = config.nixos.base;
    }