键子图这将是一个有点疯狂,但我相信,如果有可能,这将是在手头的任务最易维护的解决方案。解决与级联后备
我们的应用程序使用Autofac进行依赖注入。
我们使用,我们需要能够演进技术(性能/存储空间优化)或域名的原因自定义数据文件格式。该应用程序将永远只能写最新格式的版本,但需要能够阅读所有以前的版本了。在各个版本之间进化通常是相当缓慢的,只有少数几个地方有变化,所以很多读取代码的代码将保持不变。
文件格式的版本号被存储为在文件的开头的整数值。读取任何版本的文件格式将始终导致相同的数据结构,这里称为Scenario
。
,可以从文件中读取数据的一类呈现IReadDataFile
依赖性:
public interface IReadDataFile
{
Scenario From(string fileName);
}
背后即用于读取场景的各个部分的非平凡的对象图。然而,所需的图形看起来对于每个文件格式的版本的稍有不同(说明性示例,而不是实际的类型;真正的图是复杂得多):
版本1:
ReadDataFileContents : IReadDataFileContents
└> ReadCoreData : IReadCoreData
└> ReadAdditionalData : IReadAdditionalData
└> NormalizeName : INormalizeName
2版:
ReadDataFileContentsV2 : IReadDataFileContents
└> ReadCoreData : IReadCoreData
└> ReadAdditionalDataV2 : IReadAdditionalData
└> NormalizeNameV2 : INormalizeName
└> AdditionalNameRegex : IAdditionalNameRegex
版本3:
ReadDataFileContentsV2 : IReadDataFileContents
└> ReadCoreData : IReadCoreData
└> ReadAdditionalDataV3 : IReadAdditionalData
└> NormalizeNameV2 : INormalizeName
└> AdditionalNameRegexV3 : IAdditionalNameRegex
(我只考虑ENTI依赖这样的单独图表;在单个图表中处理这种情况,并且每次切换时版本相关的差异显然非常迅速地变得非常混乱)。
现在无论何时调用IReadDataFile.From()
方法来加载文件,都需要获取适当的子图为文件格式版本。实现这一目的的一种简单方法是通过注入工厂:
public class ReadDataFile : IReadDataFile
{
private readonly IGetDataFileVersion getDataFileVersion;
private readonly Func<int, IReadDataFileContents> createReadDataFileContents;
public ReadDataFile(
IGetDataFileVersion getDataFileVersion,
Func<int, IReadDataFileContents> createReadDataFileContents)
{
this.getDataFileVersion = getDataFileVersion;
this.createReadDataFileContents = createReadDataFileContents;
}
public Scenario From(string fileName)
{
var version = this.getDataFileVersion.From(fileName);
var readDataFileContents = this.createReadDataFileContents(version);
return readDataFileContents.From(fileName);
}
}
问题是如何注册和解决这些子图的工作。
手动注册完整的子图为Keyed<T>
非常复杂且容易出错,并且对于其他文件格式版本(特别是图形比示例复杂得多)无法很好地扩展。
相反,我想如上所述,看起来像这样对整个事情的注册:
builder.RegisterAssemblyTypes(typeof(IReadDataFile).Assembly).AsImplementedInterfaces();
builder.RegisterType<ReadDataFileContents>().As<IReadDataFileContents>();
builder.RegisterType<ReadDataFileContentsV2>().Keyed<IReadDataFileContents>(2);
builder.RegisterType<ReadAdditionalData>().As<IReadAdditionalData>();
builder.RegisterType<ReadAdditionalDataV2>().Keyed<IReadAdditionalData>(2);
builder.RegisterType<ReadAdditionalDataV3>().Keyed<IReadAdditionalData>(3);
builder.RegisterType<NormalizeName>().As<INormalizeName>();
builder.RegisterType<NormalizeNameV2>().Keyed<INormalizeName>(2);
builder.RegisterType<AdditionalNameRegex>().As<IAdditionalNameRegex>();
builder.RegisterType<AdditionalNameRegexV3>().Keyed<IAdditionalNameRegex>(3);
builder.Register<Func<int, IReadDataFileContents>>(c =>
{
var context = c.Resolve<IComponentContext>();
return version => // magic happens here
});
这意味着只有该图形之间变化的成分明确的注册。并以“奇迹发生在这里”,我的意思是,对于越来越远这个最小的注册,该决议将不得不做繁重。
我希望这样做的方式是这样的:对于要解决的每个组件(在此子图中),它都试图解析注册到所请求的文件格式版本的注册。如果该尝试失败,则为另一个更低的版本进行,等等;当密钥2
的分辨率失败时,将解决默认注册。
一个完整的例子:
- 的
createReadDataFileContents
工厂被调用的3
一个version
值,所以所要求的图形是一个用于以上给出文件格式版本3。 - 尝试解决
IReadDataFileContents
与关键3
。这是不成功的;没有这样的注册。 - 现在尝试使用密钥
2
解决IReadDataFileContents
。这成功了。 - 构造函数需要一个
IReadCoreData
。试图用密钥3
,然后2
来解决这个问题;都会失败,所以默认注册已解决,这是成功的。 - 第二个构造函数参数是
IReadAdditionalData
;尝试使用成功的密钥3
来解决此问题。 - 构造函数需要
INormalizeName
;分辨率为3
失败,则尝试2
成功。 - 这个构造函数反过来需要
IAdditionalNameRegex
;关键字3
的解析度尝试成功。
这里棘手的事情(和一个我不知道怎样做)是该版本“倒计时”回退过程需要发生的每个个人的依赖得到解决,每次从开始初始值为version
。
围绕Autofac API和一些Google搜索产生了一些看起来很有趣的事情,但他们都没有提供一个明显的解决方案路径。
Module.AttachToComponentRegistration()
- 我已经使用这个别处使用registration.Preparing
挂钩到解析过程;然而,只有在找到合适的注册时才会发生该事件,并且在此之前似乎没有发生过事件,也没有办法在解决失败(我感到意外)的情况下注册回调。IRegistrationSource
- 这似乎是实现这种更一般的注册/解决方案原则的方式,但我无法理解我内部需要做的事情,以防事实上是我所在的地方寻找。WithKeyAttribute
- 我们不能在这里使用它,因为我们需要控制从外部注入的依赖项的“版本”(同样,实际的业务代码将依赖于Autofac,这从来都不是好的)。ILifetimeScope.ResolveOperationBeginning
- 这看起来很有希望,但事件只是提出了已经成功的决议。IIndex<TKey, TValue>
- 另一件看起来非常好的东西,但它包含已经构建的实例,无法获得较低级别分辨率的版本密钥。
解决问题的一方将限制整个事情只是实际上与此相关的类型,但我想这可以做基于惯例(命名空间等),如果需要的话。
另一个可能有用的想法是,在完成所有注册(必须以某种方式确定)之后,“缺口”可以“填满” - 意味着如果注册键与3键但没有键2,将会添加一个等于默认注册的值。这将允许用相同的关键字解决子图中的所有所有依赖关系,并取消对可能是整个事情中最困难部分的“级联后备”机制的需要。
Autofac有什么办法可以实现吗?
(另外,感谢首先阅读这部史诗!)
你有没有想过使用特定于版本的标记接口? '公共类ReadDataFileContentsV2:IReadDataFileContents,IReadDataFileContentsV2'种事情? –
不具体,不。你有什么样的使用方法?在其他类中依赖它们会产生与使用'WithKeyAttribute'类似的问题 - 类本身永远无法决定它需要哪个版本的依赖关系。例如,请参阅上面的'NormalizeNameV2' - 读取版本2和3的文件是一样的,但'IAdditionalNameRegex'的必需实现不同。 – TeaDrivenDev
顺便说一句,_excellent_写起来。我知道记录这类事情的复杂性需要永远的时间,并且其他问答者和答复者都会对此表示赞赏。我希望更多的人会花费这样的时间和精力。 –