iT邦幫忙

2021 iThome 鐵人賽

DAY 12
0
Modern Web

Let's Go! 解剖Go server開發到部署的過程系列 第 12

day 12 - API組裝實作

零件都準備好就可以組裝起來了!
前幾天分別完成了redis, error, log的封裝, 接下來就是依照API的input/output分別組合出各個API的內容。
API可以善用defer來log API的進出資訊以及執行時間, 記錄下總執行時間可以分析出API處理時長是否符合期望值, ex: 一句redis語法預期會是1ms, 打一個外部gRPC API要在50ms以內得到回應...等等, 紀錄時間可以做為服務調校的依據, 當API流量超過預期時, 這些時間就會是API優化的參考值。

服務log下來的各項資訊及error可以透過ELK收集起來, 收集之後還可以依照訊息的等級決定是否轉發到email或通訊軟體, 目前通訊轉體都有開放API提供介接轉發訊息。

把處理行為都封裝好之後, error 發生時也可以透過log清楚地知道是哪一個funcion發生問題, 不僅每支function行數變簡潔, 對於問題發生時釐清查找範圍也很有幫助!

組合出來的結果大致如下:

package main

import (
	"context"
	"errors"
	"regexp"
	"strconv"
	"time"

	coconutError "github.com/evelynocean/coconut/lib/error"
	coconut_model "github.com/evelynocean/coconut/model"
	coconut "github.com/evelynocean/coconut/pb"
	coconut_redis "github.com/evelynocean/coconut/redis"
)
// Ping 提供服務運行確認
func (s *server) Ping(ctx context.Context, in *coconut.PingRequest) (r *coconut.Pong, err error) {
	r = &coconut.Pong{
		Pong: "pong",
	}

	return r, err
}
// 更新統計點數
func (s *server) UpdatePoints(ctx context.Context, in *coconut.PointsRequest) (r *coconut.RetResultStatus, err error) {
	start := time.Now()

	defer func() {
		Logger.WithFields(map[string]interface{}{
			"input":        in,
			"execute_time": time.Since(start).Seconds(),
			"response":     r,
		}).Debugf("UpdatePoints")

		// HandlerPanicRecover(&err)
	}()

	// TODO: input data check
	if in.Level_1 == "" || in.Level_2 == "" || in.Level_3 == "" {
		return nil, coconutError.ParseError(coconutError.ErrServer, errors.New("invalid parameter"))
	}

	r = &coconut.RetResultStatus{}
	sets := &coconut_redis.KeySet{
		Level1:   in.Level_1,
		Level2:   in.Level_2,
		Level3:   in.Level_3,
		UserName: in.UserName,
	}
	keys := coconut_redis.GetPointKey(sets)
	limitSettings, err := coconut_model.GetLimit(s.ScyllaSession)

	for idx, v := range keys {
		limit := limitSettings[strconv.Itoa(idx)]
		err = coconut_redis.PointSet(s.RedisClient, v, int(in.Point), time.Duration(30)*time.Second, limit)
		if err != nil {
			Logger.WithFields(map[string]interface{}{
				"key": v,
                "point": in.Point, 
                "limit": limit,
				"time": time.Now().UnixNano(),
				"err:": err.Error(),
			}).Errorf("redis.PointSet")
            
			return nil, coconutError.ParseError(coconutError.ErrRedis, err)
		}
	}

	r = &coconut.RetResultStatus{
		Success: true,
	}

	return r, nil
}
// 查詢統計點數
func (s *server) GetPoints(ctx context.Context, in *coconut.GetPointsRequest) (r *coconut.RetPoints, err error) {
	start := time.Now()

	var data []*coconut.PointInfo

	defer func() {
		Logger.WithFields(map[string]interface{}{
			"input":        in,
			"execute_time": time.Since(start).Seconds(),
			"response":     r,
		}).Debugf("GetPoints")

		// HandlerPanicRecover(&err)
	}()

	if in.Level_1 == "" || in.Level_2 == "" || in.Level_3 == "" {
		return nil, coconutError.ParseError(coconutError.ErrServer, errors.New("invalid parameter"))
	}
    // 解析出各個層級
	reg := regexp.MustCompile(`^LEVEL.*:(\w+)$`)

	sets := &coconut_redis.KeySet{
		Level1: in.Level_1,
		Level2: in.Level_2,
		Level3: in.Level_3,
	}
	keys := coconut_redis.GetPointKey(sets)

	for _, v := range keys {
		resultPoint, err := coconut_redis.PointGet(s.RedisClient, v)
		if err != nil {
        	Logger.WithFields(map[string]interface{}{
				"key": v,
				"time": time.Now().UnixNano(),
				"err:": err.Error(),
			}).Errorf("redis.PointGet")
            
			return nil, coconutError.ParseError(coconutError.ErrRedis, err)
		}
		if resultPoint > 0 {
			matchSlice := reg.FindStringSubmatch(v)
			d := &coconut.PointInfo{
				Name:   matchSlice[1],
				Points: int32(resultPoint),
			}
			data = append(data, d)
		}
	}

	r = &coconut.RetPoints{
		Data: data,
	}

	return r, nil
}


上一篇
day 11 - log服務想說的話
下一篇
day 13 - go mod & vendor 簡介
系列文
Let's Go! 解剖Go server開發到部署的過程30

尚未有邦友留言

立即登入留言