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

发布于 2024-08-04 14:04:00 字数 1201 浏览 5 评论 0原文

这是“分析瘫痪”似乎已经发生的场景之一,所以请提出建议!

该项目

一个相当简单的汽车产品列表,其中包括零件参考、适合哪些车辆等详细信息。

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

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

功能

我们的屏幕之一是产品详细信息屏幕。 ASP.NET MVC 控制器调用产品存储库来检索产品详细信息,并将这些详细信息(通过某些自动映射到 viewModel)返回到视图。

现在最致命的细节是我们有两个或三个进入网站的渠道,根据渠道的不同,用户需要看到不同的零件号。

举例来说,如果是零售渠道,则零件编号与数据库中的编号相同,但如果用户通过贸易渠道访问该网站,则零件参考的开头将被替换为替代编号。

例如,如果通过贸易渠道查看,0900876 就会变成 1700876。

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

我已经考虑过这些替代方案。

将逻辑直接写入域对象

Product类上,我们可以有一个方法/属性来获取翻译后的部件引用。

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

在这种情况下,产品实例必须了解通道,这对我来说似乎是错误的。

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

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

但我不明白的是如何协调这两个类。

如果控制器调用存储库来检索产品,那么它是否应该找出使用的通道并翻译零件参考?如果是这样,我该如何将产品及其翻译后的零件参考发送回视图?

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

This is one of those scenarios where "Paralysis by Analysis" seems to have taken hold so advice please!

The project

A fairly simple list of automotive products which include details such as part reference, which vehicles they fit etc.

The front end is an asp.net MVC application.

The backend is SQL, using Subsonic to project the products into domain objects.

Functionality

One of our screens is a product details screen. An ASP.NET MVC Controller calls the product repository to retrieve product details, returns these details (via some automapping to a viewModel) to the view.

Now the killer detail is that we have two or three channels into the web site, depending on the channel, the user needs to see different part numbers.

Lets say for example if it's the Retail channel then the part numbers are as they are in the database, but if the user has come to the site through the Trade channel, the beginning of the part reference is replaced with alternative numbers.

e.g. 0900876 if viewed via the Trade channel becomes 1700876.

Where I'm struggling is in deciding where to encapsulate the "Channel rules" regarding part references (and other details which may alter).

I've considered these alternatives.

Write the logic directly into the domain object

On the Product class we could have a method/property to get the translated part reference.

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

In this scenario the Product instance must know about the channel, which seems wrong to me.

Encapsulate the logic in another object

We could write a class to handle this part reference translation, or create a Channel class which contains this logic.

What I don't understand though is how to then co-ordinate the two classes.

If the controller calls the repository to retrieve the Product, should it then work out what channel was used and translate the part reference? if so how do I then send the product with it's translated part reference back to the view?

It's also worth noting that this part reference has to show up in search results and other scenarios as well, for that reason I think it needs to be neatly contained within the domain somewhere.

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

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

发布评论

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

评论(5

自我难过 2024-08-11 14:04:00

我不是 C# 人员,但我想我会用 Java 中的装饰器来解决这个问题。

假设您有一个产品接口,那么您可以创建一个装饰器来管理部件号问题。

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
}

I'm not a C# guy, but I would attack this with a Decorator in Java, I think.

Assuming you have an interface for Product, then you can create a Decorator which manages the part number issue.

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
}
假装爱人 2024-08-11 14:04:00

您需要问自己的第一个问题是 Channel 的概念是否是领域概念。您的问题似乎表明事实并非如此,但另一方面,我认为这听起来也不是特定于应用程序的。

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

我的猜测是答案可能是

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

无论如何,我都会认真思考它是否真的不是一个领域概念。如果是,那么它可以很好地属于域对象。

如果没有,ptomli 建议的装饰器实现听起来是一个不错的方法。

The first question you need to ask yourself is whether the concept of a Channel is a domain concept or not. Your question seems to indicate that it isn't, but on the other hand, I don't think it sounds application-specific either.

An additional question you could ask is: if, in the future, I need to build another application on top of this domain model (e.g. a web service or a rich client), would I still need to deal with the concept of a Channel?

My guess is that the answer might be yes.

As far as I understand your question, the Channel is related to the request context in some way. Perhaps it is really an attribute of the user. Or perhaps in is an attribute of the application configuration itself.

In any case, I would think hard about whether it isn't really a Domain concept after all. If it is, then it could belong nicely in a Domain Object.

If not, the Decorator implementation suggested by ptomli sounds like a good approach.

如日中天 2024-08-11 14:04:00

零件号会有多少种不同的变体。如果只是贸易与零售,我很想简单地将两个数字都放在 Product 对象中,并让 UI 决定显示哪个。当对产品进行操作时,身份可以是“类型{贸易,零售},数字”。

对于模式灵活的东西,我认为你的频道想法很好。但如果它有双向责任,将零售与贸易进行映射,这似乎会起作用。 Channel 对象可以被视为一个适配器,能够进行其他转换和丰富。

作为一个实现,我将为每个 Channel 创建一个单独的 Channel 对象,试图避免 case 语句和 if else else 逻辑。对于零售业,渠道对象可以是一个 NOOP 对象,对于贸易业,它可以进行映射。工厂可以创建适当的 Channel 对象。

How many different varients of part number would there be. If it's just Trade v Retail i'd be very tempted to simply have both numbers in the Product object, and have the UI decide which to display. When acting on product the identity can be "type{Trade, Retail}, number".

For something mode flexible I think that's your Channel idea is fine. But if it had bi-directional responsibilities, mapping retail to and from trade, this would seem to work. The Channel object con be seen as an adapter, capable of other transforms and enrichments.

As an implementation I would be creating a separate Channel object for each Channel, trying to avoid case statements and if else else logic. For Retail the Channel object could be a NOOP object for Trade it can do teh mappings. A factory can creat the approporaye Channel object.

叹倦 2024-08-11 14:04:00

如果零件编号映射可能发生变化怎么办?现在它是一个会发生变化的前缀,但是您是否还需要满足其他类型的变化?也许您不需要这个,但是:

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

在对象级别,这映射到具有 DictionaryProduct 实例,可用于在给定 Channel< 的情况下获取适当的部件号/代码>。

What if the part number mapping could change? Right now it's a prefix which changes, but could there be other types of changes you have to cater for? Maybe you don't need this, but:

At the business level, you're saying that a product can have different part numbers, dependent on channel (which is after all a fundamental business concept). So that suggests that at the database level, there could be a PartNumber table somewhere which has ProductId, ChannelId and PartNumber columns. This will certainly cover the case where more channels appear over time (today it's Retail or Trade, tomorrow they might add Web, Mail-Order etc. all of which conceivably could want different part numbers).

At the object level, this maps to a Product instance having a Dictionary<Channel, PartNumber> which can be used to get the appropriate part number given a Channel.

逐鹿 2024-08-11 14:04:00

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

一个简单的解决方案:

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

Now the killer detail is that we have two or three channels into the web site, depending on the channel, the user needs to see different part numbers.

A straight forward solution:

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

No decorators, no switches, no inversion of control.

Every time you need part number for a specific channel you call this method.

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

Every time you need a different part number calculation method you simply implement this interface.

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