通过依赖注入、服务定位实现控制反转
Go kit - Frequently asked questions https://gokit.io/faq/
Dependency Injection — Why is func main always so big?
Go kit encourages you to design your services as multiple interacting components, including several single-purpose middlewares. Experience has taught us that the most comprehensible, maintainable, and expressive method of defining and wiring up the component graph in a microservice is through an explicit and declarative composition in a large func main.
Inversion of control is a common feature of other frameworks, implemented via Dependency Injection or Service Locator patterns. But in Go kit, you should wire up your entire component graph in your func main. This style reinforces two important virtues. By strictly keeping component lifecycles in main, you avoid leaning on global state as a shortcut, which is critical for testability. And if components are scoped to main, the only way to provide them as dependencies to other components is to pass them explicitly as parameters to constructors. This keeps dependencies explicit, which stops a lot of technical debt before it starts.
As an example, let’s say we have the following components:
- Logger
- TodoService, implementing the Service interface
- LoggingMiddleware, implementing the Service interface, requiring Logger and concrete TodoService
- Endpoints, requiring a Service interface
- HTTP (transport), requiring Endpoints
Your func main should be wired up as follows:
logger := log.NewLogger(...)
var service todo.Service // interface
service = todo.NewService() // concrete struct
service = todo.NewLoggingMiddleware(logger)(service)
endpoints := todo.NewEndpoints(service)
transport := todo.NewHTTPTransport(endpoints)
At the cost of having a potentially large func main, composition is explicit and declarative. For more general Go design tips, see Go best practices, six years in.