public class Foo_Handler : IConsumer
{
public async Task Consume(ConsumeContext context)
{
var message = context.Message;
// do whatever I like with and instance of Foo
}
}
This looks great, the handler can access the actual message in a strongly-typed fashion but ... ... all these handlers have to be either written down in an explicit way for each data type or I need a clever dynamic code generator to handle multiple data types. What's worse, my "bus" doesn't even sometimes know all the data types that are used, or rather - I don't want to recompile the data bus each time a new type appears in the family.
// A generic envelope type
public class MessageEnvelope
{
public string MessageTypeName { get; set; }
// the actual payload serialized to JSON
public string Content { get; set; }
public MessageEnvelope()
{
}
public MessageEnvelope(object payload)
{
if (payload == null) throw new ArgumentNullException();
this.Content = JsonConvert.SerializeObject(payload);
this.MessageTypeName = payload.GetType().Name;
}
public T GetPayload()
{
if (string.IsNullOrEmpty(this.Content)) throw new ArgumentException();
return JsonConvert.DeserializeObject(this.Content);
}
}
Suddenly, it's much easier to maintain the growing list of possible data types. I could handle types that are not known at the time the core library is compiled - the envelope
will happily handle a payload of any type.
public class Foo
{
public string Data { get; set; }
}
public class Bar
{
public string Something { get; set; }
}
public static class EnvelopedExtensions
{
public static string ToJson( this object payload )
{
if (payload == null) throw new ArgumentNullException();
return JsonConvert.SerializeObject(payload);
}
}
public class MessageEnvelope_Handler : IHandleMessages
{
private IBus bus { get; set; }
private string agent { get; set; }
public MessageEnvelope_Handler(IBus bus, string agent)
{
this.bus = bus;
this.agent = agent;
}
public async Task Handle(MessageEnvelope message)
{
var context = MessageContext.Current;
await Console.Out.WriteLineAsync($"{message.MessageTypeName} received by {this.agent}\r\n{message.ToJson()}");
}
}
static IBus ConfigureActivator(
BuiltinHandlerActivator activator,
string queueName,
int workers = 1,
int parallel = 1)
{
var bus = Configure.With(activator)
.Transport(t => t.UseRabbitMq("amqp://username:password@localhost", queueName))
.Logging(l => l.ColoredConsole(LogLevel.Warn))
.Options(conf =>
{
conf.SetNumberOfWorkers(workers);
conf.SetMaxParallelism(parallel);
})
.Start();
return bus;
}
static async Task Work()
{
using (var publisher = new BuiltinHandlerActivator())
using (var subscriber1 = new BuiltinHandlerActivator())
using (var subscriber2 = new BuiltinHandlerActivator())
{
var publisherBus = ConfigureActivator(publisher, "publisher");
var subscriber1Bus = ConfigureActivator(subscriber1, "subscriber1", 2, 2);
var subscriber2Bus = ConfigureActivator(subscriber2, "subscriber2", 2, 2);
subscriber1.Register(() => new MessageEnvelope_Handler(subscriber1Bus, "agent1"));
subscriber2.Register(() => new MessageEnvelope_Handler(subscriber2Bus, "agent2"));
await subscriber1Bus.Advanced.Topics.Subscribe("Foo");
await subscriber2Bus.Advanced.Topics.Subscribe("Foo");
await subscriber2Bus.Advanced.Topics.Subscribe("Bar");
Console.WriteLine("publishing");
await publisherBus.Advanced.Topics.Publish("Foo", new MessageEnvelope(CreateFoo()));
await publisherBus.Advanced.Topics.Publish("Bar", new MessageEnvelope(CreateBar()));
Console.WriteLine("published");
Console.WriteLine("Press enter to quit");
Console.ReadLine();
}
}
publishing
published
Press enter to quit
Bar received by agent2
{"MessageTypeName":"Bar","Content":"{\"Something\":\"and this is bar\"}"}
Foo received by agent2
{"MessageTypeName":"Foo","Content":"{\"Data\":\"hello foo world\"}"}
Foo received by agent1
{"MessageTypeName":"Foo","Content":"{\"Data\":\"hello foo world\"}"}



