SoFunction
Updated on 2025-03-04

Go dependency injection library samber/do example explanation

introduce

Go, known for its simplicity and efficiency, introduced generics in its version 1.18, which significantly reduces the need for large amounts of code generation, making the language more powerful and flexible. If you are interested,Go generic tutorialIt is a good learning resource.

By using generics from Go, the samber/do library provides a good solution for dependency injection (DI). Dependency injection is an important design pattern that promotes loose coupling between objects and their dependencies, thereby improving code modularity, testability, and maintainability. The combination of generics and dependency injection further enhances Go's potential in creating efficient, scalable software. In this article, you will learn how to provide dependency injection using samber/do.

Code structure

.
├── cmd
│   └── web
│       └── 
├── domain
│   └── 
├── 
├── 
└── user
    ├── 
    ├── 
    └── 

We use this articleblogSame example, but using the samber/do library to implement DI instead of Google Wire. As we can see, the structure of the code becomes simpler. You can find the source code in /Shujie-Tan/do-example.

Service Relationship
domain/ defines the business logic structure and interface as shown below.

type (
	User struct {
		ID       string `json:"id"`
		Username string `json:"username"`
	}
	UserEntity struct {
		ID       string
		Username string
		Password string
	}
	UserRepository interface {
		FetchByUsername(ctx , username string) (*UserEntity, error)
	}
	UserService interface {
		FetchByUsername(ctx , username string) (*User, error)
	}
	UserHandler interface {
		FetchByUsername() 
	}
)

The implementation of these interfaces can be seen in the user directory. The relationship can be expressed as

UserHandler -> UserService -> UserRepository ->

This means that the UserHandler depends on the UserService, which in turn depends on the UserRepository, and finally the UserRepository depends on the database operation. These dependencies can be reversed by using interfaces.

This is a very simple example. Now we build the object and its dependencies.

cmd/web/

package main
import (
	"database/sql"
	"example/domain"
	"example/user"
	"fmt"
	"net/http"
	_ "/lib/pq"
	"/samber/do"
)
func main() {
	injector := () // 1
	connStr := "user=root dbname=mydb"
	db, err := ("postgres", connStr) // 2
	if err != nil {
		panic(err)
	}
	defer ()
	[*](injector, "user", func(i *) (*, error) {
		return db, nil
	}) // 3
    (injector, )
	(injector, )
	(injector, ) // 4
	userHandler := [](injector) // 5
	("/user", ())
	("Try run server at :%d\n", 8080)
	if err := (":8080", nil); err != nil {
		("Error: %v", err)
	}
}

Let's analyze the code step by step:

  • The main function is first usedinjector := ()Create a new DI container. This container will be used to manage dependencies of application objects.
  • useFunction establishes a connection to the PostgreSQL database.
  • useFunctions add database connections to DI containers.
  • This function takes three parameters: the DI container, the name of the dependency, and the provider function that returns the dependency and error. In this case, the dependency is a database connection, and the function returns only the connection and returns nil to indicate an error. useFunctions add repository, service, and handler to DI containers. This function has two parameters: the DI container and the function that returns dependencies and errors.
  • In this case, the function isand, they create instances of repository, service and handler respectively. Please note that the return type of the provider function should be an interface, not a specific type. Go language mode "accept interface, return structure" will be supported in v2 version. useThe function retrieves the userHandler from the DI container and registers it to the http package.
  • This function takes two parameters: the DI container and the type of dependency to be retrieved. In this case, it retrieves the user handler and registers its FetchByUsername method as the handler for the /user route.

user/

package user
import (
	"context"
	"database/sql"
	"example/domain"
	"/samber/do"
)
type repository struct {
	db *
}
func (r *repository) FetchByUsername(ctx , username string) (*, error) {
	// use db here
}
// the return type of NewRepository should be interface, rather than the concrete type!
func NewRepository(i *) (, error) {
	db := [*](i, "user")
	return &repository{db: db}, nil
}

user/

package user
import (
	"context"
	"example/domain"
	"/samber/do"
)
type service struct {
	repo 
}
func (s *service) FetchByUsername(ctx , username string) (*, error) {
	// use repository here
}
func NewService(i *) (, error) {
	repo := [](i)
	return &service{repo: repo}, nil
}

user/

package user
import (
	"example/domain"
	"net/http"
	"/samber/do"
)
type handler struct {
	svc 
}
func (h *handler) FetchByUsername()  {
    // use service here
}
func NewHandler(i *) (, error) {
	svc := [](i)
	return &handler{svc: svc}, nil
}

in conclusion

In this article, we learned how to provide dependency injection in Go using samber/do. We have learned how to create DI containers, add dependencies to containers, and retrieve dependencies from containers. We also learned how to use containers to manage dependencies for your application. By using samber/do, we can create more modular, testable and maintainable code and take advantage of Go's new generic capabilities.

This is the article about the example usage of the go dependency injection library samber/do. This is all about this article. For more related contents of go dependency injection library, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!