In this post, I want to cover another usage for Actions and Funcs in the context of Domain Driven Design (DDD) or Onion Architecture. In fact, for this article, it would be better to think in terms of the Onion instead of DDD.
Simply, the Onion Architecture protects the core business logic (domain) from all of the application concerns. However, pretty soon when crafting a wicked domain, the domain needs to perform some kind of behavior based on the result of a business rule. Maybe a concrete example will help here.
Let's say we have a domain entity named Order and the Order entity is involved in a workflow that is modeled using State Pattern. For this example, we won't concern ourselves with all of the Order workflow. All I want to focus on is when the Order is ready to be placed by using the Place method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Order | |
{ | |
private Customer customer; | |
public Order(Customer customer) | |
{ | |
this.customer = customer; | |
} | |
// snip | |
public void Place() | |
{ | |
// place order | |
// send email to customer | |
} | |
} |
- Create an interface called ISendEmail and make Order depend on it
- Pass in the ISendEmail inteface to the Place method
- Use an Action to fulfill the Sending of the Email
I'm going to work through these one at a time and comment on each.
First, why not just have Order depend on ISendEmail.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Order | |
{ | |
private Customer customer; | |
private ISendEmail sendEmail; | |
public Order(Customer customer, ISendEmail sendEmal) | |
{ | |
this.customer = customer; | |
this.sendEmail = sendEmail; | |
} | |
// snip | |
public void Place() | |
{ | |
// place order | |
sendEmail.ConfirmationTo(customer.EmailAddress); | |
} | |
} |
This is the first step towards creating a domain model that is not sustainable. Already we are bleeding application services into our domain entities. Should the Order entity be able to exist without a way of sending an email? If the answer is yes, then ISendEmail has no business being in the constructor. Also, what if we decide not to send emails at some point in the future. Do we really want to make a "do nothing" implementation for this case?
The second option is a bit more viable. We pass in the dependent interfaces to the Place method:
The second option is a bit more viable. We pass in the dependent interfaces to the Place method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Order | |
{ | |
private Customer customer; | |
public Order(Customer customer) | |
{ | |
this.customer = customer; | |
} | |
// snip | |
public void Place(ISendEmail sendEmail) | |
{ | |
// place order | |
sendEmail.ConfirmationTo(customer.EmailAddress); | |
} | |
} |
At least this option localizes our use of the ISendEmail interface. However, it still feels like the code sendEmail.ConfirmationTo(customer.EmailAddress) doesn't really below in the Order entity. In most real scenarios, the ConfirmationTo method will need to know a lot more about the Customer and Order in order to build an email message dynamically. Keeping all of that out of the Order entity is important when attempting to separate its concerns (the order workflow) with fulfillment concerns (sending the confirmation email). So, now let's look at using an Action:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Order | |
{ | |
private Customer customer; | |
public Order(Customer customer) | |
{ | |
this.customer = customer; | |
} | |
// snip | |
public void Place(Action<Customer> notifyCustomer) | |
{ | |
// place order | |
notifyCustomer(customer); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class OrderingService | |
{ | |
private ISendEmail sendEmail; | |
public OrderingService(ISendEmail sendEmail) | |
{ | |
this.sendEmail = sendEmail; | |
} | |
// snip | |
public void PlaceOrder(int orderId) | |
{ | |
var order = FindOrder(orderId); | |
order.Place((customer) => sendEmail.ConfirmationTo(customer.EmailAddress)); | |
} | |
} |
Hope this helps.