【问题标题】:Service behaviour in Entity - how to avoid service injection into entity?实体中的服务行为 - 如何避免服务注入实体?
【发布时间】:2011-03-10 02:19:18
【问题描述】:

我的实体结构如下:

IManager: IDeletable
{
IEnumerable<IFund> Funds {get;}
IFailureNotification Delete();
}

IFund : IDeletable
{
IEnumerable<IFundClass> FundClasses
IFailureNotification Delete();
}

IFundClass: IDeletable, IInvestable
{
IFailureNotification Delete();
}

我有一个服务,它接受 IDeletable 并在其上调用 Delete。然后根据返回值提交事务或回滚事务。我正在使用 NHibernate 来持久化类,因此无法将 RI 放入数据库并捕获异常(无论如何我都不喜欢)。

这是多态性的经典案例,Manager 循环遍历其 Funds 并在删除自身之前将其删除,Fund 在删除自身之前依次删除 FundClasses,因此服务可以采取任何实现IDeletable 并知道删除将在所有级别执行适当操作的实体。

问题出在:fund 类需要使用他们一无所知的IInvestable 接口来查找它们是否在完全独立的上下文中使用。这需要一个服务 - IInvestmentCalculationService

显然,我不想将InvestmentCalculationService 注入基金类实体构造函数,也不想将其注入删除方法,因为这是在 Funds 和 Managers 以及许多其他类上,所以不没有任何意义 - 也意味着一旦我们有更多要求,我们就必须更改所有内容的删除方法。

我对这里的域事件模型有点动心:http://www.udidahan.com/2009/06/14/domain-events-salvation/ 但我不确定它是否正确,因为我正试图从触发的事件处理程序中取回数据 - 这会起作用,但有点味道有点不对,所有的例子都只显示了火和忘记的情况。

有人有什么建议吗?

【问题讨论】:

    标签: c# nhibernate dependency-injection dns domain-driven-design


    【解决方案1】:

    我们在这种情况下所做的一件事是让 Delete 不执行实际删除,而是使用收集参数来删除要删除的内容。 Delete() 方法将注册自身和任何其他对象,然后由另一个服务重播。

    【讨论】:

    • 嗨,吉米,我不确定这是如何解决问题的。第二个删除服务如何知道检查FundClass 是否被投资?这不是以 if (x is IFundClass) then _fundClassChecker.check(x); 结尾吗? ?我今天早上想到的一种可能的解决方案是将IDeletionValidationFactory 注入到 Delete 方法中。然后基金类可以调用GetFundClassDeletionValidator 并使用此检查是否可以删除。这对我来说感觉像是双重调度的稍微抽象的版本。你觉得这怎么样?感谢您的关注。
    【解决方案2】:

    “显然我不想注入InvestmentCalculationService”。

    我不喜欢“显然”这个词。我仍然没有被谷歌搜索“将服务注入实体”的结果所强迫。该主题的热门帖子归结为“感觉不对,您可以使用域事件/双重调度来做,所以不要这样做”。

    我个人认为将服务注入实体是很好的,并且认为您应该停止担心并去做。也许不要注入整个 InvestmentCalculationService,但如果您觉得实体不需要访问整个事物,则注入 BitsOfInvestmentCalculationServiceThatINeedToKnowAboutService。

    除非你添加一个返回值(这基本上使它成为一个修饰的服务定位器)并且使用双重调度,否则域事件对你的情况没有好处,你注入的东西必须来自调用堆栈更高的地方 -可能是入口点类的注入值,它很可能不使用该依赖项。

    只需将 InvestmentCalculationService 注入实体并继续您的生活。

    【讨论】:

    • “只需将 InvestmentCalculationService 注入实体”,是指通过构造函数注入,还是作为 Delete 方法中的参数注入?
    • 使用构造函数,如果你把它放在delete方法上,调用者需要知道InvestmentCalculationService,这是破坏封装的。
    • 跟进问题,通常这可以正常工作,除了例如,当我从数据库加载实体时,如果我在数据库中有一千行,这是否意味着这千条记录,我然后必须将这些依赖项注入每个实体,即使我不需要调用 delete 方法?例如当我想列出网页上的所有行时。
    • 如果您要加载 10000 条记录,则 ctor 参数的成本可以忽略不计。成本将在于对实体进行反序列化和水合,这将涉及许多其他对象的创建、使用和丢弃。 ayende.com/blog/3167/creating-objects-perf-implications
    • 如果您愿意,您还可以注入工厂 Func 而不是 InvestmentCalculationService,并且由于某种原因创建服务的成本很高。
    【解决方案3】:

    有一个接口怎么样

    public interface ICanBeDeleted<T>
    {
        bool CanBeDeleted(T itemToBeDeleted);
    }
    

    在实际删除之前,请向您的容器询问此接口的所有实现,调用 CanBeDeleted 函数,如果有任何返回 false 则不要删除。您的 InvestmentCalculationService 将实现 ICanBeDeleted&lt;FundClass&gt; 并在容器中注册。

    【讨论】:

      【解决方案4】:

      感谢大家的帮助,我吸引的观众给我留下了深刻的印象!我特别喜欢 mcintyre 的哲学,从那以后它真的影响了我的思想。在这种情况下,虽然我们进行了双重调度。感觉更稳定一些。

      干杯

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-20
        • 2019-04-25
        • 2012-10-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多