[Golang] microservice implementation tool -- go kit is easy to understand

Keywords: Java Go Linux Microservices tcp

Go kit is a distributed development tool set, which can be used to build micro services in large organizations (businesses). It solves most common problems in distributed systems. Therefore, users can focus on business logic

First of all, we should understand that go kit is not a framework. It is just a tool set. It has some toolkits to help us implement microservices, so it doesn't want SpringBoot to help us build the framework directly. We just need to write our code directly on the project framework.

However, it is similar to SpringBoot, such as the three-tier architecture of MVC and go kit.

Microservices

To understand go kit, we must first know what microservices are. After all, go kit exists to facilitate the implementation of microservices.

Wikipedia:

Microservices (English: microservices) is a software architecture style. It is based on small building blocks focusing on single responsibility and function, and uses a modular way to combine complex large-scale applications. Each functional block uses language independent / language agnostic API sets to communicate with each other.

We can know that microservice is a small function block in a large project, and this function block can communicate and combine with other function modules through API regardless of language differences.

Therefore, through microservices, we can know that the contact between work can be exactly what I want to input and get what output in this module. If there is another person who can communicate well, it doesn't matter. Just let him develop quietly. When he is finished, I will get the results I want according to this format.

Go kit three-tier structure

Like MVC in SSM, go kit also has a three-tier structure: endpoint, service and transport. It receives a request and returns a result.

  1. Transport
    Transport is at the top layer of the micro service. It is mainly responsible for HTTP, gRPC, thrift and other related logic. It is responsible for parsing requests and calling endpoint to process requests

  2. Endpoint
    endpoint belongs to the adapter of client and server. It defines the format of request and response. The upper layer calls Transport to process the request, and the lower layer links the Service to convert the result returned by the Service into the format required by the request

  3. Service
    At the bottom, we need to realize the functions of this module

Let's learn more about it through a simple example. The requirement we get is to pass in a user id and then return the name corresponding to the id.

Go kit instance

First, create our basic framework. Because it is an entry-level project, we design our framework in strict accordance with the three-tier structure of go kit.

Service

In the simplest way, let's implement our most basic requirements solutions first. The most important thing here is the GetName interface and its implementation. This layer is relatively simple

package service

type IUserService interface {
	GetName(userId int) string
}

type UserService struct {
}

func (receiver UserService) GetName(userId int) string  {
	if userId == 101 {
		return "calvin"
	}
	return "guest"
}

EndPoint

The function of endpoint is the simplest. For a request passed in from the upper Transport, we return a response, which can be written like this

// This defines an interface like method Endpoint
type EndPoint func(request) response 

Write it in more detail

type EndPoint func(ctx context.Context, request interface{}) (response interface{}, err error)

// Therefore, the main function of endpoint is to return a function, but for us, we should encapsulate the underlying Service into this function, so it should be written in more detail
func GetEndPoint(service Service) endpoint.Endpoint{
	return func(ctx context.Context, request interface{})(response interface{}, err error){
		// Implementation algorithm
	}
}

In fact, what we don't know about context, requests interface and response interface is not important. We just know that it is a fixed format. We need to learn more in the future.

After understanding the format of endpoint, let's write our own endpoint method

package endpoint

import (
	"context"
	"github.com/go-kit/kit/endpoint"
	"go-kit-test/service"
)
// Define the format of request input uid:xx json format
type UserRequest struct{
	Uid int `json: "uid"`
}
// Define the response output format result:xx json format
type UserResponse struct{
	Result string `json: "result" `
}
// interface {} means that the input and output of request and response can be in any format
func GetUserEndPonint(userService service.IUserService) endpoint.Endpoint{
	return func(ctx context.Context, request interface{})(response interface{},err error){
		r:= request.(UserRequest)
		result := userService.GetName(r.Uid)
		return UserResponse{Result: result}, nil
	}
}

Most of the above codes are easy to understand. The real processing part is r:=request.(UserRequest). We all know that after getting the information in the request, we can get the input required by our lowest service through r.Uid, but how is this function implemented? The Transport layer is needed below. After all, a request will not form the format required by the endpoint for no reason

Transport

package transport

import (
	"context"
	"encoding/json"
	"errors"
	"go-kit-test/endpoint"
	"net/http"
	"strconv"
)

//This block is the core implementation. We get the url from an http request, and then get the uid we need by parsing the url, and then convert it into the UserRequest mode defined in the endpoint
func DecodeUserRequest(ctx context.Context, r *http.Request) (interface{}, error) {
	if r.URL.Query().Get("uid") != "" {
		uid, _ := strconv.Atoi(r.URL.Query().Get("uid"))
		return endpoint.UserRequest{Uid: uid}, nil
	}
	return nil, errors.New("Parameter error")
}

//This can be left alone for the time being
func EncodeUserResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
	// transport body to json
	w.Header().Set("Content-type", "application/json")
	return json.NewEncoder(w).Encode(response)
}

It may be a little confused. Although the format of endpoint.UserRequest is returned, it is only a function and does not pass parameters to endpointa. How do we implement r:=request.(UserRequest) above.

In fact, the most important thing is that we need to build a main method to assemble the endpoint+service part with transport

package main

import (
	httpTransport "github.com/go-kit/kit/transport/http"
	"go-kit-test/endpoint"
	"go-kit-test/service"
	"go-kit-test/transport"
	"net/http"
)

func main() {
	user := service.UserService{}
	endPoint := endpoint.GetUserEndPoint(user)
	// implement http.handler and encapsulate with endpoint
	serverHandler := httpTransport.NewServer(endPoint, transport.DecodeUserRequest, transport.EncodeUserResponse)
	// listen the port
	_ = http.ListenAndServe(":8080", serverHandler)
}

The most important thing is to assemble the handler. Serverhandler: = httptransport. NewServer (endPoint, transport. DecodeUserRequest, transport. Encodeuserresponse) Here, we pass in DecodeUserRequest and endPoint together. There will be a method to automatically pass the result into request in newServer, and then open the ListenAndServer function to start listening to the 8080 window. The default is 127.0.0.1:8080


If we visit after the program runs http://127.0.0.1:8080?uid=101 We can get such a result, and the result format is actually defined by us

Posted by savagenoob on Wed, 03 Nov 2021 15:10:58 -0700