• No results found

As introduced in the previous design section, the way you implement a domain model in .NET is simply by creating POCO classes that implement your domain entities. In the following code, you can see that the Order class is defined as an entity and also as an aggregate root. Because the Order class is deriving from the custom base class Entity, it can re-use common code related to entities. Keep in mind that these base classes and interfaces are custom, so it is your code, not infrastructure code from any ORM like EF.

Entity Framework Core 1.0

public class Order : Entity, IAggregateRoot //Entity is a custom base class with the Id {

public int BuyerId { get; private set; }

public DateTime OrderDate { get; private set; } public int StatusId { get; private set; }

public ICollection<OrderItem> OrderItems { get; private set; } public Address ShippingAddress { get; private set; }

public int PaymentId { get; private set; }

protected Order() { } //Needed only by EF Core 1.0

public Order(int buyerId, int paymentId)

{

129 Architecting and developing Docker applications

BuyerId = buyerId; PaymentId = paymentId;

StatusId = OrderStatus.InProcess.Id; OrderDate = DateTime.UtcNow;

OrderItems = new List<OrderItem>(); }

public void AddOrderItem(productName, pictureUrl, unitPrice, discount, units) { //...

// Domain Rules/Logic related to the OrderItem being added to the order //...

OrderItem item = new OrderItem(this.Id, ProductId, ProductName,

PictureUrl, UnitPrice, Discount, Units); OrderItems.Add(item);

} //...

// Additional methods with Domain Rules/Logic related to the Order Aggregate //...

The important fact to highlight about the above code snippet is that this is a Domain Entity

implemented as a POCO class. It doesn’t have any direct dependency to Entity Framework Core or any other infrastructure framework. It is as it should be, just your C# code implementing your Domain Model.

In addition to that, it is also decorated with an interface named IAggregateRoot. That interface is an empty interface which is used just to say that this entity class is also an Aggregate-Root or the root entity of the aggregate. That means that most of the code related to the consistency and business rules of the aggregate’s entities should be implemented as methods in the Order Aggregate-Root class (for example, AddOrderItem() when adding an OrderItem to the Aggregate). You should not create or update OrderItems independently or directly; the AggregateRoot class must keep the control and consistency of any update operation against its child entities.

For example, you shouldn’t do the following from any CommandHandler method or application layer class:

Wrong according to DDD patterns – Code at the application layer or Command Handlers

//My code in CommandHandlers or Web API controllers

//… (WRONG) Some code with business logic out of the Domain classes…

OrderItem myNewOrderItem = new OrderItem(orderId, productId, productName, pictureUrl, unitPrice, discount, units);

//… (WRONG) Accessing the OrderItems colletion directly from the application layer or command handlers myOrder.OrderItems.Add(myNewOrderItem);

//…

In this case, the Add() operation is purely an operation to add data, with direct access to the OrderItems collection. Therefore, most of the domain logic, rules or validations related to that operation with the child entities will be spread across the application layer (Command-Handlers and

130 Architecting and developing Docker applications Web API controllers). Eventually you’ll have spaghetti code, or a transactional script code

implementation.

Following DDD patterns entities must not have public setters in any entity’s property.

Going further, collections within the entity (like the order items) should be read-only properties (check the “.AsReadOnly()” pattern explained later) so you should be only able to update it from within the Aggregate root class methods.

As you can see in the code implementing the Order Aggregate-Root, all setters should be private, so any operation against the entity’s data or its child entities will need to be performed through methods in the Aggregate-Root class. This will keep consistency in a more controlled and object-oriented way instead of doing a transactional script code implementation.

The following code snippet shows the proper code when adding an OrderItem to the Order aggregate.

Right according to DDD – Code at the application layer or Command Handlers

//My code in CommandHandlers or WebAPI controllers, only related to application stuff // NO code here related to OrderItem’s business logic

myOrder.AddOrderItem(productId, productName, pictureUrl, unitPrice, discount, units);

// T

he code related to OrderItem params validations or domain rules should be within AddOrderItem() //…

The important point here is that most of the validations or logic related to the creation of an OrderItem will be under the control of the Order aggregate-root, within the AddOrderItem() method, especially validations and logic related to other elements in the Aggregate. For instance, you might get the same product item as multiple AddOrderItem(params) invocations. In this method, you could check that out and consolidate the same product items in a single OrderItem with several units. Additionally, if there are different discount amounts but the product Id is the same, you would likely apply the higher discount. This principle applies to any other domain logic for the OrderItem. In addition, the operation new OrderItem(params) will also be controlled and performed by the AddOrderItem() method from the Order aggregate-root, so most of the logic or validations related

131 Architecting and developing Docker applications to that operation (especially if it impacts the consistency between other child entities) will be in a single place within the aggregate root. That is the ultimate purpose of the Aggregate Root pattern. When using Entity Framework 1.1, a DDD entity can be better expressed because one of the new features of Entity Framework Core 1.1 is that it allows mapping to fields in addition to properties. This is extremely useful when protecting collections of child entities or value objects.

Now, you can use simple fields instead of properties and implement any update to the field collection through methods and making it read only through the “.AsReadOnly()” pattern.

In DDD you want to update the entity only through methods in the entity (or the constructor) in order to control any invariant and consistency of the data, so properties with only a get accessor are

defined. The properties are backed by private fields. Private members can only be accessed from within the class. However, there’s one exception: EF Core needs to set these fields as well.

Entity Framework Core 1.1 or later

public class Order : Entity, IAggregateRoot //Entity is a custom base class with the Id

{

// DDD Patterns comment

// Using private fields, allowed since EF Core 1.1, is a much better encapsulation

// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)

private bool _someOrderInternalState;

private DateTime _orderDate;

public Address Address { get; private set; }

public Buyer Buyer { get; private set; }

private int _buyerId;

public OrderStatus OrderStatus { get; private set; }

private int _orderStatusId;

// DDD Patterns comment

// Using a private collection field, better for DDD Aggregate's encapsulation

// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection, // but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.

private readonly List<OrderItem> _orderItems;

public IEnumerable<OrderItem> OrderItems => _orderItems.AsReadOnly();

// Using List<>.AsReadOnly()

// This will create a read only wrapper around the private list so is protected against "external updates". // It's much cheaper than .ToList() because it will not have to copy all items in a new collection. // (Just one heap alloc for the wrapper instance)

// https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx

public PaymentMethod PaymentMethod { get; private set; }

private int _paymentMethodId;

protected Order() { }

public Order(int buyerId, int paymentMethodId, Address address)

{

_orderItems = new List<OrderItem>(); _buyerId = buyerId;

_paymentMethodId = paymentMethodId;

_orderStatusId = OrderStatus.InProcess.Id; _orderDate = DateTime.UtcNow;

Address = address; }

132 Architecting and developing Docker applications // This Order AggregateRoot's method "AddOrderitem()" should be the only way to add Items to the Order, // so any behavior (discounts, etc.) and validations are controlled by the AggregateRoot

// in order to maintain consistency between the whole Aggregate.

public void AddOrderItem(int productId, string productName, decimal unitPrice,

decimal discount, string pictureUrl, int units = 1)

{ //...

// Domain Rules/Logic related to the OrderItem being added to the order //...

OrderItem item = new OrderItem(this.Id, productId, productName,

pictureUrl, unitPrice, discount, units); OrderItems.Add(item);

} //...

// Additional methods with Domain Rules/Logic related to the Order Aggregate //...

}