invocation

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: MIT Imports: 12 Imported by: 2

README

Invocation

invocation 包定义 Firefly 当前唯一推荐的服务调用模型。

它只解决四件事:

  • 业务侧如何声明一个远程业务服务的标准 DNS
  • 如何把 DNS 组装成稳定的 gRPC target
  • 如何复用 grpc.ClientConn
  • 如何统一传递 metadata

不再负责:

  • 实例发现
  • 节点选择
  • Consul / K8s 后端适配
  • endpoint 轮询

核心理念

业务服务之间的调用,本质上就是面向一个稳定的业务服务 DNS。

例如:

auth.default.svc.cluster.local:9090

含义如下:

  • auth:业务服务名
  • default:命名空间
  • svc:Kubernetes Service 类型片段
  • cluster.local:Cluster Domain
  • 9090:业务服务端口

业务代码只需要表达这份 DNS 结构。

后续流量如何命中实例:

  • 裸机环境交给 sidecar-agent
  • 云原生环境交给 K8s

当前模型

ServiceDNS

ServiceDNS 表示业务服务的标准 DNS 配置。

它直接描述:

  • service
  • namespace
  • service_type
  • cluster_domain
  • port

当前推荐直接使用 ServiceDNS 字面量。

除非后续出现稳定且跨仓库复用的构造规则,否则不建议再额外包一层 ServiceDNS builder 或 option helper。

DNSManager

DNSManager 只负责补齐默认值并构造最终 Target

它不会做:

  • service 校验
  • endpoint 拉取
  • 实例选择
ConnectionManager

ConnectionManager 负责:

  • 基于 ServiceDNS 构造 Target
  • 按最终 gRPC target 缓存连接
  • 统一挂载 gRPC client dial options
UnaryInvoker

UnaryInvoker 负责:

  • 取连接
  • 注入调用 metadata
  • 发起真实 gRPC unary 调用

当前代码组织上,DialerInvokerUnaryInvoker 已统一收口在 invoker.go,避免把很薄的契约层单独拆成一个文件。

RemoteServiceCaller

RemoteServiceCaller 是在 UnaryInvoker 之上提供的一层薄封装。

它解决的问题不是“替代 gRPC”,而是把业务 repo 里重复出现的这组样板收口:

  • 绑定一个远程业务服务的 ServiceDNS
  • 复用同一个 UnaryInvoker
  • 让 repo 方法只保留 full method + req + resp

它绑定的是“远程业务服务”,不是某个 proto 子服务。

当前推荐直接通过 NewRemoteServiceCaller(...) 完成标准装配。

Invoke Options

当前调用侧只保留两类显式参数:

  • WithMetadata(...):补充出站 metadata
  • WithTimeout(...):设置本次调用 timeout

注意:

  • 调用选项不负责从 ServiceContext 推导字段
  • 用户相关字段应沿入站 metadata 透传,不允许由业务侧覆写
  • 若当前请求上下文中已带有入站 metadata,UnaryInvoker 会在调用时直接复用

一个业务服务多个 proto 子服务

这是当前模型里的重要约束:

  • 一个远程业务服务只维护一份 ServiceDNS
  • 同一个业务服务下的多个 proto 子服务,共用同一份 DNS 和连接
  • 具体调用哪个子服务,由 gRPC full method 决定

例如:

  • 远程业务服务:auth
  • proto 子服务:
    • AuthAppService
    • AuthUserService
    • AuthPermissionService

这些调用都应该共用:

auth.default.svc.cluster.local:9090

推荐接入方式

业务服务应在自己的 internal/data/rs_*.go 中,按“远程业务服务”聚合配置。

推荐做法:

  • New*Repo 中声明该 repo 依赖哪个远程业务服务
  • 为该远程业务服务组装一份 ServiceDNS
  • 复用同一份 ConnectionManager / UnaryInvoker
  • 通过不同 full method 区分具体 proto 子服务

不推荐做法:

  • 按每个 proto 子服务单独维护一份远程地址配置
  • 在调用侧做实例发现
  • 在调用侧感知 Consul / K8s 细节

为什么不是直接 grpc client

generated gRPC client 本身没有问题,但它更适合解决:

  • 我已经拿到了 grpc.ClientConn
  • 我已经知道要调哪个 stub client
  • 我现在只需要发起 RPC

而 invocation 当前要统一的是另一层语义:

  • 业务服务 DNS 如何表达
  • 连接如何统一复用
  • metadata 如何统一透传
  • OTel 如何统一接入

