贫血领域模型或“在哪里放逻辑”

时间:2009-09-15 07:50:02

标签: domain-driven-design business-logic

这是“分析瘫痪”似乎已经掌握的情况之一,请咨询!

项目

一个相当简单的汽车产品清单,其中包括零件参考,它们适合的车辆等细节。

前端是一个asp.net MVC应用程序。

后端是SQL,使用Subsonic将产品投影到域对象中。

功能

我们的一个屏幕是产品详细信息屏幕。 ASP.NET MVC Controller调用产品存储库以检索产品详细信息,将这些详细信息(通过一些自动化到viewModel)返回到视图。

现在杀手的细节是我们有两到三个频道进入网站,根据频道,用户需要看到不同的部件号。

让我们举例说,如果它是零售渠道,那么零件编号就像它们在数据库中一样,但如果用户通过贸易渠道来到该网站,则零件参考的开头将替换为替代编号。

e.g。 0900876如果通过交易渠道查看,则变为1700876。

我正在努力的是决定在哪里封装关于部分参考的“渠道规则”(以及可能改变的其他细节)。

我考虑过这些替代方案。

将逻辑直接写入域对象

产品类中,我们可以使用方法/属性来获取已翻译的部件参考。

public string TranslatedPartRef()
    {
        if (this.Channel == "Trade")
        {
            return PartRef.Replace("0900", "1700");
        }
        else
        {
            return PartRef;
        }
    }      

在这种情况下,Product实例必须知道频道,这对我来说似乎不对。

将逻辑封装在另一个对象中

我们可以编写一个类来处理这个部分引用转换,或者创建一个包含这个逻辑的 Channel 类。

我不明白的是如何协调这两个班级。

如果控制器调用存储库以检索产品,那么它是否应该计算出使用了哪个通道并转换了部件引用?如果是这样,我如何将产品的翻译部分参考发送回视图?

同样值得注意的是,这部分参考也必须出现在搜索结果和其他场景中,因此我认为它需要整齐地包含在某个域内。

5 个答案:

答案 0 :(得分:2)

我不是C#家伙,但我认为我会用Java中的装饰器攻击它。

假设您有Product的界面,那么您可以创建一个管理零件号问题的Decorator。

class Product implements IProduct {
    public String getProductCode();
    // etc
}

class ProductChannelDecorator implements IProduct
{
    // constructor, like this in C#?
    public ProductChannelDecorator(IProduct product, Channel channel) { 
        this.product = product;
        this.channel = channel;
    }
    public String getProductCode() {
        switch (this.channel) {
            case Channel.RETAIL:
                return this.decorated.getProductCode();
            case Channel.TRADE:
                return retailToTradeTransformer(this.product.getProductCode());
            // etc
        }
    }
    // etc
}

答案 1 :(得分:2)

您需要问自己的第一个问题是频道的概念是否是域概念。你的问题似乎表明它不是,但另一方面,我认为这听起来也不是特定于应用程序。

您可以问的另一个问题是:如果将来我需要在此域模型之上构建另一个应用程序(例如Web服务或富客户端),我是否还需要处理频道的概念?

我的猜测是答案可能是

据我了解您的问题,频道以某种方式与请求上下文相关。也许它确实是用户的一个属性。或者也许是应用程序配置本身的属性。

无论如何,我会认真思考它究竟是不是一个真正的域概念。如果是,则它可以很好地属于域对象。

如果没有,ptomli建议的Decorator实现听起来是一个很好的方法。

答案 2 :(得分:0)

会有多少种不同的零件编号。如果它只是Trade v Retail,我很想在Product对象中简单地使用这两个数字,并让UI决定显示哪个。在对产品采取行动时,身份可以是“{Trade,Retail},type”。

对于某些模式灵活的我认为这是你的渠道理念很好。但如果它具有双向责任,将零售映射到贸易和从贸易中进行映射,这似乎是有效的。 Channel对象可以看作是一个适配器,能够进行其他转换和丰富。

作为一个实现,我将为每个Channel创建一个单独的Channel对象,试图避免case语句和if else else逻辑。对于Retail,Channel对象可以是Trade的NOOP对象,它可以做映射。工厂可以创建approporaye Channel对象。

答案 3 :(得分:0)

如果零件号映射可能会发生变化怎么办?现在它是一个改变的前缀,但是你可以提供其他类型的改变吗?也许你不需要这个,但是:

在业务层面,你说产品可以有不同的部件号,取决于渠道(毕竟这是一个基本的商业概念)。这表明在数据库级别,可能存在具有ProductId,ChannelId和PartNumber列的PartNumber表。这肯定会涵盖随着时间的推移出现更多频道的情况(今天它是零售或贸易,明天它们可能会添加网络,邮购等等,所有这些都可能需要不同的部件号)。

在对象级别,这会映射到Product具有Dictionary<Channel, PartNumber>的{​​{1}}实例,该实例可用于获取Channel给出的相应部件编号。

答案 4 :(得分:0)

  

现在杀手的细节是我们有两到三个频道进入网站,根据频道,用户需要看到不同的部件号。

直接的解决方案:

public interface IChannel
    function GetNumber(Part as IPart) as String
end interface

没有装饰者,没有开关,没有控制反转。

每当您需要特定频道的部件号时,请调用此方法。

dim Channel as IChannel = ...
dim Part as IPart = ...
dim PartNumber = Channel.GetNumber(Part)

每当您需要不同的零件编号计算方法时,您只需实现此界面。

public class TradeChannel
    implements IChannel

    public function GetNumber(Part as IPart) as String implements IChannel.GetNumber
        return Part.Number.Replace("0900", "1700")
    end function
end class