【发布时间】:2021-01-24 22:26:18
【问题描述】:
好的,伙计们,这是一个相当长的问题,在我提出实际问题之前,我会尽力描述当前情况并提供一些有意义的背景信息。
TL;DR;
我需要一种方法来识别无效的枚举到枚举映射,这可能会导致运行时问题,因为它们的定义随着时间的推移而出现分歧。
一些上下文
所以我和我的团队正在维护这组相当复杂的 REST-API...至少在涉及到实际涉及的对象图时很复杂。 我们总共要处理数百个模型。 为了提高结构复杂性,原始架构在内部 API 级别上采用了完整的 n 层样式。
最重要的是,我们有多个这样的架构服务,有时需要相互调用。 这是通过这里的普通 http 调用,那里的一些消息传递来实现的,你明白了。
为了让一个 API 与另一个 API 进行通信,并维护 SOA 和/或微服务原则,每个 API 至少提供一个相应的客户端库,该库管理与其代表 API 的通信,而不管实际的底层协议如何参与。
归结起来,每个 API 至少包含以下层(自上而下)
- 客户层
- API 层
- 领域层
- 持久层
此外,所有这些层都维护自己对各种模型的表示。通常,这些是 1:1 表示,只是在另一个命名空间中。有时这些层之间存在更显着的差异。这取决于...
为了在这些层之间进行通信时减少样板,我们大部分时间都在使用 AutoMapper(讨厌或喜欢它)。
问题:
随着我们整个系统的发展,我们越来越注意到在模型的各种表示中映射枚举到枚举属性时出现的问题。 有时是因为一些开发人员只是忘记在其中一个层中添加一个新的枚举值,有时我们重新生成了一个基于 Open-API 的生成客户端等,然后导致这些定义不同步枚举。主要问题是,源枚举可能比目标枚举具有更多的值。 当命名略有不同时,可能会出现另一个问题,例如执行者与执行者
假设我们有这个(非常非常过度简化的)模型表示
public enum Source { A, B, C, D, Executer, A1, B2, C3 } // more values than below
public enum Destination { C, B, X, Y, A, Executor } //fewer values, different ordering, no D, but X, Y and a Typo
class SourceType
{
public Source[] Enums { get; set; }
}
class DestinationType
{
public Destination[] Enums { get; set; }
}
现在假设我们的 AutoMapper 配置如下所示:
var problematicMapper = new MapperConfiguration(config =>
{
config.CreateMap<SourceType, DestinationType>();
}).CreateMapper();
因此,映射以下模型在语义方面有点危险(或者至少在调试时提供了一些非常奇怪的乐趣)。
var destination = problematicMapper.Map<DestinationType>(new SourceType()
{
Enums = new []
{
Source.A,
Source.B,
Source.C,
Source.D,
Source.Executer,
Source.A1,
Source.B2,
Source.C3
}
});
var mappedValues = destination.Enums.Select(x => x.ToString()).ToArray();
testOutput.WriteLine(string.Join(Environment.NewLine, mappedValues));
/*
Source.A => A <- ✔️ ok
Source.B => b <- ✔️ok
Source.C => c <- ✔️ok
Source.D => Y <- ????♀️ whoops
Source.Executer => A <- ????♂️ wait, what?
Source.A1 => Executor <- ???? nah
Source.B2 => 6 <- ???? wtf?
Source.C3 => 7 <- ???? wth?
*/
与我无关,因为这里的某些情况是上演的,可能比现实中发现的更极端。只是想指出一些奇怪的行为,即使 AutoMapper 试图优雅地处理大多数情况,比如重新排序或不同的外壳。目前,我们在源枚举中面临更多的值,或者在命名/拼写错误方面略有不同
当这最终导致一些讨厌的生产错误时,可以观察到更少的乐趣,这也可能或多或少地对业务产生严重影响 - 特别是当这种问题只发生在运行时,而不是测试和/或构建时间。
此外,该问题不仅存在于 n 层架构,还可能是正交/洋葱/干净架构样式中的问题(而在这种情况下,这种价值更可能是——类型将放置在 API 中心的某个位置,而不是每个角落/外环/适配器层或任何当前术语)
(临时)解决方案
尽管试图减少各个层内的冗余剪切量,或者(手动)在定义本身内维护明确的枚举值(这两个都是有效的选项,但见鬼,这是很多 PITA 工作),在尝试缓解此类问题时,没有太多工作要做。
很高兴,有一个不错的选项可用,它利用 per-name 而不是 per-value 映射枚举到枚举属性,以及做更多在每个成员的基础上进行非常精细的定制。
[AutoMapper.Extensions.EnumMapping] 来救援!
来自文档:
如果两个枚举类型具有相同的值(或按名称或按值),包 AutoMapper.Extensions.EnumMapping 会将所有值从 Source 类型映射到 Destination 类型
和
这个包添加了一个额外的 EnumMapperConfigurationExpressionExtensions.EnableEnumMappingValidation 扩展方法来扩展现有的 AssertConfigurationIsValid() 方法来验证枚举映射。
要启用和自定义映射,只需在 AutoMapper-configuration 中创建相应的类型映射:
var mapperConfig = new MapperConfiguration(config =>
{
config.CreateMap<SourceType, DestinationType>();
config.CreateMap<Source, Destination>().ConvertUsingEnumMapping(opt => opt.MapByName());
config.EnableEnumMappingValidation();
});
mapperConfig.AssertConfigurationIsValid();
这将验证枚举到枚举的映射。
问题(终于^^)
由于我们的团队之前没有(不需要)为每个枚举到枚举的映射配置 AutoMapper(就像 AutoMapper 早期版本中的动态映射一样),所以我们对如何以有效和确定性地发现需要以这种方式配置的每张地图。尤其是,我们每个 api(和每个层)可能要处理几十个这样的案例。
我们怎样才能做到这一点,我们已经验证并调整了我们现有的代码库,并从一开始就进一步防止这种愚蠢行为?
【问题讨论】:
标签: c# enums automapper