如果继续让每个 repo 直接面向 generated gRPC client:

  • 上下文构造容易散在不同 repo 中
  • metadata 透传容易出现不一致
  • 调用前的统一能力不好收口

因此当前推荐是:

  • UnaryInvoker 作为底层统一调用器
  • RemoteServiceCaller 作为远程业务服务级别的薄封装
  • generated gRPC client 不作为内部统一调用主模型

为什么不再提供默认上下文拼装 helper

根据最新边界约束:

  • ServiceContext 仅供服务内读取,不反向成为出站调用参数来源
  • IncomingContext / OutgoingContext 只是 transport metadata 语义,不在 invocation 中额外落成公共上下文对象
  • 服务调用时若需要沿链路透传上下文,本质上应直接复用 metadata,而不是从服务内上下文对象反向拼装

服务端边界

invocation 只负责出站调用语义。

服务端入站 metadata 解析与主上下文注入由 middleware/grpc 负责,服务内读取入口由 service 包负责:

  • gm.NewServiceContextUnaryInterceptor(...)
  • service.FromContext(...)

示例

package example

import (
	"context"
	"time"

	"github.com/fireflycore/go-micro/invocation"
)

func Example() error {
	dnsManager := invocation.NewDNSManager(&invocation.DNSConfig{
		DefaultNamespace: "default",
		DefaultPort:      9090,
	})

	manager, err := invocation.NewConnectionManager(invocation.ConnectionManagerOptions{
		DNSManager: dnsManager,
	})
	if err != nil {
		return err
	}
	defer func() { _ = manager.Close() }()

	invoker := &invocation.UnaryInvoker{
		Dialer: manager,
	}

	caller := invocation.NewRemoteServiceCaller(
		invoker,
		&invocation.ServiceDNS{
			Service: "auth",
		},
	)

	return caller.Invoke(
		context.Background(),
		"/acme.auth.app.v1.AuthAppService/GetAppSecret",
		&struct{}{},
		&struct{}{},
		invocation.WithTimeout(3*time.Second),
	)
}

观测约定

invocation 默认和 go-micro 的 OTel 链路保持一致:

  • gRPC client 默认挂 otelgrpc
  • 出站 metadata 由 UnaryInvoker 基于继承 metadata 与显式调用选项统一构造

设计约束

  • 业务侧只表达业务服务 DNS,不表达实例选择逻辑
  • invocation 只保留通用调用语义,不承载后端专属实现
  • RemoteServiceCaller 只做样板代码收口,不替代 UnaryInvoker
  • go-consul/invocationgo-k8s/invocation 不再作为主路径保留

Documentation

Overview

Package invocation 定义面向 service-to-service 调用模型的核心错误。

Index

Constants

View Source
const (
	// DefaultServiceType 是 Kubernetes Service FQDN 中的固定服务类型片段。
	DefaultServiceType = "svc"
	// DefaultClusterDomain 是 Kubernetes 集群默认的 Cluster Domain。
	DefaultClusterDomain = "cluster.local"
	// DefaultResolverScheme 是 gRPC 默认推荐使用的 DNS resolver scheme。
	DefaultResolverScheme = "dns"
	// DefaultServicePort 是业务服务默认使用的 gRPC 端口。
	DefaultServicePort = 9090
)

Variables

View Source
var (
	// ErrServiceNameEmpty 表示服务名为空,无法构造逻辑服务身份。
	ErrServiceNameEmpty = errors.New("service name is empty")
	// ErrNamespaceEmpty 表示命名空间为空。
	ErrNamespaceEmpty = errors.New("namespace is empty")
	// ErrTargetHostEmpty 表示目标主机为空,无法生成最终拨号地址。
	ErrTargetHostEmpty = errors.New("target host is empty")
	// ErrTargetPortInvalid 表示端口既未显式提供,也无法从默认值中补齐。
	ErrTargetPortInvalid = errors.New("target port is invalid")
	// ErrDNSManagerIsNil 表示 DNS 管理器为空。
	ErrDNSManagerIsNil = errors.New("dns manager is nil")
	// ErrDialFnIsNil 表示底层拨号函数为空。
	ErrDialFnIsNil = errors.New("dial function is nil")
	// ErrConnectionManagerClosed 表示连接管理器已经关闭,不能再创建新连接。
	ErrConnectionManagerClosed = errors.New("connection manager is closed")
	// ErrInvokerDialerIsNil 表示调用器缺少拨号器依赖。
	ErrInvokerDialerIsNil = errors.New("invoker dialer is nil")
	// ErrInvokeMethodEmpty 表示调用方法名为空。
	ErrInvokeMethodEmpty = errors.New("invoke method is empty")
)

