Skip to content

Dependency Injection in Golang with Glue

How to do dependency injection in Golang with Glue Framework

Today we would talk about dependency injection in building software applications.

What is DI?

Dependency Injection is the process of composition of multiple components in to single application. Instead of building the application as monolith DI provides technology to divide the application on multiple peaces, test them independently and use them according the particular needs.

Having component in the code-base should have minimum effect if we are not enable it in our application context.

What is Application Context?

It is the graph of multiple components and sometimes also tree of multiple contexts that forms the application structure. Components has relations between each other as multi-to-multi with possible initialization ordering, so component B should always be initialized after component A, and etc. Additionally to the initialization part we have a destruction one, so the order or erasing components from context should be reverse of the their initialization order.

As we can see it is a pretty complex problem and framework that takes care of it should be build in all those details.

The most popular DI framework in Java platform is the Spring Core.

If we try to find similar frameworks in Golang space, we would not be able to find the pure reflection one except glue.

Glue DI framework does not need pre-processing stage or some additional configurations in yaml or other files, it works purely in Golang and supports all platforms.

How Glue framework works?

Golang has a limited support of reflection, not like in Java or scripting languages, but it is sufficient to build pure DI on runtime with certain limitations.

Why DI on runtime is better than pre-processing or compilation stage?

Just because you can have pretty complex DI based on current parameters or command line arguments, for example applciation profile, depending on user needs you can build different context for dev, qa or prod. Additionally, runtime application context could have child contexts, that form a graph/tree of contexts depending on user actions.

Suppose you have application that starts the server. At start you need to know only command supported by application, but if command is “run” you need to create a child context with http server, or several child contexts if you write the mail server that should support SMPT, POP and IMAP protocols at once.

The last but not least benefit of the runtime DI is the FactoryBean. You can have any program code that creates and initializes your bean, especially if your bean is just the common instance for many golang applications, for example *http.Server. This instance you can initialize with numerous ways.

Let’s come back to glue. This framework not only designed to support runtime DI, but also provides flexible in-place properties that could be injected to the components like values.

Limitations of glue.

Glue framework supports components of specific type:

  • interfaces
  • pointers to structures
  • functions

It very important to remember that Glue framework does not support for injection

  • primitive types
  • structs

This is done not because of some technical limitations of reflection of golang, no, it is made by design.

Suppose you have in context instance of struct. As we know the operator “=” in golang copy struct by content every time on assignment. So, the injection of this component is not possible by design, it would copy content and create a second instance, not the original one. That’s why it is important to send to glue all instances as pointer to struct.

Another type of objects that is not supported is primitive types. The reason is the same as structs plus that could not be unique. Pointer as minimum has address and we can find this instance and identify, but not the primitive type.

Interfaces are the preferred way to inject the instances to another components.

Instead of injecting &UserService{} directly, consider the common Java practice to split service in to interface and implementation.

The common patter I use for this is extracting the interface, and making sure that all methods in this interface form unique interface. Golang trying to match each object and any interface and does not track the inheritance.

type UserService interface {}

And then I am creating the implementation

type implUserService struct {}func NewUserService() UserService {}

I am using the “impl” prefix for the service implementation automatically making it as private. And using prefix “New” to make the contructor of the service as a method. This pattern is self-descriptive and code extremely readable after that.

Ideally, I am recommending to place interfaces in to separate package, it automatically makes the golang building process easy, since majority of code would depend on interfaces package avoiding cyclic dependencies.

Let’s build the first DI application in golang.

The first step is to add dependency. Select the latest version of the glue and add the go.mod to the project.

module github.com/youname/yourprojectgo 1.17require ( go.arpabet.com/glue v1.2.3)

Then you can define beans in the application and create the first context.

