Skip to content

v0.8.0

Choose a tag to compare

@floatdrop floatdrop released this 06 Sep 04:06
· 23 commits to main since this release
2dc9c50

v0.8.0

Two features and one rule. Explain and Graph render the dependency graph as it
was actually built. Modules compose by Use, with every registration attributed
to the module that made it. And the rule that makes modules safe to compose: a
second registration of a key within one scope must say so with Override(), or
it is rejected naming both sites. That last one is breaking, and the reason for
the minor bump.

go doc -all against v0.7.0 adds Binding.Override, Module, Scope.Use,
Scope.Explain and Scope.Graph, and changes one signature: Test takes
...Module.

Behaviour that can break an upgrade:

  • A duplicate registration of a key within one scope no longer wins silently.
    It is rejected at the next resolution, naming both registrations and, when
    they came from modules, both modules:

    di: *app.DB is provided at app.Storage (wire.go:12) and again at
    app.Caching (cache.go:8): a second registration of a key must be
    marked Override() to replace the first

    Mark the registration that means to replace the earlier one: for the test
    seam, s.Value(&fake).Override(). An Override() with nothing to override in
    its scope is rejected too, since a fake for a service that has since been
    renamed would otherwise be a registration nobody resolves. A child scope
    still shadows its parent without any marker. A key that has served a value
    still cannot be replaced at all.

  • Test takes ...Module. Function literals and named functions are assignable,
    so those calls compile unchanged; a call that spreads a []func(*Scope) with
    ... needs the slice typed []Module.

Added:

  • Scope.Explain[T] prints the dependency tree of a service as it was built,
    with each node's lifetime, scope, lifecycle state and registration site, and
    the instances that needed it. Scope.Graph exports everything built as
    Graphviz DOT, one cluster per scope. Neither builds anything. The recording
    behind them costs nothing on the warm path.

  • Module is a named func(*Scope); Scope.Use applies modules in order and
    attributes every registration they make -- directly, from a child the module
    opens, or later from a constructor the module registered -- to the module's
    function name. Event.Module carries the same name to observers.

Fixed:

  • A panicking OnDrain or OnStop hook is reported as that hook's failure rather
    than propagating out of Stop. The start step was always recovered this way;
    the other two were not, so a panic in either abandoned the teardown halfway:
    stopOnce claimed and never settled, every later Stop waiting on it until its
    context ran out, every instance behind it never released. Found by the
    concurrent fuzzer the moment it could build a scope with a permanently
    rejected registration -- a drain hook resolving through one meets the
    rejection as a panic.

Internal: the machinery the 0.7.0 API cuts left behind is gone. A resolution
path node is 32 bytes rather than 48, so a warm resolve allocates 64 B rather
than 96 B and runs about 8% faster.

Testing: the generators register through Use and carry an override bit, the
property model gained the override rule and was mutation-tested clause by
clause, and generator coverage went from 88.5% to 92.1% with the CI floor
raised to 90.