invocation

package
v1.3.2 Latest Latest
Warning

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

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

README

Invocation

invocation 包定义了 Firefly 新一代的服务调用模型。

它不再围绕“节点注册中心”组织能力,而是围绕下面几个问题组织能力:

  • 我要调用哪个服务
  • 如何把服务身份解析为最终目标
  • 如何复用连接
  • 如何统一注入 metadata
  • 如何在调用前接入 Authz

包定位

invocation 是新的主路径能力,适合承载:

  • K8s + Istio 标准实现
  • etcd / consul 轻量实现
  • 统一的 service -> service 调用模型
  • 与 OTel 对齐的统一调用观测模型

它不再承担旧 registry 中的中心语义,例如:

  • ServiceNode
  • Network
  • Discovery(method -> nodes)

核心概念

ServiceRef

ServiceRef 用于表达一次调用面向的服务身份。

当前字段包括:

  • Service
  • Namespace
  • Env
  • Port

其中:

  • Service + Namespace 主要参与目标地址生成
  • Env 更偏向环境与策略域语义
  • Port 作为可选覆盖项存在,默认建议由核心库补齐
Target

Target 表示最终可拨号目标。

默认构造规则是:

<service>.<namespace>.svc.cluster.local:<port>

例如:

auth.default.svc.cluster.local:9000
ServiceEndpoint

ServiceEndpoint 表示底层实例级端点。

注意:

  • 它只服务于基础设施层
  • 不作为业务调用侧主模型
Locator

Locator 负责把 ServiceRef 解析成 Target

标准实现下,它可以只是一个简单的目标构造器;
轻量实现下,它也可以在内部维护缓存、watch 和服务地址解析逻辑。

Dialer

Dialer 负责把 ServiceRef 转成 grpc.ClientConn

Invoker

Invoker 是统一调用入口,负责把:

  • 服务身份
  • 调用上下文
  • Authz 预检查
  • 连接获取
  • 实际 gRPC 调用

串起来。

InvocationContext

InvocationContext 表示一次调用附带的统一上下文,例如:

  • trace
  • metadata
  • 调用方身份
  • timeout / deadline
AuthzContext

AuthzContext 表示外挂 Authz 所需的标准化输入,例如:

  • 调用者是谁
  • 调用目标服务是谁
  • 调用的完整 method 是什么
  • 当前身份元信息是什么

观测约定

invocation 在设计上默认与 go-micro 现有的 OpenTelemetry 体系对齐。

这意味着:

  • 调用链默认应接入 OTel trace
  • gRPC client 默认应挂载 OTel 相关 handler
  • 调用前后的关键行为应便于接入 metric / trace / log
  • AuthzLocatorDialerInvoker 的后续实现都不应绕开统一观测体系

当前版本中,ConnectionManager 的默认拨号选项已经默认挂载了 otelgrpc client handler, 这是后续统一观测体系的基础入口之一。

当前默认实现

当前版本已经提供:

  • StaticLocator
  • ConnectionManager
  • UnaryInvoker

它们共同构成一个最小可用的调用模型闭环:

  1. 通过 ServiceRef 标识目标服务
  2. 通过 Locator 解析目标
  3. 通过 ConnectionManager 缓存连接
  4. 通过 UnaryInvoker 执行 Authz + metadata + gRPC 调用

示例

package example

