Models of anemic domains or "where to put the logic"

This is one of those scenarios where Analysis Paralysis seems to have strengthened, so advice please!

Project

A fairly simple list of automotive products that include details such as a link to parts, which vehicles they fit, etc.

The front end is an asp.net MVC application.

The backend is SQL, using Subsonic to design products in domain objects.

Functionality

One of our screens is the product details screen. The ASP.NET MVC controller calls the product repository to obtain product information, returns this data (after some automatic conversion to viewModel) to the view.

Now the killerโ€™s detail is that we have two or three channels on the website, depending on the channel, the user needs to see different part numbers.

Suppose, for example, if it is a retail channel, then the part numbers are as they are in the database, but if the user came to the site through the trading channel, the beginning of the circulation of the part is replaced by alternative numbers.

eg. 0900876, if you look through the trading channel, it becomes 1700876.

Where I am afraid, I have to decide where to encapsulate the Channel Rules for references to details (and other details that may change).

I reviewed these alternatives.

Writing logic directly to a domain object

In the Product class, we could get a method / property to get a link to the translated part.

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

In this case, the product instance should know about the channel, which seems wrong to me.

Encapsulate logic in another object

We could write a class to handle this translation of a link to a part, or create a Channel class that contains this logic.

What I do not understand is how to coordinate these two classes then.

If the controller calls the repository to retrieve the Product, should it then determine which channel was used and translate the link to the details? if so, how can I then send the product with it, translating part of the link back to the view?

In addition, it is worth noting that this part of the link should appear in search results and other scripts, and also, for this reason, I think it should be neatly contained in an area somewhere.

+4
source share
5 answers

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

Assuming you have an interface for the product, you can create a Decorator that manages the part number problem.

 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 } 
+2
source

The first question you need to ask yourself is the concept of the channel - is it the concept of a domain or not. Your question seems to indicate that this is not the case, but, on the other hand, I do not think this sounds like an application as well.

An additional question that you can ask is this: if in the future I need to create another application on top of this domain model (for example, a web service or a rich client), will I still have to deal with the concept of a channel?

I guess the answer may be yes.

As far as I understand your question, the Channel is somehow related to the request context. Perhaps this is really a user attribute. Or perhaps this is an attribute of the application configuration itself.

In any case, I would think about whether this is really not a domain concept. If so, then it may well belong to a domain object.

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

+2
source

How many different options for part numbers will be. If it's just Trade v Retail, it would be very tempting for me to just have both numbers in the Product object, and the user interface will decide what to display. When acting on a product, the identifier may be "type {Trade, Retail}, number".

For something flexible mode, I think your channel idea is great. But if he had bidirectional duties related to retail and trade, it would seem to work. The Con channel object is considered as an adapter capable of other transformations and enrichments.

As an implementation, I would create a separate channel object for each channel, trying to avoid the case arguments and, if there was something else logical. For retail, a channel object can be a NOOP object for trading, it can perform mappings. A factory can create a suitable channel object.

0
source

What if the part number display can change? Right now it's a prefix that is changing, but can there be other types of changes you need to service? You may not need this, but:

At the business level, you say that a product may have different part numbers, depending on the channel (which is ultimately the fundamental concept of the business). Thus, this suggests that at the database level there may be a PartNumber table where there are ProductId, ChannelId and PartNumber columns. This, of course, will cover the case when more channels appear over time (today it is retail trade or trade, tomorrow they can add Web, Mail-Order, etc., All of which may want different part numbers).

At the object level, it is mapped to a Product instance that has Dictionary<Channel, PartNumber> , which can be used to get the corresponding part number given by a Channel .

0
source

Now the killerโ€™s detail is that we have two or three channels on the website, depending on the channel, the user needs to see different part numbers.

Direct solution:

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

No decorators, no switches, no inversion of control.

Each time you need a 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 method for calculating the number of parts, 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 
0
source