Szymon Kulec @Scooletz http://blog.scooletz.com
Tomasz Frydrychewicz @frydrychewicz
is an an architectural pattern for building loosely coupled systems and services in a reactive way
and that's the way we've done it.
JavaScript
AngularJS
NodeJS
Bower
Gulp
Karma
ASP.NET MVC :-)
...
From callback-driven asynchrony
To events-driven asynchrony
The Dispatcher
Controller
View
Router
Action
Store
...
Actions | Events | |
Reactions | Events | |
Queries | NOT | Events |
angular.module("app.someModule")
.factory('someModuleActions', someModuleActions);
function someModuleActions(bcDispatcher) {
var service = {};
service.action1 = action1;
service.action2 = action2;
return service;
///////////////
function action1(a, b) {
bcDispatcher.dispatch('someModule:action1', {
param1: a,
param2: b
});
}
function action2() {
bcDispatcher.dispatch('someModule:action2');
}
}
angular.module("app.someModule")
.factory("$$someModuleService", $$someModuleService)
.factory("someModuleStore", someModuleStore)
.run(registersomeModuleStore);
function someModuleStore($$someModuleService) {
var service = {
dispatcherToken: null
};
service.getSomeData = $$someModuleService.getSomeData;
return service;
}
function $$someModuleService(bcDispatcher) {
var service = {
someData: []
};
service.getSomeData = getSomeData;
service.setSomeData = setSomeData;
return service;
////////////////
function getSomeData() {
return service.someData;
}
function setSomeData(a, b) {
someData.push(a + b);
bcDispatcher.dispatch('someModule:someDataSet', { value: a + b });
}
}
function registersomeModuleStore(
bcDispatcher,
someModuleStore,
$$someModuleService) {
someModuleStore.dispatcherToken =
bcDispatcher.register(function(action, attrs) {
switch(action) {
case "someModule:action1":
$$someModuleService.setSomeData(attrs.a, attrs.b);
break;
}
});
}
angular.module("app.someModule")
.factory("someController", SomeController);
function SomeController(someModuleActions, someModuleStore, bcDispatcher, $scope) {
var vm = this;
vm.action1 = someModuleActions.action1;
vm.getSomeData = someModuleStore.getSomeData;
var dispatchToken = bcDispatcher.register(function (action, attrs) {
switch (action) {
case "someModule:someDataSet":
alert("New value: " + attrs.value);
break;
}
});
$scope.$on('$destroy', function () {
bcDispatcher.unregister(dispatchToken);
});
}
Version | Event | The state |
---|---|---|
1 | RegistrationCompleted | {"CandidateName":"Tomasz", "OtherImportantValue":"Value"} |
2 | Paid | {"Paid":"true", CandidateName":"Tomasz", "OtherImportantValue":"Value"} |
What if we add a sequence to all the events in the system... we could use it as a append only log.
Sequence | Aggregate id | Version | Event | The state |
---|---|---|---|---|
1 | A1 | 1 | RegCompleted | {"CandidateName":"Tomasz", ...} |
2 | A1 | 2 | Paid | {"Paid":"true", CandidateName":"Tomasz", ...} |
3 | A2 | 1 | RegCompleted | {"CandidateName":"Szymon", ...} |
Given
-- no events--
When
On ( Register {
"Address": "http://test.yourwork.com/",
"Name": "test" } )
Then
ModuleRegistered {
"Address": "test.yourwork.com",
"Name": "test" }
public class MarkAsPaid :
IProcessEvent<PaymentGateway.PaymentReceived>
{
public void On(PaymentReceived e, DispatchEnvelope env)
{
// get registration aggregate
// registration.MarkAsPaid();
}
}
namespace MyNamespace.Hubs
{
using Microsoft.AspNet.SignalR;
public class SomeHub : Hub
{
public void Login(string userName)
{
Groups.Add(Context.ConnectionId, userName);
}
public void SendTo(string userName, string @event, object attr)
{
Clients.Group(userName).Raise(@event, attr);
}
public void SendToAll(string @event, object attr)
{
Clients.All.Raise(@event, attr);
}
}
};
namespace MyNamespace.SomeModule
{
using EventSourcing;
using Events = MyNamespaced.SomeModule.Events;
public class SignalREventsRouter : SignalREventsRouterBase,
IWantEventsWithMetadata,
IProcessEvent<Events.SomethingHappened>,
IProcessEvent<Events.SomethingElseHappened>,
{
public SignalREventsRouter(ISignalRMessenger messenger)
: base(messenger)
{
Messenger.UseHubFrom(SystemName.B2B);
}
public void On(Events.SomethingHappened e, DispatchEnvelope env)
{
Messenger.SendToAll(
"someModule:somethingHappened",
new { env.LogInfo.AggregateId });
}
public void On(Events.SomethingElseHappened e, DispatchEnvelope env)
{
Messenger.Send(
env.GetMetadata(this).Audit.UserName,
"someModule:somethingElseHappened",
new { env.LogInfo.AggregateId });
}
}
}
angular.module("app.core")
.config(configure);
function configure(fluxSignalRRegistryProvider) {
// SignalR Hubs configuration
fluxSignalRRegistryProvider.register('someHub');
}
Szymon Kulec @Scooletz http://blog.scooletz.com
Tomasz Frydrychewicz @frydrychewicz