将 DI 实践应用于遗留应用系统

发布于 2025-01-04 05:41:15 字数 2730 浏览 4 评论 0原文

我们的客户拥有一个庞大的遗留系统,正在从 .NET 1.1 升级到 .NET 4.0。除此之外,还将把 Web 应用程序的底层框架移植到 WCSF 2010,并将 Enterprise Library 5.0 ou1 作为基础企业框架。伴随这些框架而来的是 DI 设施。

WCSF 有效地强制 MVP 和模块设计依赖于 ObjectBuilder 及其属性,因此 Web 应用程序升级遵循该模式。

Web 应用程序使用应用程序层上的附加应用程序服务(通过 WCF)。由于底层框架已成为企业库,Unity应用程序块已“免费滑入”。但现有的编码风格仍然是遗留的,因此与 Web 层不同,没有从根本上切换到 DI。

应用程序层的典型层是

  • 业务门面 (BF) - 公开公共粗粒度业务功能,控制事务。
  • 业务组件 (BC) - 由外观方法协调的粒度逻辑组件,以完成事务中的实际工作。
  • 数据访问 (DA) - 业务组件的读/写数据

为什么我上面提到这种风格仍然是遗留的,因为层之间的服务通信是通过通过网络传递数据集(而且它们很大)来进行的。该数据集被传递到每个对象的构造函数中。例如伪代码

BF.SomeExposedMethod(StrongTypeDS ds)
{
    this.BeginTransaction();
    BizComp bc = new BizComp(ds);
    bc.ShareTransaction();
    bc.DoWork();
    this.CommitTransaction();
}

BC.DoWork()
{
    DataAccess da = new DataAccess(this.transaction, this.ds);
    // perform work
    da.SaveWork(this.ds);
}

此外,这些类的构造函数基于其父抽象类的风格;他们期望在实例化时接收数据集。

我一直在阅读 Mark Seeman 的DI in .NET一书以及各种其他有关 DI 问题的公共互联网资源。从所有这些讨论中我似乎了解到,DI 看起来很适合准备一个准备好工作的干净对象图表。我所说的干净是指没有上下文数据。这些遗留代码模式的构造函数从一开始就期望工作数据和上下文。

如果我正确理解组合根的原理,则必须重新设计构造函数和方法,以便通过属性传入上下文数据集。我不清楚 DI 容器(在本例中为 Unity)如何自行确定要注入的 DataSet。当然它不能是一个空的全新 DataSet。在开发的这个阶段(我没有直接参与该项目,而是从侧面提供支持),我将无法建议对 WCF 实现进行根本性更改,以使其在实例化外观对象之前成为组合根。

此外,我还没有收集关于 DI 如何应用于基于运行时条件实例化对象的重要建议?根据数据的执行状态,可以实例化附加对象,并且类型可以根据数据类别而不同。将某种程度的 DIP 和 DI 实践引入此应用程序的最简单方法似乎是使用 Unity 容器作为服务定位器;仅在需要时在执行点获取实例。

更新答案

根据 Mark Seeman 的建议,我

使用 Unity 容器制作了以下 POC BF。虽然我已经成功地尝试了 Unity.Wcf 包,但我还为每个 BF 做了下一个最接近的组合根,其中每个外观方法将调用容器来解析执行该方法工作所需的对象图。 (更现实的是,在给定情况下,该环境可能会采用这种模式。)

static ExampleBF()
{
    container = new UnityContainer().LoadConfiguration();
}

BF 获取已解析的 IBCFactory,它将使用传入的实时上下文数据实例化具体的 BC。BCFactory

DataSet IExampleService.GetProducts(DataSet ds)
{
    IExampleBC bc = container.Resolve<IBCFactory>().CreateBC(ds);
    // GetProducts() takes no parameter because DataSet already passed in on construction.
    return bc.GetProducts();
}

通过构造函数注入接收 IDACFactory。它把它交给每个实例化的 BC。

public BCFactory(IDACFactory DACFactory)
{
    this.DACFactory = DACFactory;
}

IExampleBC IBCFactory.CreateBC(DataSet ds)
{
    return new ExampleBC(this.DACFactory, ds);
}

BC 将依赖 IDACFactory 为其提供 DAC。

DataSet IExampleBC.GetProducts()
{
    IExampleDAC dac = this.dacFactory.CreateDAC(this.ds);
    return dac.GetProducts();
}

DACFactory 类似地根据上下文数据实例化 DAC。

IExampleDAC IDACFactory.CreateDAC(DataSet ds)
{
    return new OrderDAC(ds);
}

当然,代码库的实际复杂性情况要大得多,但这个简单的示例应该足以向他们演示 DI 概念。

Our customer has a massive legacy system that is in the process of upgrade from .NET 1.1 to .NET 4.0. Along with that will be the porting of underlying frameworks to WCSF 2010 for web applications, and Enterprise Library 5.0 ou1 as the base enterprise framework. Along with those frameworks come DI facilities.

