improve shutdown and app service boilerplate
All checks were successful
Build and Publish / check-chart (push) Successful in 14s
Build and Publish / helm-release (push) Has been skipped
Build and Publish / release (push) Successful in 4m30s

This commit is contained in:
2025-07-20 18:16:12 -04:00
parent 378df897b2
commit c1c296a0c0
11 changed files with 185 additions and 98 deletions

View File

@ -1,13 +1,14 @@
// This serves as a reference sample configuration
// to show how to merge custom config fields into
// go-app configuration
// Package config contains ServiceConfig, which is intended
// to extend go-app/pkg/config.AppConfig. Add any custom fields
// here, and it will automatically be handled by go-app, including
// overrides by environment, and json schema generation
package config
import (
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config"
)
type DemoConfig struct {
type ServiceConfig struct {
Timezone string `yaml:"timezone" json:"timezone,omitempty" default:"UTC"`
Opts *DemoOpts `yaml:"opts" json:"opts,omitempty"`

54
pkg/demo/demo.go Normal file
View File

@ -0,0 +1,54 @@
// Package demo provides a reference implementation
// of service.AppService. It packages out the GRPC and HTTP
// functionality, for the sake of structure.
package demo
import (
"context"
optsgrpc "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/grpc/opts"
optshttp "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/http/opts"
"gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/config"
"gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/demo/demogrpc"
"gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/demo/demohttp"
"gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/service"
)
type DemoService struct {
ctx context.Context
config *config.ServiceConfig
http *demohttp.DemoHTTPServer
grpc *demogrpc.DemoGRPCServer
}
func (d *DemoService) Init(ctx context.Context, config *config.ServiceConfig,
) (service.ShutdownFunc, error) {
d.config = config
d.ctx = ctx
// These don't HAVE to be split into separate packages
// This is, after all, a demo app. Just implement the interface.
d.http = demohttp.NewDemoHTTPServer(ctx, config)
d.grpc = demogrpc.NewDemoGRPCServer(ctx, config)
// TODO: This should actually do shutdown stuff
return func(_ context.Context) (string, error) {
return "DemoService", nil
}, nil
}
func (d *DemoService) GetGRPC() *optsgrpc.AppGRPC {
return &optsgrpc.AppGRPC{
Services: d.grpc.GetServices(),
GRPCDialOpts: d.grpc.GetDialOpts(),
}
}
func (d *DemoService) GetHTTP() *optshttp.AppHTTP {
return &optshttp.AppHTTP{
Ctx: d.ctx,
Funcs: d.http.GetHandleFuncs(),
HealthChecks: d.http.GetHealthCheckFuncs(),
}
}

View File

@ -27,12 +27,12 @@ const (
type DemoGRPCServer struct {
tracer trace.Tracer
ctx context.Context
cfg *config.DemoConfig
cfg *config.ServiceConfig
client *resty.Client
pb.UnimplementedDemoAppServiceServer
}
func NewDemoGRPCServer(ctx context.Context, cfg *config.DemoConfig) *DemoGRPCServer {
func NewDemoGRPCServer(ctx context.Context, cfg *config.ServiceConfig) *DemoGRPCServer {
if cfg.Opts == nil {
cfg.Opts = &config.DemoOpts{}
}

View File

@ -11,10 +11,10 @@ import (
type DemoHTTPServer struct {
ctx context.Context
cfg *config.DemoConfig
cfg *config.ServiceConfig
}
func NewDemoHTTPServer(ctx context.Context, cfg *config.DemoConfig) *DemoHTTPServer {
func NewDemoHTTPServer(ctx context.Context, cfg *config.ServiceConfig) *DemoHTTPServer {
return &DemoHTTPServer{
ctx: ctx,
cfg: cfg,

63
pkg/service/service.go Normal file
View File

@ -0,0 +1,63 @@
// Package service defines generics around implementation of
// a new service that provides configuration for go-app
//
// The interface is meant to make the template more clear to implement
// without unnecessary changes in main.go
package service
import (
"context"
"gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/config"
optsgrpc "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/grpc/opts"
optshttp "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/http/opts"
)
type ShutdownFunc func(ctx context.Context) (string, error)
type AppService interface {
Init(ctx context.Context, config *config.ServiceConfig) (ShutdownFunc, error)
GetGRPC() *optsgrpc.AppGRPC
GetHTTP() *optshttp.AppHTTP
}
// MergeServices is used if multiple services are served by this server
// with their own distinct packages and configuration
//
// You should be overriding the done chan, listener, or any other
// configuration used by the SERVER rather than the individual services.
func MergeServices(ctx context.Context, appServices ...AppService) (*optsgrpc.AppGRPC, *optshttp.AppHTTP) {
mergedGRPC := &optsgrpc.AppGRPC{}
for _, svc := range appServices {
mergedGRPC = mergeGRPC(mergedGRPC, svc.GetGRPC())
}
mergedHTTP := &optshttp.AppHTTP{}
for _, svc := range appServices {
mergedHTTP = mergeHTTP(mergedHTTP, svc.GetHTTP())
}
mergedHTTP.Ctx = ctx
return mergedGRPC, mergedHTTP
}
func mergeHTTP(a *optshttp.AppHTTP, b *optshttp.AppHTTP) *optshttp.AppHTTP {
return &optshttp.AppHTTP{
Funcs: append(a.Funcs, b.Funcs...),
Middleware: append(a.Middleware, b.Middleware...),
Handlers: append(a.Handlers, b.Handlers...),
HealthChecks: append(a.HealthChecks, b.HealthChecks...),
}
}
func mergeGRPC(a *optsgrpc.AppGRPC, b *optsgrpc.AppGRPC) *optsgrpc.AppGRPC {
return &optsgrpc.AppGRPC{
Services: append(a.Services, b.Services...),
GRPCOpts: append(a.GRPCOpts, b.GRPCOpts...),
UnaryInterceptors: append(a.UnaryInterceptors, b.UnaryInterceptors...),
StreamInterceptors: append(a.StreamInterceptors, b.StreamInterceptors...),
GRPCDialOpts: append(a.GRPCDialOpts, b.GRPCDialOpts...),
GRPCGatewayOpts: append(a.GRPCGatewayOpts, b.GRPCGatewayOpts...),
}
}