• This repository has been archived on 29/May/2024
  • Stars
    star
    233
  • Rank 172,230 (Top 4 %)
  • Language
    C#
  • License
    MIT License
  • Created over 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

❌ d60 event sourcing + CQRS framework

d60 Cirqus

Simple but powerful event sourcing + CQRS kit.

Provides a model for creating commands, aggregate roots, events, and views, tying it all up in one simple and neat CommandProcessor.

How simple?

You do this:

var processor = CommandProcessor.With()
    .(...)
    .Create();

and let the API guide you through selecting an event store, possibly configuring some more stuff as well, and then you can do this:

processor.ProcessCommand(myCommand);

and then everything will flow from there. The command processor is reentrant and is meant to be kept around for the duration of your application's lifetime. Remember to

processor.Dispose();

when the application shuts down.

More docs

Check out the official documentation wiki.

Is Cirqus for you?

It depends ;) to answer that question, it is necessary to think a little bit about your requirements. Cirqus is not in the extreme high performance camp (if there is such a camp), although it doesn't mean that Cirqus cannot perform extremely well - it's more that on the following scale:

Raw performance                  Polished APIs and general usefulness
================================================(+)==================

Cirqus is positioned around the (+) - it doesn't mean that you cannot achieve great - excellent even - performance with Cirqus, it just means that certain choices have probably been made for your with Cirqus that makes certain things easier, even though it may sacrifice a little bit of raw performance.

And on this scale:

Complex event processing        Domain-model-centric event processing
================================================(+)==================

Cirqus is again positioned around the (+) - it is mostly meant to be used in conjunction with an event-driven domain model, so if you're content with driving everything directly off of events, you will probably not benefit as much from using Cirqus.

Again, it's not that Cirqus doesn't do complex event processing - Cirqus' views can easily be brought to do that - it's just that that is not really the focus (at least not with the existing implementations).

Lastly, on this scale:

.NET-centric                                            Interoperable
=================(+)=================================================

it shows that many things are just made extremely easy for you if you're using Cirqus and .NET. You can of course easily set up something else to read events since all the existing event stores are simply using JSON to represent their contents, but from time to time a .NET type name might show up in the JSON data - which means that it's easier to use from .NET, but not impossible to use from something else (e.g. to have node.js-driven projections or whatever).

Configuration example

This is how you can set up a fully functioning command processor, including a view:

// configure one single view manager
var viewManager = new MsSqlViewManager<CounterView>("sqltestdb");

// let's create & initialize the command processor
var processor = CommandProcessor.With()
    .EventStore(e => e.UseSqlServer("sqltestdb", "Events"))
    .EventDispatcher(e => e.UseViewManagerEventDispatcher(viewManager))
    .Create();

// use the command processor, possibly from multiple threads,
// for the entire lifetime of your application....

// and then, when your application shuts down:
processor.Dispose();

Elaborate configuration example

If you're interested in seeing which moving parts are involved in the command processor, here's the equivalent configuration where all the things are wired together manually. As you can see, it's actually fairly simple (although the configuration API is much more intuitive and concise).

// this is the origin of truth - let's keep it in SQL Server!
var eventStore = new MsSqlEventStore("sqltestdb", "Events", 
                                     automaticallyCreateSchema: true);

// aggregate roots are simply built when needed by replaying events
// for the requested root
var repository = new DefaultAggregateRootRepository(eventStore);

// configure one single view manager in another table in our SQL Server
var viewManager = new MsSqlViewManager<CounterView>("sqltestdb", "CounterView", 
                                                    automaticallyCreateSchema: true);

// Cirqus will deliver emitted events to the event dispatcher when they have
//  been persisted
var eventDispatcher = new ViewManagerEventDispatcher(repository, eventStore, viewManager);

// we can create the processor now
var processor = new CommandProcessor(eventStore, repository, eventDispatcher);

// and then, when your application shuts down:
processor.Dispose();

Code example

This is an example of a command whose purpose it is to instruct the Counter aggregate root to increment itself by some specific value, as indicated by the given delta parameter:

public class IncrementCounter : Command<Counter>
{
    public IncrementCounter(string aggregateRootId, int delta)
        : base(aggregateRootId)
    {
        Delta = delta;
    }

    public int Delta { get; private set; }

    public override void Execute(Counter aggregateRoot)
    {
        aggregateRoot.Increment(Delta);
    }
}

Note how the command indicates the type and ID of the aggregate root to address, as well as an Execute method that will be invoked by the framework. Let's take a look at Counter - aggregate roots must be based on the AggregateRoot base class and must of course follow the emit/apply pattern for mutating themselves - it looks like this:

public class Counter : AggregateRoot, IEmit<CounterIncremented>
{
    int _currentValue;

    public void Increment(int delta)
    {
        Emit(new CounterIncremented(delta));
    }

    public void Apply(CounterIncremented e)
    {
        _currentValue += e.Delta;
    }

    public int CurrentValue
    {
        get { return _currentValue; }
    }

