核心
kernel、config、interceptor、auth 與 apperr 是組裝一個 Gortexa 服務的五個核心 package:kernel 擁有那個單一 h2c 埠與生命週期,config 提供分層設定,interceptor 是固定八段治理鏈,auth 是可插拔的驗證,apperr 是三協定共用的錯誤模型。
先取得模組:go get github.com/yshengliao/gortexa@v0.28.3。以下片段皆對 v0.28.3 編譯驗證過。
kernel
Section titled “kernel”composition root:App 擁有單一 h2c listener(多工 gRPC、HTTP/JSON、MCP)、gRPC server、health registry、in-process loopback 與 graceful shutdown。用 kernel.New(opts...) 建立,options 有 WithConfig、WithLogger、WithInterceptors(吃 interceptor.Set 值,非指標)、WithoutInterceptors(明示不要治理鏈——WithInterceptors 與它必須恰好給一個,都不給會啟動失敗,兩個都給則互斥並回傳錯誤)、WithStatsHandler、WithHTTPWrap、WithGateway、WithMCPHandler、WithShutdownHook、WithServerOptions(append gRPC ServerOption,讓你加自己的 interceptor/stats,接在框架鏈之後)、WithAdminListener(opt-in 獨立 ops 埠只服務 /healthz+/readyz)、WithExtraListener(在 caller 自有 listener 上服務任意 handler)。主 h2c 埠始終單埠多工不變;app.AdminAddr() 可查 admin 埠實綁位址。
import ( "context" "github.com/yshengliao/gortexa/config" "github.com/yshengliao/gortexa/health" "github.com/yshengliao/gortexa/kernel")
cfg := config.MustBuild()// New 要求 WithInterceptors / WithoutInterceptors 恰好給一個;set 見下面的 interceptor 段。app, err := kernel.New(kernel.WithConfig(cfg), kernel.WithInterceptors(set))if err != nil { return err}// 在此把 gRPC service 註冊到 app.GRPCServer()app.Health().Register("self", func(context.Context) health.State { return health.Healthy })return app.Run(ctx) // /healthz、/readyz 內建App 的方法:GRPCServer()、Health()、Loopback()、Logger()、SetGateway()、SetMCPHandler()、Run(ctx)、Shutdown(ctx)。SetGateway 與 SetMCPHandler 是組裝期 setter:伺服器開始服務後再呼叫會 panic。WithHTTPWrap 可以疊加而不是覆蓋(先傳的包在內層),所以第二次 wrap 不會默默丟掉前一層的安全處理;WithAdminListener 最多只能用一次(兩個 admin listener 會互相競爭 AdminAddr 的回報值)。
config
Section titled “config”固定優先序:內建預設 < YAML 檔 < .env 檔 < 環境變數(GORTEXA_ 前綴、__ 表巢狀)。入口是 MustBuild(fail-loud,jwt_secret 為空/預設 placeholder/< 32 bytes 或 issuer 空會 panic)與 BuildUnvalidated。沒有 Load。WithConfigFile 指定的檔案是必要的:檔案不存在會建置失敗(MustBuild panic)。
import "github.com/yshengliao/gortexa/config"
cfg := config.MustBuild(config.WithConfigFile("etc/config.yaml"))dsn := cfg.DB.DSN.Reveal() // Secret 只在使用點還原secret 型別 config.Secret 的 String()/log/JSON 一律遮罩成 ****,要真值用 Reveal()。Config 的子結構:Server、Auth、DB、Cache、MQ、Log、Observ(全欄位見設定)。
interceptor
Section titled “interceptor”NewSet(Config) 產出八段固定順序的鏈(recover → request-id → logger → load-shed → rate-limit → circuit-breaker → auth → validation),交給 kernel.WithInterceptors。
import ( "time" "github.com/yshengliao/gortexa/auth" "github.com/yshengliao/gortexa/interceptor")
set, err := interceptor.NewSet(interceptor.Config{ Verifier: auth.MustNewVerifier([]byte(secret), "gortexa"), RateLimit: interceptor.RateLimitConfig{RPS: 200, Burst: 100, TTL: 10 * time.Minute},})// set 傳給 kernel.WithInterceptors(set)Config 另有 Logger、AuthSkip、CircuitBreaker、LoadShedding、Metrics 欄位。Set 提供 UnaryChain()/StreamChain()。CBConfig.Logger 每次狀態轉換會記一行;NewSet 會用 Config.Logger 自動補上,但直接自建 breaker 時不設它就只有 metric 與 span(沒有 OTLP endpoint 時等於靜默)。CBConfig 的失敗定義是三值的:只有伺服端失敗(Unavailable、Internal、request context 仍存活時的 DeadlineExceeded)計數;呼叫端造成的結果(ctx 取消/逾時、Canceled、InvalidArgument、Unauthenticated、PermissionDenied)中性——不觸發也不重置;half-open 探測超過 OpenInterval 未回報即放棄該回合,長串流不會把方法卡在 half-open。
auth 段可插拔:Verifier 走內建 HS256 JWT;非 JWT 方案改設 Authenticator(一個 auth.Authenticator,Authenticate(ctx) (ctx, error)),例如 static bearer/mTLS/API-key。兩者都設時 Authenticator 優先。JWT 也可用 auth.NewJWTAuthenticator(v) 顯式取得。
HS256 JWT 驗證/簽發與 context helper。secret 須 ≥ 32 bytes。
import "github.com/yshengliao/gortexa/auth"
v := auth.MustNewVerifier([]byte(secret), "gortexa")tok, err := v.Sign("user-123", []string{"admin"}, time.Hour)// 在 handler 內取回驗證過的 claims:if claims, ok := auth.ClaimsFrom(ctx); ok { _ = claims.Subject // "user-123"}Verify(token) 回 *Claims(含 Roles 與內嵌的 jwt.RegisteredClaims);BearerToken(header) 從 Authorization header 取 token。
apperr
Section titled “apperr”單一對應表驅動 gRPC status、HTTP status 與 MCP error envelope。*apperr.Error 實作了 GRPCStatus(),handler 直接回傳即可。
import "github.com/yshengliao/gortexa/apperr"
func (s *svc) GetThing(ctx context.Context, req *pb.GetThingRequest) (*pb.Thing, error) { t, err := s.repo.Find(ctx, req.GetId()) if err != nil { return nil, apperr.Wrap(apperr.CatNotFound, "thing not found", err) } return t, nil}只有 CatInvalidArgument 與 CatUnauthenticated 會把開發者訊息轉給呼叫端;其餘 category 只露出 registry 的 SafeMessage,內部原因不外洩。validation 失敗轉發的訊息有上限——最多 10 條違規、512 bytes,超出以 (+N more) 收尾——所以一個帶大型 repeated 欄位的請求無法把單一 InvalidArgument 放大成數 MB 的回應與 log 紀錄。以 %w 包裝 status 時只轉發最內層 status 自己的訊息,包裝層的上下文一律換成 SafeMessage。category 常數涵蓋 gRPC 的完整清單(CatNotFound、CatPermissionDenied、CatFailedPrecondition、CatInternal 等)。