ARTICLE DETAIL

资讯详情

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

Dapr 错误码体系重构指南:基于 gRPC 富错误模型的 Rich Error 实践

Dapr 错误码体系重构指南:基于 gRPC 富错误模型的 Rich Error 实践 Dapr 错误码体系重构指南基于 gRPC 富错误模型的 Rich Error 实践【免费下载链接】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 代码库中工作的开发者系统讲解 Dapr 如何将错误处理对齐到 gRPC Richer Error Model富错误模型并基于pkg/api/errors包统一各服务daprd、injector、placement、scheduler、sentry的错误响应。读完本文你将掌握 Dapr 新错误体系的完整脉络错误码ErrorCode与分类Category的组织方式、ErrorBuilder与WithErrorInfo/WithResourceInfo等辅助方法的用法、如何按照building-block.go的规范新增一个富错误以及如何为它补齐集成测试。为什么 Dapr 需要富错误模型传统 gRPC 错误只包含一个状态码如InvalidArgument和一段描述文本客户端难以对错误进行结构化处理。而 gRPC 富错误模型源自 Google API Design Guide 的 Error Model在标准状态码之外额外携带结构化明细details例如ErrorInfo机器可读的错误码reason与键值元数据metadataResourceInfo指明出错资源类型、名称、所属者FieldViolation定位到具体请求字段HelpLink为开发者提供解决问题的帮助链接。Dapr 的目标是让这些信息在 HTTP 与 gRPC 两条 API 通道上保持一致、可机器解析。为此Dapr 正在把散布在代码中的预定义错误 动态构造错误统一迁移到位于pkg/api/errors的富错误模型。现状盘点Dapr 旧有的两类错误处理方式预定义错误Predefined Errors旧式预定义错误集中在 pkg/messages/predefined.go例如ErrStateGet fail to get %s from state store %s: %s ErrPubsubForbidden topic %s is not allowed for app id %s其底层载体是 pkg/messages/api_error.go 中定义的APIError结构体包含四个字段message人类可读信息、tag错误码、httpCodeHTTP 状态码、grpcCodegRPC 状态码。它通过实现GRPCStatus() *grpcStatus.Status兼容status.FromError()因此可以同时用于 HTTP 与 gRPC 响应。这类错误被整个代码库复用为常见场景提供了一致的错误处理机制。动态构造错误Dynamically Constructed Errors另一类错误是在代码中临时fmt.Errorf拼出来的通常用于预定义集合没有覆盖的特殊场景。它们格式不统一难以被客户端按错误码识别。迁移方向富错误迁移的第一步就是熟悉上述两类既有模式——尤其是APIError的字段语义message/tag/httpCode/grpcCode因为在替换为富错误模型时这些信息会被映射到ErrorBuilder的对应参数中。新错误体系的地基ErrorCode 与 Category在 pkg/messages/errorcodes/errorcodes.go 中定义了错误码的统一模型type Category string const ( CategoryActor Category actor CategoryWorkflow Category workflow CategoryState Category state CategoryConfiguration Category configuration CategoryCrypto Category crypto CategorySecret Category secret CategoryPubsub Category pubsub CategoryConversation Category conversation CategoryServiceInvocation Category service-invocation CategoryBinding Category binding CategoryLock Category lock CategoryJob Category job CategoryHealth Category health CategoryCommon Category common CategoryPluggable Category pluggable-component ) type ErrorCode struct { Code string GrpcCode string Category Category }每个ErrorCode由三部分组成字段含义示例Code通用错误标识HTTP 与 gRPC 均可见ERR_STATE_STORE_NOT_FOUNDGrpcCodegRPC 侧使用的专用错误码DAPR_STATE_NOT_FOUNDCategory所属构建块分类state、pubsub、job…Category覆盖了 Dapr 的全部构建块actor、workflow、state、configuration、crypto、secret、pubsub、conversation、service-invocation、binding、lock、job、health、common 以及 pluggable-component。状态管理 API 的典型错误码如下StateStoreNotFound ErrorCode{ERR_STATE_STORE_NOT_FOUND, DAPR_STATE_NOT_FOUND, CategoryState} StateStoreNotConfigured ErrorCode{ERR_STATE_STORE_NOT_CONFIGURED, DAPR_STATE_NOT_CONFIGURED, CategoryState} StateMalformedRequest ErrorCode{ERR_MALFORMED_REQUEST, DAPR_STATE_ILLEGAL_KEY, CategoryState}注意GrpcCode可能为空如 actor 类错误码此时只使用Code作为统一标识。在 pkg/api/errors/state.go 中可以看到这些错误码如何被引用例如errorcodes.StateStoreNotFound.Code与errorcodes.StateStoreNotFound.GrpcCode分别作为 legacy tag 与 ErrorInfo reason 传入构建器。富错误构建器ErrorBuilder 与辅助方法富错误消息的底层定义位于github.com/dapr/kit/errors包Dapr 的独立工具库作为 go.mod 依赖引入核心入口是errors.NewBuilder辅以一组链式方法方法作用必填性NewBuilder(grpcCode, httpCode, msg, legacyTag, category)创建构建器指定 gRPC/HTTP 状态码、消息、旧标签与分类必填WithErrorInfo(reason, metadata)注入 ErrorInfo机器可读 reason如DAPR_STATE_ILLEGAL_KEY与键值元数据必填WithResourceInfo(type, name, owner, description)注入 ResourceInfo指明出错的资源类型与名称可选推荐WithFieldViolation(field, description)注入 FieldViolation定位到具体请求字段可选推荐WithHelpLink(link, description)注入帮助链接指引开发者解决问题可选Build()结束链式调用生成最终 error必填按照 Google Cloud Error Model 的最佳实践ErrorInfo 是必需字段ResourceInfo 及其他 details 字段虽为可选但在能指明资源时应尽量使用。通用构造函数pkg/api/errors/errors.gopkg/api/errors/errors.go 提供了三个可在各构建块间复用的顶层构造函数func Basic(grpcCode codes.Code, httpCode int, errorCode errorcodes.ErrorCode, msg string) error { return kiterrors.NewBuilder( grpcCode, httpCode, msg, , string(errorCode.Category), ). WithErrorInfo(errorCode.Code, nil). Build() } func NotFound(name string, componentType string, metadata map[string]string, grpcCode codes.Code, httpCode int, legacyTag string, reason string, category errorcodes.Category) error { message : fmt.Sprintf(%s %s is not found, componentType, name) return kiterrors.NewBuilder( grpcCode, httpCode, message, legacyTag, string(category), ). WithErrorInfo(reason, metadata). Build() } func Empty(name string, metadata map[string]string, errorCode errorcodes.ErrorCode) error { message : name is empty return kiterrors.NewBuilder( codes.InvalidArgument, http.StatusBadRequest, message, , string(errorCode.Category), ). WithErrorInfo(errorCode.Code, metadata). Build() }三个函数的分工Basic通用兜底仅携带 ErrorInfoNotFound统一某组件不存在语义自动拼接componentType name is not found消息Empty统一某参数为空语义固定使用InvalidArgument/400 Bad Request。参考实现以StateStoreError为例Step 1 Step 3 的核心样板文档推荐的参考实现是 pkg/api/errors/state.go。它展示了以构建块命名错误类型 内部build统一装配的完整模式type StateStoreError struct { name string skipResourceInfo bool } func StateStore(name string) *StateStoreError { return StateStoreError{name: name} } func (s *StateStoreError) build(err *errors.ErrorBuilder, errCode string, metadata map[string]string) error { if !s.skipResourceInfo { err err.WithResourceInfo(state, s.name, , ) } return err. WithErrorInfo(errCode, metadata). Build() }每个具体错误方法如NotFound、NotConfigured、InvalidKeyName只负责描述这是什么错统一交给build装配 ResourceInfo 与 ErrorInfo。原文档中给出的InvalidKeyName示例为func (s *StateStoreError) InvalidKeyName(key string, msg string) error { return s.build( errors.NewBuilder( codes.InvalidArgument, http.StatusBadRequest, msg, ERR_MALFORMED_REQUEST, ).WithFieldViolation(key, msg), errors.CodeIllegalKey, nil, ) }当前仓库中该方法的实际实现已演进为引用集中定义的错误码常量func (s *StateStoreError) InvalidKeyName(key string, msg string) error { return s.build( errors.NewBuilder( codes.InvalidArgument, http.StatusBadRequest, msg, errorcodes.StateMalformedRequest.Code, string(errorcodes.StateMalformedRequest.Category), ).WithFieldViolation(key, msg), errorcodes.StateMalformedRequest.GrpcCode, nil, ) }相比文档示例演进版把字符串字面量替换为errorcodes中的常量避免错误码散落各处。WithFieldViolation(key, msg)让客户端能够精确定位是哪个 key 非法。StateStoreError还演示了多种富错误形态NotFound / NotConfigured将appID写入 metadata并设置skipResourceInfo true此时 store 本身不存在无法作为资源引用TransactionsNotSupported使用WithHelpLink附上支持事务的状态存储组件清单链接TooManyTransactionalOps在 metadata 中携带currentOpsTransaction与maxOpsPerTransaction两个可机读字段QueryUnsupported / QueryFailed用于状态查询 API 的错误分支。分步实战如何新增一个富错误Step 2/4/5 完整流程Step 2新增错误文件先检查现有错误文件在 pkg/api/errors 目录下查找是否已有对应构建块的文件避免重复定义。不存在则新建文件按building-block.go命名例如状态管理是state.go、发布订阅是pubsub.go、调度器是scheduler.go。定义错误类型并实现具体方法以StateStore(name string) *StateStoreError这样的构造器为入口让调用方在拿到 store 名称的上下文中即可构造错误。Step 3设计富错误消息明确 gRPC 状态码与 HTTP 状态码的映射如InvalidArgument↔400 Bad Request、FailedPrecondition↔500 Internal Server Error、PermissionDenied↔403 ForbiddenErrorInfo 必须填充reason 取GrpcCodemetadata 放可机读的上下文能指明资源时使用WithResourceInfo涉及请求字段时使用WithFieldViolation可提供WithHelpLink引导用户。Step 4实施新错误模型保证整个代码库一致使用新模型避免新旧混用用新定义的富错误替换既有错误。在 pkg/api/grpc/grpc.go 中可以看到 gRPC 侧的实际调用err apierrors.PubSub(pubsubName).WithMetadata(nil).NotConfigured() err apierrors.PubSub(pubsubName).WithMetadata(reqMeta).NameEmpty() err apierrors.PubSub(pubsubName).WithMetadata(nil).NotFound() err apierrors.PubSub(pubsubName).WithMetadata(reqMeta).TopicEmpty()而在同文件的行 964 与 1029 处状态事务相关错误则通过apierrors.StateStore(storeName).TransactionsNotSupported()与apierrors.StateStore(storeName).TooManyTransactionalOps(len(operations), max)构造说明新的富错误 API 已覆盖 HTTP 与 gRPC 双通道的核心路径。Step 5测试与集成新增错误后必须补充集成测试并遵循既有测试模式。状态 API 的富错误测试位于tests/integration/suite/daprd/state/grpc/errors.gogRPC 通道使用google.golang.org/genproto/googleapis/rpc/errdetails解析富错误详情用grpc/status提取status.Code与 details 断言tests/integration/suite/daprd/state/http/errors.goHTTP 通道验证 HTTP 状态码与错误响应体。测试中通过statestore.New构造带不同能力无事务、支持查询、限制事务操作数上限的内存态存储再逐个断言对应错误码与 details 内容是以测试锁定富错误契约的最佳参考。更多构建块的富错误实现Pub/Sub三层错误对象链pkg/api/errors/pubsub.go 展示了更精细的错误建模——三层对象链PubSubError→PubSubMetadataError→PubSubTopicErrorPubSub(name)创建基础错误对象.WithMetadata(meta)或.WithAppError(appID, err)升级为携带元数据的PubSubMetadataError.WithTopic(topic)再升级为PubSubTopicError此后可调用MarshalEnvelope、MarshalEvents、UnmarshalEvents等主题级错误方法。典型调用链apierrors.PubSub(pubsubName).WithAppError(a.AppID(), err).NotFound() apierrors.PubSub(pubsubName).WithMetadata(reqMeta).TopicEmpty() apierrors.PubSub(pubsubName).PublishForbidden(topic, a.AppID(), err) apierrors.PubSub(pubsubName).PublishMessage(topic, err)其中PublishForbidden返回PermissionDenied/403 ForbiddenPublishMessage返回Internal/500 Internal Server Errormetadata 中附带topic与底层error字符串outbox 场景则由独立的PubSubOutbox(appID, err)函数处理错误码前缀CodePrefixPubSub OUTBOX。SchedulergRPC→HTTP 状态码动态映射pkg/api/errors/scheduler.go 展示了另一类模式——当错误根源来自下游 gRPC 服务时先通过status.Code(err)还原 gRPC 状态码再用grpccodes.HTTPStatusFromCode(code)动态推导 HTTP 状态码func SchedulerScheduleJob(metadata map[string]string, err error) error { code : status.Code(err) if code codes.Unknown { code codes.Internal } httpCode : grpccodes.HTTPStatusFromCode(code) return kiterrors.NewBuilder( code, httpCode, failed to schedule job due to: err.Error(), , string(errorcodes.SchedulerScheduleJob.Category), ). WithErrorInfo(errorcodes.SchedulerScheduleJob.Code, metadata). Build() }这样可以保证 daprd 与 scheduler 服务之间透传的 gRPC 状态码最终在 daprd 的 HTTP API 上映射为语义正确的 HTTP 状态码。Job 相关错误码统一使用CategoryJob分类如DAPR_SCHEDULER_SCHEDULE_JOB、DAPR_SCHEDULER_JOB_NAME。新旧模型对照与迁移要点维度旧模型APIError / predefined.go新模型pkg/api/errors dapr/kit/errors载体APIError{message, tag, httpCode, grpcCode}ErrorBuilder链式构建错误码来源pkg/messages/errorcodes常量同一套errorcodes常量结构化信息仅 message tagErrorInfo / ResourceInfo / FieldViolation / HelpLink双通道适配GRPCStatus()兼容status.FromError()构建器同时产出 gRPC status 与 HTTP 状态码复用粒度包级变量按构建块组织的错误类型StateStoreError、PubSubError…迁移时的要点保持errorcodes常量作为唯一事实来源每个新错误至少携带 ErrorInfo在能确定资源时补充 ResourceInfo为 HTTP 与 gRPC 双通道分别补集成测试。结语通过pkg/api/errors包与dapr/kit/errors的ErrorBuilderDapr 正在将分散的错误处理统一到 gRPC 富错误模型之下。对 Dapr 开发者而言迁移路径清晰可循先在 pkg/messages/errorcodes/errorcodes.go 确认或新增错误码再按building-block.go规范在 pkg/api/errors 中组织错误类型参考 pkg/api/errors/state.go 的build装配模式最后用 tests/integration/suite/daprd/state/grpc/errors.go 与 tests/integration/suite/daprd/state/http/errors.go 的既有测试模式锁定契约。这套机制让 Dapr 的 HTTP 与 gRPC API 对客户端输出结构一致、语义精确、可机读的错误响应是分布式应用可观测性与排障体验的重要基础设施。【免费下载链接】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),仅供参考
返回列表