samber/do dependency injection
Work from the module path selected by the project. github.com/samber/do and github.com/samber/do/v2 are different major-version contracts; inspect go.mod, imports, wrappers, and generated wiring before choosing API names.
Map the graph
Locate:
- the root injector and any
Scopecalls; Provide*, package-based registrations, names, aliases, and overrides;- the
Invoke*roots that force construction; - constructors that open files, sockets, clients, workers, or timers;
- health checks, signal handling, shutdown, and test clones.
Keep injector access at composition boundaries. Providers can accept the injector as required by the library, while constructed domain services should normally receive typed dependencies.
Select registration and lookup APIs
Verify these families against the selected major version:
| Need | Representative API |
|---|---|
| Lazy singleton | Provide, ProvideNamed |
| Existing value | ProvideValue, ProvideNamedValue |
| New value per lookup | ProvideTransient, named transient variant |
| Grouped v2 registrations | Package with Lazy, Eager, Transient, or Value specs |
| Typed lookup | Invoke, InvokeNamed |
| Interface lookup or alias | InvokeAs, explicit As APIs |
| Tagged struct population | InvokeStruct |
Preserve lifetime, name, and scope when changing a registration. Interface lookup can become ambiguous when several concrete services satisfy the same interface; use the project's named or explicit aliasing convention in that case.
Use error-returning Invoke* calls where the caller handles construction failure. MustInvoke* panics on failure. Some versions convert a provider-side MustInvoke* panic back into the enclosing Invoke error; confirm that behavior in the exact dependency before relying on it.
Scopes, overrides, and packages
- A child scope can resolve parent services, but sibling visibility and ownership follow the selected implementation.
CloneplusOverride*can support isolated test graphs when available; check whether already-instantiated services are copied, shared, or rebuilt.- v2
Packagevalues make registration groups reusable. Keep package registration free of unrelated runtime work and preserve ordering when eager providers depend on it. - Treat injector explanations and service listings as diagnostics, not proof that every provider constructs successfully.
Lifecycle
The library recognizes health and shutdown contracts in multiple forms across versions, including context-aware and error-returning methods. Inspect the exact interfaces before implementing them.
Establish which injector owns each resource, when it becomes instantiated, and whether shutdown follows dependency, invocation, or registration order. Bound shutdown with the application's context policy. Signal-aware helpers belong at the executable boundary so the graph has one shutdown owner.
Exercise health checks only after the required service is constructed, and keep them bounded. A health method should report readiness or liveness without silently changing lifecycle state.
Test the real composition root
Create a fresh injector or verified clone per test, override external boundaries, and invoke the same root service used by the executable. Cover missing and duplicate bindings, constructor errors, named/interface resolution, scope isolation, and cleanup for resources touched by the change.
Compile and run the affected application root as well as unit tests. An uninvoked lazy provider can compile while still containing a broken graph.