    public double GetSecretBizValue()
    {
        return CurrentValue%2 == 0
            ? Math.PI
            : CurrentValue;
    }
}

As you can see, the command's Execute method will invoke the Increment(delta) method on the root, which in turn will emit a CounterIncremented event, which simply looks like this:

public class CounterIncremented : DomainEvent<Counter>
{
    public CounterIncremented(int delta)
    {
        Delta = delta;
    }

    public int Delta { get; private set; }
}

The event is immediately applied (via the root's Apply method that comes from implementing IEmit<CounterIncremented>), and this is the place where the root is free to mutate itself - in this case, we increment the private _currentValue variable, which serves to demonstrate that aggregate roots are free to keep their privates private.

Note also how the aggregate root is capable of calculating a secret business value, which happens to alternate between the counter's value and π, depending on whether the counter's value is odd or even.

Lastly, we have set up a MsSqlViewManager that operates on a CounterView that looks like this:

public class CounterView : IViewInstance<InstancePerAggregateRootLocator>,
    ISubscribeTo<CounterIncremented>
{
    public CounterView()
    {
        SomeRecentBizValues = new List<double>();
    }

    public string Id { get; set; }

    public long LastGlobalSequenceNumber { get; set; }

    public int CurrentValue { get; set; }

    public double SecretBizValue { get; set; }

    public List<double> SomeRecentBizValues { get; set; }

    public void Handle(IViewContext context, CounterIncremented domainEvent)
    {
        CurrentValue += domainEvent.Delta;

        var id = domainEvent.GetAggregateRootId();
        var version = domainEvent.GetGlobalSequenceNumber();
        var counter = context.Load<Counter>(id, version);

        SecretBizValue = counter.GetSecretBizValue();

        SomeRecentBizValues.Add(SecretBizValue);

        // trim to 10 most recent biz values
        while(SomeRecentBizValues.Count > 10) 
            SomeRecentBizValues.RemoveAt(0);
    }
}

Views can define how processed events are mapped to view IDs via the ViewLocator implementation that closes the IViewInstance<> interface - in this case, we're using InstancePerAggregateRootLocator which means that the aggregate root ID of the processed event is simply used as the ID of the view, in turn resulting in one instance of the view per aggregate root.

In order to actually get to receive events, the view class must implement one or more ISubscribeTo<> interfaces - in this case, we subscribe to CounterIncremented which requires that we implement the Handle method.

In addition to the two required properties, Id and LastGLobalSequenceNumber, we've added a property for the current value of the counter (CurrentValue), a property for the secret business value (SecretBizValue), and a list that can contain the 10 most recent business values (SomeRecentBizValues).

Note how the IViewContext gives access to a Load method that can be used by the view to load aggregate roots if it needs to invoke domain logic to extract certain values out of the domain (like e.g. our secret business value).

Note also how an aggregate root must be loaded by specifying a global sequence number which will serve as a "roof" for applied event, thus ensuring that the loaded aggregate root has the version that corresponds to the time when the event was emitted, thus allowing for eternally consistent replay of events. It also allows for peeking back and forth in time, but that's a story for another time... ;)

More Repositories

1

Topos

🌀 .NET Event Processing library
C#
23
star
2

Kafkaesque

📒 Simple file-based event store
C#
8
star
3

DillPickle

Slim Gherkin-compliant BDD story runner for .NET
C#
6
star
4

MiniScheduler

📆 The smallest scheduler ever
C#
5
star
5

TypedFactoryExample

Simple Windows Forms application sample showing a way to implement an MVC pattern using Castle Windsor and TypedFactoryFacility
C#
5
star
6

Brutus

:neckbeard: How many SHA512 hashes can we make?
C#
2
star
7

F.T.Windsor

Experimental augmentation of Castle Windsor to take greater advantage of the container when extending it
C#
2
star
8

CorrelationIds

Two Web APIs and two Rebus endpoints that display just how contagious correlation IDs can be
C#
2
star
9

Permitski

👔 Signature thing
C#
2
star
10

RespectOrderDirectivesHandlersFilter

C#
2
star
11

NServiceBus-Contrib

Contrib project for NServiceBus
2
star
12

Semicolon

🔗 Just a small and extensible CSV parser
C#
2
star
13

Ormish

🔜 MongoDB ORM
C#
1
star
14

ConsulPlayground

1
star
15

Jug

Experimental DI container
C#
1
star
16

HybridDb.Superiolizer

😤 Great low-friction HybridDB serializer
C#
1
star
17

vippedag

🍺 Er det egentlig vippedag?
HTML
1
star
18

Ponder

C#
1
star
19

Blabber

MongoDB demo project
C#
1
star
20

AnugComp

Parses a Meetup.com event attendee list
C#
1
star
21

PriorityQ

JavaScript
1
star
22

Ignite

Visual Studio code repository skeleton creator
C#
1
star
23

OrderedArrayResolver

Example ISubDependencyResolver for Windsor that can order injected arrays. Note that this functionality can be made much sweeter when Windsor 3.0 arrives with its IHandlerFilter
C#
1
star