Aspects of Domain Design
On of the things that I like most about conference is that they are the place to meet and exchange a whole boat load of ideas with a large number of people. Most of the time, this is actually done on a normal level, just far more frequently than usually happen. Just this is a reason enough to visit conferences.
Sometimes, however, you get into a conversation that really open your mind. That is what Aslam Khan managed to do to me in about five minutes of hallway conversation. This short talk literally opened up for me a whole new way of thinking about the utilization of aspects and other behavioral patterns. I am going to try explaining this in this post.
Aspects are a way to add behavior to an object. Usually we are talking about being able to add pre and post actions to method calls on the fly. Aspects are commonly used to handle cross cutting infrastructure concerns. The typical examples are: logging, transactions, security and caching.
In the past, I have put domain logic in aspects, and has come to regret it very much, to the point where I believed that the only reason to use aspects is to handle cross cutting infrastructure concerns. Aslam changed my view on that.
Let us take the typical bank account example, with the Withdraw method. Ideally, I want the Withdraw method to contain just the core logic relating to its operation, so it would look something like this:
public virtual Status Withdraw(Money money) { ammountInAccount -= money;
return Status.Success; }
However, what is going to happen when we have the following business role?
You may not withdraw from the account in the first week after is was opened, you may only deposit.
As usual, stupid business rule, but the example doesn't matter. The question is, where does this behavior belongs? Something that you should take into account is that there are likely to be many such rules, and any answer that you give should handle this scenario gracefully.
According to Aslam, the answer for this is in an aspect. Something like this:
public class MayNotWithdrawFromAccountOnFirstWeek : AnAspectOf<Account> // this is invoked on Account.Withdraw method { public override void BeforeExecution() { if( (DateTime.Now - Entity.DateCreated).Days > 7 ) ReturnValue = Status.ActionCancelled; } }
This allow to maintain both the single responsibility principle and create clean separation between the core entity behavior and extended behavior, which is usually implemented in a service layer somewhere.
My first thought when I heard this (I have a deep rooted suspicion of aspects for domain logic, remember) is that I want to make this explicit, so have something like the entity publish some sort of event that the container can route to all subscribers. Thinking about this further, I realize that the code to actually raise the event is exactly the kind of cross cutting technological concern that I want to use an aspect for anyway.
I am not sure about the implications of this technique, but it resonates very well with my JFHCI notion.
Comments
This is about dealing with contextualized specifications isn't it?
I just implemented a similar way of dealing with Rounding rules using PostSharp but before I do something like this I exhaust the possibility of decoration / strategy or make sure I am not actually creating an implicit DomainService that should be explicit.
I'm surprised by this quote:
"In the past, I have put domain logic in aspects, and has come to regret it very much, to the point where I believed that the only reason to use aspects is to handle cross cutting infrastructure concerns."
seems contradictory with a previous post of you wich was nice:
ayende.com/.../...ng-everything-the-smart-way.aspx
In this particular example, would a simple application of decorators on the account class be another alternative to full blown AOP (I share your suspicions)
Thanks for sharing this thought Ayende! It will to keep my brain occupied for a while.
A finishing touch would be to somehow use the Specification pattern to get the code out of the aspect implementation.
Marc
Whats wrong with:
if (AccountActiveLessThanOneWeek) { return ReturnValue = Status.ActionCancelled; } // Business Rule Requirement...
Developers can actually read what is actually happenning, instead of searching for that AOP needle in the 30,000 lines of code haystack. I mean just look at your example, if there is a bug there somewhere a developer would be like 'How the hell is that returning Status.ActionCacelled????????'.
Guido
I agree with Guido. This is part of the business logic and should be in the called method.
And this is a very simple example. There are some times where you can not completely separate the restrictions from the functional code.
And more. In some cases your event will tell more about the process than the called method.
I think aspect is great for what you said before like loggin, security.....
If the aspect is applying a specification based on the state or strategy of the entity, then the 'how' is easily discoverable using something like Resharper.
I have an IRoundingStrategy that is consumed within an aspect to round the operation's result. Doing a 'ctl+alt+F7' on the strategy's method signature would lead me to the aspect.
This is just as ambiguous as using an container for injecting dependencies into services to me.
For Mike and FutureTurnip: Aspects may be used as an alternative technique for introducing patterns, but an aspect is not an alternative to a pattern. For example, if you want to use a decorator that spans types then I may use an aspect to introduce that pattern weaving into the types that I want to decorate. Sometimes, patterns increase complexity by creating heavy and deep type hierarchies and an aspect can help lower type taxes.
For Marc: Absolutely agree. Aspects with Specification or Policy patterns are great. And depending on your IoC+Aspect container/framework, you can then inject into the aspect itself, and let the code weaving side of the aspect framework "inject" the strategy into the code at your join points.
For Cassio and Guido: Yes, you can put the business logic in the method and it will hold for simple classes. However, an aspect is a cross_cutting_concern and not a single responsibility pattern alone. Being cross cutting, it means that the business logic inside an aspect can span type hierarchies. The example above is simplistic but if you extend it with a hierarchy of accounts, that share multiple, selective behavior (i.e. multiple inheritance) then an aspect is cleaner.
My key theme is that aspects force us to think differently and there are times when it makes really good sense to solve domain problems. I still start with good old OO and then use AO as part of my thinking toolbox. And when you do use aspects, then aspects must be first class citizens.
Guido,
It breaks down when you have 50 of those.
Gauthier,
As I said, I didn't consider this an aspect.
It was the discussion with Aslam that made me realize how close those concepts are
Who does undo "ammountInAccount -= money;" if the aspect worked?
I too have been looking at AOP for domains. I on the other hand want to use AOP to structure my model, not just modify it. For example I would want to be able to do stuff like
[CustomerRole]
[SupplierRole]
public class Person { }
[CustomerRole]
[SupplierRole]
public class Company { }
If you have a pattern implementation for CustomerRole and SupplierRole then you can be guaranteed the two implementations are identical, and a single attribute is more descriptive than any number of lines of source code!
I've been working on the guys who write Chrome ( www.remobjects.com) to see if they are willing to add it to their language. If they do I am 100% definitely going to switch to that language for defining my business models!
I've blogged more on my ideas here:
mrpmorris.blogspot.com/.../...e-to-write-code.html
Pete
Not sure I yet like the approach but it is interesting. You might also be interested in this:
http://www.infoq.com/news/2008/11/Qi4j
I'm not totally sure I like that approach though, but it is early days.
Peter,
You might want to consider Boo, in which you can do something like this natively right now.
What we're talking about here is muilt-dimensional separation of concerns, which is often implemented very much like AOP (subclass proxies etc.), but has a very different intention.
Anyone trying to wrap their brains around that concept might like the talk given by Fabian Schmied and me at lang.net:
www.langnetsymposium.com/.../...20-%20rubicon.html
Or just have a look at our blog at http://www.re-motion.org/blogs/team where we introduce the concept of mixins for C#.
Here's one for the skeptics: an example where being explicit in the code does not work is extensibility. If you want to enable other modules to extend or modify your logic, there's no way to make these modifications visible in your code, or otherwise your extensibility would be "just go change my code (oh, and don't forget to merge any updates!)".
Granted, you could be more explicit about being extensible, by calling some rules engine. But here I'm with Ayende - this is something you could easily delegate to an AOP framework. Which could actually get you quite close to what we're doing with mixins, only without the nice syntax. In our case, you declare extensibility by making your methods virtual. When you think about it, this is really what virtual is all about, not?
At rubicon, we're using this heavily for product line development, and so far it works fine. As the projects that use mixins grow larger, however, developers are asking for some cross-referencing tooling (they are going to get them, too).
Or have a look at Hammet's view on this: http://hammett.castleproject.org/?p=323
Whether this is a nice idea without the requirement of extensibility is another matter. There's certainly a lot of advantages to having your features separate (basically by creating a class/feature grid). But it has its downsides, too, obviously. Anyone really interested in this might want to read Ivar Jacobson's "Aspect-Oriented Software Development with Use Cases". I guess the jury is still out on this idea.
Oh, here's your rule in our mixin notation:
[Extends (typeof (Account))]
public class MayNotWithdrawFromAccountOnFirstWeek : Mixin <account,>
{
[OverrideTarget]
public Status Withdraw (int money)
{
}
}
(The idea behind this syntax is to make a mixin definition as close to a subclass definition as C# will let us do. Thinking of mixins as subclasses minus some constraints actually makes it very easy to grasp the concept and use it in a way that makes sense.)
A new pre-release will be out in a few days, this time with full LGPL licensing and source code repository access. Subscribe to our blog to get notified. Give it a try and leave a comment!
(Ayende: This is the framework that has re-linq, which I suggested for easy implementations of LINQ providers, in your case Linq2NHibernate. Did you give it a look yet?)
PS: actually, it is
public class MayNotWithdrawFromAccountOnFirstWeek : Mixin Of (Account, IAccount)
Somehow the angle brackets didn't get encoded...
@ayende
sorry I "skipped" your last § about the event thing
@guido and @Cassio
what if you need to apply multi-tenancy separation of concerns?
Stefan,
Aspect frameworks, under the hood, do make use of proxy, mixin and interceptor patterns. Generally, the proxy is just so that we don't touch the original class. But the mixin is an an introduction and and interceptor is an advice. The pointcut for the mixin is a class and the pointcut for an interceptor is a method.
So, using mixins and interceptors directly is a feasible alternative.
I really don't like using aspects for this sort of business rule. Mainly I'm concerned about discoverability and maintainability.
I would much rather see something explicit, perhaps something where you load a specification for when funds can be withdrawn. The specification would be separate, it would contain the rules, and all you would add to your withdraw method is a check to ensure that the request passes the specification.
Aslam,
I don't know what you mean if you say that aspect frameworks use mixins, but I guess that's because the term mixin means a lot of things to different people. After all, there's no such thing in C# or the CLR, and the implementations in libraries and languages vary wildly.
My point is that although implementations are often technically alike, AOP and multi-dimensional separation of concerns are really two very different things. Fabian (who wrote our mixin library) has shown that it's possible to use our flavor of mixins for simple cross-cutting stuff, but it's still a long way from full AOP. On the other hand, what we do with mixins is possible, but much more difficult to achieve using generic AOP, because we just focus on the multi-dimensional stuff, providing a simple syntax and built-in solutions for common problems that AOP has not. (Especially in the category of "what happens if you have 10 overlapping mixins, extending and overriding stuff in the same target class", but also basic stuff like generics and attributes)
So yes, you can use one as a low-level foundation for the other. But for anything serious, this is rather unlikely.
You can also directly use an AOP framework for this kind of things, but you'll quickly feel that AOP was not designed for it. Also, I think AOP is not for everyone. When I write a subclass, I don't need the power of AOP, so why should I wrap my brains around the subtle differences of "call" and "execution", and why do I have to learn about "handler" if I could just override and do a simple try/catch? This gives us the additional advantage of limiting extensibility to the stuff that was designed to be extensible by its author. (At least in C#, in Java where everything is virtual so you cannot limit this.) AOP lets you do more, but I guess if you want no limits and no simple syntax, you'd better go write your code in Lisp ;-)
Multi-dimensional separation of concerns can be achieved very well with the vocabulary of subclassing, you just need to take a few restrictions away. (Mainly, the single dimension of inheritance, but that's not to say that mixins == multiple inheritance.)
I'd be very concerned on how this might end up hiding important domain logic. My take is that the whole DDD proposal was to revel intend, not to hide it.
To measure such thing all you need to do is putting in a new developer coming to the team and tasked with change MayNotWithdrawFromAccountOnFirstWeek logic. Will he figure out that an aspect controls that? After how long?
@John Chapman, fully agreed. An specification seems like the best place for this.
"what if you need to apply multi-tenancy separation of concerns? "
To Gauthier: In this case maybe it is ok, but not for core business. That's my opinion.
I hear that it can't fit all cases especially for "core business" concerns.
But the example seems to fit well, there will be countless of such requirements and they may clutter the core business logic, it also provide some goodness for rule testing in isolation, or even verbose validation support:
ReturnValue = Status.ActionCancelled("May not withdraw from account on first week"); // dumb sample
What about supporting varying number of anti-corruption layers on specific purposes?
What I am saying is: Let's suppose your app will only run in a determined country. In this country there is a law saying:
"You may not withdraw from the account in the first week after is was opened, you may only deposit."
To me this should not be an aspect.
But if there is no law and a bank that is you client has a rule saying:
"You may not withdraw from the account in the first week after is was opened, you may only deposit."
Now it can be an aspect to that.
Of course that are a lot of other scenarios but each one has to be studied, and even in this case above you can find a lot of pros and cons
And that's just my opinion. Since a lot of people here have more knowledge than me, probably you too Gauthier, don't take this too seriously. I'm here to learn. ;)
I took this as an opportunity to write about some of our own experiences with domain design using mixins, which comes quite close to matters discussed here and might inform this debate. After all, there's little real-world experience with this kind of design.
I also provided a simple example that shows how SoC can be implemented easily with mixins, using the simplicity of polymorphism. I'd be glad about comments either here or over there.
www.re-motion.org/.../mixins-in-domain-design.aspx
Comment preview