Git Product home page Git Product logo

cirqus's Introduction

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 = new CommandProcessor(...);

(of course satisfying the processor's few dependencies), and then you can do this:

processor.ProcessCommand(myCommand);

and then everything will flow from there.

More docs

Check out the official documentation wiki.

Full example

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

// 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", "counters", 
                                                    automaticallyCreateSchema: true);

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

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

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(Guid 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... ;)

cirqus's People

Contributors

mookid8000 avatar

Watchers

Frits Duus Jørgensen avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.