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
golang native library | fx library | |
---|---|---|
Development complexity | Medium | High |
Decoupling degree | Medium | High |
Operation and maintenance cost | Medium | High |