目录

GoCommerce

summary

相对完整的一个 http 项目, 但也比较简单, 没有 mvc 分层, api 层直接调用 gorm 操作数据库, 所以说很简单.

tech stack

UsePackage
Routergithub.com/go-chi/chi
UUIDgithub.com/pborman/uuid

contextKey

context 的 key 实现有点意思, 实现了 Stringer 接口, 至于相对于直接用 string 的好处, 没想 明白.

type contextKey string

func (c contextKey) String() string {
    return "api context key " + string(c)
}

const (
    tokenKey           = contextKey("jwt")
    configKey          = contextKey("config")
    couponsKey         = contextKey("coupons")
    requestIDKey       = contextKey("request_id")
    adminFlagKey       = contextKey("is_admin")
    mailerKey          = contextKey("mailer")
    // ...
)

gcontext

通过 context.WithValue() 来向 context 中设置值, 提供 get 函数来取对应的 value.

// WithPaymentProviders adds the payment providers to the context.
func WithPaymentProviders(ctx context.Context, provs map[string]payments.Provider) context.Context {
    return context.WithValue(ctx, paymentProviderKey, provs)
}

// GetPaymentProviders reads the payment providers from the context
func GetPaymentProviders(ctx context.Context) map[string]payments.Provider {
    provs, _ := ctx.Value(paymentProviderKey).(map[string]payments.Provider)
    return provs
}

health check

Just a get api returns some server info:

// HealthCheck endpoint
func (a *API) HealthCheck(w http.ResponseWriter, r *http.Request) error {
    return sendJSON(w, http.StatusOK, map[string]string{
        "version":     a.version,
        "name":        "GoCommerce",
        "description": "GoCommerce is a flexible Ecommerce API for JAMStack sites",
    })
}

request id

func withRequestID(w http.ResponseWriter, r *http.Request) (context.Context, error) {
    id := uuid.NewRandom().String()
    ctx := r.Context()
    ctx = gcontext.WithRequestID(ctx, id)
    return ctx, nil
}

routes

简单地封装了一下 chi, 入口函数: NewAPIWithVersion().

gocommerce 的 apiHandler 实际上就是标准库的 HandlerFunc 加了一个 error 返回值:

type apiHandler func(w http.ResponseWriter, r *http.Request) error

// net/http
type HandlerFunc func(ResponseWriter, *Request)

service exit

就是起一个协程, 监听系统信号, 如果有信号, 就调用 server.Shutdown() 退出:

// WaitForShutdown blocks until the system signals termination or done has a value
func waitForTermination(log logrus.FieldLogger, done <-chan struct{}) {
    signals := make(chan os.Signal, 1)
    signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
    select {
    case sig := <-signals:
        log.Infof("Triggering shutdown from signal %s", sig)
    case <-done:
        log.Infof("Shutting down...")
    }
}