Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="Chinchilla Software Limited">
4 : // // Copyright Chinchilla Software Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections.Generic;
11 : using System.Linq;
12 : using Cqrs.Domain;
13 : using Cqrs.Domain.Factories;
14 : using Cqrs.Events;
15 : using Cqrs.Infrastructure;
16 :
17 : namespace Cqrs.Snapshots
18 : {
19 : /// <summary>
20 : /// Provides basic repository methods for operations with instances of <see cref="IAggregateRoot{TAuthenticationToken}"/>
21 : /// utilising <see cref="Snapshot">snapshots</see> for optimised rehydration.
22 : /// </summary>
23 : /// <typeparam name="TAuthenticationToken">The <see cref="Type"/> of authentication token.</typeparam>
24 : public class SnapshotRepository<TAuthenticationToken> : IAggregateRepository<TAuthenticationToken>
25 1 : {
26 : /// <summary>
27 : /// Gets or sets the <see cref="ISnapshotStore"/>.
28 : /// </summary>
29 : protected ISnapshotStore SnapshotStore { get; private set; }
30 :
31 : /// <summary>
32 : /// Gets or sets the <see cref="ISnapshotStrategy{TAuthenticationToken}"/>.
33 : /// </summary>
34 : protected ISnapshotStrategy<TAuthenticationToken> SnapshotStrategy { get; private set; }
35 :
36 : /// <summary>
37 : /// Gets or sets the <see cref="IAggregateRepository{TAuthenticationToken}"/>.
38 : /// </summary>
39 : protected IAggregateRepository<TAuthenticationToken> Repository { get; private set; }
40 :
41 : /// <summary>
42 : /// Gets or sets the <see cref="IEventStore{TAuthenticationToken}"/>.
43 : /// </summary>
44 : protected IEventStore<TAuthenticationToken> EventStore { get; private set; }
45 :
46 : /// <summary>
47 : /// Gets or sets the <see cref="IAggregateFactory"/>.
48 : /// </summary>
49 : protected IAggregateFactory AggregateFactory { get; private set; }
50 :
51 : /// <summary>
52 : /// Instantiates a new instance of <see cref="SnapshotRepository{TAuthenticationToken}"/>.
53 : /// </summary>
54 1 : public SnapshotRepository(ISnapshotStore snapshotStore, ISnapshotStrategy<TAuthenticationToken> snapshotStrategy, IAggregateRepository<TAuthenticationToken> repository, IEventStore<TAuthenticationToken> eventStore, IAggregateFactory aggregateFactory)
55 : {
56 : SnapshotStore = snapshotStore;
57 : SnapshotStrategy = snapshotStrategy;
58 : Repository = repository;
59 : EventStore = eventStore;
60 : AggregateFactory = aggregateFactory;
61 : }
62 :
63 : /// <summary>
64 : /// Calls <see cref="TryMakeSnapshot"/> then <see cref="IAggregateRepository{TAuthenticationToken}.Save{TAggregateRoot}"/> on <see cref="Repository"/>.
65 : /// </summary>
66 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
67 : /// <param name="aggregate">The <see cref="IAggregateRoot{TAuthenticationToken}"/> to save and persist.</param>
68 : /// <param name="expectedVersion">The version number the <see cref="IAggregateRoot{TAuthenticationToken}"/> is expected to be at.</param>
69 1 : public void Save<TAggregateRoot>(TAggregateRoot aggregate, int? expectedVersion = null)
70 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
71 : {
72 : TryMakeSnapshot(aggregate);
73 : Repository.Save(aggregate, expectedVersion);
74 : }
75 :
76 : /// <summary>
77 : /// Retrieves an <see cref="IAggregateRoot{TAuthenticationToken}"/> of type <typeparamref name="TAggregateRoot"/>,
78 : /// First using <see cref="TryRestoreAggregateFromSnapshot{TAggregateRoot}"/>, otherwise via <see cref="IAggregateRepository{TAuthenticationToken}.Get{TAggregateRoot}"/> on <see cref="Repository"/>
79 : /// Then does rehydration.
80 : /// </summary>
81 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
82 : /// <param name="aggregateId">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to retrieve.</param>
83 : /// <param name="events">
84 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
85 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
86 : /// </param>
87 1 : public TAggregateRoot Get<TAggregateRoot>(Guid aggregateId, IList<IEvent<TAuthenticationToken>> events = null)
88 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
89 : {
90 : var aggregate = AggregateFactory.Create<TAggregateRoot>();
91 : int snapshotVersion = TryRestoreAggregateFromSnapshot(aggregateId, aggregate);
92 : if (snapshotVersion == -1)
93 : {
94 : return Repository.Get<TAggregateRoot>(aggregateId);
95 : }
96 : IEnumerable<IEvent<TAuthenticationToken>> theseEvents = events ?? EventStore.Get<TAggregateRoot>(aggregateId, false, snapshotVersion).Where(desc => desc.Version > snapshotVersion);
97 : aggregate.LoadFromHistory(theseEvents);
98 :
99 : return aggregate;
100 : }
101 :
102 : /// <summary>
103 : /// Calls <see cref="ISnapshotStrategy{TAuthenticationToken}.IsSnapshotable"/> on <see cref="SnapshotStrategy"/>
104 : /// If the <typeparamref name="TAggregateRoot"/> is snapshot-able <see cref="ISnapshotStore.Get{TAggregateRoot}"/> is called on <see cref="SnapshotStore"/>.
105 : /// The Restore method is then called on
106 : /// </summary>
107 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
108 : /// <param name="id">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to restore, since the <paramref name="aggregate"/> may be completely uninitialised.</param>
109 : /// <param name="aggregate">The <typeparamref name="TAggregateRoot"/></param>
110 : /// <returns>-1 if no restoration was made, otherwise version number the <typeparamref name="TAggregateRoot"/> was rehydrated to.</returns>
111 : /// <remarks>There may be more events after the snapshot that still need to rehydrated into the <typeparamref name="TAggregateRoot"/> after restoration.</remarks>
112 1 : protected virtual int TryRestoreAggregateFromSnapshot<TAggregateRoot>(Guid id, TAggregateRoot aggregate)
113 : {
114 : int version = -1;
115 : if (SnapshotStrategy.IsSnapshotable(typeof(TAggregateRoot)))
116 : {
117 : Snapshot snapshot = SnapshotStore.Get<TAggregateRoot>(id);
118 : if (snapshot != null)
119 : {
120 : aggregate.AsDynamic().Restore(snapshot);
121 : version = snapshot.Version;
122 : }
123 : }
124 : return version;
125 : }
126 :
127 : /// <summary>
128 : /// Calls <see cref="ISnapshotStrategy{TAuthenticationToken}.ShouldMakeSnapShot"/> on <see cref="SnapshotStrategy"/>
129 : /// If the <see cref="IAggregateRoot{TAuthenticationToken}"/> is snapshot-able <see cref="SnapshotAggregateRoot{TAuthenticationToken,TSnapshot}.GetSnapshot"/> is called
130 : /// The <see cref="Snapshot.Version"/> is calculated, finally <see cref="ISnapshotStore.Save"/> is called on <see cref="SnapshotStore"/>.
131 : /// </summary>
132 : /// <param name="aggregate">The <see cref="IAggregateRoot{TAuthenticationToken}"/> to try and snapshot.</param>
133 1 : protected virtual void TryMakeSnapshot(IAggregateRoot<TAuthenticationToken> aggregate)
134 : {
135 : if (!SnapshotStrategy.ShouldMakeSnapShot(aggregate))
136 : return;
137 : dynamic snapshot = aggregate.AsDynamic().GetSnapshot().RealObject;
138 : var rsnapshot = snapshot as Snapshot;
139 : if (rsnapshot != null)
140 : {
141 : rsnapshot.Version = aggregate.Version + aggregate.GetUncommittedChanges().Count();
142 : SnapshotStore.Save(rsnapshot);
143 : }
144 : else
145 : {
146 : snapshot.Version = aggregate.Version + aggregate.GetUncommittedChanges().Count();
147 : SnapshotStore.Save(snapshot);
148 : }
149 : }
150 : }
151 : }
|