Dependency Injection in Go without the help of convenience containers?

https://medium.com/@nvcnvn/how-difficult-for-handle-dependency-injection-in-go-without-the-help-with-container-a3f570a23d62

I think I need to talk about Java before go with Go on this topic.

So, last few weeks I have been doing a small project with Java Spring boot. I was quite amazed by the “Annotation” Oriented Programming. And the best in all of that is the @Bean related stuff.

  1. @Service
  2. public class Car {
  3. public void Move(Destination destination) {}
  4. }
  1. @Service
  2. public class Transporter {
  3. @Autowired
  4. private Car vehicle;
  5. public void Deliver(Lady prettyOne, Destination to) {
  6. makeOutWith(prettyOne);
  7. hitAllTheBadGuysAt(to)
  8. }
  9. private void makeOutWith(Lady prettyOne) {
  10. // ...
  11. }
  12. private void hitAllTheBadGuysAt(Destination to) {
  13. vehical.Move(to)
  14. // then full of violence
  15. }
  16. }

Yes, I know that many bad stuffs in my code block (the way I don’t use constructor, naming bla bla bla whatever) but anyway that code work, you done your dependency injection may be in a not-perfect-way but still work. And for me, this code consider as good for 2 weeks trained guy (honestly I almost forget all the stuff from university).

For testing, I can make a class extend Car then override the Move method, inject it by 2 annotations. Now go back to Go.

  1. type Car struct {}
  2. func (c *Car) Move(to Desitation) {}
  3. type Transporter struct {
  4. vehicle *Car
  5. }
  6. func (t *Transporter) makeOutWith(pretty Lady) {
  7. // ...
  8. }
  9. func (t *) hitAllTheBadGuysAt(dest Desination) {
  10. t.vehicle.Move(dest)
  11. }

OK, bad example, bad naming (somehow I keep thinking about the Transporter movie)… anyway, that code somewhat equivalent to 2 Java classes except we still not have any lazy-magic in automatic putting you dependency to Transporter.

But wait, there is a big problem.. the transporter in Go universe can only drive Car, he cannot drive a Boat or jet or a Tank… too bad for comparing the Java guy! (in Java universe, if we really want, we can provide him a Spaceship extended Car and tell him that Spaceship can Move to Destination. Then our guy can drive that Spaceship just like driving a Car… genius!). Your code cannot be lazy and short as Java anymore, need a bit longer:

  1. type Car struct {}
  2. func (c *Car) Move(to Desitation) {}
  3. type Vehicle interface {
  4. Move(to Desitation)
  5. }
  6. type Transporter struct {
  7. vehicle Vehicle
  8. }
  9. func (t *Transporter) makeOutWith(pretty Lady) {
  10. // ...
  11. }
  12. func (t *) hitAllTheBadGuysAt(dest Desination) {
  13. t.vehicle.Move(dest)
  14. }

And know how you can lazy inject your dependency:

I won’t need to write it here, you can find loads of good article already for all sort of container libs you can find in Go.

BAD NEWS for you guys, the Go libs can help you if you just a little lazy but not a really lazy guy like me. In the most the convenient DI lib you will found, it always needs kind off:

  1. // init all your component [or maybe doing some weird stuff like get a pointer of a nil interface]
  2. c1 = &Car{}
  3. c2 = & Transporter{}
  4. // put them all in your container
  5. container.MagicStuff(c1, c2)
  6. // and hope it work as you expect

I still not found any libs that can help you config the “struct path” string then inject them depending on env. All the “config” do by code. Ofc you can write that feature for yourself quite simple.
All the libs I know need you to handle by writing more code if you have 2 components with the same interface and you want to inject them. Or if you lucky enough you can find a lib support struct tag that works similar to @Qualifier but that also you need to write some
I almost end up to just write normal code in some bad case that A need B and B need A.

My lazy solution: your own lazy container

  1. func main() {
  2. assasinService := &KindOfAssasinService{}
  3. transporter := NewTransporter(assasinService)
  4. hitman := NewHitMan(assasinService);
  5. }
  6. type KindOfAssasinService {}
  7. func (ass *KindOfAssasinService) GetVehicle(t string) Vehicle {
  8. if t == "car" {
  9. return &Car{}
  10. }
  11. return &Tank{}
  12. }
  13. func (ass *KindOfAssasinService) GetWeapon(t string) Weapon {
  14. if t == "gun" {
  15. return &Gun{}
  16. }
  17. return &Dagger{}
  18. }
  1. type WeaponFactory interface {
  2. GetWeapon(t string) Weapon
  3. }
  4. type Hitman struct {
  5. weaponFactory WeaponFactory
  6. }
  7. func (h *Hitman) doHitmanStuff() {
  8. h.weaponFactory.GetWeapon("gun").KickSomeAss()
  9. }
  1. type VehicleFactory interface {
  2. GetVehicle(t string) Vehicle
  3. }
  4. type Transporter struct {
  5. vehicle Vehicle
  6. }
  7. func (t *Transporter) makeOutWith(pretty Lady) {
  8. // ...
  9. }
  10. func (t *) hitAllTheBadGuysAt(dest Desination) {
  11. t.vehicle.Move(dest)
  12. }
  13. func NewTransporter(f VehicleFactory) *Transporter {
  14. return &Transporter{
  15. vehicle: f.Getvehicle("car"),
  16. }
  17. }

https://gist.github.com/nvcnvn/3271863ac703d5f103098fc03b9050bf/raw/4dba042cd0f37bfe540ca492b68a8749ed34fe0b/transporter3.go

OK, not really short but that the best I can come up.

  • More code, but anyway you can write all in 1 package for your container and init block in 1 function. Multi ENV can be config in a single main file for sure.
  • Stuff like Qualifier, Scope not available in a single line of code (or string of struct tag) but you can have all your complex condition.
  • Testing quite easy already.
ft_authoradmin  ft_create_time2018-03-20 14:54
 ft_update_time2018-03-20 14:58