Dependency injection of the Go language

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Dependency Injection (DI) is a design pattern for decoupling the dependencies between components. When needed, objects and states in other components can be obtained through a unified interface between different components. The interface design of the go language avoids a lot of situations that require the use of third-party dependency injection frameworks (such as Java, etc.). Our injection scheme only provides very few injection schemes similar to those in Dager or Guice, and focuses on avoiding manually configuring dependencies between objects and components. Because we think that if injection is easier to understand in the go code base, there is no need to do that at all.

There are just a few simple steps to implementing injection in Go:

Global variables

Starting with a consistent, lofty goal, we need global connection objects such as MONGO, Memcache, and so on. This is roughly the case:


Typically, the main () function invokes various initialization functions that are configured in flags or configuration files, such as Initmongoservice. At this point, functions such as Getapp can use these services and connections. Of course, sometimes we forget to initialize the global variable and be thrown panic by nil.

Although sharing resources at the time of creating global variables gives them (at least) two drawbacks:

First, the code is hard to write because the dependencies of the components are ambiguous;

Second, it's hard to test the code you write, which is almost impossible under parallel conditions.

Although testing is very fast (we want to make sure that it is fast), testing in a parallel environment is the most important. When using a global connection object, the background service cannot test the same data under concurrency conditions.

Clear Global variables

To clear the global variables, we start with a common pattern. Our components now show dependencies, we will, a MONGO service, or a caching service. Roughly speaking, the naïve example we have above now looks like this:


Many functions that refer to global variables now become dependent on the structure in which they are stored.

The new question

That's great! In the main () method, we used a series of constructs instead of global variables and functions to solve the problems we encountered before. But... A look at the main () function will know, too messy.

In the beginning it was such a mess:



If you keep writing like this, the method body of the main () function will be occupied by a large amount of code. And the code just does two trivial things: allocating memory space, assembling objects, and component relationships. If we have a lot of binary code and libraries to reference, we need to write these silly code over and over again. In particular, it is important to note that nil is not the cause of panic. For example, we forget to pass Cacheservice to Handlertwo, and then trigger a runtime panic. We tried to construct a method, but it got a little out of control. Also need to write a lot of code manually check nil. Our development was annoyed by the fact that we had to assemble the objects manually and make sure they were working properly. Testers even need to assemble objects themselves, build relationships, and obviously they won't share the code in the main () function. So the test code has become more and more complex, redundant, but still often can not find the actual problem. In short, we solved one problem, but it created another problem.

Logo mundane

Some of us are more experienced with DI systems, and we don't think this is just a purely entertaining experience. So when we first talked about solving this new problem with the DI system, there was a lot of push back (which I understand as an empirical reserve ...). Expert solution).

According to these rules, when we need something, we decide that we need to ensure that we avoid the known complexities and develop some basic guidelines:

    1. No code generation. Our development compile step is only with go install, we do not want to introduce additional steps. Related to this rule is no file scanning, we do not want to turn the project into an O (large file) system, but also to prevent the increase in compilation time.
    2. There are no sub-graphs. The concept of a sub-graph is to allow injection to occur on a per-request basis (a per-request basis), simply, a sub-graph must be able to completely differentiate between the "global" life cycle and the "Per-request" (each request) life cycle of the object, And make sure that these "per-request" objects are not confused in all requests. We decided to allow only the injection of "global" life-cycle objects, because that is the problem we are facing now.
    3. Avoid code execution. Di essentially makes the code difficult to understand, and we want to avoid custom code execution/hooks that make it easier to understand.
    4. According to these guidelines, our goals have become clearer:
    • The injection should be allocated to the object.
    • The injection should link the object graph together.
    • Injections should be run only once when the program starts.

We also discussed the supporting constructor (support constructors) feature, but now avoid adding support to them.

Inbound is the result of this work and our solution. It uses structural tags (struct tags) to implement the injection function, can be injected for specific types, and also support the injection of interface type, as long as the specific type of interface type, it also has some less common features, such as by name injection (named injection). The previous simple example now looks like this:



There is no change except that the injection label is added to the Mongoservice field. There are several different ways to use injection tags, but this is the most common usage, and it succinctly illustrates the expectation of injecting a MONGO. Service instance. Similarly, you can imagine that the Handlerone,handlertwo and Roothandler fields also have an injection label on them.

Our main () now looks like this:



Shorter! The whole process of injection is probably this:

    1. View each provided instance and end up with an app instance of the Roothandler type.
    2. Look at the Roothandler field, look for the *handlerone with the inject tag, and find that no *handlerone instance exists, so create one and assign it to this field.
    3. For the Handlerone instance you just created, continue with a lookup similar to step 2, find the Apploader field, and simply create it.
    4. For Apploader instances, it requires a MONGO. Service instance, which finds that an instance has been created when we call populate, so it assigns that instance to the value here.
    5. When it makes the same lookup for Handlertwo, it uses the Apploader instance that has already been created, so the two handlers share the Apploader instance.
    6. Inject the allocated object and connect graph to us. After calling populate, the injection doesn't do anything, and the rest is the same as the behavior that was not injected before.

Victory.

Our main () function is easier to control. Now, manually create an instance of only two cases: if the instance needs to get configuration information in main, or if it needs to request an interface type. Even so, we tend to create some incomplete instances, so that dependency injection complements our integrity. The test code is drastically streamlined, and it is now possible to provide execution for the test without needing to know the object graph. This makes testing more resilient and can vary considerably. Refactoring also becomes simpler, like extracting logic without having to manually adjust our new object graph in various main ().

Overall, we were delighted with the results and the evolution of our code base since we introduced dependency injection.

Resources

You can find resources for the library on GitHub:

Https://github.com/facebookgo/inject

We provide documentation at the same time, although the best way to learn is to actually "play" a bit:

Https://godoc.org/github.com/facebookgo/inject

We love being able to contribute, so make sure that the following tests are available through:

Https://travis-ci.org/facebookgo/inject

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.