3, User management microservice (Library User Service)

Keywords: Go Docker Kubernetes Microservices

Microservice library user service, user management service. It provides a Restful interface for user management, which mainly realizes the functions of user registration, querying users according to user ID or email, querying books borrowed by users, etc.

Full code:

https://github.com/Justin02180218/micro-kit

Package structure description

 

  • dao: data access layer

  • dto: data transmission layer

  • models: database table mapping layer

  • service: business logic layer

  • Endpoint: the concept of go kit encapsulates every method provided by the service into an endpoint.

  • Transport: go kit concept, protocol transport layer, support   grpc, thrift, http and other protocols are used in combination with gin web framework.

  • user.yaml: configuration file used by the service

  • main.go: Service initiator

code implementation

Database table

First, create the user table in the library database

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT '',
  `password` varchar(255) DEFAULT '',
  `email` varchar(255) DEFAULT '',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

models layer

Create user.go in the models layer and define the User struct corresponding to the table user:

type User struct {
    ID        uint64    `gorm:"primary_key" json:"id" form:"id"`
    CreatedAt time.Time `form:"created_at" json:"created_at"`
    UpdatedAt time.Time `form:"updated_at" json:"updated_at"`
    Username  string
    Password  string
    Email     string
}

dao layer

Create a user that interacts with the database in the Dao layer_ dao.go

Define UserDao interface and its implementation:

type UserDao interface {
    SelectByID(id uint64) (*models.User, error)
    SelectByEmail(email string) (*models.User, error)
    Save(user *models.User) error
}

type UserDaoImpl struct{}

func NewUserDaoImpl() UserDao {
    return &UserDaoImpl{}
}
  • SelectByID: query user information according to user ID

  • SelectByEail: query user information according to user email

  • Save: save user information

Function implementation of UserDao interface:

func (u *UserDaoImpl) SelectByID(id uint64) (*models.User, error) {
    user := &models.User{}
    err := databases.DB.Where("id = ?", id).First(user).Error
    if err != nil {
        return nil, err
    }
    return user, nil
}

func (u *UserDaoImpl) SelectByEmail(email string) (*models.User, error) {
    user := &models.User{}
    err := databases.DB.Where("email = ?", email).First(user).Error
    if err != nil {
        return nil, err
    }
    return user, nil
}

func (u *UserDaoImpl) Save(user *models.User) error {
    return databases.DB.Create(user).Error
}

dto layer

User in dto layer_ Create struct UserInfo and RegisterUser for data transmission in dto.go:

