I have to load certain object from the DB, and propagate it through few state and save after each state propagation.
Let us illustrate this with the example of an Order entity.
I now have to load few pending orders from DB and Cancel them based on few criteria. If these criteria don't match I should not cancel them.
I have the below code in my Application layer:
public void CancelPendingOrders() {
var pendingOrders = _orderRepository.GetPendingOrders();
_orderService.CancelOrders(pendingOrders);
}
public class CancelOrderCommand {
//other classes which are used in OrderCanBeCancelled are injected in .ctor
public void Execute(Order order) {
if(CanExecute(order)) // OrderCanBeCancelled
order.SetStatus(OrderStatus.Cancelled);
}
}
And my domain service like below
public class OrderCancelService : IOrderCancel{
void CancelPendingOrders(Orders[] orders){
foreach (var order in orders)
{
_orderCancelCommand.Execute(order);
}
}
}
public class Order{
public Order(OrderId orderId, OrderStatus orderStatus){
// assigned to local private fields.
}
public event EventHandler<OrderStatusChangedEventArgs> OrderStatusChanged;
internal void ChangeOrderStatus(OrderStatus status){
var oldStatus = new OrderStatus(_status);
_status = status;
if(OrderStatusChanged != null){
OrderStatusChanged(this, new OrderStatusChangedEventArgs(oldStatus,_status));
}
}
}
I have few classes which listen to the OrderStatusChangedEvent and perform other tasks like for example sending a notification and saving the updated status back to the database.
If you observe the above Order class I don't have separate commands for each OrderStatus like Cancel() and is rather anaemic.
I have it this way because I do not want to inject other commands and services into the Order class, because I have to perform CanOrderBeCancelled for each status change.
From what I have currently it is pretty straight forward for me to introduce a new Command class for each status if required and inject other services in those classes leaving my Order class pure.
Other option
public class Order{
public Order(OrderId orderId, OrderStatus orderStatus, ICommandFactor orderCommandFactory){
//assigned to local private fields.
}
public event EventHandler<OrderStatusChangedEventArgs> OrderStatusChanged;
public void Cancel(){
_orderCommandFactory.GetCancelCommand().Execute(this);
}
public void Success(){
_orderCommandFactory.GetSuccessCommand().Execute(this);
}
internal void SetStatus(OrderStatus status){
var oldStatus = new OrderStatus(_status);
_status = status;
if(OrderStatusChanged != null){
OrderStatusChanged(this, new OrderStatusChangedEventArgs(oldStatus,_status));
}
}
}
The above approach has well defined methods on the Order class but for ideal separation of concerns I'll have to inject a command factory, and each commands would call the internal SetOrderStatus method like earlier. The commands without being injected to the IOrderService will now be injected to the Order themselves.
I can also inject a IOrderService into the Order class instead of the command factory and my class would look like below:
public class Order{
public Order(OrderId orderId, OrderStatus orderStatus, IOrderService orderService){
//assigned to local private fields.
}
public event EventHandler<OrderStatusChangedEventArgs> OrderStatusChanged;
public void Cancel(){
if(orderService.CanCancel(this)){
SetStatus(OrderStatus.Cancel);
}
}
public void Success(){
if(orderService.IsSuccessful(this)){
SetStatus(orderStatus.Success);
}
}
internal void SetStatus(OrderStatus status){
var oldStatus = new OrderStatus(_status);
_status = status;
if(OrderStatusChanged != null){
OrderStatusChanged(this, new OrderStatusChangedEventArgs(oldStatus,_status));
}
}
}
Also my Application class will look like below:
public class OrderCancelApplicaiton{
public void CancelPendingOrders(){
var pendingOrders = _orderRepository.GetPendingOrders();
foreach (var pendingOrder in pendingOrders)
{
pendingOrder.Cancel();
}
}
}
Finally I can also think of not having any commands which check if an Order can be cancelled, but rather have an additional OrderStatusChanged event handler that checks if the Order can indeed be cancelled, if not move it back to the previous state, the problem with this approach is that the 'Order' object will remain in an invalid state even if it is in-memory and momentarily before it is moved back to its previous state if it should not be cancelled.
Are you comfortable injecting Commands and Services into your domain models ?
What would you have done and what is the prescribed method for such problems ?
Would you rather Cancel on the domain object and let a handler catch it and 'Revert' it back if it should not have been cancelled ?