88
99## Description
1010
11- Package izidic defines a tiny dependency injection container for Go projects.
11+ Package [ izidic] ( https://github.com/fgm/izidic ) defines a tiny dependency injection container for Go projects.
1212
1313That container can hold two different kinds of data:
1414
1515- parameters, which are mutable data without any dependency;
16- - services, which are functions providing a typed object providing a feature,
16+ - services, which are functions returning a typed object providing a feature,
1717 and may depend on other services and parameters.
1818
1919The basic feature is that storing service definitions does not create instances,
20- allowing users to store definitions of services requiring other services
20+ allowing users to store definitions of services requiring other services,
2121before those are actually defined.
2222
2323Notice that parameters do not need to be primitive types.
2424For instance, most applications are likely to store a ` stdout ` object with value ` os.Stdout ` .
2525
2626Unlike heavyweights like google/wire or uber/zap, it works as a single step,
27- explicit, process, without reflection or code generation, to keep everything in sight.
27+ explicit process, without reflection or code generation, to keep everything in sight.
2828
2929## Usage
3030
@@ -37,8 +37,8 @@ explicit, process, without reflection or code generation, to keep everything in
3737| Store parameters in the DIC | ` dic.Store("executable", os.Args[0]) ` |
3838| Register services with the DIC | ` dic.Register("logger", loggerService) ` |
3939| Freeze the container | ` dic.Freeze() ` |
40- | Read a parameter from the DIC | ` dic.Param(name) ` |
41- | Get a service instance from the DIC | ` dic.Service(name) ` |
40+ | Read a parameter from the DIC | ` p, err := dic.Param(name)` |
41+ | Get a service instance from the DIC | ` s, err := dic.Service(name)` |
4242
4343Freezing applies once all parameters and services are stored and registered,
4444and enables concurrent access to the container.
@@ -54,9 +54,9 @@ Parameters can be any value type. They can be stored in the container in any ord
5454Services like ` loggerService ` in the previous example are instances ot the ` Service ` type,
5555which is defined as:
5656
57- ` type Service func(* Container) (any, error) `
57+ ` type Service func(Container) (any, error) `
5858
59- - Services can use any other service and parameters to return the instance they
59+ - Services can reference any other service and parameters from the container, to return the instance they
6060 build. The only restriction is that cycles are not supported.
6161- Like parameters, services can be registered in any order on the container,
6262 so feel free to order the registrations in alphabetical order for readability.
@@ -68,22 +68,20 @@ which is defined as:
6868
6969### Accessing the container
7070
71- - General parameter access: ` s, err := dic.Param("name") `
71+ - Parameter access: ` s, err := dic.Param("name") `
7272 - Check the error against ` nil `
73- - Type-assert the parameter value: ` name := s.(string) `
74- - The type assertion cannot fail if the error was ` nil `
75- - Simplified parameter access: ` name := dic.MustParam("name").(string) `
76- - General service access: ` s, err := dic.Service("logger") `
73+ - Type-assert the parameter value: ` name, ok := s.(string) `
74+ - Or use shortcut: ` name := dic.MustParam("name").(string) `
75+ - Service access: ` s, err := dic.Service("logger") `
7776 - Check the error against ` nil `
78- - Type-assert the service instance value: ` logger := s.(*log.Logger) `
79- - The type assertion cannot fail if the error was ` nil `
80- - Simplified service access: ` logger := dic.MustService("logger").(*log.Logger) `
77+ - Type-assert the service instance value: ` logger, ok := s.(*log.Logger) `
78+ - Or use shortcut: ` logger := dic.MustService("logger").(*log.Logger) `
8179
8280
8381## Best practices
8482### Create a simpler developer experience
8583
86- One limitation of having ` Container.(Must)Param() ` and ` Container.(MustService ) `
84+ One limitation of having ` Container.(Must)Param() ` and ` Container.(Must)Service( ) `
8785return untyped results as ` any ` is the need to type-assert results on every access.
8886
8987To make this safer and better looking, a neat approach is to define an application
@@ -100,54 +98,63 @@ import (
10098 " github.com/fgm/izidic"
10199)
102100
103- type container struct {
104- * izidic.Container
101+ type Container struct {
102+ izidic.Container
105103}
106104
107105// Logger is a typed service accessor.
108- func (c *container ) Logger () *log .Logger {
106+ func (c *Container ) Logger () *log .Logger {
109107 return c.MustService (" logger" ).(*log.Logger )
110108}
111109
112110// Name is a types parameter accessor.
113- func (c *container ) Name () string {
111+ func (c *Container ) Name () string {
114112 return c.MustParam (" name" ).(string )
115113}
116114
117115// loggerService is an izidic.Service also containing a one-time initialization action.
118- func loggerService (dic * izidic .Container ) (any , error ) {
116+ func loggerService (dic izidic .Container ) (any , error ) {
119117 w := dic.MustParam (" writer" ).(io.Writer )
120- log.SetOutput (w) // Support dependency code not taking an injected logger.
121- logger := log.New (w, " " , log.LstdFlags )
118+ log.SetOutput (w) // Support dependency code not taking an injected logger.
119+ logger := log.New (w, " " , log.LstdFlags )
122120 return logger, nil
123121}
124122
125- func appService (dic * izidic .Container ) (any , error ) {
126- wdic := container {dic} // wrapped container with typed accessors
127- logger := dic .Logger () // typed service instance
128- name := dic .Name () // typed parameter value
123+ func appService (dic izidic .Container ) (any , error ) {
124+ wdic := Container {dic} // wrapped container with typed accessors
125+ logger := wdic .Logger () // typed service instance
126+ name := wdic .Name () // typed parameter value
129127 appFeature := makeAppFeature (name, logger)
130128 return appFeature, nil
131129}
132130
133- func resolve (w io .Writer , name string , args []string ) izidic .Container {
131+ func Resolve (w io .Writer , name string , args []string ) izidic .Container {
134132 dic := izidic.New ()
133+ dic.Store (" name" , name)
135134 dic.Store (" writer" , w)
136135 dic.Register (" logger" , loggerService)
136+ dic.Register (" app" , appService)
137137 // others...
138138 dic.Freeze ()
139139 return dic
140140}
141141```
142142
143143These accessors will be useful when defining services, as in ` appService ` above,
144- or in the boot sequence, which typically neeeds at least a ` logger ` and one or
144+ or in the boot sequence, which typically needs at least a ` logger ` and one or
145145more application-domain service instances.
146146
147147
148+ ### Create the container in a ` Resolve ` function
149+
150+ The cleanest way to initialize a container is to have the
151+ project contain an function, conventionally called ` Resolve ` , which takes all globals used in the project,
152+ and returns an instance of the custom container type defined above, as in [ examples/di/di.go] ( examples/di/di.go ) .
153+
154+
148155### Do not pass the container
149156
150- Passing the container, although it works, defines the "service locator" anti-pattern.
157+ Passing the container to application code , although it works, defines the "service locator" anti-pattern.
151158
152159Because the container is a complex object with variable contents,
153160code receiving the container is hard to test.
@@ -160,3 +167,5 @@ Instead, in the service providing a given feature, use something like `appServic
160167
161168In most cases, the value obtained thus will be a ` struct ` or a ` func ` ,
162169ready to be used without further data from the container.
170+
171+ See a complete demo in [ examples/demo.go] ( examples/demo.go ) .
0 commit comments