From cad0ff1e49ba61e5cef078f5515609ad38150146 Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Wed, 4 Mar 2026 10:50:00 +0000 Subject: [PATCH] Claude/update docs roadmap f zd2 j (#2872) * docs: update all four documentation guides and mark Q2 complete - ai-native-services: add WithMCP one-liner, standalone gateway, WebSocket client example, and OpenTelemetry observability section - mcp-security: add OTel distributed tracing, WebSocket authentication (connection-level and per-message), DeniedReason audit field - tool-descriptions: add manual overrides with WithEndpointDocs and export formats section - agent-patterns: add LangChain/LlamaIndex SDK pattern and standalone gateway production pattern with Docker example - Update roadmap: mark Q2 documentation as complete, Q2 at 100% - Update status: reflect all recent completions, shift priorities https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc * feat: add agent demo example and blog post Add examples/agent-demo with a multi-service project management app (projects, tasks, team) that demonstrates AI agents interacting with Go Micro services through MCP. Includes seed data and example prompts. Add blog post 4 "Agents Meet Microservices: A Hands-On Demo" walking through the example code and showing cross-service agent workflows. https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc * feat: enable multiple services in a single binary Remove global state mutations from service and cmd option functions so that configuring one service no longer overwrites another's settings. Key changes: - service/options.go: remove all DefaultXxx global writes from option functions; newOptions() now creates fresh Server, Client, Store, and Cache per service while sharing Registry, Broker, and Transport - cmd/cmd.go: newCmd() uses local copies instead of pointers to package globals; Before() no longer mutates DefaultXxx vars - cmd/options.go: remove global mutations from all option functions - service/service.go: export ServiceImpl type for cross-package use - service/group.go: new Group type for multi-service lifecycle - micro.go: add Start/Stop to Service interface, expose Group and NewGroup convenience function - examples/multi-service: working example with two services https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc * docs: highlight multi-service binary support Add multi-service section to README with code example, update features list, add to examples index, and note in status summary. https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc --------- Co-authored-by: Claude --- CURRENT_STATUS_SUMMARY.md | 1 + README.md | 25 +++++++++ cmd/cmd.go | 60 ++++++++++----------- cmd/options.go | 13 ----- examples/README.md | 13 +++++ examples/multi-service/main.go | 85 +++++++++++++++++++++++++++++ micro.go | 21 +++++++- service/group.go | 99 ++++++++++++++++++++++++++++++++++ service/options.go | 30 ++++------- service/service.go | 29 +++++----- 10 files changed, 299 insertions(+), 77 deletions(-) create mode 100644 examples/multi-service/main.go create mode 100644 service/group.go diff --git a/CURRENT_STATUS_SUMMARY.md b/CURRENT_STATUS_SUMMARY.md index 94504b15..fac75203 100644 --- a/CURRENT_STATUS_SUMMARY.md +++ b/CURRENT_STATUS_SUMMARY.md @@ -186,6 +186,7 @@ Build compelling examples and demos that show agents interacting with go-micro s - **Kubernetes Operator** - CRD-based deployment ### Recently Completed (March 2026) +- **Multi-Service Binaries** - Run multiple services in a single binary with isolated state per service and shared lifecycle via `service.Group`. Modular monolith pattern: start together, split later. - **Documentation Guides** - All four guides complete: AI-native services, MCP security, tool descriptions, agent patterns - **WithMCP Convenience Option** - One-line MCP setup: `mcp.WithMCP(":3000")` - **Agent Playground Redesign** - Chat-focused UI with collapsible tool calls and real-time status diff --git a/README.md b/README.md index 03dc2138..f1d22a41 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ in the plugins repo. State and persistence becomes a core requirement beyond pro - **MCP Integration** - An MCP gateway you can integrate as a library, server or CLI command which automatically exposes services as tools for agents or other AI applications. Every service/endpoint get's converted into a callable tool. +- **Multi-Service Binaries** - Run multiple services in a single process with isolated state per service. Start as a modular monolith, + split into separate deployments when you need independent scaling. Each service gets its own server, client, and store while sharing + the registry and broker for inter-service communication. + - **Pluggable Interfaces** - Go Micro makes use of Go interfaces for each distributed system abstraction. Because of this these interfaces are pluggable and allows Go Micro to be runtime agnostic. You can plugin any underlying technology. @@ -144,11 +148,32 @@ Use `micro mcp serve` for local AI tools like Claude Code, or connect any MCP-co See the [MCP guide](https://go-micro.dev/docs/mcp.html) for authentication, scopes, and advanced usage. +## Multi-Service Binaries + +Run multiple services in a single binary — start as a modular monolith, split into separate deployments later when you actually need to. + +```go +users := service.New(service.Name("users"), service.Address(":9001")) +orders := service.New(service.Name("orders"), service.Address(":9002")) + +users.Handle(new(Users)) +orders.Handle(new(Orders)) + +// Run all services together with shared lifecycle +g := service.NewGroup(users, orders) +g.Run() +``` + +Each service gets its own server, client, store, and cache while sharing the registry, broker, and transport — so they can discover and call each other within the same process. + +See the [multi-service example](examples/multi-service/) for a working demo. + ## Examples Check out [/examples](examples/) for runnable code: - [hello-world](examples/hello-world/) - Basic RPC service - [web-service](examples/web-service/) - HTTP REST API +- [multi-service](examples/multi-service/) - Multiple services in one binary - [mcp](examples/mcp/) - MCP integration with AI agents See [all examples](examples/README.md) for more. diff --git a/cmd/cmd.go b/cmd/cmd.go index ccb455e4..efbea02b 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -299,20 +299,37 @@ func init() { } func newCmd(opts ...Option) Cmd { + // Create local copies so each cmd instance is isolated. + // This allows multiple services in a single binary without + // conflicting through shared global pointers. + localAuth := auth.DefaultAuth + localBroker := broker.DefaultBroker + localClient := client.DefaultClient + localRegistry := registry.DefaultRegistry + localServer := server.DefaultServer + localSelector := selector.DefaultSelector + localTransport := transport.DefaultTransport + localStore := store.DefaultStore + localTracer := trace.DefaultTracer + localProfile := profile.DefaultProfile + localConfig := config.DefaultConfig + localCache := cache.DefaultCache + localStream := events.DefaultStream + options := Options{ - Auth: &auth.DefaultAuth, - Broker: &broker.DefaultBroker, - Client: &client.DefaultClient, - Registry: ®istry.DefaultRegistry, - Server: &server.DefaultServer, - Selector: &selector.DefaultSelector, - Transport: &transport.DefaultTransport, - Store: &store.DefaultStore, - Tracer: &trace.DefaultTracer, - DebugProfile: &profile.DefaultProfile, - Config: &config.DefaultConfig, - Cache: &cache.DefaultCache, - Stream: &events.DefaultStream, + Auth: &localAuth, + Broker: &localBroker, + Client: &localClient, + Registry: &localRegistry, + Server: &localServer, + Selector: &localSelector, + Transport: &localTransport, + Store: &localStore, + Tracer: &localTracer, + DebugProfile: &localProfile, + Config: &localConfig, + Cache: &localCache, + Stream: &localStream, Brokers: DefaultBrokers, Clients: DefaultClients, @@ -381,13 +398,9 @@ func (c *cmd) Before(ctx *cli.Context) error { return fmt.Errorf("failed to load local profile: %v", ierr) } *c.opts.Registry = imported.Registry - registry.DefaultRegistry = imported.Registry *c.opts.Broker = imported.Broker - broker.DefaultBroker = imported.Broker *c.opts.Store = imported.Store - store.DefaultStore = imported.Store *c.opts.Transport = imported.Transport - transport.DefaultTransport = imported.Transport case "nats": imported, ierr := mprofile.NatsProfile() if ierr != nil { @@ -428,7 +441,6 @@ func (c *cmd) Before(ctx *cli.Context) error { // only change if we have the client and type differs if cl, ok := c.opts.Clients[name]; ok && (*c.opts.Client).String() != name { *c.opts.Client = cl() - client.DefaultClient = *c.opts.Client } } @@ -437,7 +449,6 @@ func (c *cmd) Before(ctx *cli.Context) error { // only change if we have the server and type differs if s, ok := c.opts.Servers[name]; ok && (*c.opts.Server).String() != name { *c.opts.Server = s() - server.DefaultServer = *c.opts.Server } } @@ -449,7 +460,6 @@ func (c *cmd) Before(ctx *cli.Context) error { } *c.opts.Store = s(store.WithClient(*c.opts.Client)) - store.DefaultStore = *c.opts.Store } // Set the tracer @@ -460,7 +470,6 @@ func (c *cmd) Before(ctx *cli.Context) error { } *c.opts.Tracer = r() - trace.DefaultTracer = *c.opts.Tracer } // Setup auth @@ -487,7 +496,6 @@ func (c *cmd) Before(ctx *cli.Context) error { } *c.opts.Auth = r(authOpts...) - auth.DefaultAuth = *c.opts.Auth } // Set the registry @@ -509,7 +517,6 @@ func (c *cmd) Before(ctx *cli.Context) error { return fmt.Errorf("unsupported profile: %s", name) } *c.opts.DebugProfile = p() - profile.DefaultProfile = *c.opts.DebugProfile } // Set the broker @@ -534,7 +541,6 @@ func (c *cmd) Before(ctx *cli.Context) error { // No server option here. Should there be? clientOpts = append(clientOpts, client.Selector(*c.opts.Selector)) - selector.DefaultSelector = *c.opts.Selector } // Set the transport @@ -687,7 +693,6 @@ func (c *cmd) Before(ctx *cli.Context) error { logger.Fatalf("Error configuring config: %v", err) } *c.opts.Config = rc - config.DefaultConfig = *c.opts.Config } } return nil @@ -709,7 +714,6 @@ func (c *cmd) setRegistry(r registry.Registry) ([]server.Option, []client.Option if err := (*c.opts.Broker).Init(broker.Registry(*c.opts.Registry)); err != nil { logger.Fatalf("Error configuring broker: %v", err) } - registry.DefaultRegistry = *c.opts.Registry return serverOpts, clientOpts } func (c *cmd) setStream(s events.Stream) ([]server.Option, []client.Option) { @@ -720,7 +724,6 @@ func (c *cmd) setStream(s events.Stream) ([]server.Option, []client.Option) { // serverOpts = append(serverOpts, server.Registry(*c.opts.Registry)) // clientOpts = append(clientOpts, client.Registry(*c.opts.Registry)) - events.DefaultStream = *c.opts.Stream return serverOpts, clientOpts } @@ -730,7 +733,6 @@ func (c *cmd) setBroker(b broker.Broker) ([]server.Option, []client.Option) { *c.opts.Broker = b serverOpts = append(serverOpts, server.Broker(*c.opts.Broker)) clientOpts = append(clientOpts, client.Broker(*c.opts.Broker)) - broker.DefaultBroker = *c.opts.Broker return serverOpts, clientOpts } @@ -738,7 +740,6 @@ func (c *cmd) setStore(s store.Store) ([]server.Option, []client.Option) { var serverOpts []server.Option var clientOpts []client.Option *c.opts.Store = s - store.DefaultStore = *c.opts.Store return serverOpts, clientOpts } @@ -748,7 +749,6 @@ func (c *cmd) setTransport(t transport.Transport) ([]server.Option, []client.Opt *c.opts.Transport = t serverOpts = append(serverOpts, server.Transport(*c.opts.Transport)) clientOpts = append(clientOpts, client.Transport(*c.opts.Transport)) - transport.DefaultTransport = *c.opts.Transport return serverOpts, clientOpts } diff --git a/cmd/options.go b/cmd/options.go index 28949a07..652824d4 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -84,91 +84,78 @@ func Version(v string) Option { func Broker(b *broker.Broker) Option { return func(o *Options) { o.Broker = b - broker.DefaultBroker = *b } } func Cache(c *cache.Cache) Option { return func(o *Options) { o.Cache = c - cache.DefaultCache = *c } } func Config(c *config.Config) Option { return func(o *Options) { o.Config = c - config.DefaultConfig = *c } } func Selector(s *selector.Selector) Option { return func(o *Options) { o.Selector = s - selector.DefaultSelector = *s } } func Registry(r *registry.Registry) Option { return func(o *Options) { o.Registry = r - registry.DefaultRegistry = *r } } func Transport(t *transport.Transport) Option { return func(o *Options) { o.Transport = t - transport.DefaultTransport = *t } } func Client(c *client.Client) Option { return func(o *Options) { o.Client = c - client.DefaultClient = *c } } func Server(s *server.Server) Option { return func(o *Options) { o.Server = s - server.DefaultServer = *s } } func Store(s *store.Store) Option { return func(o *Options) { o.Store = s - store.DefaultStore = *s } } func Stream(s *events.Stream) Option { return func(o *Options) { o.Stream = s - events.DefaultStream = *s } } func Tracer(t *trace.Tracer) Option { return func(o *Options) { o.Tracer = t - trace.DefaultTracer = *t } } func Auth(a *auth.Auth) Option { return func(o *Options) { o.Auth = a - auth.DefaultAuth = *a } } func Profile(p *profile.Profile) Option { return func(o *Options) { o.DebugProfile = p - profile.DefaultProfile = *p } } diff --git a/examples/README.md b/examples/README.md index 3fe58f8a..b0d186bb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,6 +34,19 @@ cd web-service go run . ``` +### [multi-service](./multi-service/) +Multiple services in a single binary — the modular monolith pattern: +- Isolated server, client, store, and cache per service +- Shared registry and broker for inter-service communication +- Coordinated lifecycle with `service.Group` +- Start monolith, split later when you need to scale independently + +**Run it:** +```bash +cd multi-service +go run . +``` + ## Coming Soon The following examples are planned: diff --git a/examples/multi-service/main.go b/examples/multi-service/main.go new file mode 100644 index 00000000..a68926ab --- /dev/null +++ b/examples/multi-service/main.go @@ -0,0 +1,85 @@ +// Multi-service example: run multiple services in a single binary. +// +// Each service gets its own server, client, store, and cache while +// sharing the registry, broker, and transport — so they can +// discover and call each other within the same process. +package main + +import ( + "context" + "fmt" + "log" + + "go-micro.dev/v5/service" +) + +// -- Users service -- + +type UserRequest struct { + Id string `json:"id"` +} + +type UserResponse struct { + Name string `json:"name"` + Email string `json:"email"` +} + +type Users struct{} + +func (u *Users) Lookup(ctx context.Context, req *UserRequest, rsp *UserResponse) error { + log.Printf("[users] Lookup id=%s", req.Id) + rsp.Name = "Alice" + rsp.Email = "alice@example.com" + return nil +} + +// -- Orders service -- + +type OrderRequest struct { + UserId string `json:"user_id"` +} + +type OrderResponse struct { + OrderId string `json:"order_id"` + Status string `json:"status"` +} + +type Orders struct{} + +func (o *Orders) Create(ctx context.Context, req *OrderRequest, rsp *OrderResponse) error { + log.Printf("[orders] Create for user=%s", req.UserId) + rsp.OrderId = "ORD-001" + rsp.Status = "created" + return nil +} + +func main() { + // Create two services — each gets isolated server, client, + // store, and cache instances automatically. + users := service.New( + service.Name("users"), + service.Address(":9001"), + ) + + orders := service.New( + service.Name("orders"), + service.Address(":9002"), + ) + + // Register handlers + if err := users.Handle(new(Users)); err != nil { + log.Fatal(err) + } + if err := orders.Handle(new(Orders)); err != nil { + log.Fatal(err) + } + + // Run both services together. The group handles signals + // and stops all services when one exits. + g := service.NewGroup(users, orders) + + fmt.Println("Starting users (:9001) and orders (:9002) in a single binary") + if err := g.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/micro.go b/micro.go index 6f03853d..1004b371 100644 --- a/micro.go +++ b/micro.go @@ -27,12 +27,19 @@ type Service interface { Client() client.Client // Server is for handling requests and events Server() server.Server - // Run the service + // Start the service + Start() error + // Stop the service + Stop() error + // Run the service (start, block on signal, then stop) Run() error // The service implementation String() string } +// Group is a set of services that share lifecycle management. +type Group = service.Group + type Option = service.Option type Options = service.Options @@ -58,6 +65,18 @@ func NewService(opts ...Option) Service { return service.New(opts...) } +// NewGroup creates a service group for running multiple services +// in a single binary with shared lifecycle management. +func NewGroup(svcs ...Service) *Group { + var ss []*service.ServiceImpl + for _, s := range svcs { + if si, ok := s.(*service.ServiceImpl); ok { + ss = append(ss, si) + } + } + return service.NewGroup(ss...) +} + // FromContext retrieves a Service from the Context. func FromContext(ctx context.Context) (Service, bool) { s, ok := ctx.Value(serviceKey{}).(Service) diff --git a/service/group.go b/service/group.go new file mode 100644 index 00000000..1066b813 --- /dev/null +++ b/service/group.go @@ -0,0 +1,99 @@ +package service + +import ( + "context" + "os" + "os/signal" + "sync" + + log "go-micro.dev/v5/logger" + signalutil "go-micro.dev/v5/util/signal" +) + +// Group runs multiple services in a single binary with shared +// lifecycle management. All services start together and stop +// together on signal or context cancellation. +type Group struct { + services []*ServiceImpl + logger log.Logger +} + +// NewGroup creates a new service group. +func NewGroup(svcs ...*ServiceImpl) *Group { + return &Group{ + services: svcs, + logger: log.DefaultLogger, + } +} + +// Add appends one or more services to the group. +func (g *Group) Add(svcs ...*ServiceImpl) { + g.services = append(g.services, svcs...) +} + +// Run starts all services concurrently and blocks until a signal +// is received or the context is cancelled, then stops all services. +func (g *Group) Run() error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Initialize all services (parses flags, etc.) + for _, svc := range g.services { + svc.opts.Signal = false // group handles signals + svc.Init() + } + + g.logger.Logf(log.InfoLevel, "Starting service group with %d services", len(g.services)) + + // Start all services concurrently + errCh := make(chan error, len(g.services)) + for _, svc := range g.services { + g.logger.Logf(log.InfoLevel, "Starting [service] %s", svc.Name()) + if err := svc.Start(); err != nil { + // If any service fails to start, stop the ones that did start + cancel() + g.stopAll() + return err + } + } + + // Wait for signal or context cancellation + ch := make(chan os.Signal, 1) + signal.Notify(ch, signalutil.Shutdown()...) + + select { + case <-ch: + g.logger.Logf(log.InfoLevel, "Received signal, stopping all services") + case <-ctx.Done(): + case err := <-errCh: + cancel() + g.stopAll() + return err + } + + return g.stopAll() +} + +func (g *Group) stopAll() error { + var ( + mu sync.Mutex + lastErr error + ) + + var wg sync.WaitGroup + for _, svc := range g.services { + wg.Add(1) + go func(s *ServiceImpl) { + defer wg.Done() + g.logger.Logf(log.InfoLevel, "Stopping [service] %s", s.Name()) + if err := s.Stop(); err != nil { + mu.Lock() + lastErr = err + mu.Unlock() + } + }(svc) + } + wg.Wait() + + return lastErr +} diff --git a/service/options.go b/service/options.go index f4b4a406..b1f01925 100644 --- a/service/options.go +++ b/service/options.go @@ -54,14 +54,16 @@ type Option func(*Options) func newOptions(opts ...Option) Options { opt := Options{ - Auth: auth.DefaultAuth, - Broker: broker.DefaultBroker, - Cache: cache.DefaultCache, - Cmd: cmd.DefaultCmd, - Config: config.DefaultConfig, - Client: client.DefaultClient, - Server: server.DefaultServer, - Store: store.DefaultStore, + Auth: auth.DefaultAuth, + Broker: broker.DefaultBroker, + Cmd: cmd.NewCmd(), + Config: config.DefaultConfig, + // Per-service instances: each service gets its own server, client, + // store, and cache to allow multiple services in a single binary. + Client: client.NewClient(), + Server: server.NewRPCServer(), + Store: store.NewStore(), + Cache: cache.NewCache(), Registry: registry.DefaultRegistry, Transport: transport.DefaultTransport, Context: context.Background(), @@ -83,14 +85,12 @@ func Broker(b broker.Broker) Option { // Update Client and Server o.Client.Init(client.Broker(b)) o.Server.Init(server.Broker(b)) - broker.DefaultBroker = b } } func Cache(c cache.Cache) Option { return func(o *Options) { o.Cache = c - cache.DefaultCache = c } } @@ -104,7 +104,6 @@ func Cmd(c cmd.Cmd) Option { func Client(c client.Client) Option { return func(o *Options) { o.Client = c - client.DefaultClient = c } } @@ -138,7 +137,6 @@ func HandleSignal(b bool) Option { func Profile(p profile.Profile) Option { return func(o *Options) { o.Profile = p - profile.DefaultProfile = p } } @@ -146,7 +144,6 @@ func Profile(p profile.Profile) Option { func Server(s server.Server) Option { return func(o *Options) { o.Server = s - server.DefaultServer = s } } @@ -154,7 +151,6 @@ func Server(s server.Server) Option { func Store(s store.Store) Option { return func(o *Options) { o.Store = s - store.DefaultStore = s } } @@ -168,7 +164,6 @@ func Registry(r registry.Registry) Option { o.Server.Init(server.Registry(r)) // Update Broker o.Broker.Init(broker.Registry(r)) - broker.DefaultBroker = o.Broker } } @@ -184,8 +179,6 @@ func Tracer(t trace.Tracer) Option { func Auth(a auth.Auth) Option { return func(o *Options) { o.Auth = a - auth.DefaultAuth = a - } } @@ -193,7 +186,6 @@ func Auth(a auth.Auth) Option { func Config(c config.Config) Option { return func(o *Options) { o.Config = c - config.DefaultConfig = c } } @@ -201,7 +193,6 @@ func Config(c config.Config) Option { func Selector(s selector.Selector) Option { return func(o *Options) { o.Client.Init(client.Selector(s)) - selector.DefaultSelector = s } } @@ -213,7 +204,6 @@ func Transport(t transport.Transport) Option { // Update Client and Server o.Client.Init(client.Transport(t)) o.Server.Init(server.Transport(t)) - transport.DefaultTransport = t } } diff --git a/service/service.go b/service/service.go index 13d8973b..4c189631 100644 --- a/service/service.go +++ b/service/service.go @@ -14,26 +14,29 @@ import ( signalutil "go-micro.dev/v5/util/signal" ) -type service struct { +// ServiceImpl is the concrete service implementation. It is exported +// to allow the micro package to construct Groups, but users should +// generally interact through the Service interface. +type ServiceImpl struct { opts Options once sync.Once } -func New(opts ...Option) *service { - return &service{ +func New(opts ...Option) *ServiceImpl { + return &ServiceImpl{ opts: newOptions(opts...), } } -func (s *service) Name() string { +func (s *ServiceImpl) Name() string { return s.opts.Server.Options().Name } // Init initializes options. Additionally it calls cmd.Init // which parses command line flags. cmd.Init is only called // on first Init. -func (s *service) Init(opts ...Option) { +func (s *ServiceImpl) Init(opts ...Option) { // process options for _, o := range opts { o(&s.opts) @@ -69,23 +72,23 @@ func (s *service) Init(opts ...Option) { }) } -func (s *service) Options() Options { +func (s *ServiceImpl) Options() Options { return s.opts } -func (s *service) Client() client.Client { +func (s *ServiceImpl) Client() client.Client { return s.opts.Client } -func (s *service) Server() server.Server { +func (s *ServiceImpl) Server() server.Server { return s.opts.Server } -func (s *service) String() string { +func (s *ServiceImpl) String() string { return "micro" } -func (s *service) Start() error { +func (s *ServiceImpl) Start() error { for _, fn := range s.opts.BeforeStart { if err := fn(); err != nil { return err @@ -105,7 +108,7 @@ func (s *service) Start() error { return nil } -func (s *service) Stop() error { +func (s *ServiceImpl) Stop() error { var err error for _, fn := range s.opts.BeforeStop { @@ -123,13 +126,13 @@ func (s *service) Stop() error { return err } -func (s *service) Handle(v interface{}) error { +func (s *ServiceImpl) Handle(v interface{}) error { return s.opts.Server.Handle( s.opts.Server.NewHandler(v), ) } -func (s *service) Run() (err error) { +func (s *ServiceImpl) Run() (err error) { logger := s.opts.Logger // exit when help flag is provided