DEV Community

Cover image for 3 things to avoid when implementing Domain-Driven Design (DDD)
Artur Kedzior
Artur Kedzior

Posted on • Edited on

3 things to avoid when implementing Domain-Driven Design (DDD)

The reason of this post is due to the fact I often see developers trying to follow Domain-Driven Design (DDD) approach but make very questionable decisions during the implementation.

Here is a summary of things which should be avoided:

1. Public setters

Let's look at this simplified entity (skipped other properties):

public class Order
{
   public PaymentMethod PaymentMethod { get; set; }
   public decimal Total { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

What's wrong with it?: The entity should encapsulate both data and behaviour. Having a setter means anyone can modify the property which allows to change its state.

Let's check this example with few enforced rules:

  1. Order can't exist without payment method or order total
  2. We can't place order with 0 or negative total
  3. We can't place cash orders for orders with total value over 100 (whatever the currency)
public class Order
{
    public PaymentMethod PaymentMethod { get; private set; }
    public decimal Total { get; private set; }

    private static decimal CashOrderMaxTotal = 100;

    public Order(PaymentMethod paymentMethod, decimal orderTotal)
    {
        if (orderTotal == 0 || orderTotal < 0)
        {
            throw new ArgumentException(String.Format("{0} can't be 0 or negative", orderTotal), "orderTotal");
        }

        if (paymentMethod.Cash && orderTotal > CashOrderMaxTotal)
        {
            throw new ArgumentException(String.Format("Unable to order total {0}, cash order cannot total more than {1}", orderTotal, CashOrderMaxTotal), "orderTotal");
        }

        Total = orderTotal;
        PaymentMethod = paymentMethod;

    }
}
Enter fullscreen mode Exit fullscreen mode

To enforce rule 1 - we use a constructor to set properties and to enforce rule 2 and 3 we add validation.

2. Lack of rules validation

Let's take a look again:

public class Order
{
    public decimal Total { get; private set; }

    public Order(decimal orderTotal)
    {
        Total = orderTotal;
    }
}
Enter fullscreen mode Exit fullscreen mode

What's wrong with it?: As seen on the previous example, lack of validation rules means anyone can set this order to some unwanted values which is not good and can make its way to big fireworks of bugs. Always code defensively and never let the entity to go into invalid state.

Here is an example how one would make sure our order total is valid. For this purpose I use an amazing library called GuardClauses which is just shorter way of throwing an argument exception if the condition is not met.

public class Order
{
    public decimal Total { get; private set; }

    public Order(decimal orderTotal)
    {
        Guard.Against.NegativeOrZero(orderTotal);
        Total = orderTotal;
    }
}
Enter fullscreen mode Exit fullscreen mode

The important part here is, the code that creates order or modifies it in any sense should be validated and rejected on any bad intention.

2. Adding Persistence Logic

Now let's persist our order:

[Table("Orders")]
public class Order
{
    public decimal Total { get; private set; }
}
Enter fullscreen mode Exit fullscreen mode

What's wrong with it: By adding [Table("Orders")] which is Entity Framework data annotation we are mixing persistence stuff within entities, we're violating the Single Responsibility Principle and we could lead to tightly coupled code that's harder to maintain and test.

This post doesn't focus on the design. Domain-Driven Design covers more concepts such us defining the domain, aggregate roots, value objects and using ubiquitous language. I might cover it in the next post. 🔥

To read more about Domain Driven Design I recommend the best book out there:

Domain Driven Design by Eric Evans.

Top comments (0)