3

I am working on the domain model of an application that stores information provided by sellers about the goods they sell on a platform. The application just acts as a data store for this information. The information is called as an "ProductFact", and may be diverse. Each example below represents one "ProductFact" instance.

  1. the selling price of the item for general public.
  2. the discount the seller can offer for some specific customers (an absolute amount, or percentage of the selling price).
  3. the minimum quantity a customer has to buy to avail free shipping.

The above are some cases that are known as of now, but there might be a few more in the future, with no guarantee of sharing any property with the already existing "ProductFact"s. Different clients will be calling this application, each one looking for a specific type of "ProductFact".

We will be using a NoSQL backend, so db schema is not an issue. The problem is with the application layer. I am not sure how to model this particular object. I have been trying to model it as :

public class ProductFact {
    private ProductFactType productFactType; //enum for different types like SELLING_PRICE, DISCOUNTS, MIN_QUANT, etc.
    private ProductFactData productFactData; // for representing the actual data.
}

It would have been ideal if ProductFactData had a behavior, so I could have used it as an interface and created implementations or each use-case. But this is just data, and inheritance seems to be a hacky approach.

Another approach could be:

public class ProductFact {
    private ProductFactType productFactType;

    private SellingPriceFactData sellingPriceFactData;
    private DiscountFactData discountFactData;
    .
    .
    .
    private SomeProductFactData someProductFactData;
}

The client could check for the value of "productFactType" and then call the getter for the appropriate FactData type.But even this approach seems hacky.

What would be a good way of modelling this scenario?

Edit
It seems the problem statement could use some more clarity. Discount and Price are two of the many pieces "ProductFact"s that the application could store. There could be others in the future, including:

  1. Information about express delivery eligibility of the product, say "ExpressDeliveryProductFact" - which areas is it available for, what is the minimum quantity required.
  2. Information about the condition of the product - is it used, new?

There would be different clients querying this application, each looking for the type of ProductFact it are interested in. E.g., a DeliveryService client would be interested in the "ExperssDeliveryProductFact".
As can be seen, these pieces of information seemingly don't have anything in common, which is posing problems in the domain model.

  • Why private SellingPriceFactData sellingPriceFactData and not private decimal sellingPrice? – John Wu Apr 15 '18 at 8:55
  • @JohnWu : edited the question to add more clarity - Selling price may not be the only type that could be a ProductFact, hence the abstraction. – user3024475 Apr 15 '18 at 20:05
  • A price and a delivery method do not have an inheritance relationship. They are not specializations of a common type, other than the fact that they both hold data, which means they are both objects, and possibly that they can be represented as a string, meaning they both get a ToString() override. Other than that, their only relationship is that they are both participants in a composition relationship with Product, meaning that they are properties of that type. That is all. Unless there is some other requirement to treat them the same? – John Wu Apr 15 '18 at 21:40
  • @JohnWu : "They are not specializations of a common type, other than the fact that they both hold data" - you are absolutely correct, which is why I am having problem creating a type for them. Do you suggest keeping them as a String or a JSON object, and letting the clients figure out how to parse them? I am more inclined towards creating a marker interface and then have concrete types for each type of data. I know that they are not recommended, but this will at least let my clients have a concrete type to work with. – user3024475 Apr 16 '18 at 21:13
  • The concrete type of Price is decimal (probably). The way you prevent the caller from assigning a Price to something that isn't supposed to have a price is: Don't give anything a Price property that isn't supposed to have one. The way to prevent the caller from assigning the wrong property to a Product is: only give Product the properties that belong to a Product. I don't understand this obsession with creating a special type for it. The composition relationship has nothing to do with the types of the properties being composed. – John Wu Apr 16 '18 at 21:37
2

To properly model in an object oriented way shift your mindset to object oriented design patterns.

For example, you can apply the Strategy pattern to apply the discount to the product price per case.

Define an interface for your discount strategy in your model:

public interface iDiscountStrategy
{
     decimal ApplyExtraDiscountTo(decimal originalSalePrice);
}

Add each type of discount as an implementation of the discount strategy. Here is a percentage discount and a no discount implementations (define them in your model, too):

public class TradeDiscountDiscountStrategy : IDiscountStrategy
{
    public decimal ApplyExtraDiscountTo(decimal originalSalePrice)
    {
        decimal price = originalSalePrice;
        price = price * 0.95M;
        return price;
    }
}

public class NullDiscountStrategy : IDiscountStrategy
{
    public decimal ApplyExtraDiscountTo(decimal originalSalePrice)
    {
        return originalSalePrice;
    }
}

Your Price model entity. It uses the setter flavor of DI to enable the discount strategy to be applied to the product's price:

public class Price
{
    private IDiscountStrategy discountStrategy = new NullDiscountStrategy();

    private decimal sellingPrice;

    public Price(decimal sellingPrice)
    {
        this.sellingPrice = sellingPrice;
    }

    public void SetDiscountStrategyTo(IDiscountStrategy discountStrategy)
    {
        this.discountStrategy = discountStrategy;
    }

    public decimal SellingPrice
    {
        get
        {
            return this.discountStrategy.ApplyExtraDiscountsTo(this.sellingPrice);
        }
    }
}

Please, note that Price may contain any other logic (i.e. "behavior") such as savings calculation etc.

The ProductFact becomes:

public class ProductFact
{
    public Price Price { get; set; }
    // ...
}

To apply the discount per customer type you need to create the following enumeration:

public enum CustomerType
{
    Standard = 0,
    Trade = 1
}

You also need to create a factory class whose sole responsibility will be to return the matching discount strategy for a given CustomerType:

public static class DiscountFactory
{
    public static IDiscountStrategy GetDiscountStrategyFor(CustomerType customerType)
    {
        switch (customerType)
        {
            case CustomerType.Trade:
                return new TradeDiscountStrategy();
            default:
                return new NullDiscountStrategy();
        }
    }
}

And that's it. Then, in your service layer you can write something like:

public class ProductFactService
{
    public IList<ProductFact> GetAllProducts(CustomerType customerType)
    {
        IDiscountStrategy discountStrategy = DiscountFactory.GetDiscountStrategyFor(customerType);

        IList<ProductFact> products = // get products from database

        foreach (ProductFact pf in products)
        {
            pf.Price.SetDiscountStrategyTo(discountStrategy);
        }

        retur products;
    }
}

Have the same mindset for the rest of your domain and most of your modelling issues will go away.

  • Your solution would work in cases where we have to deal with Discounts and Prices only. I think my question did not bring it out clearly that there could be others as well. Edited the question to clarify that. – user3024475 Apr 15 '18 at 20:12
  • @user3024475 The price/discount was intented as an example to showcase certain patterns and a way of design that can be applied to the rest of your model(s). Thank you for the clarification even though your original question hinted there could be many more properties besides the price, of course. I am not sure if it is possible or meaningful to provide you with a more complete, more specific model (is this what you are asking for?). In any case, I believe you need to apply the Factory Pattern to provide the appropriate type of ProductFact per client, at least. – dpant Apr 16 '18 at 4:39
  • I see what you mean now. The implication would be to create a new vending API for each type, right? It would work, although, I was hoping to avoid the need for creating a bespoke API every time a new ProductFact is introduced. – user3024475 Apr 16 '18 at 21:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.