1+ using System ;
2+ using System . Collections . Generic ;
3+ using Xer . DomainDriven . Exceptions ;
4+
5+ namespace Xer . DomainDriven
6+ {
7+ public abstract partial class AggregateRoot
8+ {
9+ protected interface IApplyActionConfiguration
10+ {
11+ /// <summary>
12+ /// Register an apply action to execute whenever a <typeparamref name="TDomainEvent"/> domain event is applied to the aggregate root.
13+ /// </summary>
14+ /// <typeparam name="TDomainEvent">Type of domain event to apply.</typeparam>
15+ IApplyActionSelector < TDomainEvent > Apply < TDomainEvent > ( ) where TDomainEvent : class , IDomainEvent ;
16+
17+ /// <summary>
18+ /// Require that an applier is registered for any domain events.
19+ /// </summary>
20+ void RequireApplyActions ( ) ;
21+
22+ /// <summary>
23+ /// Set the action that executes whenever a domain event is successfully applied.
24+ /// </summary>
25+ /// <param name="onApplySuccess">Action to executes whenever a domain event is successfully applied.</param>
26+ void OnApplySuccess ( Action < IDomainEvent > onApplySuccess ) ;
27+ }
28+
29+ protected interface IApplyActionSelector < TDomainEvent > where TDomainEvent : class , IDomainEvent
30+ {
31+ /// <summary>
32+ /// Set apply action to apply the domain event.
33+ /// </summary>
34+ /// <param name="applyAction">Action to apply the domain event.</param>
35+ void With ( Action < TDomainEvent > applyAction ) ;
36+ }
37+
38+ protected interface IApplyActionResolver
39+ {
40+ /// <summary>
41+ /// Resolve apply action to execute for the domain event.
42+ /// </summary>
43+ /// <param name="domainEvent">Domain event to apply.</param>
44+ /// <exception cref="Xer.DomainDriven.Exceptions.DomainEventNotAppliedException">
45+ /// This exception might be thrown if <see cref="Xer.DomainDriven.AggregateRoot.IApplyActionConfiguration.RequireApplyActions"/>
46+ /// method was invoked and no registered domain event applier was found.
47+ /// </exception>
48+ /// <returns>Action that applies the domain event to the aggregate. Otherwise, null.</returns>
49+ Action < IDomainEvent > ResolveApplyActionFor ( IDomainEvent domainEvent ) ;
50+ }
51+
52+ private class ApplyActionConfiguration : IApplyActionConfiguration , IApplyActionResolver
53+ {
54+ #region Declarations
55+
56+ private readonly Dictionary < Type , Action < IDomainEvent > > _applyActionByDomainEventType = new Dictionary < Type , Action < IDomainEvent > > ( ) ;
57+ private bool _requireDomainEventAppliers = false ;
58+ private Action < IDomainEvent > _onApplySuccess = ( e ) => { } ;
59+
60+ #endregion Declarations
61+
62+ #region IApplyActionConfiguration Implementation
63+
64+ /// <summary>
65+ /// Register an apply action to execute whenever a <typeparamref name="TDomainEvent"/> domain event is applied to the aggregate root.
66+ /// </summary>
67+ /// <typeparam name="TDomainEvent">Type of domain event to apply.</typeparam>
68+ public IApplyActionSelector < TDomainEvent > Apply < TDomainEvent > ( ) where TDomainEvent : class , IDomainEvent
69+ {
70+ return new ApplyActionSelector < TDomainEvent > ( this ) ;
71+ }
72+
73+ /// <summary>
74+ /// Require that an applier is registered for all domain events.
75+ /// </summary>
76+ public void RequireApplyActions ( )
77+ {
78+ _requireDomainEventAppliers = true ;
79+ }
80+
81+ /// <summary>
82+ /// Set the action that executes each time a domain event is successfully applied.
83+ /// This overrides the any previously set action delegate.
84+ /// </summary>
85+ /// <param name="onDomainEventApplied">Action that executes each time a domain event is successfully applied.</param>
86+ public void OnApplySuccess ( Action < IDomainEvent > onDomainEventApplied )
87+ {
88+ _onApplySuccess = onDomainEventApplied ?? throw new ArgumentNullException ( nameof ( onDomainEventApplied ) ) ;
89+ }
90+
91+ #endregion IApplyActionConfiguration Implementation
92+
93+ #region IApplyActionResolver Implementation
94+
95+ /// <summary>
96+ /// Resolve action to execute for the applied domain event.
97+ /// </summary>
98+ /// <param name="domainEvent">Domain event to apply.</param>
99+ /// <exception cref="Xer.DomainDriven.Exceptions.DomainEventNotAppliedException">
100+ /// This exception will be thrown if <see cref="Xer.DomainDriven.AggregateRoot.IApplyActionConfiguration.RequireApplyActions"/>
101+ /// was invoked and no domain event applier can be resolved for the given domain event.
102+ /// </exception>
103+ /// <returns>Action that applies the domain event to the aggregate. Otherwise, null.</returns>
104+ public Action < IDomainEvent > ResolveApplyActionFor ( IDomainEvent domainEvent )
105+ {
106+ if ( domainEvent == null )
107+ {
108+ throw new ArgumentNullException ( nameof ( domainEvent ) ) ;
109+ }
110+
111+ Type domainEventType = domainEvent . GetType ( ) ;
112+
113+ bool found = _applyActionByDomainEventType . TryGetValue ( domainEventType , out Action < IDomainEvent > applyAction ) ;
114+ if ( ! found && _requireDomainEventAppliers )
115+ {
116+ throw new DomainEventNotAppliedException ( domainEvent ,
117+ $@ "{ GetType ( ) . Name } has no registered apply action for domain event of type { domainEventType . Name } .
118+ Configure domain event apply actions by calling { nameof ( Configure ) } method in constructor." ) ;
119+ }
120+
121+ return applyAction ;
122+ }
123+
124+ #endregion IApplyActionResolver Implementation
125+
126+ #region Methods
127+
128+ /// <summary>
129+ /// Register action to be executed for the domain event.
130+ /// </summary>
131+ /// <typeparam name="TDomainEvent">Type of domain event to apply.</typeparam>
132+ /// <param name="applyAction">Action to apply the domain event to the aggregate.</param>
133+ public void RegisterApplyAction < TDomainEvent > ( Action < TDomainEvent > applyAction ) where TDomainEvent : class , IDomainEvent
134+ {
135+ if ( applyAction == null )
136+ {
137+ throw new ArgumentNullException ( nameof ( applyAction ) ) ;
138+ }
139+
140+ Type domainEventType = typeof ( TDomainEvent ) ;
141+
142+ if ( _applyActionByDomainEventType . ContainsKey ( domainEventType ) )
143+ {
144+ throw new InvalidOperationException ( $ "A apply action for { domainEventType . Name } has already been registered.") ;
145+ }
146+
147+ Action < IDomainEvent > apply = ( e ) =>
148+ {
149+ TDomainEvent domainEvent = e as TDomainEvent ;
150+ if ( domainEvent == null )
151+ {
152+ throw new ArgumentException (
153+ $@ "An invalid domain event was provided to the apply action.
154+ Expected domain event is of type { typeof ( TDomainEvent ) . Name } but { e . GetType ( ) . Name } was provided." ) ;
155+ }
156+
157+ applyAction . Invoke ( domainEvent ) ;
158+
159+ _onApplySuccess . Invoke ( domainEvent ) ;
160+ } ;
161+
162+ _applyActionByDomainEventType . Add ( domainEventType , apply ) ;
163+ }
164+
165+ #endregion Methods
166+ }
167+
168+ private class ApplyActionSelector < TDomainEvent > : IApplyActionSelector < TDomainEvent > where TDomainEvent : class , IDomainEvent
169+ {
170+ private readonly ApplyActionConfiguration _configuration ;
171+
172+ /// <summary>
173+ /// Constructor.
174+ /// </summary>
175+ /// <param name="configuration">Apply action configuration.</param>
176+ public ApplyActionSelector ( ApplyActionConfiguration configuration )
177+ {
178+ _configuration = configuration ;
179+ }
180+
181+ /// <summary>
182+ /// Set apply action tp apply the domain event.
183+ /// </summary>
184+ /// <param name="applyAction">Action to apply the domain event.</param>
185+ public void With ( Action < TDomainEvent > applyAction )
186+ {
187+ if ( applyAction == null )
188+ {
189+ throw new ArgumentNullException ( nameof ( applyAction ) ) ;
190+ }
191+
192+ _configuration . RegisterApplyAction < TDomainEvent > ( applyAction ) ;
193+ }
194+ }
195+ }
196+ }
0 commit comments