Wednesday 27 March 2013

Actions and Funcs (part 4).....Onions

It has been a while since my last post.  Hopefully, it will be useful to continue this series a bit.  In the past few posts about Actions and Funcs in C#, I have covered a few techniques where delegating responsibility for behavior to the client can be useful.

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:

So, the question is, how do we send an email confirmation to the Customer without mixing our application level concerns (like SMTP services etc) with our domain entities?  Well, lets explore our options.
  1. Create an interface called ISendEmail and make Order depend on it
  2. Pass in the ISendEmail inteface to the Place method
  3. 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 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:

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:

You can see right off, that using an Action does not expose the inner workings of the actual email fulfillment.  ISendEmail is not exposed inside the entity.  To make this really clear, let's see one of our domain services that will use the Place method:

What the Action is allowing us to do here, is inject the notification, but not tie our entity directly to the application service that will be doing our fulfillment.  When designing clean domain models, I really like this approach.  It allows the domain's business rules to run in isolation of the behaviors that are dependent on them and creates a nice separation between the domain and application level concerns.
Hope this helps.

No comments:

Post a Comment