blob: dc99b66e8f063891e0cd873e09bf491ddb492f91 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package server
import (
"fmt"
"reflect"
"strconv"
"sync"
"sync/atomic"
"time"
)
import (
"github.com/creasty/defaults"
"github.com/dubbogo/gost/log/logger"
)
import (
"dubbo.apache.org/dubbo-go/v3/common"
commonCfg "dubbo.apache.org/dubbo-go/v3/common/config"
"dubbo.apache.org/dubbo-go/v3/common/constant"
"dubbo.apache.org/dubbo-go/v3/common/dubboutil"
aslimiter "dubbo.apache.org/dubbo-go/v3/filter/adaptivesvc/limiter"
"dubbo.apache.org/dubbo-go/v3/global"
"dubbo.apache.org/dubbo-go/v3/graceful_shutdown"
"dubbo.apache.org/dubbo-go/v3/internal"
"dubbo.apache.org/dubbo-go/v3/metrics/probe"
"dubbo.apache.org/dubbo-go/v3/protocol"
"dubbo.apache.org/dubbo-go/v3/protocol/base"
"dubbo.apache.org/dubbo-go/v3/registry"
"dubbo.apache.org/dubbo-go/v3/tls"
)
type ServerOptions struct {
Provider *global.ProviderConfig
Application *global.ApplicationConfig
Registries map[string]*global.RegistryConfig
Protocols map[string]*global.ProtocolConfig
Shutdown *global.ShutdownConfig
Metrics *global.MetricsConfig
Otel *global.OtelConfig
TLS *global.TLSConfig
}
func defaultServerOptions() *ServerOptions {
return &ServerOptions{
Application: global.DefaultApplicationConfig(),
Provider: global.DefaultProviderConfig(),
Shutdown: global.DefaultShutdownConfig(),
Metrics: global.DefaultMetricsConfig(),
Otel: global.DefaultOtelConfig(),
TLS: global.DefaultTLSConfig(),
}
}
// todo(DMwangnima): think about the timing to initialize Registry, Protocol, Tracing
func (srvOpts *ServerOptions) init(opts ...ServerOption) error {
for _, opt := range opts {
opt(srvOpts)
}
if err := defaults.Set(srvOpts); err != nil {
return err
}
providerConf := srvOpts.Provider
providerConf.RegistryIDs = commonCfg.TranslateIds(providerConf.RegistryIDs)
if len(providerConf.RegistryIDs) <= 0 {
providerConf.RegistryIDs = getRegistryIds(srvOpts.Registries)
}
if err := internal.ValidateRegistryIDs(providerConf.RegistryIDs, srvOpts.Registries); err != nil {
return err
}
providerConf.ProtocolIDs = commonCfg.TranslateIds(providerConf.ProtocolIDs)
if err := commonCfg.Verify(providerConf); err != nil {
return err
}
// enable adaptive service verbose
if providerConf.AdaptiveServiceVerbose {
if !providerConf.AdaptiveService {
return fmt.Errorf("the adaptive service is disabled, " +
"adaptive service verbose should be disabled either")
}
logger.Info("[Server] adaptive service verbose is enabled")
logger.Debug("[Server] debug-level info could be shown")
aslimiter.Verbose = true
}
// init graceful_shutdown
graceful_shutdown.Init(graceful_shutdown.SetShutdownConfig(srvOpts.Shutdown))
// init probe
if probeCfg := probe.BuildProbeConfig(srvOpts.Metrics.Probe); probeCfg != nil {
probe.Init(probeCfg)
}
return nil
}
type ServerOption func(*ServerOptions)
// ---------- For user ----------
// ========== LoadBalance Strategy ==========
// WithServerLoadBalanceConsistentHashing advertises consistent hashing as the default
// consumer load balancer for every service. Calls with the same configured arguments tend
// to reach the same provider while the provider set is stable. Use it when most services need
// cache or session affinity; service-level load-balancing options can override it.
func WithServerLoadBalanceConsistentHashing() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Loadbalance = constant.LoadBalanceKeyConsistentHashing
}
}
// WithServerLoadBalanceLeastActive advertises least-active load balancing as the default for
// consumers, favoring providers with fewer in-flight requests and using weight to break ties.
// Use it when request duration varies and busy instances should receive less work.
func WithServerLoadBalanceLeastActive() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Loadbalance = constant.LoadBalanceKeyLeastActive
}
}
// WithServerLoadBalanceRandom advertises weighted-random provider selection as the default
// consumer load-balancing policy. It is a low-overhead general default for statistically even
// traffic across services.
func WithServerLoadBalanceRandom() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Loadbalance = constant.LoadBalanceKeyRandom
}
}
// WithServerLoadBalanceRoundRobin advertises smooth weighted round-robin provider selection
// as the default consumer load-balancing policy. Use it when requests have similar cost and
// predictable per-instance traffic shares are desirable.
func WithServerLoadBalanceRoundRobin() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Loadbalance = constant.LoadBalanceKeyRoundRobin
}
}
// WithServerLoadBalanceP2C advertises P2C as the default consumer load balancer. Consumers
// sample two providers and favor the one reporting more remaining capacity. Use it together
// with WithServerAdaptiveService for capacity-aware routing.
func WithServerLoadBalanceP2C() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Loadbalance = constant.LoadBalanceKeyP2C
}
}
// WithServerLoadBalance advertises a registered load-balancing extension as the default for
// consumers. Use it for domain-specific provider placement; a service-level option overrides it.
func WithServerLoadBalance(lb string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Loadbalance = lb
}
}
// WithServerWarmUp gradually increases newly started providers' effective weight over the
// supplied duration, reducing traffic while caches and other resources warm up. Durations
// shorter than one second are truncated. Use it when cold instances cannot safely receive full
// traffic immediately; WithServerWarmup preserves sub-second duration strings.
func WithServerWarmUp(warmUp time.Duration) ServerOption {
return func(opts *ServerOptions) {
warmUpSec := int(warmUp / time.Second)
opts.Provider.Warmup = strconv.Itoa(warmUpSec)
}
}
// ========== Cluster Strategy ==========
// WithServerClusterAvailable tells consumers to invoke the first available provider without
// load balancing or retries by default. Use it only when any healthy provider is sufficient
// and balanced traffic is not required.
func WithServerClusterAvailable() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyAvailable
}
}
// WithServerClusterBroadcast tells consumers to invoke every provider sequentially by
// default and report an error if any provider fails. Use it for operations such as cache
// invalidation that intentionally run on every instance.
func WithServerClusterBroadcast() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyBroadcast
}
}
// WithServerClusterFailBack tells consumers to hide an initial failure and retry the call in
// the background with exponential backoff. Use it for best-effort notifications where eventual
// delivery matters more than returning the initial error.
func WithServerClusterFailBack() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyFailback
}
}
// WithServerClusterFailFast tells consumers to invoke once and return the error without
// retrying another provider. Use it for non-idempotent operations where duplicate execution
// would be more harmful than an immediate failure.
func WithServerClusterFailFast() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyFailfast
}
}
// WithServerClusterFailOver tells consumers to retry non-business failures on reselected
// providers. Use it for idempotent calls that should survive one unavailable instance;
// WithServerRetries controls the additional attempts after the first call.
func WithServerClusterFailOver() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyFailover
}
}
// WithServerClusterFailSafe tells consumers to log and suppress invocation failures,
// returning an empty result. Use it only for optional best-effort work, such as audit events,
// because callers cannot distinguish a suppressed failure from an empty success.
func WithServerClusterFailSafe() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyFailsafe
}
}
// WithServerClusterForking tells consumers to invoke multiple providers concurrently and
// return the first completed result. Use it for idempotent, latency-sensitive reads and accept
// the duplicate work and extra provider load it creates.
func WithServerClusterForking() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyForking
}
}
// WithServerClusterZoneAware tells consumers using multiple registries to prefer an explicitly
// preferred registry, then the request's zone, before falling back by registry weight. Use it
// for multi-region services that should keep traffic local while retaining fallback.
func WithServerClusterZoneAware() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyZoneAware
}
}
// WithServerClusterAdaptiveService advertises adaptive remaining-capacity routing to consumers.
// Consumers must also use P2C and providers must publish adaptive capacity metrics. Use it for
// services whose effective instance capacity changes significantly under load.
func WithServerClusterAdaptiveService() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = constant.ClusterKeyAdaptiveService
}
}
// WithServerCluster advertises a registered cluster extension as the default consumer fault
// handling policy. Use it for an application-specific failure policy; a service-level cluster
// option overrides it.
func WithServerCluster(cluster string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Cluster = cluster
}
}
// WithServerGroup publishes services in the supplied group by default, allowing multiple
// logical implementations of one interface to coexist. Use groups for environments, tenants,
// or alternate implementations; consumers must request the same group.
func WithServerGroup(group string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Group = group
}
}
// WithServerVersion publishes services under the supplied version by default. Consumers with
// another version cannot discover them. Use it during incompatible API migrations; a
// service-level WithVersion overrides this value.
func WithServerVersion(version string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Version = version
}
}
// WithServerJSON uses JSON as the default wire serialization for exported services. Consumers
// and the selected protocol must support JSON or requests cannot be decoded. Use it for
// interoperability when readable payloads matter more than compact binary encoding.
func WithServerJSON() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Serialization = constant.JSONSerialization
}
}
// WithServerToken requires consumers to present the same service token when the token provider
// filter is active. Use it for simple shared-secret protection and pair it with
// WithServerFilter("token") or a chain containing that filter.
func WithServerToken(token string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Token = token
}
}
// WithServerNotRegister prevents services from being published to registries by default while
// still allowing the server to listen. Use it for local tests or private fixed endpoints; such
// services must be reached by a direct URL.
func WithServerNotRegister() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.NotRegister = true
}
}
// WithServerWarmup gradually increases newly started providers' effective load-balancing
// weight over the supplied duration. Use it for cold-start protection when caches or connection
// pools need time to fill. It preserves values such as "500ms" in the provider URL.
func WithServerWarmup(warmupDuration time.Duration) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Warmup = warmupDuration.String()
}
}
// WithServerRetries advertises how many additional attempts retry-capable consumers may make
// after the initial call. Zero means one total attempt. Use retries only for idempotent services;
// service-level settings take precedence.
func WithServerRetries(retries int) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Retries = strconv.Itoa(retries)
}
}
// WithServerSerialization selects the default wire serialization by extension name. Consumers
// and the selected protocol must support the same serialization. Use it when both sides install
// the same non-default serialization extension.
func WithServerSerialization(ser string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Serialization = ser
}
}
// WithServerAccesslog enables provider access logging by default. A file path writes access
// records there; "true" or "default" sends them to the application logger. Logging is
// asynchronous and records may be dropped if its channel is full. Use it for request auditing
// or troubleshooting, accounting for payload visibility and storage cost.
func WithServerAccesslog(accesslog string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.AccessLog = accesslog
}
}
// WithServerTpsLimiter enables the named TPS limiter for services by default. An empty name
// disables TPS limiting; an unregistered name causes service validation to panic. Use it to
// protect provider capacity from bursts, together with rate, strategy, and rejection settings.
func WithServerTpsLimiter(limiter string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.TpsLimiter = limiter
}
}
// WithServerTpsLimitRate sets the default maximum request rate enforced by the selected TPS
// limiter. Use it to express the sustainable throughput of services. It has no effect until
// WithServerTpsLimiter selects a limiter.
func WithServerTpsLimitRate(rate int) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.TpsLimitRate = strconv.Itoa(rate)
}
}
// WithServerTpsLimitStrategy selects the registered rate-limiting strategy used by the default
// TPS limiter. Use it to choose how bursts are measured, such as a fixed or sliding window;
// an unregistered name causes service validation to panic.
func WithServerTpsLimitStrategy(strategy string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.TpsLimitStrategy = strategy
}
}
// WithServerTpsLimitRejectedHandler selects the handler invoked when the default TPS limit is
// exceeded. Use a custom handler to return a domain-specific error or fallback result;
// an unregistered name causes service validation to panic.
func WithServerTpsLimitRejectedHandler(rejHandler string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.TpsLimitRejectedHandler = rejHandler
}
}
// WithServerExecuteLimit caps concurrent in-flight provider invocations by default. The value
// must be an integer string; a negative value disables the cap, while an invalid value returns
// an empty result without invoking the service. Use it when concurrency, rather than request
// rate, is the scarce resource, such as a bounded database connection pool.
func WithServerExecuteLimit(exeLimit string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.ExecuteLimit = exeLimit
}
}
// WithServerExecuteLimitRejectedHandler selects the registered handler for calls rejected after
// the default in-flight limit is reached. Use it to return a specific overload response. If
// lookup fails, the call proceeds after a warning.
func WithServerExecuteLimitRejectedHandler(exeRejHandler string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.ExecuteLimitRejectedHandler = exeRejHandler
}
}
// WithServerAuth enables AK/SK request-signature verification for services by default when set
// to "true" and the provider filter chain contains "auth". Missing or invalid signatures are
// rejected before service execution. Use it when providers must authenticate calling applications;
// configure access-key storage and signing on both provider and consumer.
func WithServerAuth(auth string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Auth = auth
}
}
// WithServerParamSign includes request parameters in AK/SK signature verification by default
// when set to "true". Use it when signatures must detect parameter tampering; both sides must
// canonicalize the same values. It requires authentication and the "auth" filter.
func WithServerParamSign(paramSign string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.ParamSign = paramSign
}
}
// WithServerTag publishes services with the supplied routing tag by default, allowing tagged
// consumers to target this provider group. Use tags for canary, tenant, or hardware-specific
// pools without changing the service interface.
func WithServerTag(tag string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Tag = tag
}
}
// WithServerParam publishes one custom provider URL parameter for filters, routers, protocols,
// or extensions. Use it to configure an extension not covered by a typed option. A later call
// with the same key replaces the earlier value.
func WithServerParam(k, v string) ServerOption {
return func(opts *ServerOptions) {
if opts.Provider.Params == nil {
opts.Provider.Params = make(map[string]string)
}
opts.Provider.Params[k] = v
}
}
// WithServerFilter selects the comma-separated provider filter chain applied to incoming calls
// by default, in execution order. Use it for shared middleware such as authentication, metrics,
// or custom validation. A service-level WithFilter replaces this chain.
//
// todo(DMwangnima): change Filter Option like Cluster and LoadBalance
func WithServerFilter(filter string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.Filter = filter
}
}
// WithServerRegistryIDs limits service publication to the named registries by default. Each ID
// must match a registry added with WithServerRegistry or server initialization fails. Use it to
// publish all services to selected environments or regions when several registries exist.
//
// todo(DMwangnima): think about a more ideal configuration style
func WithServerRegistryIDs(registryIDs []string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.RegistryIDs = registryIDs
}
}
// WithServerRegistry makes a registry available for publishing services. Give each registry a
// distinct registry.WithID and use WithServerRegistryIDs to publish only to a subset. Configure
// shared registries here instead of repeating WithRegistry for every service.
func WithServerRegistry(opts ...registry.Option) ServerOption {
regOpts := registry.NewOptions(opts...)
return func(srvOpts *ServerOptions) {
if srvOpts.Registries == nil {
srvOpts.Registries = make(map[string]*global.RegistryConfig)
}
srvOpts.Registries[regOpts.ID] = regOpts.Registry
}
}
// WithServerProtocolIDs limits service export to the named server protocols by default. Each ID
// must match a protocol added with WithServerProtocol. Use it when the server listens on several
// endpoints but most services should be exposed through only a selected subset.
//
// todo(DMwangnima): think about a more ideal configuration style
func WithServerProtocolIDs(protocolIDs []string) ServerOption {
return func(opts *ServerOptions) {
opts.Provider.ProtocolIDs = protocolIDs
}
}
// WithServerProtocol configures a protocol endpoint on which services may be exported. Give
// each endpoint a distinct protocol.WithID when serving multiple protocols or ports. Configure
// shared listeners here, then choose them globally or per service with protocol IDs.
//
// For example, this exposes services on a named Triple endpoint:
//
// server.NewServer(
// server.WithServerProtocol(
// protocol.WithTriple(),
// protocol.WithID("triple"),
// protocol.WithPort(20000),
// ),
// server.WithServerProtocolIDs([]string{"triple"}),
// )
func WithServerProtocol(opts ...protocol.ServerOption) ServerOption {
proOpts := protocol.NewServerOptions(opts...)
return func(srvOpts *ServerOptions) {
if srvOpts.Protocols == nil {
srvOpts.Protocols = make(map[string]*global.ProtocolConfig)
}
srvOpts.Protocols[proOpts.ID] = proOpts.Protocol
}
}
// WithServerAdaptiveService enables provider-side capacity measurement and publishes the
// remaining-capacity metrics required by adaptive-service consumers. Use it with consumer-side
// adaptive cluster and P2C options when static provider weights do not reflect current load.
func WithServerAdaptiveService() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.AdaptiveService = true
}
}
// WithServerAdaptiveServiceVerbose enables detailed adaptive limiter diagnostics. Server
// initialization fails unless WithServerAdaptiveService is also enabled. Use it while tuning or
// diagnosing adaptive limits; verbose output may be too noisy for normal production operation.
func WithServerAdaptiveServiceVerbose() ServerOption {
return func(opts *ServerOptions) {
opts.Provider.AdaptiveServiceVerbose = true
}
}
// WithServerTLSOption configures credentials and peer verification for encrypted server
// connections. Use it for transport encryption and, when configured, mutual authentication.
// Clients must use compatible trust and certificate settings.
func WithServerTLSOption(opts ...tls.Option) ServerOption {
tlsOpts := tls.NewOptions(opts...)
return func(srvOpts *ServerOptions) {
if srvOpts.TLS == nil {
srvOpts.TLS = new(global.TLSConfig)
}
srvOpts.TLS = tlsOpts.TLSConf
}
}
// ========== For framework ==========
// These functions should not be invoked by users
// SetServerApplication assigns framework-loaded application configuration to ServerOptions.Application.
func SetServerApplication(application *global.ApplicationConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Application = application
}
}
// SetServerRegistries replaces ServerOptions.Registries with framework-loaded configuration.
// User code should prefer WithServerRegistry and WithServerRegistryIDs.
func SetServerRegistries(regs map[string]*global.RegistryConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Registries = regs
}
}
// SetServerProtocols replaces ServerOptions.Protocols with framework-loaded configuration.
// User code should prefer WithServerProtocol and WithServerProtocolIDs.
func SetServerProtocols(pros map[string]*global.ProtocolConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Protocols = pros
}
}
// SetServerShutdown assigns framework-loaded shutdown configuration to ServerOptions.Shutdown.
func SetServerShutdown(shutdown *global.ShutdownConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Shutdown = shutdown
}
}
// SetServerMetrics assigns framework-loaded metrics configuration to ServerOptions.Metrics.
func SetServerMetrics(metrics *global.MetricsConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Metrics = metrics
}
}
// SetServerOtel assigns framework-loaded OpenTelemetry configuration to ServerOptions.Otel.
func SetServerOtel(otel *global.OtelConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Otel = otel
}
}
// SetServerTLS assigns framework-loaded TLS configuration to ServerOptions.TLS.
// User code should prefer WithServerTLSOption.
func SetServerTLS(tls *global.TLSConfig) ServerOption {
return func(opts *ServerOptions) {
opts.TLS = tls
}
}
// SetServerProvider assigns framework-loaded provider defaults to ServerOptions.Provider.
func SetServerProvider(provider *global.ProviderConfig) ServerOption {
return func(opts *ServerOptions) {
opts.Provider = provider
}
}
// FIXME: ServiceOptions contains ServerOptions?
// Not ServerOptions contains ServiceOptions?
// we need to find a way to fix it.
type ServiceOptions struct {
Application *global.ApplicationConfig
Provider *global.ProviderConfig
Service *global.ServiceConfig
Registries map[string]*global.RegistryConfig
Protocols map[string]*global.ProtocolConfig
srvOpts *ServerOptions
Id string
unexported *atomic.Bool
exported *atomic.Bool
needExport bool
metadataType string
info *common.ServiceInfo
ProxyFactoryKey string
rpcService common.RPCService
cacheMutex sync.Mutex
cacheProtocol base.Protocol
exportersLock sync.Mutex
exporters []base.Exporter
adaptiveService bool
// for triple non-IDL mode
// consider put here or global.ServiceConfig
// string for url
//
// Deprecated: this implementation will be removed in the next release.
// The IDLMode switch will no longer be supported by dubbo-go.
IDLMode string
// openapi group for documentation
openapiGroup string
}
func defaultServiceOptions() *ServiceOptions {
return &ServiceOptions{
Service: global.DefaultServiceConfig(),
Application: global.DefaultApplicationConfig(),
unexported: new(atomic.Bool),
exported: new(atomic.Bool),
needExport: true,
}
}
func (svcOpts *ServiceOptions) init(srv *Server, opts ...ServiceOption) error {
for _, opt := range opts {
opt(svcOpts)
}
if err := defaults.Set(svcOpts); err != nil {
return err
}
svcOpts.srvOpts = srv.cfg
svc := svcOpts.Service
dubboutil.CopyFields(reflect.ValueOf(srv.cfg.Provider).Elem(), reflect.ValueOf(svc).Elem())
svcOpts.exported = new(atomic.Bool)
application := svcOpts.Application
if application != nil {
if svc.Group == "" {
svc.Group = application.Group
}
if svc.Version == "" {
svc.Version = application.Version
}
}
svcOpts.unexported = new(atomic.Bool)
// initialize Registries
if len(svc.RCRegistriesMap) == 0 {
svc.RCRegistriesMap = svcOpts.Registries
}
// initialize Protocols
if len(svc.RCProtocolsMap) == 0 {
svc.RCProtocolsMap = svcOpts.Protocols
}
svc.RegistryIDs = commonCfg.TranslateIds(svc.RegistryIDs)
if len(svc.RegistryIDs) <= 0 {
svc.RegistryIDs = svcOpts.Provider.RegistryIDs
}
if len(svc.RegistryIDs) <= 0 {
svc.NotRegister = true
} else if err := internal.ValidateRegistryIDs(svc.RegistryIDs, svc.RCRegistriesMap); err != nil {
return err
}
svc.ProtocolIDs = commonCfg.TranslateIds(svc.ProtocolIDs)
if len(svc.ProtocolIDs) <= 0 {
svc.ProtocolIDs = svcOpts.Provider.ProtocolIDs
}
if len(svc.ProtocolIDs) <= 0 {
for name := range svcOpts.Protocols {
svc.ProtocolIDs = append(svc.ProtocolIDs, name)
}
}
if svc.TracingKey == "" {
svc.TracingKey = svcOpts.Provider.TracingKey
}
for _, method := range svc.Methods {
if err := internal.ValidateMethodConfig(method); err != nil {
return err
}
}
err := svcOpts.check()
if err != nil {
panic(err)
}
svcOpts.needExport = true
return commonCfg.Verify(svcOpts)
}
type ServiceOption func(*ServiceOptions)
// ---------- For user ----------
// WithInterface publishes this service under the supplied discovery and routing identifier.
//
// As a functional option, it is passed to a service registration function
// (e.g., RegisterGreetServiceHandler) to configure the service's properties.
//
// The `interfaceName` acts as the unique identifier for this service in the registry.
// Clients (consumers) must use this exact name to discover and invoke the service.
//
// Usage:
//
// err := greet.RegisterGreetServiceHandler(
// srv,
// &GreetTripleServer{},
// server.WithInterface("com.your.company.GreetService"),
// )
func WithInterface(interfaceName string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Interface = interfaceName
}
}
// WithRegistryIDs publishes this service only to the named registries. Each ID must match a
// registry added with WithRegistry or inherited from the server, otherwise registration fails.
// Use it when one service belongs in a different environment or region from the server default.
//
// todo(DMwangnima): think about a more ideal configuration style
func WithRegistryIDs(registryIDs []string) ServiceOption {
return func(cfg *ServiceOptions) {
if len(registryIDs) > 0 {
cfg.Service.RegistryIDs = registryIDs
}
}
}
// WithFilter selects the comma-separated provider filter chain applied to incoming calls for
// this service, in execution order. Use it to add service-specific middleware such as "auth"
// or a custom validator. It replaces the server-level default filter chain.
//
// todo(DMwangnima): change Filter Option like Cluster and LoadBalance
func WithFilter(filter string) ServiceOption {
return func(cfg *ServiceOptions) {
cfg.Service.Filter = filter
}
}
// WithProtocolIDs exports this service only through the named protocol endpoints. Each ID must
// match a protocol added with WithProtocol or inherited from the server. Use it when this
// service should expose only Triple, Dubbo, or a dedicated listener.
//
// todo(DMwangnima): think about a more ideal configuration style
func WithProtocolIDs(protocolIDs []string) ServiceOption {
return func(cfg *ServiceOptions) {
if len(protocolIDs) > 0 {
cfg.Service.ProtocolIDs = protocolIDs
}
}
}
// ========== LoadBalance Strategy ==========
// WithLoadBalanceConsistentHashing tells consumers of this service to route calls with the same
// configured arguments to the same provider while the provider set is stable. Use it for
// per-user caches or session affinity; membership changes can remap some keys.
func WithLoadBalanceConsistentHashing() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Loadbalance = constant.LoadBalanceKeyConsistentHashing
}
}
// WithLoadBalanceLeastActive tells consumers to favor providers with fewer in-flight requests,
// using warm-up-adjusted weight when active counts are equal. Use it when this service has
// uneven request durations and busy instances should receive less new work.
func WithLoadBalanceLeastActive() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Loadbalance = constant.LoadBalanceKeyLeastActive
}
}
// WithLoadBalanceRandom tells consumers to choose providers randomly in proportion to their
// effective weight. Use it as a low-overhead general choice for statistically even traffic.
func WithLoadBalanceRandom() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Loadbalance = constant.LoadBalanceKeyRandom
}
}
// WithLoadBalanceRoundRobin tells consumers to distribute calls using smooth weighted
// round-robin selection. Use it when calls have similar cost and predictable instance shares
// are desirable.
func WithLoadBalanceRoundRobin() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Loadbalance = constant.LoadBalanceKeyRoundRobin
}
}
// WithLoadBalanceP2C tells consumers to sample two providers and favor the one reporting more
// remaining capacity. Use it with adaptive-service metrics when instance capacity changes
// dynamically under load.
func WithLoadBalanceP2C() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Loadbalance = constant.LoadBalanceKeyP2C
}
}
// WithLoadBalance advertises a registered load-balancing extension to consumers of this
// service. Use it for a domain-specific placement rule; it overrides the server-level default.
func WithLoadBalance(lb string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Loadbalance = lb
}
}
// WithWarmUp gradually increases this provider's effective weight over the supplied duration,
// reducing traffic after startup. Use it when this service needs to fill caches or pools before
// receiving full traffic. Durations shorter than one second are truncated.
func WithWarmUp(warmUp time.Duration) ServiceOption {
return func(opts *ServiceOptions) {
warmUpSec := int(warmUp / time.Second)
opts.Service.Warmup = strconv.Itoa(warmUpSec)
}
}
// ========== Cluster Strategy ==========
// WithClusterAvailable tells consumers to invoke the first available provider without load
// balancing or retries. Use it only when any healthy provider is sufficient and even traffic
// distribution is not required.
func WithClusterAvailable() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyAvailable
}
}
// WithClusterBroadcast tells consumers to invoke every provider sequentially and report an
// error if any provider fails. Use it for operations such as invalidating a cache on every
// instance; the operation should tolerate repeated calls.
func WithClusterBroadcast() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyBroadcast
}
}
// WithClusterFailBack tells consumers to hide an initial failure and retry in the background
// with exponential backoff, which suits eventual-delivery notifications.
func WithClusterFailBack() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyFailback
}
}
// WithClusterFailFast tells consumers to invoke once and return the error without retrying
// another provider. Use it for non-idempotent operations where duplicate execution would be
// more harmful than an immediate failure.
func WithClusterFailFast() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyFailfast
}
}
// WithClusterFailOver tells consumers to retry non-business failures on reselected providers.
// Use it for idempotent operations that should survive one unavailable instance. WithRetries
// controls the additional attempts after the first call.
func WithClusterFailOver() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyFailover
}
}
// WithClusterFailSafe tells consumers to log and suppress invocation failures, returning an
// empty result. Use it only for optional best-effort work because callers cannot distinguish a
// suppressed failure from an empty success.
func WithClusterFailSafe() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyFailsafe
}
}
// WithClusterForking tells consumers to invoke multiple providers concurrently and return the
// first completed result. Use it for idempotent, latency-sensitive reads and accept the
// duplicate work and extra provider load.
func WithClusterForking() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyForking
}
}
// WithClusterZoneAware tells consumers using multiple registries to prefer an explicitly
// preferred registry, then the request's zone, before falling back by registry weight. Use it
// for multi-region services that should keep traffic local while retaining fallback.
func WithClusterZoneAware() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyZoneAware
}
}
// WithClusterAdaptiveService tells consumers to route using remaining-capacity metrics. It
// requires P2C load balancing and providers with adaptive-service metrics enabled. Use it when
// this service's instance capacity varies significantly at runtime.
func WithClusterAdaptiveService() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = constant.ClusterKeyAdaptiveService
}
}
// WithCluster advertises a registered cluster extension as the consumer fault-handling policy
// for this service. Use it for a domain-specific failure policy; it overrides the server default.
func WithCluster(cluster string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Cluster = cluster
}
}
// WithGroup publishes this service in the supplied group, allowing multiple implementations
// of one interface to coexist. Use groups for environments, tenants, or alternate implementations;
// consumers requesting another group cannot discover it.
func WithGroup(group string) ServiceOption {
return func(cfg *ServiceOptions) {
cfg.Service.Group = group
}
}
// WithVersion publishes this service under the supplied version. Consumers requesting another
// version cannot discover it even when the interface and group match. Use it during incompatible
// API migrations so old and new providers can run concurrently.
func WithVersion(version string) ServiceOption {
return func(cfg *ServiceOptions) {
cfg.Service.Version = version
}
}
// WithJSON encodes this service's request and response payloads with JSON. Consumers and the
// selected protocol must support JSON or requests cannot be decoded. Use it for interoperability
// when readable payloads matter more than compact binary encoding.
func WithJSON() ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Serialization = constant.JSONSerialization
}
}
// WithToken requires consumers to present the same service token when the token provider filter
// is active. Use it for simple shared-secret protection and pair it with WithFilter("token") or
// a chain containing that filter.
func WithToken(token string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Token = token
}
}
// WithNotRegister keeps this service out of all registries while still exporting it on its
// protocol endpoint. Use it for tests, internal health services, or fixed private endpoints;
// consumers must use a direct URL to reach it.
func WithNotRegister() ServiceOption {
return func(cfg *ServiceOptions) {
cfg.Service.NotRegister = true
}
}
// WithWarmup gradually increases this provider's effective load-balancing weight over the
// supplied duration. Use it for cold-start protection while caches or pools initialize. It
// preserves values such as "500ms" in the provider URL.
func WithWarmup(warmupDuration time.Duration) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Warmup = warmupDuration.String()
}
}
// WithRetries advertises how many additional attempts retry-capable consumers may make after
// the initial call. Zero means one total attempt. Use retries only for idempotent service methods.
func WithRetries(retries int) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Retries = strconv.Itoa(retries)
}
}
// WithSerialization selects this service's wire serialization by extension name. Consumers
// and the selected protocol must support the same serialization. Use it when both sides install
// the same non-default serialization extension.
func WithSerialization(ser string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Serialization = ser
}
}
// WithAccesslog enables access logging for this service. A file path writes records there;
// "true" or "default" sends them to the application logger. Logging is asynchronous and
// records may be dropped if its channel is full. Use it for auditing or troubleshooting while
// accounting for payload visibility and storage cost.
func WithAccesslog(accesslog string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.AccessLog = accesslog
}
}
// WithTpsLimiter enables the named requests-per-second limiter for this service. An empty name
// disables TPS limiting; an unregistered name causes service validation to panic. Use it to
// protect this provider from request bursts, together with rate and rejection settings. For
// example, use WithTpsLimiter("default") and WithTpsLimitRate(100) to allow 100 requests per
// configured limiter interval before invoking the default rejection handler.
func WithTpsLimiter(limiter string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.TpsLimiter = limiter
}
}
// WithTpsLimitRate sets the maximum request rate enforced by this service's selected TPS
// limiter. Set it to the service's sustainable throughput; it has no effect until WithTpsLimiter
// selects a limiter.
func WithTpsLimitRate(rate int) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.TpsLimitRate = strconv.Itoa(rate)
}
}
// WithTpsLimitStrategy selects the registered rate-limiting strategy used for this service.
// Use it to choose how bursts are measured, such as a fixed or sliding window. An unregistered
// name causes service validation to panic.
func WithTpsLimitStrategy(strategy string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.TpsLimitStrategy = strategy
}
}
// WithTpsLimitRejectedHandler selects the handler invoked when this service exceeds its TPS
// limit. Use a custom handler to return a domain-specific overload error or fallback result;
// an unregistered name causes service validation to panic.
func WithTpsLimitRejectedHandler(rejHandler string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.TpsLimitRejectedHandler = rejHandler
}
}
// WithExecuteLimit caps concurrent in-flight invocations of this service. The value must be an
// integer string; a negative value disables the cap, while an invalid value returns an empty
// result without invoking the service. Use it when concurrency is bounded by resources such as
// database connections. For example, WithExecuteLimit("32") allows at most 32 simultaneous
// invocations; method-level execution limits can override it.
func WithExecuteLimit(exeLimit string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.ExecuteLimit = exeLimit
}
}
// WithExecuteLimitRejectedHandler selects the registered handler for calls rejected after this
// service's in-flight limit is reached. Use it to return a specific overload response. If lookup
// fails, the call proceeds after a warning.
func WithExecuteLimitRejectedHandler(exeRejHandler string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.ExecuteLimitRejectedHandler = exeRejHandler
}
}
// WithAuth enables AK/SK request-signature verification when set to "true" and the provider
// filter chain contains "auth". Use it when this service must authenticate calling applications;
// missing or invalid signatures are rejected before execution. Enable both parts with
// WithFilter("auth") and WithAuth("true"), then configure compatible access-key storage and
// consumer signing.
func WithAuth(auth string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Auth = auth
}
}
// WithParamSign includes request parameters in AK/SK signature verification when set to
// "true". Use it when the signature must also detect parameter tampering. It only has an effect
// when WithAuth and the "auth" provider filter are enabled on both sides.
func WithParamSign(paramSign string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.ParamSign = paramSign
}
}
// WithTag publishes this service instance with the supplied routing tag, allowing tagged
// consumers to select it. Use tags for canary, tenant, or hardware-specific pools without
// changing the interface, group, or version.
func WithTag(tag string) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service.Tag = tag
}
}
// WithProtocol configures a protocol endpoint available to this service. Give each endpoint a
// distinct protocol.WithID and use WithProtocolIDs when the service should use only a subset.
// Use it for a listener needed only by this service; shared listeners belong on NewServer.
func WithProtocol(opts ...protocol.ServerOption) ServiceOption {
proOpts := protocol.NewServerOptions(opts...)
return func(opts *ServiceOptions) {
if opts.Protocols == nil {
opts.Protocols = make(map[string]*global.ProtocolConfig)
}
opts.Protocols[proOpts.ID] = proOpts.Protocol
}
}
// WithRegistry makes a registry available for publishing this service. Give each registry a
// distinct registry.WithID and use WithRegistryIDs to publish only to a subset. Use it for a
// service-specific registry; shared registries are normally configured on NewServer.
func WithRegistry(opts ...registry.Option) ServiceOption {
regOpts := registry.NewOptions(opts...)
return func(opts *ServiceOptions) {
if opts.Registries == nil {
opts.Registries = make(map[string]*global.RegistryConfig)
}
opts.Registries[regOpts.ID] = regOpts.Registry
}
}
// WithMethod adds method-specific behavior such as timeout, retries, or execution limits.
// Method settings take precedence over the corresponding service defaults. Use it when one
// method is slower, non-idempotent, or has a different capacity limit from the rest.
func WithMethod(method *global.MethodConfig) ServiceOption {
return func(opts *ServiceOptions) {
if method == nil {
return
}
if opts.Service.Methods == nil {
opts.Service.Methods = make([]*global.MethodConfig, 0)
}
opts.Service.Methods = append(opts.Service.Methods, method)
}
}
// WithParam publishes one custom service URL parameter for filters, routers, protocols, or
// extensions. A later call with the same key replaces the earlier value.
func WithParam(k, v string) ServiceOption {
return func(opts *ServiceOptions) {
if opts.Service.Params == nil {
opts.Service.Params = make(map[string]string)
}
opts.Service.Params[k] = v
}
}
// WithOpenAPIGroup places this service's generated operations in the supplied OpenAPI group,
// allowing related services to be presented together in generated API documentation. Use the
// same group for APIs that should appear as one logical section to documentation consumers.
func WithOpenAPIGroup(group string) ServiceOption {
return func(opts *ServiceOptions) {
opts.openapiGroup = group
}
}
// WithIDLMode sets ServiceOptions.IDLMode for legacy services.
//
// TODO: remove when config package is removed
//
// Deprecated: this option will be removed in the next version. The IDL mode
// switch is no longer supported by dubbo-go.
func WithIDLMode(IDLMode string) ServiceOption {
return func(opts *ServiceOptions) {
opts.IDLMode = IDLMode
}
}
// ----------For framework----------
// These functions should not be invoked by users
// SetApplication assigns framework-loaded application configuration to ServiceOptions.Application.
func SetApplication(application *global.ApplicationConfig) ServiceOption {
return func(opts *ServiceOptions) {
opts.Application = application
}
}
// SetProvider assigns framework-loaded provider configuration to ServiceOptions.Provider.
func SetProvider(provider *global.ProviderConfig) ServiceOption {
return func(opts *ServiceOptions) {
opts.Provider = provider
}
}
// SetService assigns framework-loaded service configuration to ServiceOptions.Service.
func SetService(service *global.ServiceConfig) ServiceOption {
return func(opts *ServiceOptions) {
opts.Service = service
}
}
// SetRegistries replaces ServiceOptions.Registries with framework-loaded configuration.
// User code should prefer WithRegistry and WithRegistryIDs.
func SetRegistries(regs map[string]*global.RegistryConfig) ServiceOption {
return func(opts *ServiceOptions) {
opts.Registries = regs
}
}
// SetProtocols replaces ServiceOptions.Protocols with framework-loaded configuration.
// User code should prefer WithProtocol and WithProtocolIDs.
func SetProtocols(pros map[string]*global.ProtocolConfig) ServiceOption {
return func(opts *ServiceOptions) {
opts.Protocols = pros
}
}