type UserInfo struct {
    ID       uint64 `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
}

type RegisterUser struct {
    Username string
    Password string
    Email    string
}

service layer

Create user in the service layer_ service.go

Define UserService interface and Implementation:

type UserService interface {
    Register(ctx context.Context, vo *dto.RegisterUser) (*dto.UserInfo, error)
    FindByID(ctx context.Context, id uint64) (*dto.UserInfo, error)
    FindByEmail(ctx context.Context, email string) (*dto.UserInfo, error)
}

type UserServiceImpl struct {
    userDao dao.UserDao
}

func NewUserServiceImpl(userDao dao.UserDao) UserService {
    return &UserServiceImpl{
        userDao: userDao,
    }
}

Function implementation of UserService interface:

func (u *UserServiceImpl) Register(ctx context.Context, vo *dto.RegisterUser) (*dto.UserInfo, error) {
    user, err := u.userDao.SelectByEmail(vo.Email)
    if user != nil {
        log.Println("User is already exist!")
        return &dto.UserInfo{}, ErrUserExisted
    }
    if err == gorm.ErrRecordNotFound || err == nil {
        newUser := &models.User{
            Username: vo.Username,
            Password: vo.Password,
            Email:    vo.Email,
        }
        err = u.userDao.Save(newUser)
        if err != nil {
            return nil, ErrRegistering
        }
        return &dto.UserInfo{
            ID:       newUser.ID,
            Username: newUser.Username,
            Email:    newUser.Email,
        }, nil
    }
    return nil, err
}

func (u *UserServiceImpl) FindByID(ctx context.Context, id uint64) (*dto.UserInfo, error) {
    user, err := u.userDao.SelectByID(id)
    if err != nil {
        return nil, ErrNotFound
    }
    return &dto.UserInfo{
        ID:       user.ID,
        Username: user.Username,
        Email:    user.Email,
    }, nil
}

func (u *UserServiceImpl) FindByEmail(ctx context.Context, email string) (*dto.UserInfo, error) {
    user, err := u.userDao.SelectByEmail(email)
    if err != nil {
        return nil, ErrNotFound
    }
    return &dto.UserInfo{
        ID:       user.ID,
        Username: user.Username,
        Email:    user.Email,
    }, nil
}

 

endpoint layer

Create user in the endpoint layer_ endpoint.go,

Define UserEndpoints struct, and each request corresponds to an endpoint.

type UserEndpoints struct {
    RegisterEndpoint    endpoint.Endpoint
    FindByIDEndpoint    endpoint.Endpoint
    FindByEmailEndpoint endpoint.Endpoint
}

 

Create each endpoint

func MakeRegisterEndpoint(svc service.UserService) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        req := request.(*dto.RegisterUser)
        user, err := svc.Register(ctx, req)
        if err != nil {
            return nil, err
        }
        return user, nil
    }
}

func MakeFindByIDEndpoint(svc service.UserService) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        id, _ := strconv.ParseUint(request.(string), 10, 64)
        user, err := svc.FindByID(ctx, id)
        if err != nil {
            return nil, err
        }
        return user, nil
    }
}

func MakeFindByEmailEndpoint(svc service.UserService) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        email := request.(string)
        user, err := svc.FindByEmail(ctx, email)
        if err != nil {
            return nil, err
        }
        return user, nil
    }
}

transport layer

Request forwarding adopts the combination of gin web framework and go kit transport. Therefore, http_ Define a Router in util.go and return to gin.Engine

func NewRouter(mode string) *gin.Engine {
    gin.SetMode(mode)
    r := gin.New()
    r.Use(gin.Recovery())
    return r
}

In user_ The NewHttpHandler defined in transport.go returns gin.Engine, and the request handler of gin is processed by http.NewServer of go kit.

func NewHttpHandler(ctx context.Context, endpoints *endpoint.UserEndpoints) *gin.Engine {
    r := utils.NewRouter(ctx.Value("ginMod").(string))

    e := r.Group("/api/v1")
    {
        e.POST("register", func(c *gin.Context) {
            kithttp.NewServer(
                endpoints.RegisterEndpoint,
                decodeRegisterRequest,
                utils.EncodeJsonResponse,
            ).ServeHTTP(c.Writer, c.Request)
        })

        e.GET("findByID", func(c *gin.Context) {
            kithttp.NewServer(
                endpoints.FindByIDEndpoint,
                decodeFindByIDRequest,
                utils.EncodeJsonResponse,
            ).ServeHTTP(c.Writer, c.Request)
        })

        e.GET("findByEmail", func(c *gin.Context) {
            kithttp.NewServer(
                endpoints.FindByEmailEndpoint,
                decodeFindByEmailRequest,
                utils.EncodeJsonResponse,
            ).ServeHTTP(c.Writer, c.Request)
        })
    }

    return r
}

 

Start service

Complete main.go

var confFile = flag.String("f", "user.yaml", "user config file")

func main() {
    flag.Parse()

    err := configs.Init(*confFile)
    if err != nil {
        panic(err)
    }

    err = databases.InitMySql(configs.Conf.MySQLConfig)
    if err != nil {
        fmt.Println("load mysql failed")
    }

    ctx := context.Background()

    userDao := dao.NewUserDaoImpl()
    userService := service.NewUserServiceImpl(userDao)
    userEndpoints := &endpoint.UserEndpoints{
        RegisterEndpoint:    endpoint.MakeRegisterEndpoint(userService),
        FindByIDEndpoint:    endpoint.MakeFindByIDEndpoint(userService),
        FindByEmailEndpoint: endpoint.MakeFindByEmailEndpoint(userService),
    }

    ctx = context.WithValue(ctx, "ginMod", configs.Conf.ServerConfig.Mode)
    r := transport.NewHttpHandler(ctx, userEndpoints)

    errChan := make(chan error)
    go func() {
        errChan <- r.Run(fmt.Sprintf(":%s", strconv.Itoa(configs.Conf.ServerConfig.Port)))
    }()

    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
        errChan <- fmt.Errorf("%s", <-c)
    }()
    fmt.Println(<-errChan)
}

start-up

Enter the library user service directory and execute go run main.go, as shown:  

The service starts successfully. Listen to port 10086

Interface test

postman is used for interface test, and register interface test is carried out here. The results are as follows:

The test is successful, and a user record is successfully inserted into the database

 

In the next article, we begin to write a Book Management micro service: library book service

Full code:

https://github.com/Justin02180218/micro-kit

More distributed album [Architecture album] series, please pay attention to the official account.

Posted by chele on Sat, 11 Sep 2021 23:19:42 -0700