This framework is an abstraction layer for eventbusses. As of now the only implementation is for RabbitMq. There are plans to support more eventbusses in the future.
The core library contains multiple classes to communicate with an eventbus implementation of choice.
Before Remiworks can be used, it needs to be added to your project. This is done via a dependency injection mechanism. This can be both a simple console project or a more sophisticated MVC project (which also is a console application).
Add a reference to Microsoft.Extensions.DependencyInjection
.
Important: In case of a controller which only listens to commands or events, it is important to still call serviceProvider.GetService<...>();
. By doing this, you let the dependency injection mechanism create an instance of the desired class. This in turn, instantiates any listeners which enables the class to listen to incoming events/commands.
var serviceProvider = new ServiceCollection()
.AddTransient<SomeRandomClass>() // Add your dependency stuff here
.AddRabbitMq(new BusOptions()) // Use a RabbitMq implementation (Remiworks.RabbitMQ)
.BuildServiceProvider();
// Option 1:
var randomClass = serviceProvider.GetService<SomeRandomClass>();
randomClass.DoSomething();
// This class will have the required dependencies injection together with required Remiworks classes
// Option 2:
serviceProvider.UseAttributes(); // Remiworks.Attributes
Adding Remiworks to an MVC project is similar to adding it to a console project.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<SomeRandomClass>()
services.AddRabbitMq(new BusOptions());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// Option 1:
serviceProvider.GetService<SomeRandomClass>(); // Let the class be instantiated
// Option 2:
app.ApplicationServices.UseAttributes();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=RDW}/{action=Index}");
});
}
}
Remiworks also allows for a logger to be added. This is done as an extra parameter in the AddRabbitMQ
method. The following example illustrates how Serilog
can be added to the mix:
var logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
var loggerFactory = new LoggerFactory()
.AddSerilog(logger);
services.AddRabbitMq(new BusOptions(), loggerFactory);
Events are the core mechanism of web scale architecture. Remiworks provides methods for both sending and receiving events.
First, add Remiworks.Core.Event.Publisher.IEventPublisher
to the constructor of the desired class. Secondly, define a method which sends an event. This event is just a simple POCO.
public class SomeRandomClass
{
private readonly IEventPublisher _eventPublisher;
public SomeRandomClass(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
public async Task SendSomeEventMessage(SomeEvent someEvent)
{
await _eventPublisher.SendEventAsync(
message: someEvent,
routingKey: "some.event.sent");
}
}
Receiving events is very similar to sending them. Again, create a constructor. Add Remiworks.Core.Event.Listener.IEventListener
to the constructor this time. Also define a method which will be called when the some.event.sent
message comes in.
public class SomeRandomClass
{
public SomeRandomClass(IEventListener eventListener)
{
eventListener
.SetupQueueListenerAsync<SomeEvent>(
queueName: "someEventQueue",
topic: "some.event.sent",
callback: SomeEventReceived)
.Wait();
}
private void SomeEventReceived(SomeEvent receivedEvent)
{
// Do something to handle the event
}
}
Events can also be received by using the Remiworks.Attributes
library. This allowes you to use attributes for event receiving. This can be done as follows:
First, add the attributes library to the dependency injection mechanism
var serviceProvider = new ServiceCollection()
.AddTransient<SomeRandomClass>() // Add your dependency stuff here
.AddRabbitMq(new BusOptions()) // Use a RabbitMq implementation (Remiworks.RabbitMQ)
.BuildServiceProvider();
serviceProvider.UseAttributes();
Secondly, add the correct attributes to the desired class
[QueueListener("someEventQueue")]
public class SomeRandomClass
{
[Event("some.event.sent")]
private void SomeEventReceived(SomeEvent receivedEvent)
{
// Do something to handle the event
}
}
Sometimes, events are not enough. There are situations where a callback is desired. Commands are a good solution to this problem.
Sending commands is very similar to sending events.
First, let Remiworks.Core.Command.Publisher.ICommandPublisher
be injected in the constructor of the desired class.
public class SomeRandomClass
{
private readonly ICommandPublisher _commandPublisher;
public SomeRandomClass(ICommandPublisher commandPublisher)
{
_commandPublisher = commandPublisher;
}
}
Secondly, define a method which publishes a command. This command waits for a response from the server
public async Task SendSomeCommand(SomeCommand command)
{
return _commandPublisher.SendCommandAsync(
message: command,
queueName: "someCommandQueue",
key: "some.command.sent");
}
Alternatively, a command can expect a result from the server.
public async Task<int> CalculateSomethingOnServer(SomeCommand command)
{
return await _commandPublisher.SendCommand<int>(
message: command,
queueName: "someCommandQueue",
key: "some.calculation.sent");
}
First, add a constructor to the desired class which injects Remiworks.Core.Command.Listener.ICommandListener
Secondly, add callbacks for the command without and with a return value. Please note: Right now, using Task
or void
as the return type is not supported. Returning null results in the command to be handled correctly on the sending end when used as void. This will be fixed in an future patch.
public class SomeRandomClass
{
public SomeRandomClass(ICommandListener commandListener)
{
commandListener.SetupCommandListenerAsync<SomeCommand>(
queueName: "someCommandQueue",
key: "some.command.sent",
callback: HandleVoidCommand);
commandListener.SetupCommandListenerAsync<SomeCommand>(
queueName: "someCommandQueue",
key: "some.calculation.sent",
callback: HandleCalculateCommand);
}
private Task<object> HandleVoidCommand(SomeCommand command)
{
// Do something with the command
return null;
}
private Task<object> HandleCalculateCommand(SomeCommand command)
{
// Imagine command has a Value property
return Task.FromResult<object>(command.Value * 10);
}
}
Commands can also be received by using the Remiworks.Attributes
library. This allowes you to use attributes for command receiving. This can be done as follows:
First, add the attributes library to the dependency injection mechanism
var serviceProvider = new ServiceCollection()
.AddTransient<SomeRandomClass>() // Add your dependency stuff here
.AddRabbitMq(new BusOptions()) // Use an RabbitMq implementation (Remiworks.RabbitMQ)
.BuildServiceProvider();
serviceProvider.UseAttributes();
Secondly, add attributes to the desired class
[QueueListener("someCommandQueue")]
public class SomeRandomClass
{
[Command("some.command.sent")]
private void SomeEventReceived(SomeEvent receivedEvent)
{
// Do something to handle the command
}
[Command("some.calculation.sent")]
public int HandleCalculateCommand(SomeCommand command)
{
// Imagine that SomeCommand has a Value property
return 10 * command.Value
}
}
There are some example projects set up. These can be found in the Examples
folder in this repository. Here are example projects defined for:
EventPublishExample
EventListenExample
RpcTest
RpcServerTest