import (
	"context"
	"time"

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

func Example() error {
	locator := invocation.StaticLocator{
		Options: invocation.TargetOptions{
			DefaultPort: 9000,
		},
	}

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

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

	ref := invocation.ServiceRef{
		Service:   "auth",
		Namespace: "default",
		Env:       "dev",
	}

	return invoker.Invoke(
		context.Background(),
		ref,
		"/acme.auth.v1.AuthService/Check",
		&struct{}{},
		&struct{}{},
		invocation.WithInvocationContext(invocation.InvocationContext{
			Timeout: 3 * time.Second,
		}),
	)
}

设计约束

  • 业务侧优先面向 ServiceRef,而不是节点列表
  • invocation 不承载后端专属实现细节
  • Authz 默认作为调用前外挂能力接入
  • K8s + Istio 是标准路径
  • etcd / consul 应实现相同的调用语义,而不是暴露另一套模型
  • OTel 是统一观测标准,新的实现不应引入另一套平行观测语义

Documentation

Overview

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

Index

Constants

View Source
const (
	// DefaultClusterDomain 是 K8s Service 的默认集群域。
	// 在标准 K8s + Istio 路径下,最终拨号目标通常会落到该域名体系下。
	DefaultClusterDomain = "svc.cluster.local"
	// DefaultResolverScheme 是 gRPC 推荐使用的 DNS resolver scheme。
	// 统一生成 dns:/// 前缀可以让 target 在不同实现中保持一致。
	DefaultResolverScheme = "dns"
)

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")
	// ErrLocatorIsNil 表示定位器为空,调用方无法把服务身份解析为最终目标。
	ErrLocatorIsNil = errors.New("locator 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 覆盖该行为。

Types

type Authorizer

type Authorizer interface {
	Authorize(ctx context.Context, input AuthzContext) error
}

Authorizer 定义外挂 Authz 的最小能力集合。

返回 nil 表示允许调用; 返回非 nil error 表示拒绝调用或 Authz 本身发生错误。

type AuthzContext

type AuthzContext struct {
	// Service 表示目标服务身份。
	Service ServiceRef `json:"service"`
	// FullMethod 表示完整 gRPC 方法,例如 /acme.user.v1.UserService/GetUser。
	FullMethod string `json:"full_method"`
	// TraceID 表示链路 ID。
	TraceId string `json:"trace_id"`
	// Caller 表示调用方身份。
	Caller Caller `json:"caller"`
	// Metadata 表示构造判定时附带的完整 metadata 副本。
	Metadata metadata.MD `json:"-"`
}

AuthzContext 表示外挂 Authz 所需的标准化输入。

该对象不直接绑定某个 Authz 实现, 只表达“做权限判断时必须稳定得到的字段”。

func NewAuthzContext

func NewAuthzContext(ref ServiceRef, method string, invocation InvocationContext) AuthzContext

NewAuthzContext 根据 ServiceRef、方法名和 InvocationContext 生成标准 AuthzContext。

type Caller

type Caller struct {
	// UserId 表示当前用户身份,可为空。
	UserId string `json:"user_id"`
	// AppId 表示当前应用身份,可为空。
	AppId string `json:"app_id"`
	// TenantId 表示当前租户身份,可为空。
	TenantId string `json:"tenant_id"`
	// OrgIds 表示当前组织范围,可为空。
	OrgIds []string `json:"org_ids"`
	// RoleIds 表示当前角色范围,可为空。
	RoleIds []string `json:"role_ids"`
}

Caller 表示一次服务调用的发起方身份。

这里聚焦的是调用治理与 Authz 所需的统一字段, 避免让每个服务都手工拼装一套不同的 metadata。

type ConnectionManager

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

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

它把“服务身份 -> 目标解析 -> 连接缓存”统一收敛在一处, 让业务层无需关心: - 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

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

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

type ConnectionManagerOptions

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

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

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(ctx context.Context, ref ServiceRef) (*grpc.ClientConn, error)
	Close() error
}

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

这一层通常会组合 Locator 与连接缓存, 从而让业务侧始终面向 ServiceRef 而不是 host:port 字符串。

type InvocationContext

type InvocationContext struct {
	// TraceId 表示当前调用使用的链路 ID。
	TraceId string `json:"trace_id"`
	// Caller 表示调用者身份。
	Caller Caller `json:"caller"`
	// Metadata 表示调用方额外附带的 metadata。
	// 这里允许业务侧做补充,但核心库仍会统一注入标准字段。
	Metadata metadata.MD `json:"-"`
	// Timeout 表示本次调用的超时时间。
	Timeout time.Duration `json:"timeout"`
}

InvocationContext 表示一次调用附带的统一上下文。

它的职责不是替代 context.Context, 而是把“应当被稳定传递和审计的调用元信息”沉淀为一个结构化对象。

func (InvocationContext) BuildMetadata

func (c InvocationContext) BuildMetadata() metadata.MD

BuildMetadata 将 InvocationContext 转换为标准 gRPC metadata。

约定: - 调用方显式提供的 Metadata 会被保留; - 标准字段由核心库统一写入,避免上层重复设置; - 对于切片字段,会整体覆盖为当前上下文中的值。

func (InvocationContext) Clone

Clone 返回 InvocationContext 的深拷贝。 该方法用于避免调用方复用同一个 metadata 引起数据串扰。

func (InvocationContext) NewOutgoingContext

func (c InvocationContext) NewOutgoingContext(parent context.Context) (context.Context, context.CancelFunc)

NewOutgoingContext 基于父上下文构造新的 gRPC 出站上下文。

行为说明: - 若 parent 为 nil,则使用 Background; - metadata 会统一写入 outgoing context; - 若 Timeout > 0,则自动附加超时控制。

type InvokeOption

type InvokeOption func(*InvokeOptions)

InvokeOption 表示单个调用选项。

func WithCallOptions

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

WithCallOptions 追加底层 gRPC CallOption。

func WithInvocationContext

func WithInvocationContext(invocation InvocationContext) InvokeOption

WithInvocationContext 设置本次调用使用的 InvocationContext。

type InvokeOptions

type InvokeOptions struct {
	// InvocationContext 表示本次调用要附带的统一上下文。
	InvocationContext InvocationContext
	// CallOptions 表示附加的 gRPC CallOption。
	CallOptions []grpc.CallOption
}

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

type Invoker

type Invoker interface {
	Invoke(ctx context.Context, ref ServiceRef, method string, req any, resp any, options ...InvokeOption) error
}

Invoker 定义统一调用入口。

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

type Locator

type Locator interface {
	Resolve(ctx context.Context, ref ServiceRef) (Target, error)
}

Locator 定义“如何把服务身份解析为可拨号目标”。

标准实现下,Locator 可能只是把 service + namespace 组装成 DNS; 轻量实现下,Locator 也可以在内部维护 watch、缓存、CoreDNS 同步等复杂逻辑。 这些差异都不应泄漏给业务调用方。

type ServiceEndpoint

type ServiceEndpoint struct {
	// Address 表示实例地址,通常是 ip:port 或 host:port。
	Address string `json:"address"`
	// Weight 表示实例权重。
	Weight int `json:"weight"`
	// Healthy 表示实例当前健康状态。
	Healthy bool `json:"healthy"`
	// Meta 表示实例附带的轻量元信息,例如标签、版本、可用区等。
	Meta map[string]string `json:"meta"`
}

ServiceEndpoint 表示底层实例级端点。

它只服务于 Locator、Dialer、轻量实现内部缓存等基础设施场景, 不再作为业务调用侧的中心模型暴露。

type ServiceRef

type ServiceRef struct {
	// Service 表示目标服务名。
	Service string `json:"service"`
	// Namespace 表示目标命名空间。
	// 在 K8s 主路径下,最终 DNS 主要依赖 service + namespace 组合生成。
	Namespace string `json:"namespace"`
	// Env 表示逻辑环境。
	// 它更适合参与平台治理、配置域和策略域选择,而不一定直接进入 DNS。
	Env string `json:"env"`
	// Port 表示可选端口覆盖项。
	// 大多数场景建议由核心库使用默认端口补齐;只有个别服务端口特殊时才显式设置。
	Port uint16 `json:"port"`
}

ServiceRef 表示一次调用面向的“服务身份”。

这里表达的是“我要调用哪个服务”,而不是“我要连接哪个节点”。 因此它只保留服务级别的最小身份信息,不承载节点列表等旧 registry 语义。

func (ServiceRef) EffectivePort

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

EffectivePort 返回最终应使用的端口。

规则如下: 1. 若 ServiceRef 自身显式提供端口,则优先使用; 2. 否则尝试使用调用方提供的默认端口; 3. 若两者都不可用,则返回错误。

func (ServiceRef) NamespaceName

func (s ServiceRef) NamespaceName() string

NamespaceName 返回去除首尾空格后的命名空间。

func (ServiceRef) ServiceName

func (s ServiceRef) ServiceName() string

ServiceName 返回去除首尾空格后的服务名。

func (ServiceRef) Validate

func (s ServiceRef) Validate() error

Validate 校验 ServiceRef 是否具备生成逻辑目标所需的最小字段。

当前版本仍然要求 namespace 显式存在, 因为这是最稳定、最容易排障的入参形式。

func (ServiceRef) WithPort

func (s ServiceRef) WithPort(port uint16) ServiceRef

WithPort 返回带端口覆盖项的新 ServiceRef。 该方法适合在默认端口不满足时,以不可变方式构造新的服务身份。

type StaticLocator

type StaticLocator struct {
	// Options 控制 target 的构造方式,例如默认端口、集群域和 resolver scheme。
	Options TargetOptions
}

StaticLocator 是最简单的 Locator 实现。

它不依赖任何外部注册中心,也不维护节点列表, 只负责按照统一规则把 ServiceRef 转换为标准 Target。

该实现非常适合作为: - `go-k8s/invocation` 的默认基础; - 单元测试中的轻量定位器; - 未来 etcd / consul 轻量实现的公共兜底逻辑。

func (StaticLocator) Resolve

func (s StaticLocator) Resolve(_ context.Context, ref ServiceRef) (Target, error)

Resolve 按照 StaticLocator 的固定规则,把 ServiceRef 转换为 Target。

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 表示最终可拨号的服务目标。

它是 Locator 解析后的结果: - Host/Port 用于实际网络连接; - ResolverScheme 用于生成标准的 gRPC target; - Address 是 host:port 的网络地址表示。

func BuildTarget

func BuildTarget(ref ServiceRef, options TargetOptions) (Target, error)

BuildTarget 根据 ServiceRef 构造标准 Target。

该函数是 K8s + Istio 主路径的默认实现形态: 通过 service + namespace + clusterDomain 生成统一主机名, 再配合端口和 resolver scheme 得到最终 gRPC target。

func (Target) Address

func (t Target) Address() string

Address 返回 host:port 形式的网络地址。

func (Target) GRPCTarget

func (t Target) GRPCTarget() string

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

例如: - dns:///auth.default.svc.cluster.local:9000 - 若 ResolverScheme 为空,则退化为普通 host:port。

func (Target) Validate

func (t Target) Validate() error

Validate 校验 Target 是否具备生成拨号地址所需的字段。

type TargetOptions

type TargetOptions struct {
	// DefaultPort 表示默认 gRPC 端口。
	// 当 ServiceRef.Port 为空时,会使用该字段补齐。
	DefaultPort uint16
	// ClusterDomain 表示集群域,默认值为 svc.cluster.local。
	ClusterDomain string
	// ResolverScheme 表示 gRPC resolver scheme,默认值为 dns。
	ResolverScheme string
}

TargetOptions 表示将 ServiceRef 解析为 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
	// Authorizer 是可选依赖;若为空,则跳过 Authz 预检查。
	Authorizer Authorizer
	// InvokeFunc 是可选依赖;若为空,则默认调用 grpc.ClientConn.Invoke。
	InvokeFunc UnaryInvokeFunc
}

UnaryInvoker 是默认的 Invoker 实现。

它的执行流程非常明确: 1. 基于 ServiceRef 获取连接; 2. 基于 InvocationContext 构造统一 metadata; 3. 在真正发起 gRPC 调用前执行 Authz 判定; 4. 使用 grpc.ClientConn.Invoke 发起 unary 调用。

func (*UnaryInvoker) Invoke

func (u *UnaryInvoker) Invoke(ctx context.Context, ref ServiceRef, 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