WCSF effectively forces the MVP and module designs to rely on ObjectBuilder and its attributes, so the web application upgrades are following that pattern.

The web applications consume additional application services on an app tier (via WCF). Since the underlying framework has become Enterprise Library, Unity app block has "slided in for free". But the existing coding style remains legacy, thus does not have a fundamental switch to DI unlike the web layer.

The typical layers at the app tier are

  • Business facade (BF) - exposes public coarse-grain business functions, controls transactions.
  • Business components (BC) - granular logic components coordinated by facade methods to do the actual work in the transaction.
  • Data access (DA) - read/write data for business components

Why i mentioned above the style remains legacy is because the service communication between the tiers happens by passing DataSets (and they are huge) across the network. This DataSets are passed into each object's constructor. E.g. psuedo code

BF.SomeExposedMethod(StrongTypeDS ds)
{
    this.BeginTransaction();
    BizComp bc = new BizComp(ds);
    bc.ShareTransaction();
    bc.DoWork();
    this.CommitTransaction();
}

BC.DoWork()
{
    DataAccess da = new DataAccess(this.transaction, this.ds);
    // perform work
    da.SaveWork(this.ds);
}

What's more these classes' constructors are based on the style of their parent abstract classes; they expect receiving DataSets upon instantiation.

I have been reading Mark Seeman's DI in .NET book and various other public Internet sources about DI issues. What I seem to understand from all these discussions is DI looks good for preparing a graph of clean objects ready to do work. By clean I mean empty of context data. These legacy code patterns have their constructors expecting the working data and context right from the start.

If I understand the principle of the Composition Root correctly, the constructors and methods have to be redesigned so that the context DataSets are passed in via properties. I am unclear how a DI container (Unity in this case) can figure out on its own what DataSet to inject in. For sure it cannot be an empty brand-new DataSet. At this stage of development (which I am not directly involved in the project but supporting from the side lines) I will be unable to recommend a fundamental change to the WCF implementation to make it the Composition Root before instantiating a facade object.

Additionally, I have not gathered significant advice on how DI applies to instantiating objects based on runtime conditions? Based on the execution state of the data, additional objects may be instantiated, and the type may differ based on the data category. It would appear to be the easiest way to introduce some level of DIP and DI practice into this app would be to employ the Unity container as a Service Locator; grab an instance only at that point of execution as needed.

UPDATE TO ANSWER

Based on Mark Seeman's advice, I have fabricated the following POC

BF with a Unity container. While I have successfully experimented with Unity.Wcf package, I have also made the next closest thing with each BF a Composition root where each facade method will call the container to resolve the graph of objects needed to carry out the work of the method. (This is more realistically a pattern this environment might pick up given the situation.)

static ExampleBF()
{
    container = new UnityContainer().LoadConfiguration();
}

BF gets a resolved IBCFactory, which will instantiate a concrete BC with live context data passed in.

DataSet IExampleService.GetProducts(DataSet ds)
{
    IExampleBC bc = container.Resolve<IBCFactory>().CreateBC(ds);
    // GetProducts() takes no parameter because DataSet already passed in on construction.
    return bc.GetProducts();
}

The BCFactory takes in an IDACFactory via constructor injection. Which it hands to each instantiated BC.

public BCFactory(IDACFactory DACFactory)
{
    this.DACFactory = DACFactory;
}

IExampleBC IBCFactory.CreateBC(DataSet ds)
{
    return new ExampleBC(this.DACFactory, ds);
}

The BC would rely on the IDACFactory to supply it with a DAC.

DataSet IExampleBC.GetProducts()
{
    IExampleDAC dac = this.dacFactory.CreateDAC(this.ds);
    return dac.GetProducts();
}

The DACFactory similarly instantiates a DAC based on the context data.

IExampleDAC IDACFactory.CreateDAC(DataSet ds)
{
    return new OrderDAC(ds);
}

Of course, the reality of the code base's complexity situation is way bigger in magnitude, but this simple example should hopefully suffice in demonstrating the DI concept to them.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

心安伴我暖 2025-01-11 05:41:15

据我了解有关类的描述,它们听起来更像是数据载体(实体,如果你愿意的话)而不是服务。如果这是真的,那就是 组合根不负责组合数据对象

但是,如果您必须根据运行时条件解析服务,则 抽象工厂是通用解决方案

由于您正在使用遗留代码库,我想推荐这本书 《有效工作》与遗留代码,它为如何解耦紧密耦合的遗留代码库提供了非常有价值的指导。

As far as I understand the description of the classes in question, they sound more like data carriers (Entities, if you will) than Services. If this is true, it's not the responsibility of the Composition Root to compose data objects.

However, if you must resolve services based on run-time conditions, an Abstract Factory is the universal solution.

Since you are working with a legacy code base, I'd like to recommend the book Working Effectively with Legacy Code, which provides much valuable guidance in how to decouple tightly coupled legacy code bases.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文