Scheduled Tasks in MonoRail: The Quick & Dirty solution
I need to develop a set of tasks that would run in internals. Now, we need to do several dozens of those, and at that point, it is not important how we actually schedule them, we can defer that decision. But we really need to be able to start develop them soon.
So, I came up with this idea. The basic structure is this:
[OccuresEvery(Occurances.Day)] public class SendBirthdayEmails : ScheduledTask { public override void Execute() { foreach(Employee emp in Repository<Employee>.FindAll(Where.Employee.Birthday == DateTime.Today) { Email .From(Settings.Default.HumanResourcesEmail) .To(emp.Email) .Template(Templates.Email) .Parameter("employee", emp) .Send(); } } }
This is not really interesting, but the rest is. Remember that I don't want to deal with deciding how to actually schedule them, but we need to be able to run them right now for test / debug / demo purposes.
In my windsor.boo:
//Controllers controllersAssembly = Assembly.Load("MyApp.Web") for type in controllersAssembly.GetTypes(): continue if type.Name == "ScheduledTasksController" continue if not typeof(Controller).IsAssignableFrom(type) IoC.Container.AddComponent(type.FullName, type) //register scheduled tasks scheduledTasksAssembly = Assembly.Load("MyApp.ScheduledTasks") scheduledTasks = [] for type in scheduledTasksAssembly.GetTypes(): continue if not typeof(ScheduledTask).IsAssignableFrom(type) IoC.Container.AddComponent(type.FullName, type) scheduledTasks.Add(type)//register scheduled tasks controller independently, since it requires special configuration Component("ScheduledTasksController", ScheduledTasksController, scheduledTasks: scheduledTasks.ToArray(Type) )
So it will automatically scan the scheduled tasks assembly, and the only thing that I have left is to write the ScheduledTasksController. This is a very simple one:
It has just two methods, one to list all the tasks, and the second to execute them. This is strictly a dev only part of the application, so I took most of the available shortcuts that I could. So the UI looks like this:
And the view code is:
<% for task in tasks: %> <tr> <td> ${Text.PascalCaseToWord(task.Name)} </td> <td> Every ${task.OccuranceEvery} </td> <td> ${Html.LinkTo("Execute", "ScheduledTasks", "Execute", task.FullName)} </td> </tr> <% end %>
I really like the PascalCaseToWord helper, really nice.
On the controller's side of things, I have this:
public ScheduledTasksController(Type[] scheduledTasks) { scheduledTaskDescriptions = new ScheduledTaskDescription[scheduledTasks.Length]; for (int i = 0; i < scheduledTasks.Length; i++) { scheduledTaskDescriptions[i] = new ScheduledTaskDescription(scheduledTasks[i]); } } public void Index() { PropertyBag["tasks"] = scheduledTaskDescriptions; }
Not a best practice code, but I did knock the whole thing very quickly. ScheduledTaskDescription just takes a type and unpack it in terms of attributes, name, etc.
The end result is that the other developers on my team can add a new scheduled task by simply adding a class that inherits from ScheduledTask, go to the browser, hit F5 and start executing it.
Now that is RAD.
Comments
I love this. I think that the level of benefit that this provides makes it elegant by default. Surely implementing a pattern like this, that makes development so much easier/faster/simpler, qualifies as a "good practice"?
Your own satisfaction has to be the main benchmark.
What I am trying to say is that if I wrote that code I wouldn't be too worried about people thinking it wasn't best practice :)
Nice way for testing but...
I have always written a service for running scheduled tasks instead using a web application.
The tasks themselves should be in an independent assembly and can be therefore accessed both interactively by web apps and testing projects as well as by the scheduler.
Another question that I have is why you configure the application within the code using tags. I have made the experience that this leads to unneccessary compile cycles exactly when you don't need them (non-communicated changes in production environment, oh how I like them...).
-Markus
Markus,
The tasks sits in another assembly, this is just a quick way to list & execute them.
Why configure the execution times using attributes? Because it is the simplest thing I could think about, and I know in advance what the times will be. If I will need the flexibility of an external way to specify that, I will add that.
Comment preview