views:

182

answers:

4

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.

+2  A: 

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
}
ptomli
+1, but I would change the Channel type to an abstract type that has a GetProductCode method that takes an IProduct as input. Then you could have two concrete implementations (RetailChannel and TradeChannel) the implement that method differently.
Mark Seemann
Yes, the "how to lookup/transform the code" is vague and weird above because I don't know enough about it. Your suggestion makes a lot of sense.
ptomli
OK this looks good to me, we're back to a "when" question now (which I always seem to come unstuck at), when do I instantiate ProductChannelDecorator during the process of retrieving a product from the repository and presenting it to the view? (I think my lack of knowledge is kicking in here!)
jonhilt
This is looking good so far, I can easily create a new instance of the decorator when retrieving the details of a single product. The next question would be, given a list of products, how to then wrap each one with the decorator (avoiding a foreach might be desirable)
jonhilt
That is probably dependent upon how you're retrieving the list in the first place. Like I said, C# is not my area, I'm normally digging in Java/Hibernate, so the methods would be different. Does your ORM provide for interception when hydrating the model? If this really is limited to a display/view then you could do the equivalent of a JSP tag? Sorry, I'm not familiar enough to help much here.
ptomli
@ptomli I appreciate the effort. The repository is currently a simple class to retrieve the product data and then project it to the domain model. I could put the logic down there which would mean passing the Channel as a parameter to the repository call, not that that's necessarily a bad thing
jonhilt
A: 

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.

djna
+1  A: 

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.

Mark Seemann
Very true, I hadn't actually thought too much about whether this was domain, application or view, and simply dived into implementation. Doh!
ptomli
This is I think where I've come unstuck.For various reasons (to do with agreements we have with other companies etc) the only option I have for establishing the channel is URL based.Therefore when the request comes in, I am establishing which mode we're in (based on the URL) then as people use the web site they need to see the different part numbers.We will most likely need to add more channels in the future and extend the customisations (we're already dynamically changing the site templates etc based on this)
jonhilt
I'm actually quite tempted to put this logic between the controller and the view (on the basis that we only need people to see the translated part references, but probably don't need to take any business decisions based on them)
jonhilt
@jonhilt: Okay, but just because you detect a channel via a particular technology (the URL) and also use that information for UI purposes doesn't mean that it isn't a Domain concept. You could have an IChannel interface that models the Domain concept and is being consumed by other Domain objects. Then, in the incoming HTTP pipeline, you can detect the channel from the URL and create an instance of an IChannel implementation and then invoke your Domain Model with that instance.
Mark Seemann
See also my comment to ptomli's answer.
Mark Seemann
A: 

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.

Vinay Sajip
Very good point, this has already come up where we have exceptions to the general rules (annoyingly just three or four products which have to have different numbers for some reason). This had led me to investigate holding alternative part numbers in the database.
jonhilt
I always come back to the question of "when" with this kind of thing i.e. at which point when retrieving the product (calling the repository, converting the data into a product object, mapping the object to a view, returning it to the view) do I tell it which channel we're using and ask it for it's part reference?
jonhilt