Roque
event & work queueing framework for .Net
Install / Use
/learn @BlogTalkRadio/RoqueREADME
Roque
pronounced "raw-queue"
Roque is an event & work queueing framework for .Net, made simple.
It sits on top of the C# abstractions you know (plain old C# events and methods), and uses Redis behind the scenes to make them work in an async, transparent, distributed, scalable, decoupled and failure-proof way.
Message queueing doesn't get simpler than this!
Really?? ... show me!
Example 1: Image Processing
Lets say we have a website and we want to build thumbnails for uploaded pics, that's a time-consuming operation we can't perform during the lifetime of web request.
1- Create a service interface.
public interface IImageProcessor {
void CreateThumbnail(string filename, int width, int height, AlgorithmOptions options);
}
2- Use it on your application:
public class ImageBiz {
IImageProcessor ImageProcessor = RoqueProxyGenerator.Create<IImageProcessor>("images");
public void ImageUploaded(filename){
ImageProcessor.CreateThumbnail(filename, 160, 120, new AlgorithmOptions { Quality=0.7 });
}
}
Note: add references to Roque.Core and Roque.Redis assemblies to your project.
3- Config a redis-based queue named "images":
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="roque" type="Cinchcast.Roque.Core.Configuration.Roque, Roque.Core"/>
</configSections>
<roque>
<queues>
<queue name="images" type="Cinchcast.Roque.Redis.RedisQueue, Roque.Redis">
<settings>
<setting key="host" value="localhost"/>
<!-- Optional, if not specified default Redis port is used: 6379 -->
<setting key="port" value="6379"/>
</settings>
</queue>
</queues>
</roque>
</configuration>
That's it. You're already enqueuing jobs!, let's set up a worker, hurry up!:
4- Implement your image processor service:
public class ImageProcessor : IImageProcessor {
public void CreateThumbnail(string filename, int width, int height, AlgorithmOptions options = null){
// a time-consuming task, eg: resize the image and save it adding a suffix
throw new NotImplementedException();
}
}
5- Install Roque service on a machine (with access to your Redis server).
6- On the same folder of roque.exe drop the assembly(ies) containing IImageProcessor interface and ImageProcessor class.
7- On the worker Roque.exe.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="roque" type="Cinchcast.Roque.Core.Configuration.Roque, Roque.Core"/>
</configSections>
<roque>
<queues>
<queue name="images" type="Cinchcast.Roque.Redis.RedisQueue, Roque.Redis">
<settings>
<setting key="host" value="localhost"/>
</settings>
</queue>
</queues>
<workers>
<!-- a worker poping jobs from "images" queue -->
<worker name="images" queue="images" autoStart="true"/>
</workers>
</roque>
<castle>
<components>
<!-- using Castle Windsor to tell Roque what image processing service to use. type name must be fully qualified -->
<component service="Acme.Images.IImageProcessor" type="Acme.Images.ImageProcessor, Acme.Images"/>
</components>
</castle>
</configuration>
8- Start Roque Service to start processing images!
(or you can roque.exe from a console, use: roque.exe /debug to attach your VisualStudio and debug your image processor)
You're done, now you can start adding more workers to get automatic load balancing by repeating steps 5 to 8.
To check the status of your queues you can run: roque.exe status
C:\>roque status /maxage=10 /maxlength=500000
Redis send-pump is starting
roque Information: 0 : [REDIS] connected to localhost:6379
Queue images has 262570 pending jobs. Next job was created < 1sec ago.
Queue audiofiles has 3342 pending jobs. Next job was created 12sec ago. [TOO OLD]
Queue zipping is empty.
ERROR: 1 queue have too old pending jobs
run roque.exe without arguments to see al options.
That's awesome! but I want events, I need decoupling, I want multiple and easy to add/replace/remove subscribers. But I don't want to read books on Message Queues.
(If you wonder what's the difference check the queue diagrams below showing a work queue and a pub/sub queue)
Example 2: Website User Sign-up post tasks.
Let's change the approach, let's suppose we want to perform several differnt tasks each time a user signs up. These tasks include creating a thumbnail of users profile pic, and sending a welcome email. (we could add logging, stats, analytics, etc.)
We don't want to clutter our user entity with the execution of this tasks. We already know a good solution to this problem: events.
1- Create an event-raising interface.
public interface IUserEvents {
event EventHandler<UserEventArgs> UserSignedUp;
}
2- Throw events on your application
public class UserBiz : IUserEvents {
public event EventHandler<UserEventArgs> UserSignedUp;
public void SignUp(string username, string password, string email) {
// TODO: insert the user in my database
var handler = UserSignedUp;
if (handler != null){
handler(this, new UserEventArgs(username, email));
}
// TIP: if you want you can save a few lines writting an extension method for Exception
// UserSignedUp.Raise(new UserEventArgs(username, email));
}
}
public class BizEventsInitializer {
// call this on app startup
public void Init() {
// make all events on IUserEvents raised by this instance available for remote subscription
RoqueEventBroadcaster.SubscribeToAll<IUserEvents>(UserBiz.Instance);
}
}
3- Config redis-based events queue:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="roque" type="Cinchcast.Roque.Core.Configuration.Roque, Roque.Core"/>
</configSections>
<roque>
<queues>
<!-- Reserved name _events is used by default by RoqueEventBroadcaster -->
<queue name="_events" type="Cinchcast.Roque.Redis.RedisQueue, Roque.Redis">
<settings>
<setting key="host" value="localhost"/>
</settings>
</queue>
</queues>
</roque>
</configuration>
Your website is ready!, You're events are available, they'll get in your queues as soon as you add subscribers for them.
Note: If no subscribers are found for an event, nothing is sent to Redis. You _events queue will always be empty (it won't even exist on Redis), as events never get directly enqueued, they get broadcasted to other queues.
4- Add some subscribers:
public class ThumbnailCreator {
public void SubscribeTo(IUserEvents userEvents) {
userEvents.UserSignedUp+= UserEvents_UserSignedUp;
}
public void UserEvents_UserSignedUp(object sender, UserEventArgs args) {
// let's reuse or image processing service here
new ImageProcessor().CreateThumbnail("pics/"+args.Username+".jpg", 160, 120);
}
}
public class UserGreeter {
public void SubscribeTo(IUserEvents userEvents) {
userEvents.UserSignedUp+= UserEvents_UserSignedUp;
}
public void UserEvents_UserSignedUp(object sender, UserEventArgs args) {
MailSender.SendWelcomeEmail(args.Username, args.Email);
}
}
5- Install Roque service on a machine (if you didn't before).
6- On the same folder of roque.exe drop the assembly(ies) containing IUserEvents interface and your ThumbnailCreator and UserGreeter classes.
7- On Roque.exe.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="roque" type="Cinchcast.Roque.Core.Configuration.Roque, Roque.Core"/>
</configSections>
<roque>
<queues>
<queue name="images" type="Cinchcast.Roque.Redis.RedisQueue, Roque.Redis">
<settings>
<setting key="host" value="localhost"/>
</settings>
</queue>
<queue name="greetings" type="Cinchcast.Roque.Redis.RedisQueue, Roque.Redis">
<settings>
<setting key="host" value="localhost"/>
</settings>
</queue>
</queues>
<workers>
<!-- a worker poping jobs from "images" queue -->
<worker name="images" queue="images" autoStart="true">
<subscribers>
<!-- all events that ThumbnailCreator subscribes to will be broadcasted to this worker's queue (images) -->
<subscriber type="Acme.Images.ThumbnailCreator, Acme.Images"/>
</subscribers>
</worker>
<!-- a worker poping jobs from "greettings" queue -->
<worker name="greetings" queue="greetings" autoStart="true">
<subscribers>
<!-- all events that UserGreeter subscribes to will be broadcasted to this worker's queue (greetings) -->
<subscriber type="Acme.Messaging.UserGreeter, Acme.Messaging"/>
</subscribers>
</worker>
</workers>
</roque>
</configuration>
Now this requires som
