十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Dapr gRPC 与 Protobuf 消息编码规范(ARC-003)深度解析:从设计决策到仓库落地实践

Dapr gRPC 与 Protobuf 消息编码规范(ARC-003)深度解析:从设计决策到仓库落地实践 Dapr gRPC 与 Protobuf 消息编码规范ARC-003深度解析从设计决策到仓库落地实践【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr导读本文围绕 Dapr 仓库中的架构决策记录 ARC-003: gRPC and Protobuf message coding convention 展开系统讲解 Dapr 在定义 gRPC 服务与 Protobuf 消息时必须遵循的最小化编码约定涵盖google.protobuf.Any的使用边界、Request/Response后缀约定、服务命名禁忌、共享 proto 复用策略以及 enum 类型约束五条核心规则。读完本文你将理解这些规范背后的动机并能在dapr/proto目录下看到每条规范的真实落地证据从而在自己的 Dapr 扩展或 SDK 项目中写出风格一致、可读性强、易于维护的 gRPC/Protobuf 接口定义。背景为什么需要一份编码约定Context在 ARC-003 提出之前Dapr 的 gRPC 服务和 Protobuf 消息定义处于无约定状态。正如文档 Context 一节所述这种自由放任直接导致了两个问题重复的 Protobuf 定义同一语义的消息例如状态项的key/value/etag组合、HTTP 请求扩展信息在不同 proto 文件中被反复定义改动一处而遗漏另一处极易产生漂移服务与消息命名不一致不同开发者、不同时期写出的服务名、消息名风格各异既有Dapr这样干净的服务名也可能出现DaprClient、DaprService这类冗余后缀增加了阅读与检索成本。因此该记录定义了Protobuf 消息层面的最小化编码约定minimum-level coding convention目标是提升 grpc/protobuf 消息定义的整体质量。注意其定位是最小化——不强制规定面面俱到的风格细则只约束最容易引发混乱的几个关键点。五条核心决策Decisions总览编号决策内容一句话意图D1仅当字段承载携带 type url 的序列化 protobuf 消息时使用google.protobuf.Any否则使用显式数据类型或消息防止Any滥用导致类型信息丢失D2gRPC 请求消息名使用Request后缀响应消息名使用Response后缀一眼区分请求与响应D3gRPC 服务名不得使用Client、Service后缀反例DaprClient、DaprService服务名保持简洁无冗余D4通过在共享 proto 中定义消息避免重复的 Protobuf 消息定义单一事实来源杜绝漂移D5字段只接受预定义值时定义并使用 enum 类型用类型系统约束非法取值下文逐条展开并结合dapr/proto目录下的真实 proto 文件验证每条规范的实际落地情况。决策一审慎使用google.protobuf.AnyUsegoogle.protobuf.Anydata field only if the message field conveys serialized protobuf message with type url. Otherwise, use the explicit data type or protobuf message.google.protobuf.Any是一个自带type_url的包装类型它能承载任意已序列化的 Protobuf 消息并记录其完整类型名从而实现多态传输。但它的代价是接收方必须依赖type_url做反序列化编译期类型安全消失。ARC-003 的立场是只有字段确实需要携带带 type url 的序列化 protobuf 消息时才使用它其余情况一律使用显式数据类型或具体消息类型。仓库中的合法使用范例在 dapr/proto/common/v1/common.proto 中InvokeRequest.data与InvokeResponse.data使用Any字段是这一决策的典型正例// InvokeRequest is the message to invoke a method with the data. message InvokeRequest { // Required. method is a method name which will be invoked by caller. string method 1; // Required in unary RPCs. Bytes value or Protobuf message which caller sent. // Dapr treats Any.value as bytes type if Any.type_url is unset. google.protobuf.Any data 2; string content_type 3; HTTPExtension http_extension 4; }注意这里的注释透露出一个重要细节当Any.type_url未设置时Dapr 将Any.value当作原始bytes处理。也就是说Any在这里承担了要么是序列化消息、要么是裸字节的双重职责——这正是消息字段承载序列化 protobuf 消息带 type url这一适用前提的体现。同样dapr/proto/scheduler/v1/scheduler.proto 中作业负载Job.data也使用google.protobuf.Any因为作业的数据可以是任意用户定义的消息dapr/proto/components/v1/state.proto 中Query.filter使用mapstring, google.protobuf.Any来表达不同类型的查询过滤器值。反面教训能用显式类型就不要用Any当字段的取值集合明确、类型可预期时规范要求使用显式类型。例如InvokeRequest.http_extension直接引用HTTPExtension消息TopicEventRequest中的data则直接使用bytes见下文因为它们要么有固定的消息结构要么只是普通字节载荷引入Any只会徒增解析成本与类型不确定性。决策二请求与响应消息的Request/Response后缀UseRequestsuffix for gRPC request message name andResponsesuffix for gRPC response message name.这是一条可执行性极强的命名规则每个 RPC 方法的入参消息必须以Request结尾出参消息必须以Response结尾。它让读者无需翻看rpc定义即可从消息名判断消息的角色。仓库中的大规模落地证据dapr/proto/runtime/v1/dapr.proto 定义了 56 个 RPC 方法其请求/响应消息几乎全部遵循该约定service Dapr { rpc GetState(GetStateRequest) returns (GetStateResponse) {} rpc GetBulkState(GetBulkStateRequest) returns (GetBulkStateResponse) {} rpc SaveState(SaveStateRequest) returns (google.protobuf.Empty) {} rpc DeleteState(DeleteStateRequest) returns (google.protobuf.Empty) {} rpc PublishEvent(PublishEventRequest) returns (google.protobuf.Empty) {} rpc InvokeBinding(InvokeBindingRequest) returns (InvokeBindingResponse) {} rpc GetSecret(GetSecretRequest) returns (GetSecretResponse) {} rpc InvokeActor(InvokeActorRequest) returns (InvokeActorResponse) {} rpc ScheduleJob(ScheduleJobRequest) returns (ScheduleJobResponse) {} // ... }在 dapr/proto/runtime/v1/appcallback.proto 中同样如此rpc OnTopicEvent(TopicEventRequest) returns (TopicEventResponse) {} rpc OnBindingEvent(BindingEventRequest) returns (BindingEventResponse) {} rpc OnJobEvent(JobEventRequest) returns (JobEventResponse) {} rpc ListTopicSubscriptions(google.protobuf.Empty) returns (ListTopicSubscriptionsResponse) {} rpc ListInputBindings(google.protobuf.Empty) returns (ListInputBindingsResponse) {}此外dapr/proto/scheduler/v1/scheduler.proto 中的ScheduleJobRequest/ScheduleJobResponse、GetJobRequest/GetJobResponse、WatchJobsRequest/WatchJobsResponse以及 dapr/proto/components/v1/state.proto 中的GetRequest/GetResponse、SetRequest/SetResponse、DeleteRequest/DeleteResponse、QueryRequest/QueryResponse都无一例外地遵守了该命名。值得注意的共享消息例外作为共享类型的消息见决策四不强制套用该后缀。例如common.v1.InvokeRequest/InvokeResponse被Dapr.InvokeService与AppCallback.OnInvoke两个 RPC 同时引用它们的语义是一次调用的请求/响应载荷而非某个特定 RPC 的入参出参dapr/proto/internals/v1/service_invocation.proto 中内部传输用的InternalInvokeRequest/InternalInvokeResponse则严格遵循了后缀约定。这说明规范在执行时保留了对共享抽象消息的合理豁免空间。决策三服务名禁用Client与Service后缀Do not useClientandServicesuffix for gRPC service name e.g. (x) DaprClient, DaprService.gRPC 服务名本身就是服务再叠加Service后缀属于语义冗余Client后缀则混淆了服务端接口与客户端 SDK 封装的边界。ARC-003 明确将DaprClient、DaprService列为反例。仓库中的实际服务名盘点从dapr/proto目录可以完整盘点 Dapr 全部 gRPC 服务全部符合该规范服务名定义文件职责Daprdapr/proto/runtime/v1/dapr.proto对外提供状态、发布订阅、绑定、Actor、作业、工作流等全部构建块 APIAppCallbackdapr/proto/runtime/v1/appcallback.proto用户应用实现的回调服务接收 Dapr 的事件推送AppCallbackHealthCheck同上可选的健康检查扩展AppCallbackAlpha同上可选接入 Alpha RPC 的扩展服务ServiceInvocationdapr/proto/internals/v1/service_invocation.proto调用方/被调用方 sidecar 之间的内部服务调用Placementdapr/proto/placement/v1/placement.protoActor 放置表上报与下发Schedulerdapr/proto/scheduler/v1/scheduler.proto作业调度与 Actor 提醒Operatordapr/proto/operator/v1/operator.protoOperator 管理组件资源Sentrydapr/proto/sentry/v1/sentry.protomTLS 证书签发StateStore/TransactionalStateStore/QueriableStateStore等dapr/proto/components/v1/state.proto组件可插拔 state store协议可以看到Dapr服务名没有写成DaprServiceAppCallback没有写成AppCallbackService——规范得到了严格贯彻。唯一看似特例的ServiceInvocation与StateStore其词尾分别是Invocation调用与Store存储并非Service后缀属于合法命名。决策四用共享 proto 消除重复定义Avoid the duplicated protobuf message definitions by defining the messages in shared proto.重复定义是 Proto 生态最典型的坏味道同一结构在多处复制粘贴字段增减时极易漏改消息名还会在生成的代码中造成命名冲突。ARC-003 给出的解法是把跨模块复用的消息下沉到共享 proto。共享库的枢纽dapr/proto/common/v1/common.protodapr/proto/common/v1/common.proto包名dapr.proto.common.v1正是这一决策的枢纽实现。它集中定义了以下跨模块复用的消息与枚举HTTPExtensionHTTP 动词 query string 扩展InvokeRequest/InvokeResponse服务调用的通用载荷StreamPayload流式传输的分块载荷含seq序号StateItem/Etag/StateOptions状态项及其并发/一致性选项ConfigurationItem配置项JobFailurePolicy/JobFailurePolicyDrop/JobFailurePolicyConstant作业失败策略这些消息被大量 proto 文件import dapr/proto/common/v1/common.proto复用包括但不限于dapr/proto/runtime/v1/dapr.protocommon.v1.InvokeResponsedapr/proto/runtime/v1/appcallback.protocommon.v1.InvokeRequest、common.v1.InvokeResponse、common.v1.StateItem、common.v1.HTTPExtensiondapr/proto/runtime/v1/state.protocommon.v1.StateItem、common.v1.Etag、common.v1.StateOptionsdapr/proto/internals/v1/service_invocation.protocommon.v1.InvokeRequest、common.v1.InvokeResponse、common.v1.StreamPayloaddapr/proto/components/v1/state.protocommon.v1.Etag、common.v1.StateOptionsdapr/proto/scheduler/v1/scheduler.protocommon.v1.JobFailurePolicy以Etag与StateOptions为例如果不在common.proto中统一定义那么 runtime API 与组件协议两套 proto 就必须各自维护一份etag/concurrency/consistency结构——这正是 ARC-003 要消灭的重复。通过共享定义sidecar 解析 runtime API 与调用组件协议时可以使用同构的类型语义天然对齐。包与目录的版本化管理共享化的另一面是严格的目录组织。从仓库结构看所有 proto 均遵循dapr/proto/模块/v1/模块.proto的布局包名统一为dapr.proto.模块.v1Go 生成路径统一映射到github.com/dapr/dapr/pkg/proto/模块/v1见各文件头部的option go_package。dapr/proto/runtime/v1下按构建块拆分为actors.proto、pubsub.proto、state.proto、binding.proto、secret.proto、configuration.proto、lock.proto、crypto.proto、workflow.proto、jobs.proto、ai.proto等再由dapr.proto统一 import 聚合——既避免了单文件膨胀又保持了引用关系的清晰。决策五用 enum 约束预定义取值Define and use enum type if field accepts only predefined values.当字段的合法取值是固定集合时应该定义枚举而不是让调用方自由填写字符串。enum 的优势在于编译器/工具链能在生成代码层面暴露合法值、IDE 提供补全、非法值在解析早期即可被发现。仓库中的 enum 应用实例HTTP 动词common.proto 的HTTPExtension.Verb定义了NONE/GET/HEAD/POST/PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH十个取值对齐 RFC 7231 与 RFC 5789http_extension.verb字段直接使用该枚举杜绝了任意字符串动词状态并发与一致性StateOptions.StateConcurrencyCONCURRENCY_UNSPECIFIED/FIRST_WRITE/LAST_WRITE与StateOptions.StateConsistencyCONSISTENCY_UNSPECIFIED/EVENTUAL/STRONG被 runtime API 与组件协议共同引用主题事件处理状态appcallback.proto 的TopicEventResponse.TopicEventResponseStatus定义了SUCCESS/RETRY/DROP应用侧通过返回该枚举控制消息确认、重试或丢弃绑定事件并发模式同文件BindingEventResponse.BindingEventConcurrency定义SEQUENTIAL/PARALLEL排序方向components/v1/state.proto 的Sorting.Order定义ASC/DESC放置操作类型placement.proto 的HostOperation定义UNKNOWN/REPORT/LOCK/UPDATE/UNLOCK。注意一个细节Proto3 枚举的零值必须是语义明确的占位值仓库统一使用*_UNSPECIFIED、UNKNOWN、NONE等命名来避免零值即默认合法值的陷阱这也是遵循该决策时应同步养成的习惯。规范的版本化补充API 演进与弃用标注ARC-003 未直接讨论 API 版本化但从仓库中可以看到 Dapr 在遵循上述命名规范的同时还发展出了一套与之配套的演进约定值得作为规范的自然延伸来理解Alpha/Beta 后缀不稳定接口使用Alpha1/Beta1后缀稳定后提供同名方法并标记旧方法option deprecated true。例如dapr.proto中BulkPublishEventAlpha1与BulkPublishEvent、StartWorkflowAlpha1与StartWorkflowBeta1、ScheduleJobAlpha1与ScheduleJob并存旧方法均带有option deprecated true字段级弃用如PlacementTables.version、PlacementTable.hosts等历史字段用[deprecated true]标注Host.version用optional语义来检查是否存在而非是否为 0/空。这与 ARC-003 的一致、可读目标一脉相承命名规范保证了接口的整洁版本化与弃用标注则保证了整洁能够长期维持而不破坏兼容性。规范落地的工程支撑代码生成链路Protobuf 定义本身不产生可运行代码Dapr 通过 tools/proto/generate.sh 完成多语言生成。该脚本固定使用protoc3.10.0按js、java、python、go、dotnet语言分支分别调用对应插件例如 Go 生成命令为generate go go . --plugingrpc它从pkg/proto目录出发、以仓库根目录作为--proto_path保证共享 proto如dapr/proto/common/v1/common.proto在任意语言生成时都能按统一相对路径解析——这正是决策四共享定义能够落地的工程前提只有 import 路径稳定统一共享消息才能被所有语言、所有模块一致消费。仓库中pkg/proto目录下已生成并提交的 Go 代码如pkg/proto/common/v1、pkg/proto/runtime/v1、pkg/proto/components/v1等即是该链路的产物。影响与结论ConsequencesARC-003 的预期收益在文档中凝练为一句话让我们能够定义一致、可读的 gRPC 服务和 Protobuf 消息。结合仓库现状可以从三个层面评估其实际价值一致性56 个对外 RPC、数十个组件协议 RPC 的消息命名、服务命名在长期演进后仍保持统一风格新读者进入dapr/proto时几乎不需要额外的命名学习成本可维护性common.proto作为共享枢纽使状态、调用、失败策略等核心结构只有一份事实来源跨模块改动如新增状态选项只需修改一处可扩展性enum 约束与版本化后缀的组合使新增取值如新的 HTTP 动词和新增 API 版本Alpha1 → Beta1 → 稳定都能在不破坏既有契约的前提下平滑演进。对于希望为 Dapr 贡献新 gRPC 接口或实现自有 Dapr 兼容协议的开发者ARC-003 提供了一份可直接照做的检查清单新消息命名是否带Request/Response后缀新服务名是否避免了Client/Service冗余字段能复用common.v1中的消息吗取值集合是否应该定义 enumAny的使用是否真的满足携带带 type url 的序列化消息的前提逐项核对后你的接口定义就能与 Dapr 的官方 proto 保持同一套心智模型。【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表