-
Notifications
You must be signed in to change notification settings - Fork 20
HelloWorldProjection
This page helps you write your first Hello World
projection, explaining the process step by step.
Before you can write projections, you need to define the messages you are going to project. How you define them depends on a lot of things: preference, serialization framework in use, to mutate or to be immutable, etc ... Regardless, for simplicity, we'll define our first message as follows.
public class PeopleWhoSaidHello
{
public readonly string[] People;
public PeopleWhoSaidHello(string[] people)
{
if (people == null)
throw new ArgumentNullException("people");
People = people;
}
}
This message captures a list of people, their names, that said hello. Pretty useless in and by itself, but useful enough for a Hello World
.
How we define our projection depends on the data store we target. For simplicity sake, we're going to use the Console
as our data store and print a Hello World
line for each person who said hello.
public class HelloWorldProjection : ConnectedProjection<TextWriter>
{
public HelloWorldProjection()
{
When<PeopleWhoSaidHello>((console, message) =>
{
foreach(var person in message.People)
console.WriteLine("Hello World from {0}", person);
}
}
}
Here, we're declaring that, whenever we project a PeopleWhoSaidHello
message and we pass it a TextWriter
and the message instance, we'll write Hello World from ...
for each person in that message. We're using Projac.Connector
, which contains the ConnectedProjection<TConnection>
abstract class and the boilerplate When<TMessage>
method. In essence, this is equivalent to registering a callback method. We call these type of methods projection handlers
.
Now that we've declared the projection, we can turn our attention on how to execute it. Assuming we're defining this code in a Console Application
, we can write the following code in our Program.cs.
using System;
using System.Threading.Tasks;
using Projac.Connector;
namespace HelloWorldFromProjac
{
public class Program
{
public static void Main()
{
MainAsync().Wait();
}
public static async Task MainAsync()
{
var projection = new HelloWorldProjection();
var projector = new ConnectedProjector<TextWriter>(
Resolve.WhenEqualToHandlerMessageType(projection.Handlers));
var message = new PeopleWhoSaidHello(new [] { "John", "Jane" } );
return projector.ProjectAsync(Console.Out, message);
}
}
}
The result of running this program gives us the following output:
Hello World from John
Hello World from Jane
Because Projac.Connector
is async only
, the Main
method invokes a MainAsync
method and waits for it to return. Inside MainAsync
we define a new instance of our projection and wire it up to a projector
, which will take care of dispatching messages to the appropriate projection handlers
(callback methods). The projector
doesn't actually take a dependency on the projection
. Instead it uses a resolver
which plays a role similar to an IoC container, namely resolving projection handlers
. Since we only have one projection
, the resolver
only returns projection handlers
registered with our projection
. In this case, once the ProjectAsync
method is called the projector
will ask the resolver
for the matching projection handler(s)
and invoke them using the passed in Console
and message
. Here matching
means that the type of the message must be an exact match with the projection handler's message type - this is achieved by using the WhenEqualToHandlerMessageType
built-in resolver. Also note that Console.Out
in the .NET BCL is a TextWriter
.
Authoring projections is a fairly easy exercise. Projac tries to keep the boilerplate out and makes you concentrate on the parts that matter, which is figuring out what events to subscribe to and how to transform them into something useful.