General request response format

For most systems, the request is usually the JSON format of the original request parameters + the Token of the HTTP header. The response is usually accompanied by error codes and error messages encapsulated in the JSON format of [error codes, error messages, data]. The request and response are placed in the HTTP Body.

Common options for error codes are numbers, letters, and letters plus numbers.

The HTTP status code can be 200 or 200, 400, 401, 403, 404, or 500.

For HTTP headers, some permission-related information, such as tokens, is typically placed.

implementation

Our implementation error codes choose alphabetic format, HTTP status codes use 200, 400, 500 and so on to facilitate the front end to group errors, using Gin framework.

The Service format

Service is where the business logic is implemented. In languages with exception architectures, we often use the Service name (request) response throws exception structure. But Go generally uses error to indicate errors, so Go’s service structure is: service name (request) (response, error).

Such as:

type LoginService struct{}

func (s *LoginService) Login(req *model.LoginReq) (*model.LoginRsp, result.Error) {
   ifreq.Username ! ="admin"|| req.Password ! ="admin" {
      return nil, result.NewError(result.ErrCodeInvalidParameterUsernameOrPassword)
   }

   return &model.LoginRsp{
      Token: uuid.New().String(),
   }, nil
}
Copy the code

Where REQ and RSP are simple structures:

type LoginReq struct {
   Username string `json:"username"`
   Password string `json:"password"`
}

type LoginRsp struct {
   Token string `json:"token"`
}
Copy the code

Error is an interface that inherits the Error interface:

// Unwrap retrieves an internal exception
type Unwrap interface {
   Unwrap() error
}

/ / the Error Error
type Error interface {
   error
   Unwrap
   ErrCode
}

/ / ErrorImpl error
type ErrorImpl struct {
   error
   ErrCode
}

// Unwrap implements the Unwrap interface
func (e ErrorImpl) Unwrap(a) error {
   return e.error
}

// WrapError wraps error
func WrapError(errCode ErrCode, err error) Error {
   return ErrorImpl{
      error:   err,
      ErrCode: errCode,
   }
}

// NewError Creates an Error
func NewError(errCode ErrCode) Error {
   return ErrorImpl{
      error:   errors.New(errCode.Msg()),
      ErrCode: errCode,
   }
}
Copy the code

The return value uses the custom Error interface as an Error, rather than the Error interface that comes with GO, because the Error interface ensures that the service must carry the custom Error code in the case of an Error.

Error code implementation

The Error interface above inherits the ErrCode interface, and we want the Error code to express specific errors inside the system (Error code + Error message), advise the user, and return HTTP status codes to the forward end. Therefore, the interface and implementation of the error code are as follows:

var (
   ErrCodeOK               = newErrCode(http.StatusOK, "OK".""."")
   ErrCodeInvalidParameter = newErrCode(http.StatusBadRequest, "InvalidParameter"."The required parameter is not valid."."Invalid parameter")
   ErrCodeInvalidParameterUsernameOrPassword = newErrCode(http.StatusBadRequest,
      "InvalidParameter.UsernameOrPassword"."The username or password is not valid."."Wrong account or password")
   ErrCodeInternalError = newErrCode(http.StatusInternalServerError, "InternalError"."The request processing has failed due to some unknown error."."Sorry for the inconvenience. Please try again later."))// ErrCode Error code
type ErrCode interface {
   Status() int    // HTTP status code
   Code() string   / / error code
   Msg() string    // Error message
   Advice() string // Suggest a solution
}

// errCodeImpl Error code implementation
type errCodeImpl struct {
   status int
   code   string
   msg    string
   advice string
}

func (e errCodeImpl) Status(a) int {
   return e.status
}

func (e errCodeImpl) Code(a) string {
   return e.code
}

func (e errCodeImpl) Msg(a) string {
   return e.msg
}

func (e errCodeImpl) Advice(a) string {
   return e.advice
}

// newErrCode Creates an error code
func newErrCode(status int, code, msg, advice string) ErrCode {
   return errCodeImpl{
      code:   code,
      msg:    msg,
      status: status,
      advice: advice,
   }
}
Copy the code

Here some error codes are defined at the top, and the error codes will all be defined in this file

The HTTP response

For the HTTP response, we need to bring not only the result of the Service, but also the HTTP status code, error code, and message to the user. To simplify operations, several utility functions are encapsulated here:

/ / Rsp response
type Rsp struct {
   Code string      `json:"code,omitempty"` / / error code
   Msg  string      `json:"msg,omitempty"`  / / message
   Data interface{} `json:"data,omitempty"` / / data
}

// Success The request is successful
func Success(c *gin.Context, data interface{}) {
   rsp(c, ErrCodeOK, data)
}

// Failure The request fails
func Failure(c *gin.Context, errCode ErrCode) {
   rsp(c, errCode, nil)}/ / RSP response
func rsp(c *gin.Context, errCode ErrCode, data interface{}) {
   c.JSON(errCode.Status(), &Rsp{
      Code: errCode.Code(),
      Msg:  errCode.Advice(),
      Data: data,
   })
}
Copy the code

Unlike the Error of a Service, the MSG of an HTTP response uses advice because it is intended for the user.

Gin HandlerFunc packaging

For a service, if we want to expose it when using the Gin framework, we need to write a HandlerFunc function such as:

func LoginHandler(c *gin.Context) {
   // Parameter binding
   var req model.LoginReq
   iferr := c.ShouldBindJSON(&req); err ! =nil {
      result.Failure(c, result.ErrCodeInvalidParameter)
      return
   }

   // Invoke the service
   var loginService service.LoginService
   rsp, err := loginService.Login(&req)

   // Result processing
   iferr ! =nil {
      result.Failure(c, err)
      return
   }
   result.Success(c, rsp)
}
Copy the code

However, the structure of Service, request, response and error code is uniform. For handlers of different services, the code is the same except for the type of request parameters, which leads to very redundant code of handler. Therefore, we use reflection mechanism to simply wrap Service. Eliminate writing handler:

func WrapService(service interface{}) func(*gin.Context) {
   return func(c *gin.Context) {
      // Parameter binding
      s := reflect.TypeOf(service)
      reqPointType := s.In(0)
      reqStructType := reqPointType.Elem()
      req := reflect.New(reqStructType)
      iferr := c.ShouldBindJSON(req.Interface()); err ! =nil {
         result.Failure(c, result.ErrCodeInvalidParameter)
         return
      }

      // Invoke the service
      params := []reflect.Value{reflect.ValueOf(req.Interface())}
      rets := reflect.ValueOf(service).Call(params)

      // Result processing
      if! rets[1].IsNil() {
         result.Failure(c, rets[1].Interface().(result.Error))
         return
      }
      result.Success(c, rets[0].Interface())
   }
}
Copy the code

Thus, we only need one line of code to expose the service:

r.POST("login", WrapService(loginService.Login))
Copy the code

All the code

Github:github.com/XiaoHuaShiF…