“Dependency Injection” of golang library

Article directory

    • 1. Write at the front
    • 2. Dependency injection
      • 2.1 Usage scenarios
      • 2.2 Framework comparison
    • 3. Examples of fx framework usage scenarios
      • 3.1 Example
      • 3.2 golang native library
      • 3.3 fx library
      • 3.4 Comparison
        • 3.4.1 Comparison of the above two implementation methods
        • 3.4.2 About over-design
        • 3.4.3 Enlightenment
    • 4. Thoughts
    • 5. References

1. Write at the front

A colleague used golang’s fx framework when sharing technology, and suddenly remembered that he had seen this framework before when he helped others review code. I only know that it is a “dependency injection” framework, but I have not analyzed and understood it in depth, including the advantages, disadvantages and suitable scenarios of this framework. Knowledge belongs to others when it is placed there, but it belongs to you only after you have learned it. So I’m sorting it out by the way so that when I need to use it later, I can get started quickly.

Note: There is one over and over again, but there is no over and over again. You can say that you didn’t understand it when you saw it for the first and second time, but it’s the third and fourth time, and it doesn’t make sense.

2. Dependency injection

2.1 Usage scenarios

Official words from Wikipedia: “Dependency injection is a design pattern that implements inversion of control to solve dependencies. A dependency refers to an object that can be exploited (i.e., the service provider). Dependency injection is the process of converting the dependencies Passed to the dependent object that will be used (that is, the client). The service is part of the state that will become the client. Passing the service to the client, rather than allowing the client to create or find the service, is a basic requirement of this design pattern. “

Note: I read this concept in Wikipedia three times and I can understand it, but it is difficult to distinguish in depth what scenarios are suitable for dependency injection.

This concept made me think deeply, and then I didn’t give up and asked chatgpt

chatgpt’s answer is as follows:

Summary: chatgpt’s answer is overall simpler to understand than Wikipedia. (ps: If you encounter a concept you don’t understand, you can try chatgpt

2.2 Framework comparison

The following are several commonly used golang dependency injection frameworks:

  • Google’s Wire: A code generation tool for managing dependency injection, which provides a simple way to define dependencies and generate corresponding code. Wire can check and resolve dependencies, generating reusable, efficient code. Official documentation, GitHub – google/wire: Compile-time Dependency Injection for Go

  • Uber’s fx: a more advanced dependency injection framework built on wire, which provides more features and functions, such as life cycle management, plug-in system, service registration, etc. fx aims to provide a dependency injection pattern that is easier to use and maintain. Official documentation: GitHub – uber-go/fx: A dependency injection based application framework for Go.

Note: The difference between the two is that wire uses Code Gen, while fx uses reflection.

3. Examples of fx framework usage scenarios

Before using a framework, you must first think clearly. Is the complexity of the business really such that a framework is no longer necessary?

personal opinion:

  • Introducing libraries will reduce the complexity of business development to a certain extent, but it will also increase the cost of understanding the code (ps: except for common common libraries, such as logging libraries

3.1 Example

Implement an http server that supports the following two POST methods

  • /echo: Use the requested content directly as the body of the response

  • /hello: The requested content is spliced with hello characters and used as the response body.

Shaped like:

3.2 golang native library

Code:

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
)

var handleMap = map[string]func(w http.ResponseWriter, r *http.Request){
    "/echo": handleEcho,
    "/hello": handleHello,
}

func main() {
    http.HandleFunc("/", handleRequest)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
    f := handleMap[r.URL.Path]
    if f != nil {
        f(w,r)
        return
    }

    http.NotFound(w, r)
}

func handleEcho(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Error reading request body", http.StatusInternalServerError)
        return
    }

    fmt.Fprintf(w, "%s", body)
}

func handleHello(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Error reading request body", http.StatusInternalServerError)
        return
    }

    msg := fmt.Sprintf("%s hello", body)
    fmt.Fprint(w, msg)
}

3.3 fx library

Code:

package main

import (
    "context"
    "fmt"
    "io"
    "net"
    "net/http"

    "go.uber.org/fx"
    "go.uber.org/zap"
)

// defined interface
type Route interface {
    http.Handler

    Pattern() string
}

func AsRoute(f any) any {
    return fx.Annotate(
        f,
        fx.As(new(Route)),
        fx.ResultTags(`group:"routes"`),
    )
}

// Implement the data structure of Route interface, echo interface
type EchoHandler struct {
    log *zap.Logger
}

func NewEchoHandler(log *zap.Logger) *EchoHandler {
    return &EchoHandler{
        log: log,
    }
}
func (*EchoHandler) Pattern() string {
    return "/echo"
}

func (h *EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if _, err := io.Copy(w, r.Body); err != nil {
        h.log.Warn("Failed to handle request", zap.Error(err))

    }
}

// Implement the data structure of Route interface, hello interface
type HelloHandler struct {
    log *zap.Logger
}

func NewHelloHandler(log *zap.Logger) *HelloHandler {
    return &HelloHandler{log: log}
}

