Skip to content

Commit 18278ea

Browse files
authored
Adds lazy loading support to binding methods (#44)
* Adds lazy loading support to binding methods Inspired from work by @Place1. Adds new Lazy methods that can optionally be called to lazy bind a resolver to an abstract. The resolver will not be called until the first time a resolve call is made. A strong effort was made to not cause regressions or otherwise change the behavior of the existing API. Minor changes were made to how validation occurs and in some situations a more verbose error can be presented earlier in a binding process. * Improves test coverage
1 parent bef532b commit 18278ea

7 files changed

+453
-37
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Features:
1414
- Named dependencies (bindings)
1515
- Resolve by functions, variables, and structs
1616
- Must helpers that convert errors to panics
17+
- Optional lazy loading of bindings
1718
- Global instance for small applications
1819

1920
## Documentation
@@ -280,15 +281,30 @@ container.MustCall(c, func(s Shape) {
280281

281282
// Other Must Helpers:
282283
// container.MustSingleton()
284+
// container.MustSingletonLazy()
283285
// container.MustNamedSingleton()
286+
// container.MustNamedSingletonLazy()
284287
// container.MustTransient()
288+
// container.MustTransientLazy()
285289
// container.MustNamedTransient()
290+
// container.MustNamedTransientLazy()
286291
// container.MustCall()
287292
// container.MustResolve()
288293
// container.MustNamedResolve()
289294
// container.MustFill()
290295
```
291296

297+
### Lazy Binding
298+
Both the named and normal `Singleton` and `Transient` binding calls have a lazy version.
299+
Lazy versions defer calling the provided resolver function until the first time the dependency is resolved.
300+
For singletons the resolver function is called only once and the result is stored. Transient
301+
302+
Lazy binding calls include:
303+
* container.SingletonLazy()
304+
* container.NamedSingletonLazy()
305+
* container.TransientLazy()
306+
* container.NamedTransientLazy()
307+
292308
### Performance
293309
The package Container inevitably uses reflection for binding and resolving processes.
294310
If performance is a concern, try to bind and resolve the dependencies where it runs only once, like the main and init functions.

container.go

+92-33
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,84 @@ import (
99
"unsafe"
1010
)
1111

12-
// binding holds a resolver and a concrete (if singleton).
12+
// binding holds a resolver and a concrete (if already resolved).
1313
// It is the break for the Container wall!
1414
type binding struct {
15-
resolver interface{} // resolver is the function that is responsible for making the concrete.
16-
concrete interface{} // concrete is the stored instance for singleton bindings.
15+
resolver interface{} // resolver is the function that is responsible for making the concrete.
16+
concrete interface{} // concrete is the stored instance for singleton bindings.
17+
isSingleton bool // isSingleton is true if the binding is a singleton.
1718
}
1819

1920
// make resolves the binding if needed and returns the resolved concrete.
20-
func (b binding) make(c Container) (interface{}, error) {
21+
func (b *binding) make(c Container) (interface{}, error) {
2122
if b.concrete != nil {
2223
return b.concrete, nil
2324
}
24-
return c.invoke(b.resolver)
25+
26+
retVal, err := c.invoke(b.resolver)
27+
if b.isSingleton {
28+
b.concrete = retVal
29+
}
30+
31+
return retVal, err
2532
}
2633

2734
// Container holds the bindings and provides methods to interact with them.
2835
// It is the entry point in the package.
29-
type Container map[reflect.Type]map[string]binding
36+
type Container map[reflect.Type]map[string]*binding
3037

3138
// New creates a new concrete of the Container.
3239
func New() Container {
3340
return make(Container)
3441
}
3542

3643
// bind maps an abstraction to concrete and instantiates if it is a singleton binding.
37-
func (c Container) bind(resolver interface{}, name string, isSingleton bool) error {
44+
func (c Container) bind(resolver interface{}, name string, isSingleton bool, isLazy bool) error {
3845
reflectedResolver := reflect.TypeOf(resolver)
3946
if reflectedResolver.Kind() != reflect.Func {
4047
return errors.New("container: the resolver must be a function")
4148
}
4249

4350
if reflectedResolver.NumOut() > 0 {
4451
if _, exist := c[reflectedResolver.Out(0)]; !exist {
45-
c[reflectedResolver.Out(0)] = make(map[string]binding)
52+
c[reflectedResolver.Out(0)] = make(map[string]*binding)
4653
}
4754
}
4855

49-
concrete, err := c.invoke(resolver)
50-
if err != nil {
56+
if err := c.validateResolverFunction(reflectedResolver); err != nil {
5157
return err
5258
}
5359

60+
var concrete interface{}
61+
if !isLazy {
62+
var err error
63+
concrete, err = c.invoke(resolver)
64+
if err != nil {
65+
return err
66+
}
67+
}
68+
5469
if isSingleton {
55-
c[reflectedResolver.Out(0)][name] = binding{resolver: resolver, concrete: concrete}
70+
c[reflectedResolver.Out(0)][name] = &binding{resolver: resolver, concrete: concrete, isSingleton: isSingleton}
5671
} else {
57-
c[reflectedResolver.Out(0)][name] = binding{resolver: resolver}
72+
c[reflectedResolver.Out(0)][name] = &binding{resolver: resolver, isSingleton: isSingleton}
73+
}
74+
75+
return nil
76+
}
77+
78+
func (c Container) validateResolverFunction(funcType reflect.Type) error {
79+
retCount := funcType.NumOut()
80+
81+
if retCount == 0 || retCount > 2 {
82+
return errors.New("container: resolver function signature is invalid - it must return abstract, or abstract and error")
83+
}
84+
85+
resolveType := funcType.Out(0)
86+
for i := 0; i < funcType.NumIn(); i++ {
87+
if funcType.In(i) == resolveType {
88+
return fmt.Errorf("container: resolver function signature is invalid - depends on abstract it returns")
89+
}
5890
}
5991

6092
return nil
@@ -69,17 +101,12 @@ func (c Container) invoke(function interface{}) (interface{}, error) {
69101
}
70102

71103
values := reflect.ValueOf(function).Call(arguments)
72-
73-
if len(values) == 1 || len(values) == 2 {
74-
if len(values) == 2 && values[1].CanInterface() {
75-
if err, ok := values[1].Interface().(error); ok {
76-
return values[0].Interface(), err
77-
}
104+
if len(values) == 2 && values[1].CanInterface() {
105+
if err, ok := values[1].Interface().(error); ok {
106+
return values[0].Interface(), err
78107
}
79-
return values[0].Interface(), nil
80108
}
81-
82-
return nil, errors.New("container: resolver function signature is invalid")
109+
return values[0].Interface(), nil
83110
}
84111

85112
// arguments returns the list of resolved arguments for a function.
@@ -91,10 +118,13 @@ func (c Container) arguments(function interface{}) ([]reflect.Value, error) {
91118
for i := 0; i < argumentsCount; i++ {
92119
abstraction := reflectedFunction.In(i)
93120
if concrete, exist := c[abstraction][""]; exist {
94-
instance, _ := concrete.make(c)
121+
instance, err := concrete.make(c)
122+
if err != nil {
123+
return nil, err
124+
}
95125
arguments[i] = reflect.ValueOf(instance)
96126
} else {
97-
return nil, errors.New("container: no concrete found for " + abstraction.String())
127+
return nil, errors.New("container: no concrete found for: " + abstraction.String())
98128
}
99129
}
100130

@@ -112,24 +142,52 @@ func (c Container) Reset() {
112142
// It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface).
113143
// The resolver function can have arguments of abstraction that have been declared in the Container already.
114144
func (c Container) Singleton(resolver interface{}) error {
115-
return c.bind(resolver, "", true)
145+
return c.bind(resolver, "", true, false)
146+
}
147+
148+
// SingletonLazy binds an abstraction to concrete lazily in singleton mode.
149+
// The concrete is resolved only when the abstraction is resolved for the first time.
150+
// It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface).
151+
// The resolver function can have arguments of abstraction that have been declared in the Container already.
152+
func (c Container) SingletonLazy(resolver interface{}) error {
153+
return c.bind(resolver, "", true, true)
116154
}
117155

118156
// NamedSingleton binds a named abstraction to concrete in singleton mode.
119157
func (c Container) NamedSingleton(name string, resolver interface{}) error {
120-
return c.bind(resolver, name, true)
158+
return c.bind(resolver, name, true, false)
159+
}
160+
161+
// NamedSingleton binds a named abstraction to concrete lazily in singleton mode.
162+
// The concrete is resolved only when the abstraction is resolved for the first time.
163+
func (c Container) NamedSingletonLazy(name string, resolver interface{}) error {
164+
return c.bind(resolver, name, true, true)
121165
}
122166

123167
// Transient binds an abstraction to concrete in transient mode.
124168
// It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface).
125169
// The resolver function can have arguments of abstraction that have been declared in the Container already.
126170
func (c Container) Transient(resolver interface{}) error {
127-
return c.bind(resolver, "", false)
171+
return c.bind(resolver, "", false, false)
128172
}
129173

130-
// NamedTransient binds a named abstraction to concrete in transient mode.
174+
// TransientLazy binds an abstraction to concrete lazily in transient mode.
175+
// Normally the resolver will be called during registration, but that is skipped in lazy mode.
176+
// It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface).
177+
// The resolver function can have arguments of abstraction that have been declared in the Container already.
178+
func (c Container) TransientLazy(resolver interface{}) error {
179+
return c.bind(resolver, "", false, true)
180+
}
181+
182+
// NamedTransient binds a named abstraction to concrete lazily in transient mode.
131183
func (c Container) NamedTransient(name string, resolver interface{}) error {
132-
return c.bind(resolver, name, false)
184+
return c.bind(resolver, name, false, false)
185+
}
186+
187+
// NamedTransient binds a named abstraction to concrete in transient mode.
188+
// Normally the resolver will be called during registration, but that is skipped in lazy mode.
189+
func (c Container) NamedTransientLazy(name string, resolver interface{}) error {
190+
return c.bind(resolver, name, false, true)
133191
}
134192

135193
// Call takes a receiver function with one or more arguments of the abstractions (interfaces).
@@ -214,21 +272,22 @@ func (c Container) Fill(structure interface{}) error {
214272
} else if t == "name" {
215273
name = s.Type().Field(i).Name
216274
} else {
217-
return errors.New(
218-
fmt.Sprintf("container: %v has an invalid struct tag", s.Type().Field(i).Name),
219-
)
275+
return fmt.Errorf("container: %v has an invalid struct tag", s.Type().Field(i).Name)
220276
}
221277

222278
if concrete, exist := c[f.Type()][name]; exist {
223-
instance, _ := concrete.make(c)
279+
instance, err := concrete.make(c)
280+
if err != nil {
281+
return err
282+
}
224283

225284
ptr := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
226285
ptr.Set(reflect.ValueOf(instance))
227286

228287
continue
229288
}
230289

231-
return errors.New(fmt.Sprintf("container: cannot make %v field", s.Type().Field(i).Name))
290+
return fmt.Errorf("container: cannot make %v field", s.Type().Field(i).Name)
232291
}
233292
}
234293

0 commit comments

Comments
 (0)