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>
25 : : ISnapshotAggregateRepository<TAuthenticationToken>
26 1 : {
27 : /// <summary>
28 : /// Gets or sets the <see cref="ISnapshotStore"/>.
29 : /// </summary>
30 : protected ISnapshotStore SnapshotStore { get; private set; }
31 :
32 : /// <summary>
33 : /// Gets or sets the <see cref="ISnapshotStrategy{TAuthenticationToken}"/>.
34 : /// </summary>
35 : protected ISnapshotStrategy<TAuthenticationToken> SnapshotStrategy { get; private set; }
36 :
37 : /// <summary>
38 : /// Gets or sets the <see cref="IAggregateRepository{TAuthenticationToken}"/>.
39 : /// </summary>
40 : protected IAggregateRepository<TAuthenticationToken> Repository { get; private set; }
41 :
42 : /// <summary>
43 : /// Gets or sets the <see cref="IEventStore{TAuthenticationToken}"/>.
44 : /// </summary>
45 : protected IEventStore<TAuthenticationToken> EventStore { get; private set; }
46 :
47 : /// <summary>
48 : /// Gets or sets the <see cref="IAggregateFactory"/>.
49 : /// </summary>
50 : protected IAggregateFactory AggregateFactory { get; private set; }
51 :
52 : /// <summary>
53 : /// Instantiates a new instance of <see cref="SnapshotRepository{TAuthenticationToken}"/>.
54 : /// </summary>
55 1 : public SnapshotRepository(ISnapshotStore snapshotStore, ISnapshotStrategy<TAuthenticationToken> snapshotStrategy, IAggregateRepository<TAuthenticationToken> repository, IEventStore<TAuthenticationToken> eventStore, IAggregateFactory aggregateFactory)
56 : {
57 : SnapshotStore = snapshotStore;
58 : SnapshotStrategy = snapshotStrategy;
59 : Repository = repository;
60 : EventStore = eventStore;
61 : AggregateFactory = aggregateFactory;
62 : }
63 :
64 : /// <summary>
65 : /// Calls <see cref="TryMakeSnapshot"/> then <see cref="IAggregateRepository{TAuthenticationToken}.Save{TAggregateRoot}"/> on <see cref="Repository"/>.
66 : /// </summary>
67 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
68 : /// <param name="aggregate">The <see cref="IAggregateRoot{TAuthenticationToken}"/> to save and persist.</param>
69 : /// <param name="expectedVersion">The version number the <see cref="IAggregateRoot{TAuthenticationToken}"/> is expected to be at.</param>
70 1 : public void Save<TAggregateRoot>(TAggregateRoot aggregate, int? expectedVersion = null)
71 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
72 : {
73 : // We need to grab these first as the changes will have been commitedd already by the time we go to make the snapshot.
74 : IEnumerable<IEvent<TAuthenticationToken>> uncommittedChanges = aggregate.GetUncommittedChanges();
75 : // Save the evets first then snapshot the system.
76 : Repository.Save(aggregate, expectedVersion);
77 : TryMakeSnapshot(aggregate, uncommittedChanges);
78 : }
79 :
80 : /// <summary>
81 : /// Retrieves an <see cref="IAggregateRoot{TAuthenticationToken}"/> of type <typeparamref name="TAggregateRoot"/>,
82 : /// First using <see cref="TryRestoreAggregateFromSnapshot{TAggregateRoot}"/>, otherwise via <see cref="IAggregateRepository{TAuthenticationToken}.Get{TAggregateRoot}"/> on <see cref="Repository"/>
83 : /// Then does rehydration.
84 : /// </summary>
85 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
86 : /// <param name="aggregateId">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to retrieve.</param>
87 : /// <param name="events">
88 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
89 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
90 : /// </param>
91 1 : public TAggregateRoot Get<TAggregateRoot>(Guid aggregateId, IList<IEvent<TAuthenticationToken>> events = null)
92 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
93 : {
94 : var aggregate = AggregateFactory.Create<TAggregateRoot>();
95 : int snapshotVersion = TryRestoreAggregateFromSnapshot(aggregateId, aggregate);
96 : if (snapshotVersion == -1)
97 : {
98 : return Repository.Get<TAggregateRoot>(aggregateId);
99 : }
100 : IEnumerable<IEvent<TAuthenticationToken>> theseEvents = events ?? EventStore.Get<TAggregateRoot>(aggregateId, false, snapshotVersion).Where(desc => desc.Version > snapshotVersion);
101 : aggregate.LoadFromHistory(theseEvents);
102 :
103 : return aggregate;
104 : }
105 :
106 : /// <summary>
107 : /// Retrieves an <see cref="IAggregateRoot{TAuthenticationToken}"/> of type <typeparamref name="TAggregateRoot"/> up to and including the provided <paramref name="version"/>.
108 : /// </summary>
109 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
110 : /// <param name="aggregateId">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to retrieve.</param>
111 : /// <param name="version">Load events up-to and including from this version</param>
112 : /// <param name="events">
113 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
114 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
115 : /// </param>
116 1 : public TAggregateRoot GetToVersion<TAggregateRoot>(Guid aggregateId, int version, IList<IEvent<TAuthenticationToken>> events = null)
117 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
118 : {
119 : throw new InvalidOperationException("Verion replay is not appriopriate with snapshots.");
120 : }
121 :
122 : /// <summary>
123 : /// Retrieves an <see cref="IAggregateRoot{TAuthenticationToken}"/> of type <typeparamref name="TAggregateRoot"/> up to and including the provided <paramref name="versionedDate"/>.
124 : /// </summary>
125 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
126 : /// <param name="aggregateId">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to retrieve.</param>
127 : /// <param name="versionedDate">Load events up-to and including from this <see cref="DateTime"/></param>
128 : /// <param name="events">
129 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
130 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
131 : /// </param>
132 1 : public TAggregateRoot GetToDate<TAggregateRoot>(Guid aggregateId, DateTime versionedDate, IList<IEvent<TAuthenticationToken>> events = null)
133 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
134 : {
135 : throw new InvalidOperationException("Verion replay is not appriopriate with snapshots.");
136 : }
137 :
138 : /// <summary>
139 : /// Calls <see cref="ISnapshotStrategy{TAuthenticationToken}.IsSnapshotable"/> on <see cref="SnapshotStrategy"/>
140 : /// If the <typeparamref name="TAggregateRoot"/> is snapshot-able <see cref="ISnapshotStore.Get{TAggregateRoot}"/> is called on <see cref="SnapshotStore"/>.
141 : /// The Restore method is then called on
142 : /// </summary>
143 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
144 : /// <param name="id">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to restore, since the <paramref name="aggregate"/> may be completely uninitialised.</param>
145 : /// <param name="aggregate">The <typeparamref name="TAggregateRoot"/></param>
146 : /// <returns>-1 if no restoration was made, otherwise version number the <typeparamref name="TAggregateRoot"/> was rehydrated to.</returns>
147 : /// <remarks>There may be more events after the snapshot that still need to rehydrated into the <typeparamref name="TAggregateRoot"/> after restoration.</remarks>
148 1 : protected virtual int TryRestoreAggregateFromSnapshot<TAggregateRoot>(Guid id, TAggregateRoot aggregate)
149 : {
150 : int version = -1;
151 : if (SnapshotStrategy.IsSnapshotable(typeof(TAggregateRoot)))
152 : {
153 : Snapshot snapshot = SnapshotStore.Get<TAggregateRoot>(id);
154 : if (snapshot != null)
155 : {
156 : aggregate.AsDynamic().Restore(snapshot);
157 : version = snapshot.Version;
158 : }
159 : }
160 : return version;
161 : }
162 :
163 : /// <summary>
164 : /// Calls <see cref="ISnapshotStrategy{TAuthenticationToken}.ShouldMakeSnapShot"/> on <see cref="SnapshotStrategy"/>
165 : /// If the <see cref="IAggregateRoot{TAuthenticationToken}"/> is snapshot-able <see cref="SnapshotAggregateRoot{TAuthenticationToken,TSnapshot}.GetSnapshot"/> is called
166 : /// The <see cref="Snapshot.Version"/> is calculated, finally <see cref="ISnapshotStore.Save"/> is called on <see cref="SnapshotStore"/>.
167 : /// </summary>
168 : /// <param name="aggregate">The <see cref="IAggregateRoot{TAuthenticationToken}"/> to try and snapshot.</param>
169 : /// <param name="uncommittedChanges">A collection of uncommited changes to assess. If null the aggregate will be asked to provide them.</param>
170 1 : protected virtual void TryMakeSnapshot(IAggregateRoot<TAuthenticationToken> aggregate, IEnumerable<IEvent<TAuthenticationToken>> uncommittedChanges)
171 : {
172 : if (!SnapshotStrategy.ShouldMakeSnapShot(aggregate, uncommittedChanges))
173 : return;
174 : dynamic snapshot = aggregate.AsDynamic().GetSnapshot().RealObject;
175 : var rsnapshot = snapshot as Snapshot;
176 : if (rsnapshot != null)
177 : {
178 : rsnapshot.Version = aggregate.Version;
179 : SnapshotStore.Save(rsnapshot);
180 : }
181 : else
182 : {
183 : snapshot.Version = aggregate.Version;
184 : SnapshotStore.Save(snapshot);
185 : }
186 : }
187 : }
188 : }
|