Search code examples
inheritancedesign-patternsdomain-driven-designsolid-principlesdomain-model

how to model this invoice requirements


In some business domain, I received invoicing requirements as follows:

+ Invoice Items are of two types: a service or a fee.
    - Invoice Service Item is taxable. fee is not.
+ Invoice Items (services and fees) cost is calculated in two manners: 
    - As a whole: the service or fee has a fixed cost.
    - By Individual: the cost is multiplied by Individuals count.

I’ve been reading about design patterns, domain driven design, inheritance and polymorphism, and I wanted to practice what I learnt in this side greenfield project. I want to come to a model for the Invoicing sub domain, that captures the domain ubiquitous language. I’ve come to this model, but It doesn’t feel right! model


Solution

  • When you model objects, try to think about it's behavior, and not only about data you need to store. Focusing on data often leads to anemic data models. If you need to calculate cost and tax, how about something like this?

    internal class InvoiceItem : ValueObject
    {
        private readonly string _description;
        private readonly decimal _cost;
        private readonly int _amount;
    
        private InvoiceItem(string description, decimal cost, int amount)
        {
            _description = description;
            _cost = cost;
            _amount = amount;
        }
    
        public decimal TotalCost()
        {
            return _cost * _amount;
        }
    
        public decimal Tax(ITaxCalculationPolicy taxCalculationPolicy)
        {
            return taxCalculationPolicy.CalculateTax(TotalCost());
        }
    
        public static InvoiceItem ByWholeInvoiceItem(string description, decimal cost)
        {
            return new InvoiceItem(description, cost, 1);
        }
    
        public static InvoiceItem ByIndividualInvoiceItem(string description, decimal cost, int amount)
        {
            return new InvoiceItem(description, cost, amount);
        }
    }
    
    internal interface ITaxCalculationPolicy
    {
        decimal CalculateTax(decimal cost);
    }
    
    internal class ServiceTaxPolicy : ITaxCalculationPolicy
    {
        private const decimal TaxPercent = 0.18m;
    
        public decimal CalculateTax(decimal cost)
        {
            return cost * TaxPercent;
        }
    }
    
    internal class FeeTaxPolicy : ITaxCalculationPolicy
    {
        private const decimal TaxValue = 0;
    
        public decimal CalculateTax(decimal cost)
        {
            return TaxValue;
        }
    }
    

    If you don't want a client to decide what kind of ITaxCalculationPolicy should be used, you could also try with passing some kind of ITaxCalculationPolicyFactory to Tax() method. Than you would have to store a type of Invoice Item inside it and pass it to that factory when calculating Tax.