Log in

Subscribe to site updates.

Enabling Rich Domain Models with Services

Posted by Nicholas Blumhardt | March 02, 2008 13:07

Rinat Abdullin has just posted an interesting article that cuts to the core of a very tricky problem that you face building applications on top of IoC:

Components often need to create instances of other container-managed components at will.

Calling new is a simple and natural thing to do in a non-DI application, but takes more care when using DI. After all, avoiding the use of new is half of what DI is about.

If you haven't read Rinat's article, head over and check it out. (Rinat doesn't just contribute code and CPU cycles to the Autofac project, but also a large number of brain-cycles.)

The solution is to create adapters that bridge the gap between components in the container, and the container itself. This is done using an interface that is independent of the container, to keep the components free of dependencies on the container, which I must reiterate, is the rule.

The reason this problem interests me so much is that it is so common but so infrequently addressed. One of my great passions is for domain-driven design, and I'm always looking for ways to make a domain model richer.

A typically contrived example is determining the current value of a holding of stock:

public class Shareholding
{
  public Shareholding(string symbol, int quantity) {...}
  public string Symbol { get {...} }
  public int Quantity { get {...} }
}

To get the current value of a holding, we can use a quote service:

public interface IQuoteService
{
  decimal GetQuote(string symbol);
}

All of this is well and good - determining the value of a holding is a simple operation and the quote service can obviously be injected into whichever controller is going to do the calculation.

An incremental improvement though is to encapsulate the logic in a property of the Shareholding class itself. Perhaps we'll set the quote service as an optional dependency:

public class Shareholding
{
  public IQuoteService QuoteService { set; private get; }
  public decimal Current Value
  {
    get
    {
      return QuoteService.GetQuote(Symbol) * Quantity;
    }
  }
  ...

To get the value:

// In some method...
var shareholding = new Shareholding("ABC", 1000);
shareholding.QuoteService = this.QuoteService;
Console.WriteLine(shareholding.Value);

Spot the problem? Any class that wants to create shareholdings must itself have reference to an IQuoteService so that it can manually inject the dependency.

This breaks some of the encapsulation that we were originally trying to achieve by exposing Shareholding's reliance on the service. This isn't so bad if the caller is created directly by the container, but if not, it has a flow-on effect through all components that create instances of other components - if Shareholding then creates more components themselves with dependencies, we're in real trouble. Every component along the line will have to obtain a reference to an IQuoteService and pass this along.

Now, that's the context of our problem. In Rinat's example, which deals with executing workflows, a hand-coded factory encapsulating a reference to the container is a rather nice solution. When building a domain model with hundreds of interrelated classes that all may create instances of each other, this becomes impractical in my eyes.

Aside: Udi Dahan, a man who knows more than a little about DDD, posted an article on his solution while I was writing this. Udi's solution uses events and looks very workable, but appears as though it might exhibit increasing complexity as the depth of dependencies within the domain model increases. I could certainly be wrong and definitely need to spend some more time investigating Udi's model.

A syntactic shortcut that Rinat has touched on and that I am interested in exploring further is to utilise .NET's typing features to substitute delegate signatures for the adapter/resolver interfaces:

public class Shareholding
{
  public delegate Shareholding Factory(string symbol, int quantity);
  public Shareholding(string symbol, int quantity) {...}
  ...

We can register an instance of this delegate in the container:

builder.Register<Shareholding>().FactoryScoped();
builder.RegisterGeneratedFactory<Shareholding.Factory>();

This lets us later resolve that factory delegate as a dependency, and use it to create our Shareholding without any awareness of the injected quote service:

// In some method...
var shareholding = this.ShareholdingFactory("ABC", 1000);
Console.WriteLine(shareholding.Value);

I cheated a bit just there. What the RegisterGeneratedFactory() method does, is (approximately):

builder.Register<Shareholding.Factory>(
  (c, p) =>
    (symbol, quantity) =>
      c.Resolve<Shareholding>(new Parameter("symbol", symbol), new Parameter("quantity", quantity)))
  .ContainerScoped();

Now that is a mouthful of lambda! Autofac 1.1 uses Linq to translate the parameters of the Shareholding.Factory delegate, symbol and quantity into named parameters that can be fed to the container's Shareholding constructor call.

At the moment I feel like this kind of container usage is really on the edge and perhaps you should expect some less exotic scenarios in my future posts, however DI/IoC is a very deep architectural pattern, and in the coming years (before it eliminated entirely by languages without the structural object creation issues we face in C#,) lots of people are going to hit these same challenges. I hope that we can take some of these concepts along with Autofac to make solving these problems more straightforward.

Closing note - you need a pretty flexible ORM, like NHibernate before you'll get very far trying to pull tricks like this with your domain model ;)

Comments

Posted by Jeremy Gray | March 10, 2008 16:23

Actually, as best as I could read Udi's post when I saw it this morning is that the events had very litle to do with the solution. Instead, he added a lazy-loaded list property to one of the domain objects and then modified the logic to use it, sidestepping the entire need to talk to a component through a mix of ORM lazy-loading and domain logic. As such, his post didn't end up creating a "fully encapsulated domain model" that could still make use of components and therefore left me wanting more, I've been watching the comments on it since then in order to see if anyone else noticed this and if more gets said on the subject. Your post leaves me wondering the same thing: how can our entities be enriched with component services via DI, helping us avoid the anemic domain model anti-pattern, when the instantiation of our entities is under the control of an ORM like NHibernate or iBatis.net?

Posted by Jeremy Gray | March 10, 2008 16:25

PS - as you can see from my comment above, your commenting system isn't respecting any plain text formatting (like carriage returns) but the comment entry form doesn't specify any other type of accepted formatting (like perhaps a limited subset of html.) Ugh.

Posted by Jeremy Gray | March 10, 2008 16:27

PPS - it also isn't the tenth day of March, either. ;)

Posted by Rinat Abdullin | March 10, 2008 17:57

Nick, now I need some time to digest your article)) Rinat

Posted by Rinat Abdullin | March 10, 2008 21:45

> "PPS - it also isn't the tenth day of March, either" So Nick has actually found the way to override that EarthDefaults.HoursPerDay setting)))

