Limit your abstractionsThe key is in the infrastructure…
In my previous post, I discussed actual refactoring to reduce abstraction, and I showed two very interesting methods, Query() and ExecuteCommand(). Here is the code in question:
[AcceptVerbs(HttpVerbs.Post)] public ActionResult Register(string originUnlocode, string destinationUnlocode, DateTime arrivalDeadline) { var trackingId = ExecuteCommand(new RegisterCargo { OriginCode = originUnlocode, DestinationCode = destinationUnlocode, ArrivalDeadline = arrivalDeadline }); return RedirectToAction(ShowActionName, new RouteValueDictionary(new { trackingId })); } public class RegisterCargo : Command<string> { public override void Execute() { var origin = Session.Load<Location>(OriginCode); var destination = Session.Load<Location>(DestinationCode); var trackingId = Query(new NextTrackingIdQuery()); var routeSpecification = new RouteSpecification(origin, destination, ArrivalDeadline); var cargo = new Cargo(trackingId, routeSpecification); Session.Save(cargo); Result = trackingId; } public string OriginCode { get; set; } public string DestinationCode { get; set; } public DateTime ArrivalDeadline { get; set; } }
What are they so important? Mostly because those methods [and similar, like Raise(event) and ExecuteLater(task)] are actually the back bone of the application. They are the infrastructure on top of which everything rests.
Those methods basically accept an argument (and optionally return a value). Their responsibility are:
- Setup the given argument so it can run.
- Execute it.
- Return the result (if there is one).
Here is an example showing how to implement ExecuteCommand:
protected void Default_ExecuteCommand(Command cmd) { cmd.Session = Session; cmd.Execute(); } protected TResult Default_ExecuteCommand<TResult>(Command<TResult> cmd) { ExecuteCommand((Command) cmd); return cmd.Result; }
I have code very much like that in production, because I know that in this system, there are actually only one or two dependencies that a command may want.
There are very few other dependencies, because of the limited number of abstractions that we have. This makes things very simple to write and work with.
Because we abstract away any dependency management, and because we allow only very small number of abstractions, this works very well. The amount of complexity that you have is way down, code reviewing this is very easy, because there isn’t much to review, and it all follows the same structure. The implementation of the rest are pretty much the same thing.
There is just one thing left to discuss, because it kept showing up on the comments for the other posts. How do you handle testing?
More posts in "Limit your abstractions" series:
- (22 Feb 2012) And how do you handle testing?
- (21 Feb 2012) The key is in the infrastructure…
- (20 Feb 2012) Refactoring toward reduced abstractions
- (16 Feb 2012) So what is the whole big deal about?
- (15 Feb 2012) All cookies looks the same to the cookie cutter
- (14 Feb 2012) Commands vs. Tasks, did you forget the workflow?
- (13 Feb 2012) You only get six to a dozen in the entire app
- (10 Feb 2012) Application Events–event processing and RX
- (09 Feb 2012) Application Events–Proposed Solution #2–Cohesion
- (07 Feb 2012) Application Events–Proposed Solution #1
- (06 Feb 2012) Application Events–what about change?
- (03 Feb 2012) Application Events–the wrong way
- (02 Feb 2012) Analyzing a DDD application
Comments
I like this solution. But almost everyone else tell me that you can't use session in the controller or command.:(
Soon someone will try to appear as smart and ask "but what happens when...".
I like this solution, but I'd be interested in seeing how you structure a large project using this approach.
An alternative to this is to have larger classes as a so-called "service" layer (as in previous posts), that either act as a facade or handle all the logic internally. These can become "god-classes" and suffer from the problems you are mentioning in these posts, but they do have one fairly major benefit - they result in a fairly straightforward APIs for developers i.e. I want to do something for an order - let's go to the OrderService.
The solution you are suggesting MAY result in it being difficult to find commands or queries that you need when you're not familiar with the code base. This can happen when a single logic layer is being shared between multiple applications (services, web, external API, etc).
Like I say, I like the recommended approach, I'd be interested in knowing how you structure your projects though to make it easy to navigate around for someone jumping in at a later stage, who doesn't want to have to trawl through, for example, the web project to find out how commands/queries are executed.
Matt,
That is a potential issue, yes. But because those are infrastructure classes, there is nothing that prevent you from refactoring those things out.
And my approach for discoverability relies heavily on R# code browsing abilities. It is very easy to look things out in this fashion
Hi Oren, very like to read your posts which are breaking in me zombie-thinking about how architecture should be designed.
But eventually it makes more question (among testing):
By suggesting to throw out Service/Repository layers, could it send back us to spaghetti or unsupportable code?
Again I agree with your point, that switching ORM is so hard that supporting Repository layer makes no sense.
But what if we have web app and desktop app which reuses some business logic. By removing the service layer we will need to duplicate many thing.
Did I understand right, that you suggest to break service layer (which could be presented as single or several classes) into many small commands classes (by using command pattern)? Then what difference does it makes?
Sorry if confusing, thanks.
Kirill, Even if we assume that you can use the same code in both locations (and that is by no means a given). You should have no problem doing that, you can invoke the same commands in both places.
And no, this doesn't end up being unmaintainable,quite the opposite.
I'm assuming you have added discoverability using this approach because you can simply have a "Queries" folder somewhere in a shared project that has all the queries neatly grouped in some nested folders.
If, on the other hand, you have a repository pattern you need to hunt for a) the right repository and b) inspect its interface to see what's inside.
Additionally, if you have a query that is cross-cutting in any way, how do you know which repository to put the query into?
Do you ever find that your command depends on more than a single database session object? Other dependencies might include things like access to configuration parameters, an event broker or messaging service, things like that.
It seems to me that you would have to supply any of these dependencies into a command object as well as the database session, and these dependencies might vary from command to command.
This is the sort of thing that an IoC container like Windsor is good at, but the pattern you demonstrate seems to need to create command objects using constructor.
Any thoughts on command objects that have additional dependencies besides the database session object?
John, All of those things get injected into the command by the command executer. It may decide to use a container, but that is an implementation detail.
I think the idea is that it's unnecessary overhead to litter your controllers (or similar) with calls to OrderRepository and OrderRetrievalService and other such things when chances are all the Repository will do is wrap the NHibernate/Raven/EF/Whatever call, and all the Service will do is wrap the Repository (or combine 2+ repositories under a facade). You don't get anything and have to delve one layer deeper now to figure out what's going on.
About the only reason, and it's not a very good one outside of textbook things, is it conveys more sense to see very long, descriptive methods that put things into a business perspective (that whole "ubiquitous language" thing in DDD) versus very technical-y calls to Session or Context or whatever, but realistically code that is written by developers, for developers, should be written so developers can understand it.
The only benefit, and the reason why I think DDD espouses this, is so when you're talking to the users you can say things in business terms and not reference databases or repositories or queries, which the average business user won't understand.
Ayende, how are "All of those things injected into the command by the command executer"? Property injection?
I can't think of a clean way to handle this situation if different commands require different dependencies.
Ayende: How do you organize your command and query classes? Is it by primary entity types (Commands/Cargo, Queries/Cargo) or some other way? I had an issue with finding a good way to organize classes when I started getting a lot of query and command objects.
@Wayne
The thing is, that Repository is domain concept. Repository is part of domain and is part of ubiquitous language. For business person, it would be "the thing that saves entities, so they are not lost". But how it will be implemented is technical detail. And thats where the abstraction comes in. This is also because Repository can have some business logic.
And same as code for developers should be written, so developers can understand it, the domain model code should be written so business people can understand it.
@Juanma
I would too like to know how would Ayende handle that. I can already see, that for every single dependence, you would need to add new line into Default_ExecuteCommand or give it to command manualy.
For handling dependencies you could have the concept of a CommandHandler[T] where T is the type of command it handles. This class would contain the "Execute" method from Ayende's command object. The 'Command' class then just contains the properties it needs to fulfil it's duties. The CommandHandler[T] could then use an IoC container to inject it's dependencies.
@Falhar I agree in theory on that, which is why you see those abstractions in DDD-style apps even when they aren't needed. I think Ayende is arguing that since they aren't needed, they don't need to be there just to satisfy some arbitrary notion that you "need" to have Repositories, Services, Factories and the like to have a DDD app.
I have come to loathe the DDD lingo because of the way it gets abused online. It's a shame because the original DDD book is great and valuable for the 1% of applications that need it. The blind devotion to the "patterns" in DDD are disturbing. Blind followers with no ability to question. STFU already, you are over complicating things.
Good series, Ayende.
@Bob I pretty much agree; that's why I'm so interested in Ayende's recent series. Most of what I find online espouse using Repositories for everything, with Services over that, and say how it's a cardinal sin to ever put raw data access logic like calls to NH's Session in a Controller, but really all of that is just adding complexity. If you need to use DDD then that's fine, but for the majority of applications that don't need DDD there's no reason other than dogma to use all of the DDD terminology whether it's needed or not.
What I'd really love to see is a concrete application that walks through applying things correctly, instead of just tossing in DDD-style class names to point and say "Look Ma, I'm using DDD!"
@Wayne:
DDD is not about how to abstract things. DDD is about how to make business changes easy, because you know where and how to put them into existing code. And creation and persistence of entities is important part of that.
And like someone said in comments to previous post. You can't use DDD in one part of the application and not use it in another one. You either use DDD across your whole business logic (thus introducing Repositories and Factories, that only do call-through) or you don't use DDD and freely mix business and persistence logic, but you loose way to comunicate those changes with business people.
And obviously, this example is to show rather simple Domain Model, but it is still a Domain Model. And ignoring this fact can simply turn that domain model into CRUD application.
I'm not saying to create interface and implementation of concrete repository for each agregate root entity. But having single generic repository is enough to handle all the persistence abstraction for whole DDD.
@Simon, So basically, you are changing service methods for command handlers? I like that approach and actually I do use it a lot, but I can't see how different is that from the point of view of abstractions.
When your handlers began to depend on IEmailSender, ICreditCardChecker and so on, aren't you back with a lot single-use interfaces? You have just pushed the services one layer below, from controllers to command handlers.
Also one thing, that everyone is forgetting here: DDD is about change. Constant change. Ayende is refactoring already existing code, without zero changes in between. I believe this kind of code is too hard to change and modify. While YAGNI still aplies, DDD tries to make changes managable, so you dont have to worry about that.
@Falhar: I think the argument can be (and was previously) made that NHibernate or whatever ORM is the generic Repository; there's no need to create another abstraction over it just to put the word "Repository" there.
It is possible to organize commands by domain behavior (use case), say "CargoTracking", rather than the broader domain object "Cargo". That would make commands easy to find. For example, "CargoTracking.Commands.RegisterCargo"
Here I am suggesting namespaces but your commands could be defined within your controller, if they are controller-specific but I suppose you would not have that many commands on a controller that would lead to confusion and unorganized code.
@Juanma Like Ayende said, "It may decide to use a container." If you're using an IoC there should be a BuildUp function to inject properties on that command.
Ayende, what about treating the command as a message (I.e. command in the sense of an order) and having the infrastructure dispatch the command to the piece that handles it? Resharper discoverability is given and you have another set of ways to slice and dice the stuff that does the work, e.g. Putting related execution paths into one class. Or do you think the mental overhead of 1:n publishing isnt worth it for the situations you describe?
@Falhar- Because DDD comes with a fair amount of overhead (in infrastructure, time spent talking to end users, and a learning curve) it should only be used in those parts of an application that give your company a competitive advantage in your industry. That probably means that 95+% of your LOB apps are probably going to end up CRUD and that one or two mission critical system(s) will have a lot more thought going into them. And only then if the complexity warrants it. You can do a whole lot without DDD.
What Frank said. I would prefer to see a separation of the Command and the Handler. Mainly because I think you have two different concerns... The creation of the command parameters, and the creation of the command dependencies.
You can place the handlers all in one file. With multiple execute methods each taking a different kind of message. In a sense is is what WCF does, although it tries to abstract this so it appears as RPC and just overall complicates life.
Either way what is nice about this approach is that system maintenance largely becomes append only, and there are a wide number of additional ways to easily extend the system thru command composition.
Would anyone be so kind as to add a draft implementation of the base Command object on somewhere like pastebin? I'm interested in seeing how the someone else would implement it's Query() method as opposed to the extremely naive implementation I'm playing around with. Many thanks!
@Ryan: Then how do you decide when to use DDD and when normal CRUD? From your point, If I write it as DDD, I introduce lots of overhead. But if I write it as CRUD, it might be too late to move to DDD, because the logic is already there and changing it is too much work.
I believe you can make clean DDD infrastructure so simple, that it still remains DDD, but will not introduce much development overhead.
@Falhar: I think this idea that one could be writing a CRUD app and suddenly realize they need it to be a DDD app belongs on the same shelf where the idea that you use the repository pattern because you may want to replace your ORM was tossed.
If you are 1/2 or 1/4 of the way through your project and only then do you discover that your project is important AND complex enough to require full on DDD you are already failing miserably.
Juanma, Take a look at the InjectProperties() method here: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/
You can then just call:
kernel.InjectProperties(command); command.Execute();
Matthew, Based on whatever make sense at the moment.
Simon, I really don't like the CommandHandler{T} issue. This is really annoying and means that we have to modify things in two places at once. This is only useful for things like messages and message consumers. But on the same app? Just use a single class.
Bob, Yes, exactly!
Falhar,
HUH?! What on earth gave you that idea? This is what bounded contexts are ALL about.
Frank, That is something that I would do if there is the need to distribute the work. If this is happening in a single machine, there is no real need to do that. It is simpler to just have a command.
Ayende, what are your thoughts on cases when a command/query needs to call multiple commands/queries? Do you believe this logic should only exist in the controller, even if this logic needs to be reused elsewhere?
Thanks for the reply Ayende.
I agree with what you are saying about CommandHandler. I was going to write about the fact it makes sense in a message based system. In a single app I guess the only advantage it offers is to prevent 'junior' devs calling Execute in the wrong places. But I guess code reviews and education can solve that problem too!
Would it make sense if you thought the app might need to a separate business layer though? (think typical 'enterprise' n-tier system; often you are forced into this as web layer has no access to database for example). Or do you YAGNI that and refactor when needed?
@Juanma it probably isn't too different to a service to be honest. But at least you force a class to only have one responsibility. Service classes have a tendency to break that and become god classes.
Steve, The case for multiple commands is RARE, and should be avoided. It usually means that you aren't encapsulating at the right level. The case for multiple queries is more common, but should be limited to 2 - 3 due to performance and complexity issues.
Simon, In that case, I have two apps, not a single app. One is the actual app that communicate via WCF / REST / whatever. The other is the web application whose backend are the services.
@Ayende: The key point heres is, that whole NDDD sample is only one bounded context. So either use DDD or don't. Picking parts where DDD is too complex and only "fix" those is bad example.
@Ayende, interesting way to look at it. Thanks for your thoughts. Damn, I wish I could afford to pay you to pair with me for a week!! :-)
Falhar, If your entire app is a single bounded context, there is something wrong in your apps.
@Simon, I second your wish here :)
The key to DDD is the analysis. a) Does this application need to interact with other applications? b) Will users of this application work on the same data at the same time? c) And most importantly, how much time do we have?
Maybe I didn't look hard enough, but I couldn't find a) or b) anywhere in the sample application. Which is funny because The Blue Book talks about these in its example cargo sample application: Notifying billing once a cargo has reached a destination and maximizing cargo allocation on a voyage.
This is where the competitive advantage lies and where the most time should be spent. Not in booking the cargo.
I'd really like to see how queries are structured. Is there an example somewhere in Racoonblog?
Josh, There was never any complex enough query in RaccoonBlog to warrant that.
Comment preview