Functions

func DefaultDialFunc

func DefaultDialFunc(_ context.Context, target Target, options []grpc.DialOption) (*grpc.ClientConn, error)

DefaultDialFunc 是默认的 grpc.ClientConn 创建逻辑。

当前实现采用 grpc.NewClient,并默认启用: - insecure credentials:便于在内部受控网络中快速起步; - otelgrpc client handler:保证调用链路自动接入 OTel。

后续若需要在具体实现中启用 mTLS、自定义 resolver 或更多 dial option, 可以通过 ConnectionManagerOptions 覆盖该行为。

func NewOutgoingCallContext added in v1.4.0

func NewOutgoingCallContext(parent context.Context, md metadata.MD, timeout time.Duration) (context.Context, context.CancelFunc)

NewOutgoingCallContext 基于父 context、metadata 和 timeout 构造新的 gRPC 出站 context。

这里保留父 context 的取消信号与已有 deadline, 避免像旧实现那样切断上游取消传播。

Types

type ConnectionManager

type ConnectionManager struct {
	// contains filtered or unexported fields
}

ConnectionManager 负责缓存基于 ServiceDNS 创建出的 grpc.ClientConn。

它把“业务服务 DNS -> 目标解析 -> 连接缓存”统一收敛在一处, 让业务层无需关心: - target 拼装; - resolver scheme; - 拨号选项; - 多次调用的连接复用。

func NewConnectionManager

func NewConnectionManager(options ConnectionManagerOptions) (*ConnectionManager, error)

NewConnectionManager 创建连接管理器。

func (*ConnectionManager) Close

func (m *ConnectionManager) Close() error

Close 关闭连接管理器及其持有的全部 grpc.ClientConn。

Close 会尽最大努力关闭所有连接; 若中途出现错误,当前实现返回第一条错误并继续关闭剩余连接。

func (*ConnectionManager) Dial

func (m *ConnectionManager) Dial(ctx context.Context, service *ServiceDNS) (*grpc.ClientConn, error)

Dial 根据 ServiceDNS 获取或创建对应的 grpc.ClientConn。

连接缓存键采用最终 gRPC target,而不是 ServiceDNS 原始字段, 这样可以保证: - 逻辑上等价的服务身份只会生成一条连接; - 端口覆盖、cluster domain、resolver scheme 的变化都能体现在缓存键上。

type ConnectionManagerOptions

type ConnectionManagerOptions struct {
	// DNSManager 用于把业务服务 DNS 描述解析为最终 Target。
	DNSManager *DNSManager
	// DialFunc 用于创建新的 grpc.ClientConn。
	// 若为空,则使用默认拨号实现。
	DialFunc DialFunc
	// DialOptions 表示创建 grpc.ClientConn 时使用的附加选项。
	DialOptions []grpc.DialOption
}

ConnectionManagerOptions 定义连接管理器的配置。

type DNSConfig added in v1.3.7

type DNSConfig struct {
	// DefaultNamespace 表示默认命名空间。
	DefaultNamespace string
	// DefaultServiceType 表示默认服务类型片段。
	DefaultServiceType string
	// DefaultClusterDomain 表示默认集群域名。
	DefaultClusterDomain string
	// DefaultPort 表示默认端口。
	DefaultPort uint16
	// ResolverScheme 表示默认 gRPC resolver scheme。
	ResolverScheme string
}

DNSConfig 定义标准 DNS 管理器的默认行为。

type DNSManager added in v1.3.7

type DNSManager struct {
	// contains filtered or unexported fields
}

DNSManager 负责把结构化的 ServiceDNS 转成最终 Target。

它只做一件事:组装标准 DNS。 它不做实例发现、不做节点选择,也不做后端适配。

func NewDNSManager added in v1.3.7

func NewDNSManager(config *DNSConfig) *DNSManager

NewDNSManager 创建一个标准 DNS 管理器。

func (*DNSManager) Build added in v1.3.7

func (m *DNSManager) Build(service *ServiceDNS) (*Target, error)

Build 根据 ServiceDNS 构造最终 Target。

func (*DNSManager) Config added in v1.3.7

func (m *DNSManager) Config() *DNSConfig

Config 返回当前管理器的规范化配置副本。

