Now that we know how snapshotting works and what a projection is, it is quite easy to put things together:
A SnapshotProjection is a Projection (read EventHandler) that can be stored into/created from a Snapshot. Lets go back to the example we had before:
/**
* maintains a map of UserId->UserName
**/
public class UserNames implements SnapshotProjection {
private final Map<UUID, String> existingNames = new HashMap<>();
@Handler
void apply(UserCreated created) {
existingNames.put(created.aggregateId(), created.userName());
}
@Handler
void apply(UserDeleted deleted) {
existingNames.remove(deleted.aggregateId());
}
// ...
This projection is interested in UserCreated
and UserDeleted
EventObjects and can be serialized by the SnapshotSerializer
.
If you have worked with FactCast before, you'll know what needs to be done (if you haven't, just skip this section and be happy not to be bothered by this anymore):
UserCreated
and UserDeleted
void onNext(Fact fact)
method, that
… and this is just the “happy-path”.
With Factus however, all you need to do is to use the following method:
/**
* If there is a matching snapshot already, it is deserialized and the
* matching events, which are not yet applied, will be as well. Afterwards, a new
* snapshot is created and stored.
* <p>
* If there is no existing snapshot yet, or they are not matching (see
* serialVersionUID), an initial one will be created.
*
* @return an instance of the projectionClass in at least initial state, and
* (if there are any) with all currently published facts applied.
*/
@NonNull
<P extends SnapshotProjection> P fetch(@NonNull Class<P> projectionClass);
like
UserNames currentUserNames = factus.fetch(UserNames.class);
Easy, uh? As the instance is created from either a Snapshot or the class, the instance is private to the caller here. This is the reason why there is no ConcurrentHashMap or any other kind of synchronization necessary within UserNames
.