Elsa Workflows 输出转换器(Output Converter)的显式稳定身份设计:Converter ID、注册约束与运行时校验体系
后端工作流自动化流程编排低代码【免费下载链接】elsa-coreThe Workflow Engine for .NET项目地址https://gitcode.com/gh_mirrors/el/elsa-core点击查看免费下载导读本文以 Elsa Workflows.NET 工作流引擎的架构决策记录 ADR 0012 为核心系统讲解输出转换器如何通过「显式、稳定、大小写敏感的 Converter ID」被唯一标识、注册与调用。你将掌握转换器 ID 作为持久化公共契约的语义规则、Core 如何拒绝重复注册与大小写冲突、转换器实现必须满足的同步/确定性/无副作用约束、以及从定义校验到运行时调用的完整失败阶段与隐私安全异常模型。文中所有规则均对应仓库源码与测试中的真实实现可直接用于开发自定义转换器或审计工作流绑定配置。设计动机为什么需要显式稳定的转换器身份在 Elsa 的输出绑定Output Binding机制中一个活动Activity的输出既可以原生值被直接读取也可以被显式配置的转换器变换后交付给变量或工作流输出。与「根据源类型和目标类型自动推导转换器」的隐式方案不同Elsa 的 Core 从不根据 source/destination 类型对去猜测该用哪个转换器——它要求每个绑定显式选择一个转换器并把这个选择记录为一个持久化的 ID。这条决策的直接产物就是OutputConverterConfiguration这个极小的记录类型Models/OutputConverterConfiguration.cs它只包含两个成员Id稳定转换器 ID类型为stringSettings可选的、不可变immutable构造时Clone()的 JSON 设置类型为JsonElement?。选择转换器的唯一依据就是 ID。正因如此ADR 0012 对 ID 提出了三条硬性语义要求序数ordinal大小写敏感查找查找使用序数比较MyConverter与myconverter被视为两个不同的 ID绝不会被模糊匹配重复或仅大小写不同的注册一律拒绝同一个 ID 不能被注册两次两个仅大小写不同的 ID 也不允许共存ID 语义不可变immutable semantics一旦发布ID 的行为、设置与结果契约就固定下来。任何破坏性变更——改变行为、改设置含义、改返回结果——都必须注册一个新 ID实践中通常是带版本号的 ID而不是原地修改。配套的用户指南 doc/wiki/output-converters.md 明确警告「The converter ID is a persisted public contract」——ID 会持久化进工作流 JSON因此它是跨版本、跨部署的公开契约。注册模型Descriptor、Registration 与 keyed service三个层次的注册对象理解身份设计需要先分清 Core 中三个容易混淆的类型类型文件职责OutputConverterDescriptorModels/OutputConverterDescriptor.cs转换器的描述稳定 ID、声明的源类型SourceType、声明的结果类型ResultType、展示名、可选描述与可选的设置 JSON Schema。不暴露任何实现细节。OutputConverterRegistrationModels/OutputConverterRegistration.cs把 Descriptor 与其 keyed-service 依赖注册关联起来包含 Descriptor、ServiceKey与ServiceLifetime。OutputConverterRegistryServices/OutputConverterRegistry.cs基于IReadOnlyDictionarystring, OutputConverterRegistrationStringComparer.Ordinal的内存注册表承担注册校验、按 ID 查找与按类型对筛选。Descriptor 的SettingsSchema在构造时同样被Clone()保证描述在共享后不被外部篡改不可变性。OutputConverterDescriptor构造函数签名如下public OutputConverterDescriptor( string id, Type sourceType, Type resultType, string displayName, string? description null, JsonElement? settingsSchema null)AddOutputConverter 扩展方法与注册时的双重校验生产模块通过 Extensions/OutputConverterServiceCollectionExtensions.cs 中的AddOutputConverterTConverter()扩展方法注册转换器。它完成四件事调用OutputConverterRegistry.ValidateDescriptor(descriptor)做静态校验调用ValidateUniqueId检查服务集合里是否已存在相同 IDOrdinalIgnoreCase比较即大小写不同也判为冲突的注册以descriptor.Id作为 key注册一个 keyed service默认ServiceLifetime.Scoped把OutputConverterRegistration以单例方式加入服务集合并TryAddSingleton注册IOutputConverterRegistry。注册期拒绝冲突的具体实现体现在两处扩展方法在注册时就抛异常OutputConverterServiceCollectionExtensions.ValidateUniqueId注册表在构造时再次全面校验OutputConverterRegistry.ValidateRegistrations包括Descriptor 的 ID 不能为空ServiceKey必须与 ID 序数相等按OrdinalIgnoreCase分组后任何组出现超过一个即抛InvalidOperationException异常信息明确指出该 ID 被重复注册或仅因大小写不同而冲突。另外ValidateDescriptor还拒绝开放泛型open-generic的源类型或结果类型ContainsGenericParameters为真时抛异常。这对应 ADR 中的「open-generic matching is deferred」——转换器匹配在注册期不做开放泛型推导。单例注册表、不缓存作用域实例ADR 0012 特别强调「cached descriptors never retain scoped services」缓存的描述从不持有作用域服务。这与注册表实现严格一致OutputConverterRegistry单例缓存的是OutputConverterRegistration即描述 key 生命周期元数据不是转换器实例本身。转换器实例通过GetRequiredKeyedServiceIOutputConverter(id)从当前工作流执行作用域中解析。这一行为被 test/unit/Elsa.Workflows.Core.UnitTests/OutputConverters/OutputConverterRegistrationTests.cs 明确验证ScopedRegistration_ResolvesOneConverterPerScope证明同一作用域内解析到同一实例、不同作用域解析到不同实例SingletonRegistration_ResolvesTheSameConverterAcrossScopes证明单例生命周期跨作用域共享TransientRegistration_ResolvesANewConverterForEachRequest证明每次请求都是新实例。而Registration_ExposesTheDescriptorThroughTheRegistryWithoutRetainingAConverterInstance则直接断言注册表只暴露 Descriptor且FindRegistration(...).ServiceKey与Descriptor.Id相等。Converter 实现契约同步、确定、无副作用ADR 0012 要求转换器「synchronous, deterministic, and side-effect-free」这被直接写进接口的 XML 文档注释Contracts/IOutputConverter.cspublic interface IOutputConverter { object? Convert(OutputConversionContext context); IEnumerablestring ValidateSettings(JsonElement? settings) []; }窄而不可变的转换上下文转换器拿到的不是整个执行上下文而是一个刻意收窄的OutputConversionContextModels/OutputConversionContext.cs这是 ADR 0012「narrow immutable Conversion Context」的落地Value非空的原生活动输出值SourceType该输出声明的类型DestinationType绑定目标声明的类型Settings克隆后的可选 JSON 设置。该 record 的所有属性只读Settings在构造时Clone()转换器无法反方向影响绑定决策。依赖注入方式依赖通过构造函数注入实例从当前工作流作用域解析activityExecutionContext.WorkflowExecutionContext.ServiceProvider.GetRequiredKeyedServiceIOutputConverter(configuration.Id)见 Services/OutputConverterInvoker.cs。作用域感知行为由 test/unit/Elsa.Workflows.Core.UnitTests/OutputConverters/OutputConverterInvokerTests.cs 的Invoke_ResolvesScopedKeyedConverterFromEachWorkflowProvider验证同一作用域内两次调用返回相同结果跨作用域返回不同结果。兼容性判定可赋值性assignability而非强类型绑定ADR 0012 明确了转换器兼容性的两套规则源类型使用常规的基类/接口可赋值性IsAssignableFrom判定——OutputConverterRegistry.FindCompatible要求descriptor.SourceType.IsAssignableFrom(sourceType)因此声明支持Animal的转换器也能接受运行时类型为Dog派生类的输出目标类型声明的结果类型必须可赋值给一个「可解析的 Destination Type」且目标类型允许 nullable 场景Nullable.GetUnderlyingType(destinationType)也为兼容见OutputConverterRegistry.IsAssignableToDestination。运行时侧在 Services/OutputConverterInvoker.cs 做了双重检查声明级检查descriptor 与 binding 类型与运行时值级检查IsRuntimeValueCompatible同样支持 nullable。测试Invoke_WhenDeclaredSourceDerivesFromSupportedSource_InvokesConverter用Animal/Dog的继承关系验证了源兼容的「基类声明、派生类输入」路径Invoke_WhenDeclaredSourceIsIncompatible_FailsBeforeConversion则证明源不兼容时转换器甚至不会被调用。校验时机定义校验与运行时校验的双保险ADR 0012 规定「Core validates registration, compatibility, and settings when accepting or materializing a definition and repeats safety checks at runtime」。仓库中对应两条路径定义验收期ValidateOutputConverters 通知处理器Modules/Elsa.Workflows.Management/Handlers/Notifications/ValidateOutputConverters.cs 订阅WorkflowDefinitionValidating通知在工作流定义被接受之前逐节点校验每一个带Converter配置的输出绑定校验项包括配置的 ID 非空白该输出存在可解析的目标变量或工作流输出——「没有目标就不允许配置转换器」该 ID 已注册通过FindRegistration声明输出类型可赋值给转换器的源类型转换器结果类型可赋值给目标类型含 nullable 判定CanAssign能从服务容器解析出转换器实例JSON Schema 与转换器自有的设置校验通过。任一失败都会以「Output name converter: message」的形式追加到工作流验证错误集合阻止定义被接受。对应单元测试见 test/unit/Elsa.Workflows.Management.UnitTests/Handlers/Notifications/ValidateOutputConvertersTests.cs。运行时OutputConverterInvoker 的五阶段防线运行期由OutputConverterInvoker.Invoke按顺序执行每个阶段失败都映射到 Enums/OutputConversionFailureStage.cs 中定义的五种失败阶段阶段触发条件Resolution配置缺失、ID 未注册、keyed service 解析抛异常SourceCompatibility运行时值/声明源类型与描述符源类型不兼容SettingsValidationJSON Schema 校验失败、转换器ValidateSettings返回错误或抛异常InvocationConvert方法本身抛出异常ResultValidation声明结果类型不可赋值给目标、运行时结果与声明/目标类型不符、对不可空目标返回 null测试 OutputConverterInvokerTests.cs 逐项覆盖设置违反 JSON SchemaInvoke_WhenSettingsViolateJsonSchema_FailsBeforeConversion、转换器拒绝设置Invoke_WhenConverterRejectsSettings_FailsBeforeConversion、声明结果不可赋值Invoke_WhenDeclaredResultCannotBeAssignedToDestination_FailsBeforeConversion、运行时结果违反声明Invoke_WhenRuntimeResultViolatesDescriptor_FailsResultValidation、对不可空目标返回 nullInvoke_WhenConverterReturnsNullForNonNullableDestination_FailsResultValidation——注意前三个测试都断言ConvertCalls 0证明转换器在任何校验通过前绝不会被调用。设置校验JSON Schema 与转换器自有的双重检查Services/OutputConverterSettingsValidator.cs 实现IOutputConverterSettingsValidator其校验逻辑依次为设置必须是 JSON 对象ValueKind JsonValueKind.Object若 Descriptor 注册了SettingsSchema则用JsonSchema.Build编译 Schema结果按 ID 缓存在ConcurrentDictionary中再对设置缺省为{}执行Evaluate调用转换器自有的ValidateSettings默认空实现返回空集合如果转换器抛出异常则记为「Converter-owned settings validation failed.」。IOutputConverter接口注释明确要求ValidateSettings返回的错误消息不得包含敏感的设置值——这是隐私安全设计的一部分与下文异常模型一脉相承。隐私安全的失败模型OutputConversionExceptionADR 0012 的收尾条款规定解析、校验、调用、结果任一环节的失败都进入正常的活动故障处理normal activity fault handling并包装为一个**隐私安全privacy-safe**的OutputConversionExceptionExceptions/OutputConversionException.cs。该异常的实现要点实现ISafeExceptionMetadataProviderGetSafeMetadata()只暴露结构化身份信息ConverterId、Stage、ActivityId、ActivityType、OutputName、SourceTypeName以及可选的目标 ID 与目标类型名消息模板为Output converter id failed during stage for output name of activity activityId.不含任何转换后的值保留原始异常作为InnerException诊断链完整但默认情况下既不包含原生值、也不包含原始设置日志/诊断面通过安全元数据暴露失败阶段便于运维定位问题而不泄露工作流数据。配套 wiki 进一步说明持久化的异常状态包含「safe structured identities and the failure stage」而「excludes native values, raw settings, and converter exception details」。与服务端发现机制ADR 0013的关系显式稳定身份能够成立依赖于服务端持有的描述目录Core 拥有IOutputConverterRegistry并通过 API 暴露可按源/目标类型过滤的描述符目录见 Endpoints/OutputConverters/List/Endpoint.cs路由为GET /descriptors/output-converters需要read:*或read:output-converters权限。接口端点在 clients/Elsa.Api.Client/Resources/OutputConverters/Contracts/IOutputConvertersApi.cs 中以 Refit 客户端形式暴露请求参数sourceType/destinationType见 Requests/ListOutputConvertersRequest.cs。端点返回的模型Resources/OutputConverters/Models/OutputConverterDescriptor.cs只包含Id、SourceTypeName、ResultTypeName、DisplayName、Description、SettingsSchema——绝不暴露转换器实例、实现类型或服务生命周期。这由 test/unit/Elsa.Workflows.Api.UnitTests/OutputConverters/OutputConverterEndpointTests.cs 的ListCompatible_FiltersThroughTheRegistryAndExposesOnlySafeDescriptorMetadata直接断言响应模型不含SourceType/ResultType/ServiceKey/ServiceLifetime属性。这解释了「Core ships the infrastructure and a reference converter in tests or a sample, while production modules register converters whose semantics they own」——即仓库内的参考转换器仅存在于测试夹具如 test/unit/Elsa.Workflows.Core.UnitTests/OutputConverters/Fixtures/ReferenceOutputConverter.cs中生产语义由各模块自行注册。实战示例实现并注册一个带版本 ID 的转换器将上述规则串起来一个完整的转换器生命周期如下完整示例亦见 doc/wiki/output-converters.md1. 实现转换器同步、无副作用读取设置中的formatusing System.Globalization; using System.Text.Json; using Elsa.Extensions; using Elsa.Workflows; using Elsa.Workflows.Models; public sealed class NumberToTextConverter : IOutputConverter { public object Convert(OutputConversionContext context) { var format context.Settings?.TryGetProperty(format, out var value) true ? value.GetString() : null; return ((decimal)context.Value).ToString(format, CultureInfo.InvariantCulture); } }2. 注册带版本的稳定 ID 设置 JSON Schema默认作用域生命周期using var schemaDocument JsonDocument.Parse( {type:object,properties:{format:{type:string}}}); services.AddOutputConverterNumberToTextConverter( new OutputConverterDescriptor( sample.number-to-text.v1, typeof(decimal), typeof(string), Number to text, Formats a decimal using an explicit invariant format., schemaDocument.RootElement));注意 IDsample.number-to-text.v1携带版本号将来若格式语义或返回结果变化应注册sample.number-to-text.v2而非修改 v1。3. 在绑定上配置转换器OutputT的Converter属性activity.Result new Outputdecimal(targetVariable) { Converter new OutputConverterConfiguration( sample.number-to-text.v1, JsonDocument.Parse({format:0.00}).RootElement) };4. 序列化到工作流 JSONconverter对象是唯一持久化的转换器痕迹绝不持久化 CLR 类型或描述符{ typeName: Decimal, memoryReference: { id: formatted-total }, converter: { id: sample.number-to-text.v1, settings: { format: 0.00 } } }未配置转换器的绑定直接省略converter字段走原有的赋值路径不产生任何转换器相关的查找、校验、处理或分配开销见 ADR 0011 的边界说明。运维准则与常见陷阱结合 ADR 0012 与 doc/wiki/output-converters.md 的 Operational Guidance以下几点直接影响生产稳定性ID 是部署漂移的锚点把「移除某个转换器注册」视为部署漂移——已发布工作流一旦引用它运行到赋值时就会以Resolution阶段故障。发布新版本注册应保持向后兼容不要复用 ID 改语义行为、设置、结果任一变更是破坏性变更必须换新 ID环境相关选择必须是显式设置locale、时区、舍入规则等一律做成设置项本例中的format即是保证转换器「确定性」禁止 I/O 与状态变更转换器不得读写外部系统或修改工作流状态需要异步或带副作用变换时改用活动输入或显式活动见 ADR 0011 的边界异步转换、活动输入转换、表达式强转、转换器链、无目标的转换器配置均在转换边界之外失败处理交给活动故障管线不要自行吞掉OutputConversionException它已经过隐私处理可直接用于日志、诊断与故障诊断。小结ADR 0012 为 Elsa Workflows 的转换器体系确立了「显式选择 稳定 ID 严苛校验」的架构基调序数大小写敏感的 ID 查找与拒绝重复/大小写冲突注册保证了身份的确定性不可变描述符与「注册表不缓存作用域实例」保证了并发下的内存与作用域安全定义期与运行期的双重校验加上五阶段失败模型保证了行为可预期隐私安全的异常设计则让失败信息可以安全进入诊断系统。这些规则在 doc/adr/0011-output-conversion-at-binding-is-synchronous.md 与 doc/adr/0013-output-converter-discovery-is-server-owned.md 两条相邻 ADR 中分别补充了「同步边界」与「服务端发现」两个维度三者共同构成 Elsa 输出转换机制的完整架构决策闭环。赞分享后端工作流自动化流程编排低代码【免费下载链接】elsa-coreThe Workflow Engine for .NET项目地址https://gitcode.com/gh_mirrors/el/elsa-core点击查看免费下载相关推荐Elsa Workflows 领域术语体系解析从输出转换到用户任务与外部身份认证Elsa Workflows 领域术语体系解析从输出转换到用户任务与外部身份认证 导读 本文面向使用 Elsa Workflows 构建 .NET 工作流引擎后端工作流自动化流程编排低代码RROrg/rr项目定制化引导镜像构建指南RROrg/rr项目定制化引导镜像构建指南 RROrg/rr项目是一个专注于为Synology NAS设备提供定制化引导镜像的开源项目。本文将以RS820型号后端Yolo-to-COCO-format-converter轻松转换标注格式Yolo to COCO format converter轻松转换标注格式 在机器学习和计算机视觉领域选择合适的模型和标注格式是至关重要的。Yolo模型因其上一篇Quickwit 数据目录Data Directory完全指南qwdata 布局、缓存容量规划与磁盘排障下一篇AutoClip生产环境安全配置指南HTTPS、CORS与防火墙完整清单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →