Ayende @ Rahien

It's a girl

Limit your abstractions: Commands vs. Tasks, did you forget the workflow?

imageOn my last post, I outlined the major abstractions that I tend to use in my applications.

  1. Controllers
  2. Views
  3. Entities
  4. Commands
  5. Tasks
  6. Events
  7. Queries

I also said that I like Tasks much more than Commands and I’ll explain that in the future. When talking about tasks, I usually talk about something that is based on this code. This give us the ability to write code such as this:

public class AssignCargoToRoute : BackgroundTask
{
  public Itinerary Itinerary { get;set; }
  TrackingId TrackingId { get;set; }

  public override void Execute()
  {
    
  }
}

On the face of it, this is a very similar to what we have had before. So why am I so much in favor of tasks rather than commands?

Put simply, the promises that they make are different.  A command will execute immediately, this is good when we are encapsulating common or complex piece of logic. We give it a meaningful name and move on with our lives.

The problem is that in many cases, executing immediately is something that we don’t want. Why is that?

Well, what happen if this can take a while? What if this requires touching a remote resource (one that can’t take part of our transaction)? What happen if we want this to execute, but only if the entire operation have been successful? How do we handle errors? What happen when the scenario calls for a complex workflow? Can I partially succeed in what I am doing? Can you have a compensating action if some part fail? All of those scenarios basically boil down to “I don’t want to execute it now, I want the execution to be managed for me”.

Thing about the scenario that we actually have here. We need to assign a cargo to a route. But what does that means? In the trivial example, we do that by updating some data in our local database. But in real world scenario, something like that tends to be much more complex. We need to calculate shipping charges, check manifest, verify that we have all the proper permits, etc. All of that takes time, and usually collaboration with external systems.

For the most part, I find that real world systems requires a lot more tasks than commands. Mostly because it is actually rare to have complex interaction inside your own system. If you do, you have to be cautious that you aren’t adding too much complexity. It is the external interactions that tends to makes life… interesting.

This has implications on how we are building the system, because we don’t assume immediate execution and the temporal coupling that comes with it.

Comments

Yves Reynhout
02/14/2012 08:40 AM by
Yves Reynhout

Although I get what you're getting at, I do find classifying commands as "execute immediately" a bit narrow minded. It depends on how you model and name them. For example, if I have a command called StartRework it should be pretty obvious that it's meant to trigger a process. The command itself MIGHT "execute immediately", but the associated process could take a while. Also, some of the things you attribute to it being a task rather than a command, made me think of it being a saga (which triggered my initial reaction on twitter). As far as the business is concerned it's the process that's worth the most (regardless of calling it a task/saga). Some might implement your task using a domain service (from DDD) or a saga. None of them would be "wrong", just a matter of trade-offs/complexity warranting.

Tom Janssens
02/14/2012 08:57 AM by
Tom Janssens

As mentioned on twitter, don't you think long running processes might get shattered over multiple classes, making it hard to follow what exactly is going on here? In my more recent experiments, I noticed that in a lot of simple bounded contexts a simple finite state machine driven by conditional launching of events to alter state seems to work pretty well, while still being maintainable...

Ayende Rahien
02/14/2012 09:04 AM by
Ayende Rahien

Tom, FSM is something that I handle with events, not with tasks.

Note that a Task is a good way to handle operations that you might use a saga for, but it can't handle all things that you use a saga for. It is a good way to go if you have fairly simple needs, and don't require the whole bus infrastructure setup.

Tom Janssens
02/14/2012 09:29 AM by
Tom Janssens

I assume tasks always relate to "an external context" and can only succeed or fail, no pending states etc (i.e. send an email, report a change, import some data)?

Tom Janssens
02/14/2012 09:34 AM by
Tom Janssens

Never mind answering that last question, I re-read your blog post and noticed you use tasks for managing internal transitions as well...

Thanks for the series, and I am looking forward to the next post.

Falhar
02/14/2012 09:44 AM by
Falhar

So if I get this right. Commands can be run only by themselves. Everything needed to run Command is either in class itself, or in base abstract class. But Tasks need some TaksRunner to be run. Then TaskRunner can do things like error handling, transaction and session handling. It can run complex tasks composed from different tasks.

Also, is it worth it to run code on server asynchronously? I read some opinions, that the structural overhead is not worth the performance gain.

Ayende Rahien
02/14/2012 10:09 AM by
Ayende Rahien

Falhar, No, commands also have a command executer (discussed later in the series). They also benefit from some management by the infrastructure.

There are many cases where you want to run things async. But also note that tasks can be run on a scheduler by a totally different machine, HOW they get run is immaterial.

Rafal
02/14/2012 11:37 AM by
Rafal

You're right, background processing should be built into the design from the beginning because it's hard to do it later when the application grows and async processing becomes a necessity. Users get used to synchronous GUI and will oppose any functions that don't happen instantly, synchronous integration interfaces are impossible to change to async because of their clients being synchronous too, the same with db structure, transactions, status tracking etc. On the other hand, you can't live without synchronous commands. Maybe this should be something like DPC in device drivers where an interrupt handler is synchronous and supposedly quick and if it needs more processing it can enqueue an async task.

Lars-Erik
02/14/2012 11:52 AM by
Lars-Erik

According to http://en.wikipedia.org/wiki/Command_pattern, "The invoker decides when the method should be called. The receiver is an instance of the class that contains the method's code."

How do you relate "A command will execute immediately..." to that definition?

To me, the command pattern is about the client providing all values for an operation in some data structure, then sending the instance through some abstraction to something unknown, which might or might not execute the associated operation immediately.

It might for instance go on a queue, and into a versioning history, and it might be executed once more in a restore operation later.

Thoughts?

Lars-Erik
02/14/2012 11:53 AM by
Lars-Erik

Thought it valuable to insert this to the beginning of the first quote. "The client instantiates the command object and provides the information required to call the method at a later time."

Ayende Rahien
02/14/2012 12:23 PM by
Ayende Rahien

Lars, The command pattern doesn't talk about when it gets executed. I need a distinction between "running now" "will run in the future". That is why I have commands vs. tasks. For the vast majority of cases, we execute commands immediately, so I keep this for that.

Lars-Erik
02/14/2012 12:45 PM by
Lars-Erik

I agree that it's nice to know what kind of implications sending the command will have. Your naming conventions are as good as any I guess. :) Just gets a bit confusing when using the same names as "established patterns".

Joe Young
02/14/2012 02:37 PM by
Joe Young

I experimented with the idea of using the Task.Factory to spawn off worker threads to do something similar like you do in the TaskExecutor.

I shyed away from it however, as it was my understanding that IIS does not know about those threads and cannot shut them down if the AppDomain recyles or is somehow shut down.

Are you concerned about those situtations if so how do you handle them?

I ended up firing a seperate web request to a "job" area in MVC to handle these situations.

Ayende Rahien
02/14/2012 02:45 PM by
Ayende Rahien

Joe, See: http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx

Joe young
02/14/2012 03:36 PM by
Joe young

I am still a bit confused.

In Phil's article he makes use of the IRegisteredObject to let IIS know about the threads. I expected to see the TaskExecutor doing something similar or something that makes use of IRegisteredObject.

But that does not appear to be the case.

Perhaps I am being dense, but could you elaborate a bit more?

Steve Sheldon
02/14/2012 03:49 PM by
Steve Sheldon

The idea of a Command executing now versus a Background Task, or a Delayed Job(the term used on Heroku with Ruby) is useful.

But then this will also lead to a series of posts titled 'Limit your commands' and how commands can be abused in a procedural fashion.

Shane Courtrille
02/14/2012 06:52 PM by
Shane Courtrille

Oren.. If you say you keep commands why did you put a line through it on your list? Shouldn't your list include Commands AND Tasks?

Daniel Marbach
02/15/2012 05:32 AM by
Daniel Marbach

Hy oren How do you handle exeptions in tasks? When the tasks are executed on a remote syste, (for example the server) how do you inform the client which triggered the action which lead to a new background task?

Daniel

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

Daniel, When you have tasks, you have just two ways of handling errors. Expected errors, that are part of the business processing, for example, credit card refused. This should be handled by the task itself, based on its own logic. Then you have system errors (database down, network timeout, etc). Those are handled by the task executer, retrying the task a few times and finally giving up and writing this to the log.

If you require a notification, you might include that in the task executer.

Haroon
02/22/2012 11:08 AM by
Haroon

@Ayende: Thanks for the response above to @Daniel - really useful info. I was just wondering how would you create a background task in .net 3,5, one that runs later, async? Like what you have in this blog (seen the code on github).

I gotta say, you know how to simplify things big time, reminds me of the acronym I have heard KISS (Keep it Simple Stupid) :)

Thanks...

Ayende Rahien
02/22/2012 11:19 AM by
Ayende Rahien

Haroon, On 3.5, you use the thread pool? Not sure, that depend on what sort of background processing you want.

joseph
03/03/2012 06:12 PM by
joseph

In short if the system is ready to execute tasks it van even execute the commands more efficiently, because they are just tasks to be executed now.

Comments have been closed on this topic.