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 cdmdotnet.Logging;
15 : using Cqrs.Commands;
16 : using Cqrs.Configuration;
17 : using Cqrs.Domain.Exceptions;
18 : using Cqrs.Events;
19 : using Cqrs.Infrastructure;
20 :
21 : namespace Cqrs.Domain
22 : {
23 : /// <summary>
24 : /// An independent component that reacts to domain <see cref="IEvent{TAuthenticationToken}"/> in a cross-<see cref="IAggregateRoot{TAuthenticationToken}"/>, eventually consistent manner. Time can also be a trigger. A <see cref="Saga{TAuthenticationToken}"/> can sometimes be purely reactive, and sometimes represent workflows.
25 : ///
26 : /// From an implementation perspective, a <see cref="Saga{TAuthenticationToken}"/> is a state machine that is driven forward by incoming <see cref="IEvent{TAuthenticationToken}"/> (which may come from many <see cref="AggregateRoot{TAuthenticationToken}"/> or other <see cref="Saga{TAuthenticationToken}"/>). Some states will have side effects, such as sending <see cref="ICommand{TAuthenticationToken}"/>, talking to external web services, or sending emails.
27 : /// </summary>
28 : /// <remarks>
29 : /// Isn't a <see cref="Saga{TAuthenticationToken}"/> just leaked domain logic?
30 : /// No.
31 : /// A <see cref="Saga{TAuthenticationToken}"/> can doing things that no individual <see cref="AggregateRoot{TAuthenticationToken}"/> can sensibly do. Thus, it's not a logic leak since the logic didn't belong in an <see cref="AggregateRoot{TAuthenticationToken}"/> anyway. Furthermore, we're not breaking encapsulation in any way, since <see cref="Saga{TAuthenticationToken}"/> operate with <see cref="ICommand{TAuthenticationToken}"/> and <see cref="IEvent{TAuthenticationToken}"/>, which are part of the public API.
32 : ///
33 : /// How can I make my <see cref="Saga{TAuthenticationToken}"/> react to an <see cref="IEvent{TAuthenticationToken}"/> that did not happen?
34 : /// The <see cref="Saga{TAuthenticationToken}"/>, besides reacting to domain <see cref="IEvent{TAuthenticationToken}"/>, can be "woken up" by recurrent internal alarms. Implementing such alarms is easy. See cron in Unix, or triggered WebJobs in Azure for examples.
35 : ///
36 : /// How does the <see cref="Saga{TAuthenticationToken}"/> interact with the write side?
37 : /// By sending an <see cref="ICommand{TAuthenticationToken}"/> to it.
38 : /// </remarks>
39 : public abstract class Saga<TAuthenticationToken> : ISaga<TAuthenticationToken>
40 1 : {
41 : private ReaderWriterLockSlim Lock { get; set; }
42 :
43 : private ICollection<ISagaEvent<TAuthenticationToken>> Changes { get; set; }
44 :
45 : public Guid Rsn
46 : {
47 : get { return Id; }
48 : private set { Id = value; }
49 : }
50 :
51 : public Guid Id { get; protected set; }
52 :
53 : public int Version { get; protected set; }
54 :
55 : protected ICommandPublisher<TAuthenticationToken> CommandPublisher { get; private set; }
56 :
57 : protected IDependencyResolver DependencyResolver { get; private set; }
58 :
59 : protected ILogger Logger { get; private set; }
60 :
61 : /// <summary>
62 : /// A constructor for the <see cref="Cqrs.Domain.Factories.IAggregateFactory"/>
63 : /// </summary>
64 1 : protected Saga()
65 : {
66 : Lock = new ReaderWriterLockSlim();
67 : Changes = new ReadOnlyCollection<ISagaEvent<TAuthenticationToken>>(new List<ISagaEvent<TAuthenticationToken>>());
68 : }
69 :
70 : /// <summary>
71 : /// A constructor for the <see cref="Cqrs.Domain.Factories.IAggregateFactory"/>
72 : /// </summary>
73 1 : protected Saga(IDependencyResolver dependencyResolver, ILogger logger)
74 : : this()
75 : {
76 : DependencyResolver = dependencyResolver;
77 : Logger = logger;
78 : CommandPublisher = DependencyResolver.Resolve<ICommandPublisher<TAuthenticationToken>>();
79 : }
80 :
81 0 : protected Saga(IDependencyResolver dependencyResolver, ILogger logger, Guid rsn)
82 : : this(dependencyResolver, logger)
83 : {
84 : Rsn = rsn;
85 : }
86 :
87 0 : public IEnumerable<ISagaEvent<TAuthenticationToken>> GetUncommittedChanges()
88 : {
89 : return Changes;
90 : }
91 :
92 0 : public virtual void MarkChangesAsCommitted()
93 : {
94 : Lock.EnterWriteLock();
95 : try
96 : {
97 : Version = Version + Changes.Count;
98 : Changes = new ReadOnlyCollection<ISagaEvent<TAuthenticationToken>>(new List<ISagaEvent<TAuthenticationToken>>());
99 : }
100 : finally
101 : {
102 : Lock.ExitWriteLock();
103 : }
104 : }
105 :
106 0 : public virtual void LoadFromHistory(IEnumerable<ISagaEvent<TAuthenticationToken>> history)
107 : {
108 : Type sagaType = GetType();
109 : foreach (ISagaEvent<TAuthenticationToken> @event in history.OrderBy(e => e.Version))
110 : {
111 : if (@event.Version != Version + 1)
112 : throw new EventsOutOfOrderException(@event.Id, sagaType, Version + 1, @event.Version);
113 : ApplyChange(@event, true);
114 : }
115 : }
116 :
117 0 : protected virtual void ApplyChange(ISagaEvent<TAuthenticationToken> @event)
118 : {
119 : ApplyChange(@event, false);
120 : }
121 :
122 0 : protected virtual void ApplyChange(IEvent<TAuthenticationToken> @event)
123 : {
124 : var sagaEvent = new SagaEvent<TAuthenticationToken>(@event);
125 : // Set ID
126 : this.AsDynamic().SetId(sagaEvent);
127 : ApplyChange(sagaEvent);
128 : }
129 :
130 : private void ApplyChange(ISagaEvent<TAuthenticationToken> @event, bool isEventReplay)
131 : {
132 : Lock.EnterWriteLock();
133 : try
134 : {
135 : this.AsDynamic().Apply(@event.Event);
136 : if (!isEventReplay)
137 : {
138 : Changes = new ReadOnlyCollection<ISagaEvent<TAuthenticationToken>>(new[] { @event }.Concat(Changes).ToList());
139 : }
140 : else
141 : {
142 : Id = @event.Id;
143 : Version++;
144 : }
145 : }
146 : finally
147 : {
148 : Lock.ExitWriteLock();
149 : }
150 : }
151 : }
152 : }
|