Skip to content

Differences with Integrant

Vitalie Spinu edited this page Jul 12, 2017 · 8 revisions
  • Distinction between system and component maps

    In Integrant a system map is a flat, single level, map with prototype keys as entries:

    ;; integrant config
    {:ns/key   {:a 1}
     :ns2/key2 {:b (ig/ref :ns/key)}}

    In Commix system and component configurations follow same rules. System config maps can be freely reused within other systems.

  • Integrant confounds prototype keys with names of instantiated components

    The entry :ns/key above specifies both the prototype key (providing the behavior) and the name of the "instantiated" component (providing a way to name and reference components within the system map).

    When you have several components inheriting from same prototype keys you need to recur to composite keys.

    ;; integrant config
    {[:ns/key :com1] {:a 1}
     [:ns/key :com2] {:b (ig/ref :com1)}}

    In Commix you would write

    {:com1 (cx/com :nskey {:a 1})
     :com2 (cx/com :nskey {:b (cx/ref :com1)})}
  • Provision for grouped components

    The following is not possible in integrant:

    {:coms {:A (cx/com :ns/key)
            :B (cx/com :ns/key)}
     :com1 (cx/com :ns2/key2
             {:coms (cx/ref :coms)})}
  • Provision for nested components

    Integrant enforces flat systems. Even with small systems this often makes visual inspection and understanding dependencies hard.

    Often a component :A depends on another component :B but no other component depends on :B. In integrant you have to write

    {:ns-a/A {,,,}
     :ns-b/B {:a (ig/ref :ns-a/A)}}

    In Commix you can write it directly as a nested component:

    {:B (cx/com :ns-b/B
          {:a (cx/com :ns-a/A)})}

    Besides being a bit more parsimonious Commix syntax emphasizes dependency relationship and makes it clear that component [:B :a] cannot be accessed by other components in the system map except of :B.

  • Provision for modules or plugable configurations

    Currently there is no way in Integrant to plug in an external configuration. In Commix it is as natural as adding a component to the map.

    (def sys {:par 0
              :A (cx/com :ns/key {:a (cx/ref :par)})
              :B (cx/com :ns2/key2)})
    
    (def system
      {:sub-sys1 (cx/com sys {:par 1})
       :sub-sys2 (cx/com sys {:par 2})})
  • Distinction between return and no-return methods

    In integrant some methods are named with ! (halt-key!, suspend-key!). Bang ! is there to emphasize that the return value of a method is not used to update the system map. Arguably this is a somewhat confusing and non-idiomatic semantics.

    In Commix values returned from all methods are automatically assoced into system map allowing for idiomatic clojure pipelines which allow but don't enforce side-efectfull components.

  • Provision for side-efectufull systems

    Integrant was designed for side efectfull components only. All Integrant's methods are suposed to operate on original system map. For instance return values of halt! or suspend! never end up into the system map.

    In Commix each action returns a modified system map which can be passed to the next action in the pipeline:

    (-> config
        (init)
        (suspend [[:some :path] [:other :path]])
        (resume)
        (halt))
  • Uniform life-cycle system

    Integrant's life-cycle system doesn't quite follow a coherent pattern. For instance init takes a config and returns a system by running a build process which at some stage involves running init-key on components. suspend! and halt! take in systems and their return values are suposed to be discarded. resume! takes in two system maps!

    In Commix config is part of the life-cycle - "config" == "uninitialized system" and init is no more special than any other life-cycle action. Every life-cycle action takes in a system and returns a system. Every life-cycle action maps directly to life-cycle key method and writing custom life-cycle methods is close to trivial.

  • Supervision of the life-cycle order

    In Integrant it's up to the user to ensure that methods are either fully idempotent or what they run in correct order. For instance, it's up to you to run (halt! sys [:key1 :key2]) and then ensure that next init runs on keys [:key1 :key2] and their dependencies but no other keys.

    In Commix there is an enforced order of actions. For instance, you can run init on uninitialized or halted component but not on suspended one.

  • IDE Completion of Parameters

    Because Integrant's config are plain maps, it would be hard to design completion systems for IDEs. In Commix, as components are declared with cx/com markers, it is straightforward to add support for completion which would pick argument names from init-key method.

  • Derived keys

    By design Integrant relies on derived composite keys to create unique names within the system. In Commix this is not necessary because of the clear separation between component's names within the system and component's prototype key.

    Still, derived keywords are often useful to inherit behavior from a set of keys. Commix has no provision for inline specification of composite or derived keys. Such derivation has a global scope and doesn't belong to system's configuration. You will have to do that outside the system.

  • Life-cycle methods parameters TODO:

  • Distributed vs centralized configuration TODO:

Clone this wiki locally