Limit your abstractionsAll cookies looks the same to the cookie cutter
One of the major advantages of limiting the number of abstractions you have is that you end up with a lot less “infrastructure” code. This is in quote because a lot of the time I see this type of code doing things like this:
public class BookingServiceImpl : IBookingService { public override IList<Itinerary> RequestPossibleRoutesForCargo(TrackingId trackingId) { Cargo cargo = cargoRepository.Find(trackingId); if (cargo == null) { return new List<Itinerary>(); } return routingService.FetchRoutesForSpecification(cargo.routeSpecification()); } }
I don’t want to see stuff like that. Instead, I want to be able to go into any piece of code and figure out by what it is what it must be doing. All my code follow fairly similar patterns, and the only differences that I have are actual business differences.
Here is the list of common abstractions that I gave before, this time, I am going to go over each one and explain it.
- Controllers – Stand at the edge of the system and manage interaction with the outside world. Can be MVC controllers, MVVM models, WCF Services.
- Views - The actual UI logic that is being executed. Can be MVC views, XAML, or real UI code (you know, that old WinForms stuff ).
- Entities – Data that is being persisted.
- Commands – A packaged command to do something that will execute immediately. (Usually invoked by controllers).
- Tasks – A packaged execution that will be execute at a later point in time (usually async), after the current operation have completed.
- Events – Something that happened in the system that is interesting and require action. Common place for business logic and interaction.
- Queries – Packaged query to be executed immediately. Usually only fairly complex ones gets promoted to an actual query object.
There might be a few others in your system, but for the most part, you would see those types of things over and over and over again.
Oh, sure, you might have other things as well, but those should be rare. If you need to display things in multiple currency interacting with a currency service is something that you would need to often, by all means, make it easy to do (how you do that is usually not important), but the important thing to remember is that those sort of things are one off, and they should remain one off, not the way you structure the entire app.
The reason this is important is that once you have this common infrastructure and shape (for lack of a better word), you can start working in a very rapid pace, without being distracted, and making changes becomes easy. All of your architecture is going through the same central pipes, shifting where they are going is easy to do. You don’t have to drag a rigid system made of a lot of small individual pieces, after all.
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
So, simple queries are part of Commands or Tasks themselves? They are not abstracted? What handles Events?
Great post :)
"...real UI code" - you mean dinosaur UI code :P
It would be so nice if you could build some tiny sample project showing the use of these common abstractions.
Think alot of your readers would benefit from this.
:)
One of your best posts. A lot of good advice here!
Very thought-provoking post, some interesting concepts. I agree with Mads, is there by any chance a sample project showing how you architect and structure an application according to these concepts?
I think it is important to clarify what abstractions entail transaction boundaries. For example does each command represent a transaction? what if two commands are executed in the same controller action.
But most importantly, how do you handle events, are they async, in a way that they occur after the current transaction completes, and will be "reverted" if the transaction failed. I like to handle events using msmq queus (using rhino service bus), because you get the transaction handling / rollback of events on failures for free.
My point is just that, when dealing with major application abstractions like above it is important that transaction boundaries are clear :)
Falhar, Yes, simple queries are used directly, with no abstractions. I am not sure that I understand the events question
Torkel, Sure, those are all decisions that you need to do. I tend to think about commands as participating in the same transaction, tasks have their own transaction. Note that executing multiple commands should be rare and usually considered to be a smell. If you need to do that, you aren't building your commands at the right level of abstraction.
@Ayende - in a system where there is a whole bunch of related/similar updating of entities, I am assuming from your posts you are saying they wont be held in the same class, i.e. a class for each command/task. e.g. BookIt, UpdateBooking, CancelBooking - all held in their own class and grouped by namespace (in the same folder)?
The current way I have seen this is 1 service interface and its implementation as shown in your example that will hold all the related business logic - you are moving away from this, could we also say you are breaking up the code for simplicity of understanding - therefore making the code easier to maintain?
The booking-class example is an anti-pattern I have named "pass-the-ball"-architecture.
Haroon, If you are talking about trivial commands. I don't do them. I just inline the logic inside the controller. The examples you gave are all things that are likely to have significant business logic. Cancel Booking needs to calculate cancellation fees. BookIt requires to actually do the booking, reserve things, etc. Update Booking might need to charge more money, etc.
If I have a set of commands that are related, they are likely to be in the same folder/namespace, yes.
So with this architecture would it be fair to say that:
@Ayende - When do you decide to use an event rather than call into a "service" synchronously in order to perform some action?
Also, what infrastructure do you use to raise/handle events? Do you do the in-process or out (ie in-memory or message bus)?
Flukus, Nothing as rigid as that, no. If you have a complex business process, you might need a command. And if you have something that needs to run outside the current execution thread, you might need a task.
Jonty, Events are good if I have business requirements based on events. When the booking is cancelled, we need to process a cancellation fee, for example. How to handle them depends on the system, the requirements, etc. It moves from simple in memory processing (invoked by integrating into the save pipeline of the data access) to full blown Saga handling using a message bus.
Joe, In this case, I don't care for that, so it isn't implemented.
"If you have a complex business process, you might need a command."
But are commands only ever invoked by user actions?
Flukus, For the most part, yes. If they are invoked by something else (timed trigger, an action) they are jobs / events.
I see where you're coming from on this Ayende, could you give us an example of an alternative to this code please?
I always wonder about events in a web application. How do you take advantage of that particular abstraction in a web application (please don't mention WebForms)?
Since requests are fleeting, do you use something like NServiceBus or MassTransit or do you do something else?
One of my favorite posts and comment threads. Very informative all around.
+1 for increasing the publishing frequency on your future posts pipeline. These posts are just too valuable to keep them secret!
Great post! I am curious, though. How would you handle encapsulating something like creating a new Booking (which may have default values that need to be set based upon some business rules) and returning that to a Controller to eventually be displayed? It's situations like this where I would have an IBookingService with the appropriate method.
JP, new Booking() should do that.
Hi Ayende,
+1 for Mads Laumann. Do you have a sample that illustrates this infrastructure? Have you come across a project on lets say github, that we can view a working sample application. Thanks for the post again!
Tyrone, The source code for this blog (raccoon blog) is a good example to see Oren's principles in practice.
@Ayende: if a commands and tasks have similar/exact same business logic i.e. can this booking be accessed by currentuserid, where is this code held, static classes? extension methods?
e.g. UpdateBooking (first check if user can perform action) DeleteBooking (check if user is a manager)
To further on my last comment - I remember from one of your previous posts you mentioned extension methods, however some permission checks or "background checks" can involve db, or other services, if all checks are fine then proceed...
Haroon, It is very rare for commands and tasks to do the same thing. By their very nature, they are doing different things. Authorization is something that is rarely done by tasks (they usually act on behalf of the system, not a specific user).
@Ayende: So you dont have any domain or business objects with rules and behavior but rather POCO entities? And any business logic would go inside a command or task which is sort of similar to a domain service?
Lars, In many cases, BO with rules and behaviors turn out to be things like validation. That isn't interesting and can be pushed to the infrastructure.
If you have real business logic, we need to decide whatever it is something that belongs to the entity, or are we trying to force it into it out of a sense of architectural purity.
I don't want to categorically say that I don't do that, and I don't want to say that I do.
I think it's worth noting that oftentimes WCF services that you consider a "controller" have an interface abstraction purely so that the ServiceContract can be placed in a separate assembly that will be shared by client applications to use as service proxies.
This doesn't detract from your overall message, but just wanted to point out that it should be an extra consideration before removing that abstraction.
Ayende - I would usually try and remove any business logic from controllers to enable the use of an acceptance test framework to operate on application logic independently from the UI. Controllers and Views would be tested as part of a UI automation testing suite.
To perform acceptance testing would you include controllers in what is being tested by an acceptance test framework ?
Adrian, I strongly object to this world view. I see no point in trying to do this.
Would I be correct in thinking that your objections are unnecessary architectural separation of business logic and added layering of the application, resulting in additional complexity ?
The specific application that I am referring to is a 'thick' client Silverlight app.
By separating business logic explicitly from the controllers, in this case view models, we are able run the 'application' in a non-silverlight context using an acceptance test framework. The benefits of being able to run acceptance tests on the server sans network & silverlight runtime are significant. Despite the costs involved.
Adrian, What gives you the assumption that my controller would be a view model here? Also, trying to abstract the async stuff that makes SL so annoying would create a very false sense of security.
Controllers stand at the edges of the system, but they usually don't interact directly with the UI.
Ayende - my understanding that the controllers were the VM's came from this statement 'Controllers – Stand at the edge of the system and manage interaction with the outside world. Can be MVC controllers, MVVM models, WCF Services.'
Are you referring to the M or the VM in MVVM as the controller ?
Understand the risks with asynch SL, but trade off is workable as number of asynch calls is low compared to number of possible user interactions. Asynch nature of SL is tested via traditional UI Automation framework.
Comment preview