Ayende @ Rahien

Refunds available at head office

Limit your abstractions: All 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.

  1. Controllers – Stand at the edge of the system and manage interaction with the outside world. Can be MVC controllers, MVVM models, WCF Services.
  2. Views  - The actual UI logic that is being executed. Can be MVC views, XAML, or real UI code (you know, that old WinForms stuff Smile).
  3. Entities – Data that is being persisted.
  4. Commands – A packaged command to do something that will execute immediately. (Usually invoked by controllers).
  5. Tasks – A packaged execution that will be execute at a later point in time (usually async), after the current operation have completed.
  6. Events – Something that happened in the system that is interesting and require action. Common place for business logic and interaction.
  7. 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.

Comments

Falhar
02/15/2012 08:01 AM by
Falhar

So, simple queries are part of Commands or Tasks themselves? They are not abstracted? What handles Events?

Mads Laumann
02/15/2012 08:21 AM by
Mads Laumann

Great post :)

"...real UI code" - you mean dinosaur UI code :P

Mads Laumann
02/15/2012 08:30 AM by
Mads Laumann

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.

:)

Daniel Lidström
02/15/2012 08:32 AM by
Daniel Lidström

One of your best posts. A lot of good advice here!

Joshua Lewis
02/15/2012 08:36 AM by
Joshua Lewis

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?

Torkel
02/15/2012 08:40 AM by
Torkel

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 :)

Ayende Rahien
02/15/2012 09:06 AM by
Ayende Rahien

Falhar, Yes, simple queries are used directly, with no abstractions. I am not sure that I understand the events question

Ayende Rahien
02/15/2012 09:08 AM by
Ayende Rahien

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.

haroon
02/15/2012 09:14 AM by
haroon

@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?

Even
02/15/2012 09:16 AM by
Even

The booking-class example is an anti-pattern I have named "pass-the-ball"-architecture.

Ayende Rahien
02/15/2012 09:32 AM by
Ayende Rahien

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.

Jonty
02/15/2012 10:29 AM by
Jonty

@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
02/15/2012 10:22 AM by
flukus

So with this architecture would it be fair to say that:

  1. Users issue commands (cancel booking button somehow invokes CancelBookingCommand).
  2. Tasks respond to events (IHandleSomething).
Ayende Rahien
02/15/2012 10:42 AM by
Ayende Rahien

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.

Ayende Rahien
02/15/2012 10:44 AM by
Ayende Rahien

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.

Ayende Rahien
02/15/2012 10:46 AM by
Ayende Rahien

Joe, In this case, I don't care for that, so it isn't implemented.

flukus
02/15/2012 11:10 AM by
flukus

"If you have a complex business process, you might need a command."

But are commands only ever invoked by user actions?

Ayende Rahien
02/15/2012 11:14 AM by
Ayende Rahien

Flukus, For the most part, yes. If they are invoked by something else (timed trigger, an action) they are jobs / events.

Ryan Penfold
02/15/2012 11:58 AM by
Ryan Penfold

I see where you're coming from on this Ayende, could you give us an example of an alternative to this code please?

Khalid Abuhakmeh
02/15/2012 12:32 PM by
Khalid Abuhakmeh

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?

Jesse
02/15/2012 02:02 PM by
Jesse

One of my favorite posts and comment threads. Very informative all around.

Daniel Lang
02/15/2012 02:15 PM by
Daniel Lang

+1 for increasing the publishing frequency on your future posts pipeline. These posts are just too valuable to keep them secret!

J.P. Hamilton
02/15/2012 02:39 PM by
J.P. Hamilton

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.

Ayende Rahien
02/15/2012 06:41 PM by
Ayende Rahien

JP, new Booking() should do that.

Tyrone
02/15/2012 07:31 PM by
Tyrone

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!

afif
02/15/2012 10:56 PM by
afif

Tyrone, The source code for this blog (raccoon blog) is a good example to see Oren's principles in practice.

Haroon
02/16/2012 12:15 AM by
Haroon

@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)

haroon
02/16/2012 12:18 AM by
haroon

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...

Ayende Rahien
02/16/2012 01:23 AM by
Ayende Rahien

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).

Lars-Erik Kindblad
02/16/2012 03:55 AM by
Lars-Erik Kindblad

@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?

Ayende Rahien
02/16/2012 04:01 AM by
Ayende Rahien

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.

Dan
02/16/2012 10:57 PM by
Dan

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.

Adrian
02/17/2012 11:02 PM by
Adrian

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 ?

Ayende Rahien
02/17/2012 11:04 PM by
Ayende Rahien

Adrian, I strongly object to this world view. I see no point in trying to do this.

Adrian
02/17/2012 11:50 PM by
Adrian

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.

Ayende Rahien
02/18/2012 12:01 AM by
Ayende Rahien

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.

Adrian
02/18/2012 12:16 AM by
Adrian

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.

Comments have been closed on this topic.