sgmunn

Mostly MonoTouch with a chance of Other Stuff.

CQRS for MonoTouch

A while ago I started a project somewhat similar to MonoTouch.Dialog which I called MonoKit. A couple of twists and turns and I’ve ended up with a library that has some parts of what MonoTouch.Dialog does, a little bit of (INotifyPropertyChanged) binding support and a CQRS framework. I’m going to introduce the CQRS framework first as that’s the most complete bit.

Having adopted CQRS and event sourcing for another project, I found that when I was working on a sample application for MonoKit I thought that it would be great to use something similar. It did occur that event sourcing might not be the best fit for a phone app (depending on how many events per aggregate or how efficient I can be loading events), but the core principle of separating the commands that perform actions from the queries to read data from the data store would be really good to have.

I had a couple of goals with this framework - allow event sourcing if you want to have it but not to make it be a requirement; allow some level of customisation (how events are stored, what db or storage you want to use etc); be easy to use so but do all the boring stuff I don’t want to write again and have the potential to be portable across platforms because I think I want to use it with MonoMac at some point.

For this sample, I’m going to model people I give pocket money to, I need to record their name, weekly allowance and how much pocket money they have in the bank. I won’t be using event sourcing which, with MonoKit, will require me to support ISnapshot in the data contract that will be used to persist state for the aggregate.

Aggregate State

MinionDataContract defines the state that will be persisted about our Minion aggregate. We are just going to record their name, weekly allowance, and current balance. The Id and Version properties are for ISnapshot support. The PrimaryKey attribute is for SQlite.Net.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MinionDataContract : ISnapshot
{
  [PrimaryKey]
  public Guid Id { get; set; }

  public int Version { get; set; }

  public string Name { get; set; }

  public decimal WeeklyAllowance { get; set; }

  public decimal Balance { get; set; }
}

public class Minion : AggregateRoot<MinionDataContract>
{
  ...
}

Creating the Aggregate

The design of MonoKit is to not necessarily have any given command tied specifically to any aggregate type but to allow any command to be potentially executed by any aggregate. This may or may not be the most correct, but I think it is a reasonable way to allow us to reduce overhead a little when writing simple crud style aggregates. MonoKit handles one command automatically, the CreateCommand. I still have to figure out a way to pass some initial state in a meaningful way, but basically the first command to send an aggregate is Create.

1
2
3
4
var id = new Guid.NewGuid();

var exec = context.NewCommandExecutor<Minion>();
exec.Execute(new CreateCommand() { AggregateId = id });

Updating the Aggregate

We can now define some other commands for the Minion aggregate. With the Minion aggregate there are some commands that are immediately apparent; ChangeName and ChangeWeeklyAllowance;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ChangeNameCommand : CommandBase
{
  public string Name { get; set; }
}

public class NameChangedEvent : EventBase
{
  public string Name { get; set; }
}

...

public void Execute(ChangeNameCommand command)
{
  this.NewEvent(new NameChangedEvent { Name = command.Name });
}

public void Apply(NameChangedEvent @event)
{
  this.InternalState.Name = @event.Name;
}

** it might be worth noting that we could just update internal state in the command executor and get rid of the event altogether. We might save events for only those commands that require us to publish notifications.

1
2
var exec = context.NewCommandExecutor<Minion>();
exec.Execute(new ChangeNameCommand { AggregateId = minionId, Name = "Ralf" });

Using a Read Model Builder

Now suppose we want to record that we gave pocket money to our Minions. We’ll need another command, in this case EarnPockeyMoneyCommand. We want a record of when the pocket money was earnt and how much.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EarnPocketMoneyCommand : CommandBase
{
  public DateTime Date { get; set; }

  public decimal Amount { get; set; }
}

public class PocketMoneyEarntEvent : EventBase
{
  public DateTime Date { get; set; }

  public decimal Amount { get; set; }
}

...

public void Apply(PocketMoneyEarntEvent @event)
{
  this.InternalState.Balance += @event.Amount;
}

When this command is executed, the event PocketMoneyEarntEvent will be published to an event bus. Any read model builders that we have registered will be instantiated and sent any events that were raised.

In this case we can add to a collection of PocketMoneyTransactionDataContract

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PocketMoneyTransactionDataContract
{
  [PrimaryKey]
  public Guid Id { get; set; }

  [Indexed]
  public Guid MinionId { get; set; }

  public DateTime Date { get; set; }

  public decimal Amount { get; set; }
}

public class TransactionReadModelBuilder : ReadModelBuilder
{
  private IRepository<PocketMoneyTransactionDataContract> repository;

  public TransactionReadModelBuilder(IRepository<PocketMoneyTransactionDataContract> repository)
  {
    this.repository = repository;
  }

  public void Handle(PocketMoneyEarntEvent @event)
  {
    var transaction = this.repository.New();
    transaction.MinionId = @event.AggregateId;
    transaction.Amount = @event.Amount;
    transaction.Date = @event.Date;

    this.repository.Save(transaction);
  }
}

Our UI can read the two data contracts that we defined (MinionDataContract and PocketMoneyTransactionDataContract) and display the information. We don’t update these data contracts without executing commands against the aggregate.

Closing Thoughts

This is just a very quick intro to how you might be able to use CQRS with MonoTouch. There is a bit more to be done to finish it (snapshot support combined with event sourcing) and I’d like to implement a way to fairly easily support synchronising events to a server and back down to another device - I have a couple of ideas on that.

I hope you find this useful and I’d welcome any suggestions and comments you might have.

The source is here on github.

Cheers.

Comments