@J7mbo
The Journey To
Automatic Dependency Injection*
James Mallison *In Go
Slide 2
Slide 2 text
@J7mbo
Slide 3
Slide 3 text
@J7mbo
Slide 4
Slide 4 text
@J7mbo
Not crashed yet
Slide 5
Slide 5 text
@J7mbo
• How do I communicate over TCP?
• Http? - Learned how to use Mux
• Rpc? - Learned how to use Grpc / Protobuf
• How do I fully automate dependency injection
for rapid application development?
Slide 6
Slide 6 text
@J7mbo
• My journey in attempting to achieve this in a new language
• Implementing runtime dependency injection atrociously
• A live demo of the above
• Moving on to compile-time dependency injection
• Discussion of the pros and cons
• Problems I encountered and solved
• What I learned, and what I’d love to see next
Automatic Dependency Injection
Slide 7
Slide 7 text
@J7mbo
Standard Dependency Injection
Initialise DB dependency
Inject into Repository initialiser
Slide 8
Slide 8 text
@J7mbo
Automatic Runtime Dependency Injection
Ideally with ZERO configuration
• Tell some library object to build our Repository
• It uses reflection to see dependencies
• It finds DB dependency
• It uses reflection on DB dependency
• Initialise DB dependency
• Initialise Repository with DB dependency injected
Slide 9
Slide 9 text
@J7mbo
Using Reflection
Function takes interface{} and returns
interface{}, so it can work with any object
reflect package allows us to look at
object internals at runtime
Loop around the fields in Repository
Find DB as a dependency
Dynamically initialise a new
instance of this dependency
Set the DB field to the newly initialised
dependency
Must
type assert
Slide 10
Slide 10 text
@J7mbo
Runtime Dependency Injection
How can I create a struct, from a string?
Slide 11
Slide 11 text
@J7mbo
Runtime Dependency Injection
https://stackoverflow.com/questions/32132064/how-to-discover-all-package-types-at-runtime/32135975#comment95225870_32135975
(narrator: it did)
Slide 12
Slide 12 text
@J7mbo
Runtime Dependency Injection
A central registry of types
Retrieve by name
Slide 13
Slide 13 text
@J7mbo
Generating a type registry
First, loop through all files in the directory
Slide 14
Slide 14 text
@J7mbo
Generating a type registry
At the end:
Write the
map[string]interface{}
to a new file
(registry.go)
Found an exported struct,
Store it in an array of structs
Slide 15
Slide 15 text
@J7mbo
Generating a type registry
Slide 16
Slide 16 text
@J7mbo
Generating a type registry
What about when two dependencies are called “Dependency”?
Slide 17
Slide 17 text
@J7mbo
Generating a type registry
src/
├───dir1
│ repository.go
├───dir2
│ repository.go
├───dir3
│ ├───sub1
│ │ db.go
│ └───sub2
go.mod contains “module app”
app/src/dir1/repository.go
app/src/dir2/repository.go
app/src/dir3/sub1/db.go
But how can I get this “fully qualified” module path for each file in each directory?
Slide 18
Slide 18 text
@J7mbo
Generating a type registry
Execute ‘go list’ to get the full path
Slide 19
Slide 19 text
@J7mbo
Generating a type registry
Slide 20
Slide 20 text
@J7mbo
Make()
• Look for name in registry, retrieve struct
• Reflect on struct, look at dependencies…
• For each dependency, lookup in registry, recurse
• Finally, inject each one, one-by-one, before returning Repository
Slide 21
Slide 21 text
@J7mbo
New() factory methods
Slide 22
Slide 22 text
@J7mbo
Back to the type registry…
Slide 23
Slide 23 text
@J7mbo
Back to the type registry…
Slide 24
Slide 24 text
@J7mbo
Make()
Make() now automatically invokes
NewRepository and NewDB
Slide 25
Slide 25 text
@J7mbo
Share()
Tell the injector to always inject *this* db
Slide 26
Slide 26 text
@J7mbo
Delegate()
Delegate() just saves the choice in the injector
This overrides type-registry-found factories
But what about if we have two factories?
Lambdas too
Slide 27
Slide 27 text
@J7mbo
Interfaces
Encounter this interface,
inject this concrete implementation
Slide 28
Slide 28 text
@J7mbo
Interface binding?
Back in the AST parser…
Map of interfaces -> concretes
Slide 29
Slide 29 text
@J7mbo
Go Guru
Slide 30
Slide 30 text
@J7mbo
My interface so far…
Slide 31
Slide 31 text
@J7mbo
Demo of where I got to..
Slide 32
Slide 32 text
@J7mbo
Pros and Cons
1. Very nice, simple to grok API
2. Fast, rapid app development
3. Mostly automatic registration
4. New properties auto-injected
1. I wrote it
2. Have to type assert interface{} for each call
3. Still need to generate a registry each time
4. Doesn’t work properly in edge cases
5. Doesn’t work properly in normal cases
6. Fields are struct copies - expensive
7. Requires registry of all types in memory
8. Debugging is an absolute nightmare
Slide 33
Slide 33 text
@J7mbo
Compile-Time Dependency Injection
Tell wire which factory methods it has to call
It’ll look at the return type
It’ll look at the factory methods
Slide 34
Slide 34 text
@J7mbo
Compile-Time Dependency Injection
It’ll generate this when you run wire on the command line
@J7mbo
Pros and Cons
1. I didn’t write it, the Google guys did
2. Compile-time guarantees
3. Much more of a standard solution
4. No coupling to library after generation
5. No ABSOLUTELY AWFUL DEBUGGING
1. Not as user friendly an API
2. Devs still have to write extra code
3. Not fully automatic
4. Far fewer features than typical DI libs
Slide 38
Slide 38 text
@J7mbo
Conclusion: Thoughts on runtime vs compile-time
Runtime will never provide the type guarantees
A huge suite of unit tests can cover this base (but my lib tests suck)
Until a runtime tool works 100% and is community backed..
teams should use compile-time if they want a DI tool
Use runtime in your side-projects instead if you like
Slide 39
Slide 39 text
@J7mbo
Conclusion - What next?
Considering building a tool:
• Looks at the code you want to run (AST)
• Writes wire.Build code for you
• Runs wire to generate it
• (One step closer to fully automatic with compile-time guarantees)
Turns out that what I created basically already exists
(Facebook Inject - now archived?) - without autogeneration of registry.
I’m still going to work on my runtime project for fun.
https://github.com/j7mbo/goij