Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="cdmdotnet Limited">
4 : // // Copyright cdmdotnet Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections.Generic;
11 : using System.Collections.ObjectModel;
12 : using System.Linq;
13 : using System.Threading;
14 : using Cqrs.Domain.Exceptions;
15 : using Cqrs.Events;
16 : using Cqrs.Infrastructure;
17 :
18 : namespace Cqrs.Domain
19 : {
20 : /// <summary>
21 : /// A larger unit of encapsulation than just a class. Every transaction is scoped to a single aggregate. The lifetimes of the components of an aggregate are bounded by the lifetime of the entire aggregate.
22 : ///
23 : /// Concretely, an aggregate will handle commands, apply events, and have a state model encapsulated within it that allows it to implement the required command validation, thus upholding the invariants (business rules) of the aggregate.
24 : /// </summary>
25 : /// <remarks>
26 : /// Why is the use of GUID as IDs a good practice?
27 : ///
28 : /// Because they are (reasonably) globally unique, and can be generated either by the server or by the client.
29 : /// </remarks>
30 : public abstract class AggregateRoot<TAuthenticationToken> : IAggregateRoot<TAuthenticationToken>
31 1 : {
32 : private ReaderWriterLockSlim Lock { get; set; }
33 :
34 : private ICollection<IEvent<TAuthenticationToken>> Changes { get; set; }
35 :
36 : public Guid Id { get; protected set; }
37 :
38 : public int Version { get; protected set; }
39 :
40 0 : protected AggregateRoot()
41 : {
42 : Lock = new ReaderWriterLockSlim();
43 : Changes = new ReadOnlyCollection<IEvent<TAuthenticationToken>>(new List<IEvent<TAuthenticationToken>>());
44 : }
45 :
46 0 : public IEnumerable<IEvent<TAuthenticationToken>> GetUncommittedChanges()
47 : {
48 : return Changes;
49 : }
50 :
51 0 : public virtual void MarkChangesAsCommitted()
52 : {
53 : Lock.EnterWriteLock();
54 : try
55 : {
56 : Version = Version + Changes.Count;
57 : Changes = new ReadOnlyCollection<IEvent<TAuthenticationToken>>(new List<IEvent<TAuthenticationToken>>());
58 : }
59 : finally
60 : {
61 : Lock.ExitWriteLock();
62 : }
63 : }
64 :
65 0 : public virtual void LoadFromHistory(IEnumerable<IEvent<TAuthenticationToken>> history)
66 : {
67 : Type aggregateType = GetType();
68 : foreach (IEvent<TAuthenticationToken> @event in history.OrderBy(e => e.Version))
69 : {
70 : if (@event.Version != Version + 1)
71 : throw new EventsOutOfOrderException(@event.Id, aggregateType, Version + 1, @event.Version);
72 : ApplyChange(@event, true);
73 : }
74 : }
75 :
76 0 : protected virtual void ApplyChange(IEvent<TAuthenticationToken> @event)
77 : {
78 : ApplyChange(@event, false);
79 : }
80 :
81 : private void ApplyChange(IEvent<TAuthenticationToken> @event, bool isEventReplay)
82 : {
83 : Lock.EnterWriteLock();
84 : try
85 : {
86 : this.AsDynamic().Apply(@event);
87 : if (!isEventReplay)
88 : {
89 : Changes = new ReadOnlyCollection<IEvent<TAuthenticationToken>>(new []{@event}.Concat(Changes).ToList());
90 : }
91 : else
92 : {
93 : Id = @event.Id;
94 : Version++;
95 : }
96 : }
97 : finally
98 : {
99 : Lock.ExitWriteLock();
100 : }
101 : }
102 : }
103 : }
|