Why NetEase’s ngo Go Framework Boosts Backend Efficiency and Observability
The article explains how NetEase Media rebuilt its core services with Go, created the open‑source ngo framework to unify dependencies, health‑check, tracing, configuration and OpenID support, and provides detailed code examples and integration guidelines for backend developers.
Background
In 2021 NetEase Media began migrating core online clusters to Go. To avoid duplicated research across teams, a task force surveyed, wrapped, and tested common dependencies and released a unified Go foundation called ngo for internal services.
Why existing open‑source Go frameworks were rejected
Missing essential libraries such as Kafka, Redis, RPC, XXL‑Job.
Inability to integrate NetEase‑wide internal services.
Poor HTTP server performance.
Lack of callback injection for seamless monitoring.
ORM and other modules were not developer‑friendly.
ngo goals and core features
Higher performance and lower resource consumption than the previous Java stack.
All commonly‑used tool libraries bundled (Redis, Kafka, Memcache, etc.).
Zero‑code automatic monitoring data upload.
Automatic configuration loading and environment initialization.
Alignment with health‑check and ops interfaces used in production.
Application health‑check integration
ngo provides four standard endpoints similar to SpringBoot’s Actuator: /health/online – called when a container is brought online; enables traffic. /health/offline – called before a container is taken offline; disables traffic. /health/check – Kubernetes liveness probe; restarts the pod on failure. /health/status – Kubernetes readiness probe; removes the endpoint from service if unhealthy.
Offline handler ensures all in‑flight requests finish before returning success:
func (s *Server) offlineHandler(c *gin.Context) {
atomic.StoreInt32(&s.active, 0) // mark service as unavailable
if s.requestsFinished() {
c.String(http.StatusOK, "ok")
log.Info("Server offline requested!")
} else {
c.String(http.StatusBadRequest, "bad")
log.Info("Server offline failed!")
}
}Online handler simply marks the service as active:
func (s *Server) onlineHandler(c *gin.Context) {
atomic.StoreInt32(&s.active, 1) // enable traffic
c.String(http.StatusOK, "ok")
log.Info("Server online requested!")
}Additional check and status handlers expose liveness and readiness information:
func (s *Server) checkHandler(c *gin.Context) {
ss := Get()
if !ss.Healthz() {
return
}
if s.healthy != nil && !s.healthy() {
c.String(http.StatusForbidden, "error")
return
}
c.String(http.StatusOK, "ok")
log.Info("Server check requested!")
}
func (s *Server) statusHandler(c *gin.Context) {
if atomic.LoadInt32(&s.active) == 1 {
c.String(http.StatusOK, "ok")
} else {
c.String(http.StatusForbidden, "error")
}
}Tracing integration
ngo’s tracing module abstracts both OpenTracing‑compatible systems (Jaeger, Zipkin, SkyWalking, Optimus) and non‑standard solutions (Pinpoint). The unified API lets users switch tracing back‑ends without code changes.
Key interface definitions:
type Tracer interface {
Enabled() bool
Type() string
StartSpanFromCarrier(ctx context.Context, op string, carrier interface{}) (Span, context.Context)
StartSpanFromContext(ctx context.Context, op string) (Span, context.Context)
Inject(ctx context.Context, carrier interface{})
SetSamplingRate(rate int)
Stop()
}
type Span interface {
SetTag(key string, value interface{}) Span
Finish()
GetTraceId() string
}For HTTP servers, ngo supplies a Gin middleware that creates a root span, records request/response metadata, and finishes the span after the handler returns:
func ServerTraceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if tracing.Enabled() && !strings.HasPrefix(c.Request.RequestURI, "/health") {
span, ctx := tracing.StartSpanFromCarrier(c.Request.Context(), "server", c.Request.Header)
tracing.SpanKind.Set(span, "server")
tracing.SpanType.Set(span, "HTTP_SERVER")
tracing.HttpServerRequestUrl.Set(span, c.FullPath())
tracing.HttpServerRequestHost.Set(span, c.Request.Host)
tracing.HttpServerRequestMethod.Set(span, c.Request.Method)
if ip, portStr, err := net.SplitHostPort(c.Request.RemoteAddr); err == nil {
tracing.HttpServerPeerHost.Set(span, ip)
if port, err := strconv.Atoi(portStr); err == nil {
tracing.HttpServerPeerPort.Set(span, uint16(port))
}
}
tracing.HttpServerRequestPath.Set(span, c.Request.URL.Path)
tracing.HttpServerRequestSize.Set(span, c.Request.ContentLength)
c.Request = c.Request.WithContext(ctx)
defer func() {
code := c.Writer.Status()
tracing.HttpServerResponseStatus.Set(span, uint16(code))
tracing.HttpServerResponseSize.Set(span, c.Writer.Size())
span.Finish()
}()
}
c.Next()
}
}Configuration (Apollo) integration
ngo’s config module abstracts multiple data sources (local files, etcd, Apollo, NetEase Config Center). Users start the module with command‑line flags: -c {schema}://{addr} – selects the data source (default file). -w true|false – enables or disables watching for configuration changes.
Example for Apollo:
-c apollo://host:port?appId=xx&cluster=xxx&namespaceNames=xx.yaml,xx.yaml -w trueThe module implements a generic DataSource interface:
type DataSource interface {
ReadConfig() ([]byte, error)
IsConfigChanged() <-chan struct{}
io.Closer
}OpenID / JWT authentication
ngo provides an OpenIdClient that fetches tokens and user info from an OpenID provider. A typical deployment uses JWT for external authentication while OpenID supplies the login entry point.
Authentication is added as a Gin middleware with configurable options (header name, token type, expiration, encryption, OIDC client ID/secret, route prefix, ignored paths):
httpServer:
middlewares:
jwtAuth:
enabled: false # set true to enable
authHeader: Authorization
tokenType: Bearer
accessTokenExpiresIn: 3600
refreshTokenExpiresIn: 7200
encryption: HS256
oidc:
clientId: xxxxxxxx
clientSecret: xxxxxxxx
encryption: HS256
routePathPrefix: ""
ignorePaths:
- /xxxEndpoints for token handling:
GET /auth/access-token?code=&redirectUri=&detail=true|false– obtains a token; detail=true also fetches user info. GET /auth/refresh-token?refreshToken= – refreshes an access token.
Custom authentication logic can be plugged via AddAdminAuthHandler:
func (s *Server) AddAdminAuthHandler(auth Authenticator, gtRsp GenTokenResponse, unauthRsp UnauthenticatedResponse) *ServerApplication lifecycle hooks
ngo allows custom code to run before start and after stop:
app.PreStart = func() error { /* initialization */ return nil }
app.AfterStop = func() error { /* cleanup */ return nil }Signal handling gracefully stops the service on SIGQUIT, SIGINT or SIGTERM:
func (a *application) waitSignals() {
signals.Shutdown(func(grace bool) {
if grace {
a.GracefulStop()
} else {
a.Stop()
}
})
}Getting the code
The full source, examples and documentation are hosted at:
https://github.com/NetEase-Media/ngo
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
