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 cdmdotnet.Logging;
13 : using Cqrs.Domain.Exceptions;
14 : using Cqrs.Domain.Factories;
15 : using Cqrs.Events;
16 :
17 : namespace Cqrs.Domain
18 : {
19 : /// <summary>
20 : /// Provides basic repository methods for operations with instances of <see cref="IAggregateRoot{TAuthenticationToken}"/> using an <see cref="IEventStore{TAuthenticationToken}"/>
21 : /// that also publishes events once saved.
22 : /// </summary>
23 : /// <typeparam name="TAuthenticationToken">The <see cref="Type"/> of authentication token.</typeparam>
24 : public class AggregateRepository<TAuthenticationToken> : IAggregateRepository<TAuthenticationToken>
25 1 : {
26 : /// <summary>
27 : /// Gets or sets the <see cref="IEventStore{TAuthenticationToken}"/> used to store and retrieve events from.
28 : /// </summary>
29 : protected IEventStore<TAuthenticationToken> EventStore { get; private set; }
30 :
31 : /// <summary>
32 : /// Gets or sets the Publisher used to publish events on once saved into the <see cref="EventStore"/>.
33 : /// </summary>
34 : protected IEventPublisher<TAuthenticationToken> Publisher { get; private set; }
35 :
36 : /// <summary>
37 : /// Gets or set the <see cref="IAggregateFactory"/>.
38 : /// </summary>
39 : protected IAggregateFactory AggregateFactory { get; private set; }
40 :
41 : /// <summary>
42 : /// Gets or set the <see cref="ICorrelationIdHelper"/>.
43 : /// </summary>
44 : protected ICorrelationIdHelper CorrelationIdHelper { get; private set; }
45 :
46 : /// <summary>
47 : /// Instantiates a new instance of <see cref="AggregateRepository{TAuthenticationToken}"/>
48 : /// </summary>
49 1 : public AggregateRepository(IAggregateFactory aggregateFactory, IEventStore<TAuthenticationToken> eventStore, IEventPublisher<TAuthenticationToken> publisher, ICorrelationIdHelper correlationIdHelper)
50 : {
51 : EventStore = eventStore;
52 : Publisher = publisher;
53 : CorrelationIdHelper = correlationIdHelper;
54 : AggregateFactory = aggregateFactory;
55 : }
56 :
57 : /// <summary>
58 : /// Save and persist the provided <paramref name="aggregate"/>, optionally providing the version number the <see cref="IAggregateRoot{TAuthenticationToken}"/> is expected to be at.
59 : /// </summary>
60 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
61 : /// <param name="aggregate">The <see cref="IAggregateRoot{TAuthenticationToken}"/> to save and persist.</param>
62 : /// <param name="expectedVersion">The version number the <see cref="IAggregateRoot{TAuthenticationToken}"/> is expected to be at.</param>
63 1 : public virtual void Save<TAggregateRoot>(TAggregateRoot aggregate, int? expectedVersion = null)
64 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
65 : {
66 : IList<IEvent<TAuthenticationToken>> uncommittedChanges = aggregate.GetUncommittedChanges().ToList();
67 : if (!uncommittedChanges.Any())
68 : return;
69 :
70 : if (expectedVersion != null)
71 : {
72 : IEnumerable<IEvent<TAuthenticationToken>> eventStoreResults = EventStore.Get(aggregate.GetType(), aggregate.Id, false, expectedVersion.Value);
73 : if (eventStoreResults.Any())
74 : throw new ConcurrencyException(aggregate.Id);
75 : }
76 :
77 : var eventsToPublish = new List<IEvent<TAuthenticationToken>>();
78 :
79 : int i = 0;
80 : int version = aggregate.Version;
81 : foreach (IEvent<TAuthenticationToken> @event in uncommittedChanges)
82 : {
83 : if (@event.Id == Guid.Empty)
84 : @event.Id = aggregate.Id;
85 : if (@event.Id == Guid.Empty)
86 : throw new AggregateOrEventMissingIdException(aggregate.GetType(), @event.GetType());
87 :
88 : i++;
89 : version++;
90 :
91 : @event.Version = version;
92 : @event.TimeStamp = DateTimeOffset.UtcNow;
93 : @event.CorrelationId = CorrelationIdHelper.GetCorrelationId();
94 : EventStore.Save(aggregate.GetType(), @event);
95 : eventsToPublish.Add(@event);
96 : }
97 :
98 : aggregate.MarkChangesAsCommitted();
99 : foreach (IEvent<TAuthenticationToken> @event in eventsToPublish)
100 : PublishEvent(@event);
101 : }
102 :
103 : /// <summary>
104 : /// Publish the saved <paramref name="event"/>.
105 : /// </summary>
106 1 : protected virtual void PublishEvent(IEvent<TAuthenticationToken> @event)
107 : {
108 : Publisher.Publish(@event);
109 : }
110 :
111 : /// <summary>
112 : /// Retrieves an <see cref="IAggregateRoot{TAuthenticationToken}"/> of type <typeparamref name="TAggregateRoot"/>.
113 : /// </summary>
114 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of the <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
115 : /// <param name="aggregateId">The identifier of the <see cref="IAggregateRoot{TAuthenticationToken}"/> to retrieve.</param>
116 : /// <param name="events">
117 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
118 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
119 : /// </param>
120 1 : public virtual TAggregateRoot Get<TAggregateRoot>(Guid aggregateId, IList<IEvent<TAuthenticationToken>> events = null)
121 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
122 : {
123 : return LoadAggregate<TAggregateRoot>(aggregateId, events);
124 : }
125 :
126 : /// <summary>
127 : /// Calls <see cref="IAggregateFactory.Create"/> to get a, <typeparamref name="TAggregateRoot"/>.
128 : /// </summary>
129 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
130 : /// <param name="id">The id of the <typeparamref name="TAggregateRoot"/> to create.</param>
131 1 : protected virtual TAggregateRoot CreateAggregate<TAggregateRoot>(Guid id)
132 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
133 : {
134 : var aggregate = AggregateFactory.Create<TAggregateRoot>(id);
135 :
136 : return aggregate;
137 : }
138 :
139 : /// <summary>
140 : /// Calls <see cref="IAggregateFactory.Create"/> to get a, <typeparamref name="TAggregateRoot"/> and then calls <see cref="LoadAggregateHistory{TAggregateRoot}"/>.
141 : /// </summary>
142 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
143 : /// <param name="id">The id of the <typeparamref name="TAggregateRoot"/> to create.</param>
144 : /// <param name="events">
145 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
146 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
147 : /// </param>
148 1 : protected virtual TAggregateRoot LoadAggregate<TAggregateRoot>(Guid id, IList<IEvent<TAuthenticationToken>> events = null)
149 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
150 : {
151 : var aggregate = AggregateFactory.Create<TAggregateRoot>(id);
152 :
153 : LoadAggregateHistory(aggregate, events);
154 : return aggregate;
155 : }
156 :
157 : /// <summary>
158 : /// If <paramref name="events"/> is null, loads the events from <see cref="EventStore"/>, checks for duplicates and then
159 : /// rehydrates the <paramref name="aggregate"/> with the events.
160 : /// </summary>
161 : /// <typeparam name="TAggregateRoot">The <see cref="Type"/> of <see cref="IAggregateRoot{TAuthenticationToken}"/>.</typeparam>
162 : /// <param name="aggregate">The <typeparamref name="TAggregateRoot"/> to rehydrate.</param>
163 : /// <param name="events">
164 : /// A collection of <see cref="IEvent{TAuthenticationToken}"/> to replay on the retrieved <see cref="IAggregateRoot{TAuthenticationToken}"/>.
165 : /// If null, the <see cref="IEventStore{TAuthenticationToken}"/> will be used to retrieve a list of <see cref="IEvent{TAuthenticationToken}"/> for you.
166 : /// </param>
167 : /// <param name="throwExceptionOnNoEvents">If true will throw an instance of <see cref="AggregateNotFoundException{TAggregateRoot,TAuthenticationToken}"/> if no aggregate events or provided or found in the <see cref="EventStore"/>.</param>
168 1 : public virtual void LoadAggregateHistory<TAggregateRoot>(TAggregateRoot aggregate, IList<IEvent<TAuthenticationToken>> events = null, bool throwExceptionOnNoEvents = true)
169 : where TAggregateRoot : IAggregateRoot<TAuthenticationToken>
170 : {
171 : IList<IEvent<TAuthenticationToken>> theseEvents = events ?? EventStore.Get<TAggregateRoot>(aggregate.Id).ToList();
172 : if (!theseEvents.Any())
173 : {
174 : if (throwExceptionOnNoEvents)
175 : throw new AggregateNotFoundException<TAggregateRoot, TAuthenticationToken>(aggregate.Id);
176 : return;
177 : }
178 :
179 : var duplicatedEvents =
180 : theseEvents.GroupBy(x => x.Version)
181 : .Select(x => new { Version = x.Key, Total = x.Count() })
182 : .FirstOrDefault(x => x.Total > 1);
183 : if (duplicatedEvents != null)
184 : throw new DuplicateEventException<TAggregateRoot, TAuthenticationToken>(aggregate.Id, duplicatedEvents.Version);
185 :
186 : aggregate.LoadFromHistory(theseEvents);
187 : }
188 : }
189 : }
|