Save 37% off PRO during our Black Friday Sale! »

The journey to automatic dependency injection in go - James Mallison - Sixt

6e3ea86995d93d35c0fadf2694bca773?s=47 GoDays
January 23, 2020

The journey to automatic dependency injection in go - James Mallison - Sixt

Dependency Injection is a language-agnostic approach to building well designed and testable code. In many dynamically typed languages, dependency injection can easily be automated to provide a platform for rapid application development, but runtime automatic dependency injection has it’s drawbacks. Go makes automatic DI more of a challenge; powerful languages require powerful tooling and Go is no exception.

In this talk we will explore some novel (read: extremely hacky) approaches to automatic dependency injection, from traversing the AST and the challenges and magic of runtime DI with a custom solution through to generating factories for compile-time DI with google’s wire library and beyond.

6e3ea86995d93d35c0fadf2694bca773?s=128

GoDays

January 23, 2020
Tweet

Transcript

  1. @J7mbo The Journey To Automatic Dependency Injection* James Mallison *In

    Go
  2. @J7mbo

  3. @J7mbo

  4. @J7mbo Not crashed yet

  5. @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?
  6. @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
  7. @J7mbo Standard Dependency Injection Initialise DB dependency Inject into Repository

    initialiser
  8. @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
  9. @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
  10. @J7mbo Runtime Dependency Injection How can I create a struct,

    from a string?
  11. @J7mbo Runtime Dependency Injection https://stackoverflow.com/questions/32132064/how-to-discover-all-package-types-at-runtime/32135975#comment95225870_32135975 (narrator: it did)

  12. @J7mbo Runtime Dependency Injection A central registry of types Retrieve

    by name
  13. @J7mbo Generating a type registry First, loop through all files

    in the directory
  14. @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
  15. @J7mbo Generating a type registry

  16. @J7mbo Generating a type registry What about when two dependencies

    are called “Dependency”?
  17. @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?
  18. @J7mbo Generating a type registry Execute ‘go list’ to get

    the full path
  19. @J7mbo Generating a type registry

  20. @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
  21. @J7mbo New() factory methods

  22. @J7mbo Back to the type registry…

  23. @J7mbo Back to the type registry…

  24. @J7mbo Make() Make() now automatically invokes NewRepository and NewDB

  25. @J7mbo Share() Tell the injector to always inject *this* db

  26. @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
  27. @J7mbo Interfaces Encounter this interface, inject this concrete implementation

  28. @J7mbo Interface binding? Back in the AST parser… Map of

    interfaces -> concretes
  29. @J7mbo Go Guru

  30. @J7mbo My interface so far…

  31. @J7mbo Demo of where I got to..

  32. @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
  33. @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
  34. @J7mbo Compile-Time Dependency Injection It’ll generate this when you run

    wire on the command line
  35. @J7mbo Compile-Time Dependency Injection Bind Interface -> Concrete Don’t use

    connectionString anywhere (IDE warning)
  36. @J7mbo Compile-Time Dependency Injection

  37. @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
  38. @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
  39. @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
  40. @J7mbo Questions?