EventStore Projections Best Practices

I’ve been looking at Event Store for a little while now and one thing that stumped me is when should one consider writing a projection? Is it common practice to create a projection to create a flattened view of events? For example, lets say I have the following two events (you can imagine the model a little more complex than this however for brevity I have slimmed this down for simplicity):

public class OrderPlaced

{

public OrderPlaced(string reference, Currency currency, Amount amount)

{

}

public string Reference { get; }

public Currency Currency { get; } //Custom type

public Amount Amount { get; } //Custom type

}

public class OrderCompleted

{

public OrderCompleted(string reference)

{

this.Reference = reference;

}

public string Reference { get; }

}

public class Currency

{

public Currency(string value)

{

this.Value = value;

}

public string Value { get; }

}

public class Amount

{

public Amount(decimal value)

{

this.Value = value;

}

public decimal Value { get; }

}

``

The approach I am using is an "aggregate per stream" and currently I have a projection that aggregates each stream into a single stream using the following:

fromCategory(‘MyCategory’)
.whenAny(function(s, e) {
linkTo(“Foo”, e);
})

``

The above doesn’t do a great deal and this got me thinking. Am I using projections incorrectly, is it better I created a more flattened / de-normalized view of the aggregate instead? For example, I am aware of the emit function and perhaps this is an ideal candidate to make use of it whereby I can convert the above model(s) and project something like this:

{
string Reference;
string CurrencyCode;
decimal TotalPayingAmount;
}

``

Cheers.

AFAIK, projections inside eventstore are mainly for stream re-partitioning, e.g. I want a stream per correlation id for process managers, or to use the canonical example, chat messages by user.

Theres nothing wrong with how you use projections - I use projections just like that to project all events of a certain type into a single stream for consumers to read as a group.

Sounds like you are thinking of a different problem though and I dont really understand your meaning.

You have all three of those events written to the same stream and you aggregate all the orders into a single stream? No problem with that.

Should you setup a projection to aggregate your order streams into a flat “order” object with the projection doing all the work via the state object? IMHO I think it would be better for your consumers to do that work. You could have ES do the work but then you have to deal with versioned projections in-case something in your model changes, among other things. I would say state-based projections are better for a one-time query where you want a general report like “orders over a period”

That being said there are people who write all their consumer event processing into ES projections and it works perfectly fine, since all your consumers would need to do is read from that single stream to get exactly the format of data they want.

This a very common use of projections. That said there is a built in
projection that does exactly what you are doing here by_category. if
your streams are named for instance account-00001 there will be a
stream $ce-account which has the events just like what you are doing.

Another very common use of projections is one off queries. In your
example we could imagine I want to query in order to find how many
orders there are that were for more than $1000 and took more than 3
days to complete.

fromCategory("orders").
    foreachStream().
    when({
          $init : function() {return {ordered : 0, amount : 0}},
          OrderPlaced : function(s,e) {s.amount = e.amount; s.ordered
= e.ordered},
          OrderCompleted : function(s,e) {return {amount : s.amount,
days : e.completed - s.ordered}
    }).
   filteredBy(function(s){if(s.amount > 1000 && s.days > 3) return s;
                        return null;}

Note I used ints for dates to avoid dealing with dates in js :slight_smile:

You could then run this and get the results just as a one off query.
What they allow if the ability to do complex correlations between
events.