Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
jacqueskang committed Feb 2, 2020
1 parent edcf33b commit 59e6d78
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 3 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,13 @@ giftCard.Debit(50); // ==> balance: 10
giftCard.Debit(20); // ==> invalid operation exception
```

## How to programmatically initialize event store?

See [this page](doc/StoreInitialization.md).

## How to use snapshots to optimize performance?

See [this page](doc/Snapshots.md).

---
__Please feel free to download, fork and/or provide any feedback!__
94 changes: 94 additions & 0 deletions doc/Snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# How to use snapshots to optimize performance?

Performance can become an issue when an aggregate has a large amout of historical events, because all these events must be loaded and compiled to calculate the current state of the aggregate. Snapshot can help resolving this issue.

The idea is simple: You take a snapshot of the aggregate when necessary and save it in another store (snapshot store). Then to rehydrate the aggregate again you can save IO and computation by only retrieving the snapshot and events that occurred after that snapshot.

To enable this feature you need to implement the following:

1. Add the following constructor to the domain aggregate:
```csharp
public GiftCard(
Guid id,
IAggregateSnapshot<Guid> snapshot,
IEnumerable<IAggregateEvent<Guid>> savedEvents)
: base(id, snapshot, savedEvents)
{ }
```

1. Create a class that represents the snapshot
```csharp
public class GiftCardSnapshot : AggregateSnapshot<Guid>
{
public GiftCardSnapshot(Guid aggregateId, int aggregateVersion,
decimal balance)
: base(aggregateId, aggregateVersion)
{
Balance = balance;
}

public decimal Balance { get; }
}
```

1. Override the `CreateSnapshot()` method in aggregate
```csharp
protected override IAggregateSnapshot<Guid> CreateSnapshot()
{
return new GiftCardSnapshot(Id, Version, Balance);
}
```

1. Override the `ApplySnapshot()` method
```csharp
protected override void ApplySnapshot(IAggregateSnapshot<Guid> snapshot)
{
GiftCardSnapshot giftCardSnapshot = snapshot as GiftCardSnapshot
?? throw new InvalidOperationException();

Balance = giftCardSnapshot.Balance;
}
```

Note:
- It is your resposibility to ensure that your snapshot class contains all data needed to restore that aggregate to the corresponding version.
- To use built-in stores, make sure that your snapshot class can be serialized/deserialized by Json.NET

1. Register snapshot store in `IEventSourcingBuilder`, in a similar way as registering the event stores, e.g.,

```csharp
services.AddEventSourcing(builder =>
builder
.UseTextFileEventStore<GiftCard, Guid>(x => x.Folder = "C:/Temp/GiftcardEvents")
.UseTextFileSnapshotStore<GiftCard, Guid>(x => x.Folder = "C:/Temp/GiftcardEvents"));
```

Note: You can store snapshot in the same or in a different storage with events. For example: store events in DynamoDB and store snapshots in text files:

```csharp
services.AddEventSourcing(builder =>
builder
.UseDynamoDBEventStore<GiftCard, Guid>(x => x.TableName = "GiftcardEvents")
.UseTextFileSnapshotStore<GiftCard, Guid>(x => x.Folder = "C:/Temp/GiftcardEvents"));
```

1. Optionally you can programatically initialize snapshot store in a similar way with event store ([see here](StoreInitialization.md)):
```csharp
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
IEventStoreInitializer<GiftCard, Guid> eventStoreInitializer,
ISnapshotStoreInitializer<GiftCard, Guid> snapshotStoreInitializer)
{
eventStoreInitializer.EnsureCreatedAsync().Wait();
snapshotStoreInitializer.EnsureCreatedAsync().Wait();

// configure http request pipeline
}
```

1. It's totally up to you to decide when to take an snapshot by calling `aggregate.TakeSnapshot()`. The snapshot will be saved when invoked `AggregateRepository<TAggregate, TKey>.SaveAggregateAsync()`

1. By default when calling `AggregateRepository<TAggregate, TKey>.FindAggregateAsync()` it automatically retrieve the last snapshot. You can force not loading any snapshot by calling:
```csharp
FindAggregateAsync(aggregateId, useSnapshot: false)
```
16 changes: 16 additions & 0 deletions doc/StoreInitialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# How to programmatically initialize event store?

Sometimes it's handy to programmatically initialize event store. Do do this you can resolve `IEventStoreInitializer<TAggregate, TKey>` from DI service provider and call `EnsureCreatedAsync()`.

For example to initialize event store when ASP.NET Core application is started:

```csharp
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
IEventStoreInitializer<GiftCard, Guid> eventStoreInitializer)
{
eventStoreInitializer.EnsureCreatedAsync().Wait();

// configure http request pipelines
}
```
7 changes: 4 additions & 3 deletions src/JKang.EventSourcing.TestingWebApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,16 @@ public void ConfigureServicesForDynamoDB(IServiceCollection services)
public void ConfigureServicesForEfCore(IServiceCollection services)
{
services
.AddDbContext<SampleDbContext>(x => x.UseInMemoryDatabase("eventstore"))
.AddDbContext<SampleDbContext>(x => x.UseInMemoryDatabase("eventstore"));

services
.AddEventSourcing(builder =>
{
builder
.UseEfCoreEventStore<SampleDbContext, GiftCard, Guid>()
.UseEfCoreSnapshotStore<SampleDbContext, GiftCard, Guid>()
;
})
;
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand Down

0 comments on commit 59e6d78

Please sign in to comment.