Compile Time Dependency Injection with Golang Part 2

Siddhesh Tamhanekar
3 min readMay 13, 2023

--

Image Credit: File:Go gopher five years.jpg — Wikimedia Commons

In the last part, compile-time dependency injection with golang we discussed about how to install siddhesh-tamhanekar/di library and how to use it for building go struct objects with all their dependancies. if you haven’t read that story please read it before.

Today, we will look at another feature of di library. how we can bind the interfaces to concrete implementation using siddhesh-tamhanekar/di library

If you are not familiar with interfaces pls check below article

According to me, Interfaces can be useful in many ways some of them are as follows.

  • Interfaces can help us to achieve this dependency inversion.(on of the principle of S.O.L.I.D )
  • Creating mocks for testing.

I hope you have understood the interfaces and its usage from linked articles. Let's see how siddhesh-tamhanekar/di library helps to bind interfaces.

package main

import "fmt"

type CacheReadWriter interface {
Store(key, value string) bool
Flush() bool
Forget(key string) bool
Get(key string) string
}

type Service1 struct {
Cache CacheReadWriter
}

func (s *Service1) Execute() error {
// the execute function some how uses cache and perform some operations on it.
// ...
return nil
}

// concrete implementations
type RedisCache struct {
host string
db string
user string
pass string
}

func (rc RedisCache) Store(key, value string) bool {
fmt.Printf("Storing %s=%s in redis cache\n", key, value)
return true
}
func (rc RedisCache) Get(key string) string {
fmt.Printf("Fetching %s=%s from redis cache\n", key)
return ""
}
func (rc RedisCache) Forget(key string) bool {
fmt.Printf("Removing %s from redis cache\n", key)
return true
}

func (rc RedisCache) Flush() bool {
fmt.Printf("Clear all keys from redis cache\n")
return true
}

func main() {
service1 := NewService1()
if err := service1.Execute(); err != nil {
fmt.Println("Execute failed ", err)
}

}

I will briefly explain above code. we have written one interface CacheReadWriter and its implementation RedisCache and then we created one Service struct which utilizes interface.

Now we need to instruct the library to map interface to its concrete implementation. so, when CacheReadWriter dependancy found we will use its concrete implementation to resolve it. let's write our di.go

// go:build exclude
package main

import "github.com/siddhesh-tamhanekar/di"

func Build() {
di.Bind(CacheReadWriter, RedisCache{})
}

Now to execute our di binary from terminal ~/go/bin/di and there will be di_gen.go created in our directory. if we look at the generated code its not that much helpful to as the parameters which is used are Redis specific. If we introduce InMemoryCache or FileCache parameters will be different. due to this we need to change our code which used NewCacheReadWriter function every time.

To avoid this, we will create NewRedisCache constructor manually.

// main.go -- add below function in the file.
func NewRedisCache() RedisCache {
// connect redis here and return redis object.
return RedisCache{}
}

After running binary, we will get updated di_gen.go as follows.

// Code generated by DI library. DO NOT EDIT.
// To generate file use <path_to_di>/di --path= --module
package main

func NewCacheReadWriter() (redisCache CacheReadWriter) {
redisCache = NewRedisCache()
return
}

Looks great, lets generate code for service constructor by instructing di to build service.

// go:build exclude
package main

import "github.com/siddhesh-tamhanekar/di"

func Build() {
di.Bind(CacheReadWriter, RedisCache{})
// add below line to build service constructor
di.Build(Service1{})
}

and hit di binary again to generate di_gen.go.

Final output of di_gen.go

// Code generated by DI library. DO NOT EDIT.
// To generate file use <path_to_di>/di --path= --module
package main

func NewCacheReadWriter() (redisCache CacheReadWriter) {
redisCache = NewRedisCache()
return
}

func NewService1() (service1 Service1) {
redisCache := NewRedisCache()
service1 = Service1{
Cache: redisCache,
}
return
}

I hope you enjoyed this article and learned more about our di library. Pls let me know your thoughts. In next part we will understand How DI library can used with package level variables. till then Stay Tuned.

P.S. The linked articles are for better understanding of topic and to avoid rewriting same content again and again. There is not any relation /promotional contract between the respective authors and me.

--

--