func (*HelloHandler) Pattern() string {
    return "/hello"
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        h.log.Error("Failed to read request", zap.Error(err))
        http.Error(w, "Internal server error", http.StatusInternalServerError)
        return
    }

    if _, err := fmt.Fprintf(w, "Hello, %s\\
", body); err != nil {
        h.log.Error("Failed to write response", zap.Error(err))
        http.Error(w, "Internal server error", http.StatusInternalServerError)
        return
    }
}

func NewServeMux(route []Route) *http.ServeMux {
    mux := http.NewServeMux()
    for _, r := range route {
        mux.Handle(r.Pattern(), r)
    }
    return mux
}

func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux, log *zap.Logger) *http.Server {
    srv := & amp;http.Server{Addr: ":8081", Handler: mux}
    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            ln, err := net.Listen("tcp", srv.Addr)
            if err != nil {
                return err
            }
            log.Info("Starting HTTP Server", zap.String("addr", srv.Addr))
            go srv.Serve(ln)
            return nil
        },
        OnStop: func(ctx context.Context) error {
            return srv.Shutdown(ctx)
        },
    })
    return srv
}

func UseHttpServer(*http.Server) {}

func main() {
    fx.New(
        fx.Provide(
            NewHTTPServer,
            fx.Annotate(
                NewServeMux,
                fx.ParamTags(`group:"routes"`),
            ),
            AsRoute(NewEchoHandler),
            AsRoute(NewHelloHandler),
            zap.NewExample,
        ),
        fx.Invoke(UseHttpServer),
    ).Run()

}

3.4 Comparison

The official introduction of the fx library is as follows:

  • Eliminate globals: Fx helps you remove global state from your application. No more init() or global variables. Use Fx-managed singletons.
  • Code reuse: Fx lets teams within your organization build loosely-coupled and well-integrated shareable components.
  • Battle tested: Fx is the backbone of nearly all Go services at Uber.

Summarize:

  • Eliminates the use of init() and global variables

  • Better decoupling and more convenient shared components

  • Uber has a high degree of internal maturity and is the underlying pillar of almost all go services.

but:

  • Is there really any harm in using init() and global variables?

  • Does your program really need that high degree of decoupling?

  • Does “backbone” mean 100% reliable?

Recent development experience: The code always depends on others, and the delivery time completely depends on others’ scheduling. But the code is all in your own hands, so you can easily make repairs and changes there!

“Your repair method will not affect customers. But if you delay the delivery time in pursuit of perfect code, you will definitely attract customer complaints.” (ps: Don’t ask me how I know, a history of growth and tears

3.4.1 Comparison of the above two implementation methods

< /table>

Note: The code needs to be understood and maintained first, and secondarily. If the cost of understanding becomes higher, the corresponding maintenance costs can be imagined.

 The above levels are divided into three grades: high, middle and low.

    According to the author's comparison: golang original library beats fx library

Note: It cannot be summarized comprehensively that the fx library is not good, but it can be summed up that when the size of the code is small and the delivery rhythm must be guaranteed, it is good not to introduce complex or unfamiliar libraries. A great choice.

Note: Anything you control is reliable. Anything you rely on, you need to ask more, “Is this really the case?”

3.4.2 About over-design

The above comparison made the author think of a keyword “over-design” that I had seen before.

Definition from Wikipedia:

“Over-engineering” refers to the act of designing a product or providing a solution to a problem in an overly complex way, when it can be demonstrated that a simpler solution exists that is as efficient and effective as the original design .

Note: Referring to this definition by Pawe? G?ogowski, it can be summed up as “code and design that solves all the problems you don’t have.”

Why: We try to predict the future and prepare for unknown problems. (ps: The future problems you think may have no chance of appearing at all, so reduce anxiety and don’t overdo it because of worries and designs about future things.

Solution:

  • Let engineers become real product engineers

  • Define the problem correctly to reduce ambiguity

  • Ask more: How does this help solve current users’ problems? What will happen if it is not resolved now?

3.4.3 Enlightenment
  • Developers should choose technical practices based on their own solutions. The framework provides “choice” rather than restricting developers’ freedom.

  • The purpose of the framework is to assist engineers. If you don’t know what assistance is needed, using the framework will not help, and it may be tied up.

4. Thoughts

The weather in Shanghai is really changing rapidly. Yesterday you could wear a small skirt, but today you have to wear a woolen coat. You can’t afford it!

  • As time passes, we will finally let go. It’s good to live healthily, live peacefully, smile happily, and be appropriately busy.

  • The first is to do things that make you happy, and the second is to “never do things that make you unhappy.”

  • Even if I fail, I still want to know how far away from the finish line I fell.

5. Reference materials

  • Use fx for Go dependency injection

  • Golang dependency injection wire and dig experience comparison

  • Golang dependency injection classic solution uber/fx theoretical analysis)

  • What is dependency injection? How to use it?

  • Over-engineering can kill your product_Culture & Methods_Simón Mu?oz_InfoQ Featured Articles

  • Overengineering in software development | Solidstudio

syntaxbug.com © 2021 All Rights Reserved.
golang native library fx library
Development complexity Medium High
Decoupling degree Medium High
Operation and maintenance cost Medium High