Posted by Nicholas Blumhardt | March 11, 2008 10:04

:) Sharp, Jeremy! Sorry about the comment formatting. Blog is in for a major upgrade soon. VMWare is my secret time-travel device, just key in the number of ticks per second and wham!

Posted by Nicholas Blumhardt | March 11, 2008 15:53

I should probably state a couple of things that weren't clear in the article: 1. You'll also inject dependencies into domain objects as they're loaded by the ORM 2. The problem I'm talking about really comes into play when you have something like Portfolio (a domain object) creating instances of Shareholding (another domain object) - that is where the dependency on IQuoteService becomes transitive unless you use the adapter approach to let the container create all the objects I might have a shot at a 'Part 2' to clear all of this up.

Posted by Jeremy Gray | March 12, 2008 09:43

Thanks for the response. I have to admit that while I've done #1 using iBatis.net, it's support for doing so was limited at the time (the callback only fired during creation of "top-level" objects, not any of the eager-loaded related objects) and I've not yet had a chance to combine NHibernate with IoC in this manner, so any examples you have kicking around would be truly appreciated. As for #2, it sounds to me like a case where some factory support needs to be consistently brought to bear and if there's one thing that has come to mind for me in the last few days it is that Autofac is getting me jazzed up in ways that other containers haven't, as much as I am a fan of things like Spring, Castle, StructureMap, etc. There's just something about the simple clarity, and yet power and flexibility, of the Autofac design, not to mention the informative and to-the-point documentation. Nice work!

Posted by Nicholas Blumhardt | March 12, 2008 12:28

In NHibernate you can implement IInterceptor to perform property injection whenever a domain object is loaded from the database. An alternative to creating new domain objects through the container is to intercept NHibernate's saving behaviour in the same way (IInterceptor) to perform property injection then - it isn't bulletproof (you can't use services between construction and saving) but otherwise works well. I'm really keen on symmetry, so this property-injection-all-round approach appeals, but Autofac is flexible enough that I'm hoping to experiment with using the container from the ORM to 'rehydrate' instances too... Not sure where that will lead! The NHibernate integration planned as part of the project was originally going to be pretty extensive; because of time constraints I'm going to cut it down to cover the bare-bones cases just to get it into circulation. I'm glad Autofac has struck a chord with you - looking forward to hearing about your experiences.

Posted by Jeremy Gray | March 13, 2008 14:17

Thanks for the quick update. I figured that NHibernate would have a strong mechanism in that area, stronger than what I'm familiar with from my days with iBatis, and it is nice to receive confirmation to that effect. Hopefully in not too long I'll be able to spool up my next project, which happens to be one where I'll be going down the NHibernate path.

Posted by Jeremy Gray | March 14, 2008 16:05

Thanks for the quick update. I figured that NHibernate would have a strong mechanism in that area, stronger than what I'm familiar with from my days with iBatis, and it is nice to receive confirmation to that effect. Hopefully in not too long I'll be able to spool up my next project, which happens to be one where I'll be going down the NHibernate path.

Posted by Jeremy Gray | March 14, 2008 16:05

Thanks for the quick update. I figured that NHibernate would have a strong mechanism in that area, stronger than what I'm familiar with from my days with iBatis, and it is nice to receive confirmation to that effect. Hopefully in not too long I'll be able to spool up my next project, which happens to be one where I'll be going down the NHibernate path.

Posted by Jeremy Gray | March 14, 2008 16:06

Whoa, sorry about the duplicate comments. Thank you, Internet Explorer, thank you. ;)

Encapsulated Domain Models

Posted by Udi Dahan | March 31, 2008 03:42

Jeremy Gray, > Actually, as best as I could read Udi's post when I saw it this morning is that the events had very litle to do with the solution. That's right. The events have to deal with propogated errors out of the domain model to the service layer (or anywhere else that might be interested). > Instead, he added a lazy-loaded list property to one of the domain objects and then modified the logic to use it, sidestepping the entire need to talk to a component through a mix of ORM lazy-loading and domain logic. That's correct. > As such, his post didn't end up creating a "fully encapsulated domain model" that could still make use of components. And that was a big part of the message. Domain Models should not, I repeat - NOT, make use "components" or things which integrate with other things. Integration is a big enough problem to keep it by itself. The same is true for domain models. If you're looking for a connection - it's long-running processes (since you can't really know when or if that integration technology / external system is going to respond). That is something that I handle with sagas in nServiceBus. Hope that helps a bit.

Posted by Nicholas Blumhardt | April 01, 2008 02:42

Hi Udi, I realised not too long after linking your article that you were addressing a different but related issue. Services in this article are services from the perspective of the DI container, not necessarily external systems or integrations. In fact, in the 'factories' example I gave, the service is an integral part of the domain model (as it would be in the case of other injectable dependencies like rules engines, file-based local storage or configuration parameters, (possibly) other repositories, etc.) The emphasis here is simply that the DI principle can be used to give flexibility/configurability to the domain layer. The natural caveats that apply to accessing remote or out-of-proc services still apply here. Nick

Your Comment





Reset

Disclaimer: These articles represent the opinions of the authors and may not match the official position of Ubik Systems Pty. Ltd. Confirmation should be sought on all matters involving professional advice.