func (*DNSManager) Normalize added in v1.3.7

func (m *DNSManager) Normalize(service *ServiceDNS) *ServiceDNS

Normalize 用默认配置补齐业务服务 DNS。

type DialFunc

type DialFunc func(ctx context.Context, target Target, options []grpc.DialOption) (*grpc.ClientConn, error)

DialFunc 表示底层拨号函数。

把拨号过程抽象成函数有两个目的: 1. 让 ConnectionManager 在不依赖具体后端的情况下复用连接; 2. 让单元测试可以替换真实拨号逻辑,避免触发真实网络连接。

type Dialer

type Dialer interface {
	// Dial 根据业务服务 DNS 返回可复用的 gRPC 连接。
	Dial(ctx context.Context, service *ServiceDNS) (*grpc.ClientConn, error)
	// Close 释放 Dialer 持有的底层资源。
	Close() error
}

Dialer 定义“如何把业务服务 DNS 变成 grpc.ClientConn”。

在新模型中,Dialer 只关心: - 标准 DNS target 组装结果; - 连接复用; - gRPC 连接创建。

type InvokeOption

type InvokeOption func(*InvokeOptions)

InvokeOption 表示单个调用选项。

func WithCallOptions

func WithCallOptions(callOptions ...grpc.CallOption) InvokeOption

WithCallOptions 追加底层 gRPC CallOption。

func WithMetadata added in v1.4.0

func WithMetadata(md metadata.MD) InvokeOption

WithMetadata 追加本次调用显式补充的出站 metadata。

func WithTimeout added in v1.4.0

func WithTimeout(timeout time.Duration) InvokeOption

WithTimeout 设置本次调用的显式超时时间。

type InvokeOptions

type InvokeOptions struct {
	// Metadata 表示本次调用显式补充的出站 metadata。
	//
	// 用户身份等受保护字段不会被业务侧显式值覆写,
	// 最终仍以当前链路已存在的 metadata 为准。
	Metadata metadata.MD
	// Timeout 表示本次调用的显式超时时间。
	Timeout time.Duration
	// CallOptions 表示附加的 gRPC CallOption。
	CallOptions []grpc.CallOption
}

InvokeOptions 表示一次调用的附加配置。

type Invoker

type Invoker interface {
	// Invoke 对指定远程业务服务发起一次标准 unary 调用。
	Invoke(ctx context.Context, service *ServiceDNS, method string, req any, resp any, options ...InvokeOption) error
}

Invoker 定义统一调用入口。

与业务代码直接操作 grpc.ClientConn 不同, Invoker 允许框架在调用前统一完成: - 业务服务 DNS 目标解析; - metadata 注入; - 底层连接复用。

type RemoteServiceCaller added in v1.3.8

type RemoteServiceCaller struct {
	// Service 表示当前远程业务服务的标准 DNS 身份。
	Service *ServiceDNS
	// Invoker 负责统一的连接获取、metadata 注入和实际调用。
	//
	// 当前约束下 RemoteServiceCaller 只是一个薄封装,
	// 真正的调用行为仍全部落在 UnaryInvoker 上。
	Invoker *UnaryInvoker
}

RemoteServiceCaller 表示一个远程业务服务的通用调用入口。

这个对象绑定的是“远程业务服务”而不是“某个 proto 子服务”。 因此一个业务服务下多个 proto 子服务,应共用同一个 RemoteServiceCaller。

例如: - auth 业务服务

  • AuthAppService
  • AuthUserService
  • AuthPermissionService

这些子服务都应共用一份: - ServiceDNS - ConnectionManager - UnaryInvoker

func NewRemoteServiceCaller added in v1.3.9

func NewRemoteServiceCaller(invoker *UnaryInvoker, service *ServiceDNS) *RemoteServiceCaller

NewRemoteServiceCaller 创建一个标准的远程业务服务调用器。

这个构造函数的目标是把业务侧最常见的装配模板统一收口: - 指定远程业务服务 DNS; - 指定统一复用的 UnaryInvoker。

func (*RemoteServiceCaller) Invoke added in v1.3.8

func (c *RemoteServiceCaller) Invoke(ctx context.Context, method string, req any, resp any, options ...InvokeOption) error

Invoke 对当前绑定的远程业务服务发起一次 unary 调用。

调用方只需要提供: - full method - request - response

其余通用逻辑由 ServiceDNS 与 UnaryInvoker 统一处理。

type ServiceDNS added in v1.3.7

