8
8
9
9
## Description
10
10
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.
12
12
13
13
That container can hold two different kinds of data:
14
14
15
15
- 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,
17
17
and may depend on other services and parameters.
18
18
19
19
The 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,
21
21
before those are actually defined.
22
22
23
23
Notice that parameters do not need to be primitive types.
24
24
For instance, most applications are likely to store a ` stdout ` object with value ` os.Stdout ` .
25
25
26
26
Unlike 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.
28
28
29
29
## Usage
30
30
@@ -37,8 +37,8 @@ explicit, process, without reflection or code generation, to keep everything in
37
37
| Store parameters in the DIC | ` dic.Store("executable", os.Args[0]) ` |
38
38
| Register services with the DIC | ` dic.Register("logger", loggerService) ` |
39
39
| 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)` |
42
42
43
43
Freezing applies once all parameters and services are stored and registered,
44
44
and 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
54
54
Services like ` loggerService ` in the previous example are instances ot the ` Service ` type,
55
55
which is defined as:
56
56
57
- ` type Service func(* Container) (any, error) `
57
+ ` type Service func(Container) (any, error) `
58
58
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
60
60
build. The only restriction is that cycles are not supported.
61
61
- Like parameters, services can be registered in any order on the container,
62
62
so feel free to order the registrations in alphabetical order for readability.
@@ -68,22 +68,20 @@ which is defined as:
68
68
69
69
### Accessing the container
70
70
71
- - General parameter access: ` s, err := dic.Param("name") `
71
+ - Parameter access: ` s, err := dic.Param("name") `
72
72
- 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") `
77
76
- 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) `
81
79
82
80
83
81
## Best practices
84
82
### Create a simpler developer experience
85
83
86
- One limitation of having ` Container.(Must)Param() ` and ` Container.(MustService ) `
84
+ One limitation of having ` Container.(Must)Param() ` and ` Container.(Must)Service( ) `
87
85
return untyped results as ` any ` is the need to type-assert results on every access.
88
86
89
87
To make this safer and better looking, a neat approach is to define an application
@@ -100,54 +98,63 @@ import (
100
98
" github.com/fgm/izidic"
101
99
)
102
100
103
- type container struct {
104
- * izidic.Container
101
+ type Container struct {
102
+ izidic.Container
105
103
}
106
104
107
105
// Logger is a typed service accessor.
108
- func (c *container ) Logger () *log .Logger {
106
+ func (c *Container ) Logger () *log .Logger {
109
107
return c.MustService (" logger" ).(*log.Logger )
110
108
}
111
109
112
110
// Name is a types parameter accessor.
113
- func (c *container ) Name () string {
111
+ func (c *Container ) Name () string {
114
112
return c.MustParam (" name" ).(string )
115
113
}
116
114
117
115
// 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 ) {
119
117
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 )
122
120
return logger, nil
123
121
}
124
122
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
129
127
appFeature := makeAppFeature (name, logger)
130
128
return appFeature, nil
131
129
}
132
130
133
- func resolve (w io .Writer , name string , args []string ) izidic .Container {
131
+ func Resolve (w io .Writer , name string , args []string ) izidic .Container {
134
132
dic := izidic.New ()
133
+ dic.Store (" name" , name)
135
134
dic.Store (" writer" , w)
136
135
dic.Register (" logger" , loggerService)
136
+ dic.Register (" app" , appService)
137
137
// others...
138
138
dic.Freeze ()
139
139
return dic
140
140
}
141
141
```
142
142
143
143
These 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
145
145
more application-domain service instances.
146
146
147
147
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
+
148
155
### Do not pass the container
149
156
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.
151
158
152
159
Because the container is a complex object with variable contents,
153
160
code receiving the container is hard to test.
@@ -160,3 +167,5 @@ Instead, in the service providing a given feature, use something like `appServic
160
167
161
168
In most cases, the value obtained thus will be a ` struct ` or a ` func ` ,
162
169
ready 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