Reputation: 183
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.
Upvotes: 4
Views: 488
Reputation: 1265
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
Upvotes: 0
Reputation: 99355
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
.
Upvotes: 0
Reputation: 233150
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.
Upvotes: 2
Reputation: 55907
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.
Upvotes: 0
Reputation: 11818
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
}
Upvotes: 2