type ServiceDNS struct {
	// Service 表示业务服务名,例如 auth。
	Service string `json:"service"`
	// Namespace 表示命名空间,例如 default。
	Namespace string `json:"namespace"`
	// ServiceType 表示服务类型片段,默认值为 svc。
	ServiceType string `json:"service_type"`
	// ClusterDomain 表示集群域,默认值为 cluster.local。
	ClusterDomain string `json:"cluster_domain"`
	// Port 表示业务服务监听端口,默认值为 9090。
	Port uint16 `json:"port"`
}

ServiceDNS 表示一个业务服务的标准 DNS 配置。

这里表达的是“这个业务服务在网络上的稳定入口”, 而不是“这个服务当前有哪些实例”。

例如: - service: auth - namespace: default - service_type: svc - cluster_domain: cluster.local - port: 9090

最终会得到: - host: auth.default.svc.cluster.local - address: auth.default.svc.cluster.local:9090

func (ServiceDNS) ClusterDomainName added in v1.3.7

func (s ServiceDNS) ClusterDomainName() string

ClusterDomainName 返回清理空格后的集群域名。

func (ServiceDNS) EffectivePort added in v1.3.7

func (s ServiceDNS) EffectivePort(defaultPort uint16) (uint16, error)

EffectivePort 返回当前 ServiceDNS 的最终端口。

func (ServiceDNS) NamespaceName added in v1.3.7

func (s ServiceDNS) NamespaceName() string

NamespaceName 返回清理空格后的命名空间。

func (ServiceDNS) ServiceName added in v1.3.7

func (s ServiceDNS) ServiceName() string

ServiceName 返回清理空格后的服务名。

func (ServiceDNS) ServiceTypeName added in v1.3.7

func (s ServiceDNS) ServiceTypeName() string

ServiceTypeName 返回清理空格后的服务类型。

func (ServiceDNS) Validate added in v1.3.7

func (s ServiceDNS) Validate() error

Validate 检查 ServiceDNS 是否具备生成稳定 DNS 的最小字段。

func (ServiceDNS) WithPort added in v1.3.7

func (s ServiceDNS) WithPort(port uint16) ServiceDNS

WithPort 返回带显式端口的新 ServiceDNS。

type Target

type Target struct {
	// ResolverScheme 表示 gRPC resolver scheme,例如 dns。
	ResolverScheme string `json:"resolver_scheme"`
	// Host 表示服务的标准主机名。
	Host string `json:"host"`
	// Port 表示服务端口。
	Port uint16 `json:"port"`
}

Target 表示最终可拨号的 gRPC 服务目标。

它已经不表达实例列表,只表达一个稳定的服务入口。

func BuildTarget

func BuildTarget(service *ServiceDNS, config *DNSConfig) (*Target, error)

BuildTarget 是兼容保留的辅助函数。

新代码建议直接使用 DNSManager.Build。

func (Target) Address

func (t Target) Address() string

Address 返回标准的 host:port 地址。

func (Target) GRPCTarget

func (t Target) GRPCTarget() string

GRPCTarget 返回适合 grpc.NewClient 使用的 target 字符串。

func (Target) Validate

func (t Target) Validate() error

Validate 检查 Target 是否可以用于真实拨号。

type UnaryInvokeFunc

type UnaryInvokeFunc func(ctx context.Context, conn *grpc.ClientConn, method string, req any, resp any, options ...grpc.CallOption) error

UnaryInvokeFunc 表示底层 unary 调用函数。

通过把真实调用抽象成函数, 可以让 UnaryInvoker 在测试中替换掉真实的 grpc.ClientConn.Invoke。

type UnaryInvoker

type UnaryInvoker struct {
	// Dialer 负责连接获取与复用。
	Dialer Dialer
	// InvokeFunc 是可选依赖;若为空,则默认调用 grpc.ClientConn.Invoke。
	InvokeFunc UnaryInvokeFunc
}

UnaryInvoker 是默认的 Invoker 实现。

它的执行流程非常明确: 1. 基于 ServiceDNS 获取连接; 2. 基于继承 metadata 与显式调用选项构造统一出站上下文; 3. 使用 grpc.ClientConn.Invoke 发起 unary 调用。

func (*UnaryInvoker) Invoke

func (u *UnaryInvoker) Invoke(ctx context.Context, service *ServiceDNS, method string, req any, resp any, options ...InvokeOption) error

Invoke 执行一次标准 unary 调用。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL