Even the most monolithic code base could benefit by introducing some of the most basic principles of Domain Driven Design.  The following blog post is a reproduction of a real life code base, in a simplified form, but not far from reality.

The breakdown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void Update(CustomerDto customer)
{
    Customer existingCustomer = _customerRepository.GetById(customer.Id);
    if (existingCustomer == null)
        throw new InvalidOperationException("Customer does not exist");

    // Check to see if we have changes
    bool hasChanges = !existingCustomer.Street.Equals(customer.Street)
                      || !existingCustomer.StreetNumber.Equals(customer.StreetNumber)
                      || !existingCustomer.City.Equals(customer.City)
                      || !existingCustomer.PostalCode.Equals(customer.PostalCode)
                      || !existingCustomer.Country.Equals(customer.Country);

    // Update object
    existingCustomer.Street = customer.Street;
    existingCustomer.StreetNumber = customer.StreetNumber;
    existingCustomer.PostalCode = customer.PostalCode;
    existingCustomer.City = customer.City;
    existingCustomer.Country = customer.Country;

    // If we have address changes update invoicing service
    if (hasChanges)
    {
        _invoicingService.Execute(new UpdateAddress
            {
                Id = customer.Id,
                Street = customer.Street,
                Number = customer.StreetNumber,
                Postal = customer.PostalCode,
                City = customer.City,
                Country = customer.Country
            });
    }
    _unitOfWork.SaveChanges();
}

By seeing the above code snippet, even without knowing all the specific implementations, we can clearly identify the requirements of this method:

  • Map all necessary values from the data-transfer object to the domain
  • When a customer’s address has been been changed we need inform the invoicing service of that change
  • We need to persist the changes if any

Just for the sake of clarity i will include the initial anemic domain model

1
2
3
4
5
6
7
8
9
public class Customer
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string StreetNumber { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}

The ‘Address’ value object

By introducing a DDD value object for all the address related properties we could simplify all the necessary change tracking in this code, i will include the Address code snippet just for clarity but i let my Resharper generate all equality members.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class Address
{
    public Address(string street
        , string streetNumber, string postalCode
        , string city, string country)
    {
        Street = street;
        StreetNumber = streetNumber;
        PostalCode = postalCode;
        City = city;
        Country = country;
    }

    public string Street { get; private set; }
    public string StreetNumber { get; private set; }
    public string PostalCode { get; private set; }
    public string City { get; private set; }
    public string Country { get; private set; }

    protected bool Equals(Address other)
    {
        return string.Equals(Street, other.Street) && string.Equals(StreetNumber, other.StreetNumber) &&
               string.Equals(PostalCode, other.PostalCode) && string.Equals(City, other.City) &&
               string.Equals(Country, other.Country);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (Address)) return false;
        return Equals((Address) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hashCode = (Street != null ? Street.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ (StreetNumber != null ? StreetNumber.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ (PostalCode != null ? PostalCode.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ (City != null ? City.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ (Country != null ? Country.GetHashCode() : 0);
            return hashCode;
        }
    }

    public static bool operator ==(Address left, Address right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Address left, Address right)
    {
        return !Equals(left, right);
    }
}

This is how our Customer domain object could look like at this moment:

1
2
3
4
5
public class Customer
{
    public int Id { get; set; }
    public Address Address { get; set; }
}

By doing this we can refactor our main monolithic block to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void Update(CustomerDto customer)
{
    Customer existingCustomer = _customerRepository.GetById(customer.Id);
    if (existingCustomer == null) throw new InvalidOperationException("Customer does not exist");

    // Check to see if we have changes
    var newAddress = new Address(customer.Street
                    , customer.StreetNumber
                    , customer.PostalCode
                    , customer.City
                    , customer.Country);

    bool hasChanges = existingCustomer.Address != newAddress;

    // Update object
    existingCustomer.Address = newAddress;

    // If we have address changes update invoicing service
    if (hasChanges)
    {
        _invoicingService.Execute(new UpdateAddress
            {
                Id = customer.Id,
                Street = customer.Street,
                Number = customer.StreetNumber,
                Postal = customer.PostalCode,
                City = customer.City,
                Country = customer.Country
            });
    }
    _unitOfWork.SaveChanges();
}

Introduce a domain event and put the logic where it belongs

For more details i encourage you to read Udi Dahan’s post on it.

The ‘CustomerMoved’ domain event:

This event indicates that a customer has moved

1
2
3
4
5
public class CustomerMoved : IDomainEvent
{
    public int Id { get; set; }
    public Address Address { get; set; }
}

It’s handler

Which has only the responsability to let the invoicing system know when a customer has moved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UpdateInvoicingAddressHandler : IDomainEventHandler<CustomerMoved>
{
    private readonly InvoicingService _invoicingService;

    public UpdateInvoicingAddressHandler(InvoicingService invoicingService)
    {
        _invoicingService = invoicingService;
    }

    public void On(CustomerMoved @event)
    {
        _invoicingService.Execute(new UpdateAddress
                {
                    Id = @event.Id,
                    Street = @event.Address.Street,
                    Number = @event.Address.StreetNumber,
                    Postal = @event.Address.PostalCode,
                    City = @event.Address.City,
                    Country = @event.Address.Country
                });
    }
}

Remove setters

Let’s create a move method that does all necessary mechanics and remove the address property setter

1
2
3
4
5
6
7
8
9
10
11
12
public class Customer
{
    public int Id { get; set; }
    public Address Address { get; private set; }

    public void Move(Address newAddress)
    {
        if (Address == newAddress) return;
        Address = newAddress;
        DomainEvents.Raise(new CustomerMoved {Id = Id, Address = newAddress});
    }
}

The result, which is no longer monolithic

1
2
3
4
5
6
7
8
9
10
11
12
public void Update(CustomerDto customer)
{
    Customer existingCustomer = _customerRepository.GetById(customer.Id);
    if (existingCustomer == null) throw new InvalidOperationException("Customer does not exist");

    var newAddress = new Address(customer.Street, customer.StreetNumber
                                , customer.PostalCode, customer.City, customer.Country);

    existingCustomer.Move(newAddress);

    _unitOfWork.SaveChanges();
}

I hope that i convinced or can inspire some of you, we could and should all benefit from the principles applied in DDD.

Comments