var UserServiceClass = reflect.TypeOf((*UserService)(nil)).Elem()type UserService interface {    FindUser(userId int) User}type implUserService struct {    UserRepository ORMUserRepository `inject:""`}func NewUserService() UserService {    return &implUserService{}}func (t* implUserService) FindUser(userId int) User {    user, ok := t.UserRepository.GetUser(userId)    if !ok {        return nil    }    return user}func doMain() error {    context, err := glue.New(NewUserService(), NewORMUserRepository())    if err != nil {       return err    }    defer context.Close()    context.Bean(UserServiceClass, 0)[0].Object().(UserService).FindUser(0)}func main() {    if err := doMain(); err != nil {        fmt.Printf("Error: %v\n", err)        os.Exit(1)    }}

Glue has API to access beans in context that is using in “doMain” method to get UserService pointer, but you also can use way more simple example:

func doMain() error {    userService := NewUserService(    context, err := glue.New(userService, NewORMUserRepository())    if err != nil {       return err    }    defer context.Close()    userService.FindUser(0)}

That is the beaty of the pointers, the reference is to the same object. Java does not have this problem, since all instances in Java are pointers.

Glue also supports the lifecycle of the components that could have pre-initialization stage, initialization, ready, closing and fully destroyed stage.

To operate the lifecycle there are two additional interfaces in glue:

  • glue.InitializingBean that has PostConstruct() method
  • glue.DisposableBean that has Destroy() method

If service implementation has one of those methods, those would be called by glue automatically using lifecycle.

The PostConstruct method always calling after all injections successfully done in the instance, including their PostConstruct calls.

The Destroy method would be called in reverse order of how the beans were initialized in context. Glue keeps the per context destroy list.

If two beans have injections on each other, then only one of them could have PostConstuct method, otherwise it would be considered ad cyclic dependency. For the situations where applciation context needs this and there are no interface calls from one bean to another during the PostContract stage, then it is possible to use “lazy” marker for this injection.

Lazy initialization means that component is not required that this dependency would be fully initialized on PostConstruct stage.

type implComponentA struct {  ComponentB  ComponentB `inject:"lazy"`}func (t *implComponentA) PostConstruct() error {   // here, ComponentB injected and not nil, but if it has also   // PostConstruct then it would not be called yet, not initialized}

In this example the ComponentB is not initialized but injected. The same happened in you inject the glue.Context and try to get some beans in PostConstruct, do not expect that the are initialized.

But, if you have this implementation

type implComponentA struct {  ComponentB  ComponentB `inject:""`}func (t *implComponentA) PostConstruct() error {   // here, ComponentB injected and not nil, but if it has also   // PostConstruct then it would not be called yet, not initialized}

Then, glue guarantees that ComponentB is initialized before calling PostConstruct, that required that ComponentB does not have non-lazy dependency on ComponentA.

All component that implement glue.NamedBean, and paticulary the BeanName() string method, considered as beans having names.

Is could be usefull for injections of the collections.

Glue supports major types of collections to inject with beans.

You could inject as array of beans of the particular type or interface.

Map of beans where the key always the string and represents the BeanName().

And if your beans implements the glue.OrderedBean that has method BeanOrder() int, then the array could be of ordered beans.

But the most important and flexiable interface is the glue.FactoryBean. The role of this interface to create the bean in context based on certain initialization steps. And in practice this type of initialization is the most useful for database connections, external integrations, credentials, instances, servers, logging, security, crypto and etc.

Those were basic or essential components for DI frameworks.

Let’s talk about additional features that glue supports.

glue.Scanner interface is using to create a factory bean of the list of the instances to scan. For example your application could provide two different lists depending on environment variable, for development one list, for production another for unit tests third. In this case you do not want to hardcode the list of beans that you would like to put in to context, but you would like to have an interface with some method that glue would call and you will provide this list. This interface is glue.Scanner.

The method that this interface has is ScannerBeans() []interface{}. Glue would definitely call this method as soon it finds this scanner instance.

How and where it could be used?

Suppose you are building the library that would create httpServer instance. But you do not want to hardcode the bean name and your instance should take it in constructor. Then you can create the scanner for this component.

Here is the example

type httpServerScanner struct { beanName string scan     []interface{}}func HttpServerScanner(beanName string, scan ...interface{}) glue.Scanner { return &httpServerScanner{  beanName: beanName,  scan:     scan, }}func (t *httpServerScanner) ScannerBeans() []interface{} { beans := []interface{}{  HttpServerFactory(t.beanName),  &struct {   // make them visible   Servers     []Server       `inject:"optional"`   HttpServers []*http.Server `inject:""`  }{}, } return append(beans, t.scan...)}

We see here that is it not only creating the factory of the httpService, but also customize the name of the bean of the service and expect that as minimum one bean would be in context with type *http.Server.

This notation

&struct {   // make them visible   Servers     []Server       `inject:"optional"`   HttpServers []*http.Server `inject:""`  }{},

Is using in glue to extract the interfaces that going to be used in application runtime by calling “context.getBean” method. Having those interfaces in anonymous struct would make glue prepared for unknown interfaces.

glue.Scanner is powerful tool to customize beans, but not to delay their initialization.

Sometimes it is needed to have all beans in context, but we do not want to initialize it before the use. For example for the command “info” we do not need to initialize all servers and bind the ports.

How to address this issue?

Glue has another interface that called glue.ChildContext. Your application could have a graph/tree of child contexts, and they all would not be initialized from the start. As soon your application execution step comes to the particular child context you can manually initialize it.

ChildContext is the factory for the context that would have a parent one.

Parent context could have multiple child contexts.

Let’s see how to initialize the child context:

for _, child := range parent.Children() {   // creates child context   childContext, err := child.Object()}

You can have multiple calls to method Object(), but the only first one matters for the FactoryBean. If you would like to close child context earlier, you can call the method childContext.Close(), but it is not necessary, the parent context owning all their child contexts.

Another feature of the glue is the multiple lookup options for the bean.

Having the graph/tree of the child contexts makes all things a little bit complex if we lookup the bean somewhere in the middle of this tree.

Suppose we have just the variable “ctx glue.Context” and several layers of parents and we want to lookup the logger instance “*zap.Logger”.

We are not sure if this instance initialized in our current ctx or in some of their parents, but we just need to have a logger instance to make some log. Our preference would by the first instance in the chain of ctx, parent, parent of parent and etc.

Bean(ZapLoggerClass, 0) or Bean(ZapLoggerClass, glue.DefaultLevel)

In this case the DefaultLevel or 0 would be the preffered choice. Is works the same as Spring Framework / Spring Core in Java. It searches the instance in child context first, then if not found in parent, then in parent of parent and etc. Finally, it returns the bean or nothing. Since glue is more universal DI, it returns array of found beans.

Let’s consider another example.

Suppose our application is building the *http.Server and looking components that implements the http.Handler interface. We do not want to add pages to our *http.Server from parent contexts, therefore we should use level 1.

Bean(ZapLoggerClass, 1) or Bean(ZapLoggerClass, glue.CurrentContextLevel)

Another example could be an application that prints all resources in to console, and it wants to get all instances of the Resource interface to the list from all contexts, then it is reasonable to use level -1.

Bean(ZapLoggerClass, -1) or Bean(ZapLoggerClass, glue.UnlimitedLevel)

Any number greater than 1 represents how many level are required to lookup for the bean, number 2 means the current context and parent, number 3 means the 2 and parent of parent and so on.

Glue library also comes with support of Resources and Properties.

The difference between them that properties could be saved in resources, not vise versa.

Having resources pre-defined in Glue DI helps us to get access to certain properties, whereas properties represents in-place holder properties of the application that could be injected in particular fields.

Suppose our implementation needs to know maxUsers configuiration setting.

type implUserService struct {  MaxUsers int `value:"max.users"`}

During the initialization step of this bean glue DI framework would try to resolve the property “max.users” by using local to context glue.Properties instance.

This instance is the singleton in the application context and primary interface to access all properties.

You can get it in application runtime:

ctx.Properties()

You can even initialize it:

props := glue.NewProperties()context, err := glue.NewWithProperties(props, beans...)

But sometimes properties are not static, or they could be in different places, even in databases, external services and secret managers.

How in this case glue will resolve the properties?

Glue DI framework has a special interface glue.PropertyResolver.

The purpose of this interface to provide the way how application is going to access external properties, and implementation could be complex, having own dependencies and so on. Additionally, we have a priority between properties therefore we need to control which provider should be used first, which second and so on. For this case the PropertyResolver interface has method “Priority() int”, as less number than higher priority is.

The glue.Properties itself has predefined priority.

const defaultPropertyResolverPriority = 100

So, any PropertyResolver that has less than 100 would be called before glue.Properties local store, greater — after.

You can add your own PropertyResolver beans in to context, they will all group and added to the list of PropertyResolvers of the glue.Properties instance.

That for them reserved two methods in glue.Properties:

 Register(PropertyResolver) PropertyResolvers() []PropertyResolver

PropertyResolvers returns always a sorted list of resolvers. It could be used to troubleshoot the priority of properties if needed.

PropertyResolver is the common interface of the properties, but how the glue.Properties internal storage formed with properties?

Suppose we are using the classic initialization

context, err := glue.New(beans...)

In this case glue automatically will create the glue.Properties instance and use it internally for this context.

To fill up this instance glue would use a special types of the bean

glue.PropertySource*glue.PropertySourceglue.FilePropertySourceglue.MapPropertySource

All those types would be stored in application context as pointer to PropertySource (*glue.PropertySource), since only pointers of the structs and interfaces are allowed as beans.

FilePropertySource is the wrapper around the string.

MapPropertySource is the wrapper around the map[string]interface{}.

How glue initialize context:

  • collects all beans
  • called all scanners to get beans and collect them too
  • select from the beans : glue.PropertySource, *glue.PropertySource, glue.FilePropertySource, glue.MapPropertySource, converts to *glue.PropertySource to store and context and process the PropertySource to fill up the glue.Properties.
  • lookup all PropertyResolvers and adds them to glue.Property as reference with priority.
  • then goes through all instances of beans and inject other beans and values from glue.Properties.
  • then goes though all instances and calls PostConstruct by tracking cyclic dependencies, remembers the sequence for the future Destroy calls on close of context.
  • goes through lazy instances and initialize them if needed.
  • returns the initialized context.

So, in this case PropertySource is using as primary source of the properties for the glue.Properties instance.

Both glue.PropertySource { File: … } and FilePropertySource is the same thing like glue..PropertySource { Map: … } and MapPropertySource.

If we are dealing with map, everything is clear, glue loads this map to glue.Properties.

If we are dealing with file, then there are two options:

  1. File: “file:/path”
  2. File: “<resource_name>:/path”

In the first case, if file starts with ”file:” prefix we are using the local file system, and file would be loaded from it.

In other cases it assume that we are dealing with resource, that should be in context.

The supported extensions of files:

  • application.properties
  • application.json
  • application.yaml or application.yml

The file name could be anything, glue only looks on extension.

If the file is resource, then glue is looking from resource declarations that represent by glue.ResourceSource.

Those resource declarations are embedded files to the golang application by using popular go-bind library.

The primary requirement is the name of this ResourceSource should have suffix “resource”, since there are also the “assets”.

The “assets” resources are using to build applications that has web assets like react or vue or some static pages.

glue.Context provides method to works with resources in runtime

Resource(path string) (res Resource, found bool)

Whereas Resource is the glue.Resource interface that opens resource as a file.

type Resource interface {    Open() (http.File, error)}

Now, we are covered all functionality of the glue except functions.

Function is the first class citizen in function language, and golang is that.

Therefore glue also supported injection of the functions in the same way as pointers and interfaces.

There is no principal difference between inejection of the functions and other objects, except the fact that function can not have PostConstract method and can not be the glue.InitializingBean.

What is the use case of functions?

By the nature functions are all lazy, they could emulate at the same time the FactoryBean functionality, being providers of some instances, be global callback for some events, be some complex computation functions that should be injected, or someone does not want to create interface for some method and can use as a function. The only requirement is to remember the protocol of the function should be unique in the context, if so feel free to use any function for injection.

Now, we are covered all glue DI framework.

Good luck on using it.

Last updated:

Deep Learning · Algorithms · Engineering