Skip to content
This repository

This is my suggestion on how to solve the event conversion. #76

Merged
merged 14 commits into from over 2 years ago

3 participants

Mikael Östberg Jonathan Oliver Jimit Ndiaye
Mikael Östberg

To use it, simply implement the IConvertEvents interface and hookup the EventConverter when Wiring up the store.

::m

Jimit Ndiaye

What if the interface IConvertEvents<,> is implemented explicitly?

Mikael Östberg

Thanks for noticing!

I will add support for getting explicitly implemented methods as well.

I'll submit a new Pull request in about 7 hours or so. Or something.

Jonathan Oliver
Owner

You'll probably want have the Convert function use a while loop internally because it's possible that you want to convert eventA to eventB and then eventB to eventC. It would also be really nice to have a constructor overloads so that those who wanted to use explicit registration instead of reflection-based wireup could do so (as per the project goals found in the readme). Lastly, I noticed the IConvertEvents interface was .NET 4.0-specific. I'm still supporting .NET 3.5 for this release (which should be the last one). Any particular reason for the in/out covariant/contravariant parameters on the interface?

Mikael Östberg

The looping thing is a great idea. I will implement it.

I was thinking about the explicit registration thing this morning and it should indeed be allowed. Assembly scanning is not a very nice way to do it. Also, I am thinking about if it should make it into its own thing in the Wireup but I better leave the decision about that to you.

The in/out covariant/contravariant thing is just a alt-enter Resharper reflex consequence. My brain had no part in that at all. It has no function whatsoever and I'll remove it.

Jonathan Oliver
Owner

The only other thought I had was about the location of the conversion. In the back of my mind, I always pictured conversion as something a serialization wrapper would take care of. In other words, I've been thinking we could accomplish the same thing from the serializer level. I'm still back and forth on this one though and a pipeline hook is definitely a place where it could work, but at that point, the ordering of the pipeline hooks may become important.

Mikael Östberg

You have a point. The sooner the better.

Still, the interface IConvertEvents<,> is pretty much what we want, right?

Mikael Östberg

How about promoting the conversion to its own thing?

The default behavior is no conversion.

Using Wireup,

you can choose something like .UsingEventConversion().AutoScan() for automatic scanning of converters, like I have done now.

You can also choose something like .UsingEventConversion().WithConverters.FromAssemblyContainingType(typeof(MyConverter)). Here I think we can improve the API, but you get the idea.

Then for the implementation, the conversion will not be a IPipelineHook but occur just before the pipeline hooks executes, in OptimisticEventStore.GetFrom(..). Line #65 or so. OptimisticEventStore will get the converters injected.

I think that might be the best place. Regardless, this way the functionality is kept internal so that compability is kept even though the serializer wrapper idea of yours materializes and gets implemented.

Mikael Östberg

Here is another suggestion. Forgive my commit frenzy. I evidently suck at git.

In this commit, the Event conversion has become its own thing and is configurable from the Wireup. I don't know what you think about me poking around in the API, but as this is a suggestion I took the liberty in doing so.

Have a look and tell me what you think.

Jonathan Oliver
Owner

Okay, here's what I'd like to do. I think making the IConvertCommits is a great interface and it should definitely have it's own interface like you have done. After looking at the issues and different storage engines, I determined that wrapping it up in the serializer is absolutely the wrong place, so it's good we didn't go down that road. However, I believe that wrapping things up in a "pipeline hook" is the right place. I'm trying to keep the primary EventStore objects as clean and uncluttered as possible which is one of the reasons I took the dispatcher and put it into a pipeline hook.

One other big one is that I'd like to move all reflection and type/assembly scanning into the wireup and out of the constructor. Ideally the converter which just be using a Dictionary> and however those were discovered or registered is a wireup issue.

Mikael Östberg

Ok, that sounds great!

I'll turn it back into a IPipelineHook and try to figure out a useful API for the Wireup. I'll also move all scanning things to the wireup. I might commit something tonight.

Mikael Östberg

Here we go! This version should do it!

The default behaviour now is that there are no event conversion. To enable it, you have to explicitly use the Wireup. There are three ways to do it.
The simplest way is to use .UsingEventUpconversion() which makes the Wireup scan all assemblies for converters.
The second alternative is to use .WithConvertersFrom(params Assembly[]) which only scans the supplied assemblies.
The final alternative is to use .WithConvertersFromAssemblyContaining(params Type[]) which scans all assemblies containing the supplied types.

The event converter pipeline hook will use the converters found from scanning and convert events when they are retrieved from the underlying persistence. This will take place before any other pipeline hook registered executes.

The event converter convert specific events recursively which means that an event of type Event1 will be converted to Event2 and then to Event3 and so forth. This recursion will continue until there is no converter found.

Event converters are created by implementing the IConvertEvents interface. The event converter chooses which converter to use based on TSource.

Feel free to trash the Wireup API as you see fit.

Let me know what you think.

Jonathan Oliver joliver merged commit 757026a into from September 30, 2011
Jonathan Oliver joliver closed this September 30, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
2  .gitignore
@@ -10,4 +10,4 @@ _ReSharper.*
10 10
 *.Resharper
11 11
 *.Cache
12 12
 *.cache
13  
-~$*
  13
+~$*
1  src/proj/EventStore.Core/EventStore.Core.csproj
@@ -44,6 +44,7 @@
44 44
     <Compile Include="..\VersionAssemblyInfo.cs">
45 45
       <Link>Properties\VersionAssemblyInfo.cs</Link>
46 46
     </Compile>
  47
+    <Compile Include="EventUpconverterPipelineHook.cs" />
47 48
     <Compile Include="DispatchSchedulerPipelinkHook.cs" />
48 49
     <Compile Include="Logging\ConsoleWindowLogger.cs" />
49 50
     <Compile Include="Logging\ExtensionMethods.cs" />
55  src/proj/EventStore.Core/EventUpconverterPipelineHook.cs
... ...
@@ -0,0 +1,55 @@
  1
+namespace EventStore
  2
+{
  3
+    using System;
  4
+    using System.Collections.Generic;
  5
+    using System.Linq;
  6
+    using System.Reflection;
  7
+    using Logging;
  8
+
  9
+    public class EventUpconverterPipelineHook : IPipelineHook
  10
+    {
  11
+        private static readonly ILog Logger = LogFactory.BuildLogger(typeof(EventUpconverterPipelineHook));
  12
+        private readonly Dictionary<Type, Func<object, object>> converters;
  13
+
  14
+        public EventUpconverterPipelineHook(Dictionary<Type, Func<object, object>> converters)
  15
+        {
  16
+            this.converters = converters;
  17
+        }
  18
+
  19
+        private object Convert(object body)
  20
+        {
  21
+            Func<object, object> converter;
  22
+            object result = body;
  23
+            if (this.converters.TryGetValue(body.GetType(), out converter))
  24
+            {
  25
+                result = Convert(converter(body));
  26
+                Logger.Debug(Resources.ConvertingEvent, body.GetType(), result.GetType());
  27
+            }
  28
+            return result;
  29
+        }
  30
+
  31
+        public Commit Select(Commit committed)
  32
+        {
  33
+            foreach (var eventMessage in committed.Events)
  34
+            {
  35
+                eventMessage.Body = Convert(eventMessage.Body);
  36
+            }
  37
+            return committed;
  38
+        }
  39
+
  40
+        public bool PreCommit(Commit attempt)
  41
+        {
  42
+            return true;
  43
+        }
  44
+
  45
+        public void PostCommit(Commit committed)
  46
+        {
  47
+        }
  48
+
  49
+        public void Dispose()
  50
+        {
  51
+            this.converters.Clear();
  52
+            GC.SuppressFinalize(this);
  53
+        }
  54
+    }
  55
+}
6  src/proj/EventStore.Core/OptimisticEventStore.cs
@@ -10,7 +10,7 @@ public class OptimisticEventStore : IStoreEvents, ICommitEvents
10 10
 	{
11 11
 		private static readonly ILog Logger = LogFactory.BuildLogger(typeof(OptimisticEventStore));
12 12
 		private readonly IPersistStreams persistence;
13  
-		private readonly IEnumerable<IPipelineHook> pipelineHooks;
  13
+	    private readonly IEnumerable<IPipelineHook> pipelineHooks;
14 14
 
15 15
 		public OptimisticEventStore(IPersistStreams persistence, IEnumerable<IPipelineHook> pipelineHooks)
16 16
 		{
@@ -18,7 +18,7 @@ public OptimisticEventStore(IPersistStreams persistence, IEnumerable<IPipelineHo
18 18
 				throw new ArgumentNullException("persistence");
19 19
 
20 20
 			this.persistence = persistence;
21  
-			this.pipelineHooks = pipelineHooks ?? new IPipelineHook[0];
  21
+		    this.pipelineHooks = pipelineHooks ?? new IPipelineHook[0];
22 22
 		}
23 23
 
24 24
 		public void Dispose()
@@ -73,7 +73,7 @@ public virtual IEnumerable<Commit> GetFrom(Guid streamId, int minRevision, int m
73 73
 				if (filtered == null)
74 74
 					Logger.Info(Resources.PipelineHookFilteredCommit);
75 75
 				else
76  
-					yield return filtered;
  76
+                    yield return filtered;
77 77
 			}
78 78
 		}
79 79
 		public virtual void Commit(Commit attempt)
9  src/proj/EventStore.Core/Resources.Designer.cs
@@ -169,6 +169,15 @@ internal class Resources {
169 169
         }
170 170
         
171 171
         /// <summary>
  172
+        ///   Looks up a localized string similar to Converting an Event from &apos;{0}&apos; to &apos;{1}&apos;..
  173
+        /// </summary>
  174
+        internal static string ConvertingEvent {
  175
+            get {
  176
+                return ResourceManager.GetString("ConvertingEvent", resourceCulture);
  177
+            }
  178
+        }
  179
+        
  180
+        /// <summary>
172 181
         ///   Looks up a localized string similar to Creating stream &apos;{0}&apos;..
173 182
         /// </summary>
174 183
         internal static string CreatingStream {
9  src/proj/EventStore.Core/Resources.resx
@@ -279,7 +279,10 @@
279 279
   <data name="UnableToDispatch" xml:space="preserve">
280 280
     <value>Configured dispatcher of type '{0}' was unable to dispatch commit '{1}'.</value>
281 281
   </data>
282  
-	<data name="UnableToMarkDispatched">
283  
-		<value><![CDATA[Unable to mark commit '{0}' as dispatched, the underlying storage has already been disposed]]></value>
284  
-	</data>
  282
+  <data name="UnableToMarkDispatched" xml:space="preserve">
  283
+    <value>Unable to mark commit '{0}' as dispatched, the underlying storage has already been disposed</value>
  284
+  </data>
  285
+  <data name="ConvertingEvent" xml:space="preserve">
  286
+    <value>Converting an Event from '{0}' to '{1}'.</value>
  287
+  </data>
285 288
 </root>
2  src/proj/EventStore.Wireup/EventStore.Wireup.csproj
@@ -46,6 +46,8 @@
46 46
     </Compile>
47 47
     <Compile Include="AsynchronousDispatchSchedulerWireup.cs" />
48 48
     <Compile Include="AsynchronousDispatchSchedulerWireupExtensions.cs" />
  49
+    <Compile Include="EventUpconverterWireup.cs" />
  50
+    <Compile Include="EventUpconverterWireupExtensions.cs" />
49 51
     <Compile Include="LoggingWireupExtensions.cs" />
50 52
     <Compile Include="Messages.Designer.cs">
51 53
       <AutoGen>True</AutoGen>
73  src/proj/EventStore.Wireup/EventUpconverterWireup.cs
... ...
@@ -0,0 +1,73 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Linq;
  4
+namespace EventStore
  5
+{
  6
+    using System.Reflection;
  7
+    using Logging;
  8
+
  9
+    public class EventUpconverterWireup : Wireup
  10
+    {
  11
+        private static readonly ILog Logger = LogFactory.BuildLogger(typeof(EventUpconverterWireup));
  12
+        private readonly List<Assembly> assembliesToScan = new List<Assembly>();
  13
+
  14
+        public EventUpconverterWireup(Wireup wireup) : base(wireup)
  15
+        {
  16
+            Logger.Debug(Messages.EventUpconverterRegistered);
  17
+
  18
+            this.Container.Register(c =>
  19
+            {
  20
+                if (!this.assembliesToScan.Any())
  21
+                    this.assembliesToScan.AddRange(getAllAssemblies());
  22
+                var converters = GetConverters(this.assembliesToScan);
  23
+                return new EventUpconverterPipelineHook(converters);
  24
+            });
  25
+        }
  26
+
  27
+        private Dictionary<Type, Func<object, object>> GetConverters(IEnumerable<Assembly> toScan)
  28
+        {
  29
+            var c = from a in toScan
  30
+                    from t in a.GetTypes()
  31
+                    let i = t.GetInterface(typeof (IConvertEvents<,>).FullName)
  32
+                    where i != null
  33
+                    let sourceType = i.GetGenericArguments().First()
  34
+                    let convertMethod = i.GetMethod("Convert", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)
  35
+                    let instance = Activator.CreateInstance(t)
  36
+                    select new KeyValuePair<Type, Func<object, object>>(
  37
+                        sourceType,
  38
+                        e => convertMethod.Invoke(instance, new[] {e as object})
  39
+                    );
  40
+            try
  41
+            {
  42
+                return c.ToDictionary(x => x.Key, x => x.Value);
  43
+            }
  44
+            catch (ArgumentException ex)
  45
+            {
  46
+                throw new MultipleConvertersFoundException(ex.Message, ex);
  47
+            }
  48
+        }
  49
+
  50
+        private IEnumerable<Assembly> getAllAssemblies()
  51
+        {
  52
+            return Assembly.GetCallingAssembly()
  53
+                .GetReferencedAssemblies()
  54
+                .Select(Assembly.Load)
  55
+                .Concat(new[] {Assembly.GetCallingAssembly()});
  56
+        }
  57
+
  58
+        public virtual EventUpconverterWireup WithConvertersFrom(params Assembly[] assemblies)
  59
+        {
  60
+            Logger.Debug(Messages.EventUpconvertersLoadedFrom, string.Concat(", ", assemblies));
  61
+            this.assembliesToScan.AddRange(assemblies);
  62
+            return this;
  63
+        }
  64
+
  65
+        public virtual EventUpconverterWireup WithConvertersFromAssemblyContaining(params Type[] converters)
  66
+        {
  67
+            var assemblies = converters.Select(c => c.Assembly).Distinct();
  68
+            Logger.Debug(Messages.EventUpconvertersLoadedFrom, string.Concat(", ", assemblies));
  69
+            this.assembliesToScan.AddRange(assemblies);
  70
+            return this;
  71
+        }
  72
+    }
  73
+}
10  src/proj/EventStore.Wireup/EventUpconverterWireupExtensions.cs
... ...
@@ -0,0 +1,10 @@
  1
+namespace EventStore
  2
+{
  3
+    public static class EventUpconverterWireupExtensions
  4
+    {
  5
+        public static EventUpconverterWireup UsingEventUpconversion(this Wireup wireup)
  6
+        {
  7
+            return new EventUpconverterWireup(wireup);
  8
+        }
  9
+    }
  10
+}
20  src/proj/EventStore.Wireup/Messages.Designer.cs
... ...
@@ -1,7 +1,7 @@
1 1
 //------------------------------------------------------------------------------
2 2
 // <auto-generated>
3 3
 //     This code was generated by a tool.
4  
-//     Runtime Version:4.0.30319.235
  4
+//     Runtime Version:4.0.30319.237
5 5
 //
6 6
 //     Changes to this file may cause incorrect behavior and will be lost if
7 7
 //     the code is regenerated.
@@ -205,6 +205,24 @@ internal class Messages {
205 205
         }
206 206
         
207 207
         /// <summary>
  208
+        ///   Looks up a localized string similar to Configuring the store to upconvert events when fetched..
  209
+        /// </summary>
  210
+        internal static string EventUpconverterRegistered {
  211
+            get {
  212
+                return ResourceManager.GetString("EventUpconverterRegistered", resourceCulture);
  213
+            }
  214
+        }
  215
+        
  216
+        /// <summary>
  217
+        ///   Looks up a localized string similar to Will scan for event upconverters from the following assemblies: &apos;{0}&apos;.
  218
+        /// </summary>
  219
+        internal static string EventUpconvertersLoadedFrom {
  220
+            get {
  221
+                return ResourceManager.GetString("EventUpconvertersLoadedFrom", resourceCulture);
  222
+            }
  223
+        }
  224
+        
  225
+        /// <summary>
208 226
         ///   Looks up a localized string similar to Initializing the configured persistence engine..
209 227
         /// </summary>
210 228
         internal static string InitializingEngine {
6  src/proj/EventStore.Wireup/Messages.resx
@@ -207,4 +207,10 @@
207 207
   <data name="SynchronousDispatcherTwoPhaseCommits" xml:space="preserve">
208 208
     <value>Only the synchronous dispatcher can enlist in two-phase commits.</value>
209 209
   </data>
  210
+  <data name="EventUpconverterRegistered" xml:space="preserve">
  211
+    <value>Configuring the store to upconvert events when fetched.</value>
  212
+  </data>
  213
+  <data name="EventUpconvertersLoadedFrom" xml:space="preserve">
  214
+    <value>Will scan for event upconverters from the following assemblies: '{0}'</value>
  215
+  </data>
210 216
 </root>
33  src/proj/EventStore.Wireup/Wireup.cs
... ...
@@ -1,21 +1,21 @@
1  
-namespace EventStore
  1
+namespace EventStore
2 2
 {
3 3
 	using System.Collections.Generic;
4 4
 	using System.Linq;
5 5
 	using System.Transactions;
6  
-	using Dispatcher;
  6
+	using Dispatcher;
7 7
 	using Persistence;
8 8
 	using Persistence.InMemoryPersistence;
9 9
 
10 10
 	public class Wireup
11 11
 	{
12 12
 		private readonly Wireup inner;
13  
-		private readonly NanoContainer container;
14  
-
  13
+		private readonly NanoContainer container;
  14
+
15 15
 		protected Wireup(NanoContainer container)
16 16
 		{
17 17
 			this.container = container;
18  
-		}
  18
+		}
19 19
 		protected Wireup(Wireup inner)
20 20
 		{
21 21
 			this.inner = inner;
@@ -46,35 +46,40 @@ protected NanoContainer Container
46 46
 		public virtual Wireup HookIntoPipelineUsing(IEnumerable<IPipelineHook> hooks)
47 47
 		{
48 48
 			return this.HookIntoPipelineUsing((hooks ?? new IPipelineHook[0]).ToArray());
49  
-		}
  49
+		}
50 50
 		public virtual Wireup HookIntoPipelineUsing(params IPipelineHook[] hooks)
51 51
 		{
52 52
 			ICollection<IPipelineHook> collection = (hooks ?? new IPipelineHook[] { }).Where(x => x != null).ToArray();
53 53
 			this.Container.Register(collection);
54 54
 			return this;
55 55
 		}
56  
-
  56
+
57 57
 		public virtual IStoreEvents Build()
58 58
 		{
59 59
 			if (this.inner != null)
60 60
 				return this.inner.Build();
61 61
 
62 62
 			return this.Container.Resolve<IStoreEvents>();
63  
-		}
64  
-
  63
+		}
  64
+
65 65
 		private static IStoreEvents BuildEventStore(NanoContainer context)
66 66
 		{
67 67
 			var scopeOption = context.Resolve<TransactionScopeOption>();
68 68
 			var concurrencyHook = scopeOption == TransactionScopeOption.Suppress ? new OptimisticPipelineHook() : null;
69 69
 			var dispatchSchedulerHook = new DispatchSchedulerPipelinkHook(context.Resolve<IScheduleDispatches>());
  70
+            var eventUpconverterPipelineHook = context.Resolve<EventUpconverterPipelineHook>();
70 71
 
71 72
 			var pipelineHooks = context.Resolve<ICollection<IPipelineHook>>() ?? new IPipelineHook[0];
72  
-			pipelineHooks = new IPipelineHook[] { concurrencyHook, dispatchSchedulerHook }
73  
-				.Concat(pipelineHooks)
74  
-				.Where(x => x != null)
75  
-				.ToArray();
  73
+            pipelineHooks = new IPipelineHook[] {
  74
+                    eventUpconverterPipelineHook, 
  75
+                    concurrencyHook, 
  76
+                    dispatchSchedulerHook
  77
+                }
  78
+                .Concat(pipelineHooks)
  79
+                .Where(x => x != null)
  80
+                .ToArray();
76 81
 
77 82
 			return new OptimisticEventStore(context.Resolve<IPersistStreams>(), pipelineHooks);
78 83
 		}
79  
-	}
  84
+	}
80 85
 }
2  src/proj/EventStore/EventStore.csproj
@@ -51,9 +51,11 @@
51 51
     </Compile>
52 52
     <Compile Include="Dispatcher\IDispatchCommits.cs" />
53 53
     <Compile Include="ExtensionMethods.cs" />
  54
+    <Compile Include="IConvertEvents.cs" />
54 55
     <Compile Include="Logging\ILog.cs" />
55 56
     <Compile Include="IPipelineHook.cs" />
56 57
     <Compile Include="Logging\LogFactory.cs" />
  58
+    <Compile Include="MultipleConvertersFoundException.cs" />
57 59
     <Compile Include="Persistence\StorageUnavailableException.cs" />
58 60
     <Compile Include="Serialization\IDocumentSerializer.cs" />
59 61
     <Compile Include="StreamNotFoundException.cs" />
64  src/proj/EventStore/ICommitEvents.cs
... ...
@@ -1,36 +1,36 @@
1  
-namespace EventStore
2  
-{
3  
-	using System;
4  
-	using System.Collections.Generic;
5  
-	using Persistence;
6  
-
7  
-	/// <summary>
8  
-	/// Indicates the ability to commit events and access events to and from a given stream.
  1
+namespace EventStore
  2
+{
  3
+	using System;
  4
+	using System.Collections.Generic;
  5
+	using Persistence;
  6
+
  7
+	/// <summary>
  8
+	/// Indicates the ability to commit events and access events to and from a given stream.
9 9
 	/// </summary>
10 10
 	/// <remarks>
11 11
 	/// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads.
12  
-	/// </remarks>
13  
-	public interface ICommitEvents
14  
-	{
15  
-		/// <summary>
16  
-		/// Gets the corresponding commits from the stream indicated starting at the revision specified until the
17  
-		/// end of the stream sorted in ascending order--from oldest to newest.
18  
-		/// </summary>
19  
-		/// <param name="streamId">The stream from which the events will be read.</param>
20  
-		/// <param name="minRevision">The minimum revision of the stream to be read.</param>
21  
-		/// <param name="maxRevision">The maximum revision of the stream to be read.</param>
22  
-		/// <returns>A series of committed events from the stream specified sorted in ascending order..</returns>
23  
-		/// <exception cref="StorageException" />
24  
-		/// <exception cref="StorageUnavailableException" />
25  
-		IEnumerable<Commit> GetFrom(Guid streamId, int minRevision, int maxRevision);
26  
-
27  
-		/// <summary>
28  
-		/// Writes the to-be-commited events provided to the underlying persistence mechanism.
29  
-		/// </summary>
30  
-		/// <param name="attempt">The series of events and associated metadata to be commited.</param>
31  
-		/// <exception cref="ConcurrencyException" />
32  
-		/// <exception cref="StorageException" />
33  
-		/// <exception cref="StorageUnavailableException" />
34  
-		void Commit(Commit attempt);
35  
-	}
  12
+	/// </remarks>
  13
+	public interface ICommitEvents
  14
+	{
  15
+		/// <summary>
  16
+		/// Gets the corresponding commits from the stream indicated starting at the revision specified until the
  17
+		/// end of the stream sorted in ascending order--from oldest to newest.
  18
+		/// </summary>
  19
+		/// <param name="streamId">The stream from which the events will be read.</param>
  20
+		/// <param name="minRevision">The minimum revision of the stream to be read.</param>
  21
+		/// <param name="maxRevision">The maximum revision of the stream to be read.</param>
  22
+		/// <returns>A series of committed events from the stream specified sorted in ascending order..</returns>
  23
+		/// <exception cref="StorageException" />
  24
+		/// <exception cref="StorageUnavailableException" />
  25
+		IEnumerable<Commit> GetFrom(Guid streamId, int minRevision, int maxRevision);
  26
+
  27
+		/// <summary>
  28
+		/// Writes the to-be-commited events provided to the underlying persistence mechanism.
  29
+		/// </summary>
  30
+		/// <param name="attempt">The series of events and associated metadata to be commited.</param>
  31
+		/// <exception cref="ConcurrencyException" />
  32
+		/// <exception cref="StorageException" />
  33
+		/// <exception cref="StorageUnavailableException" />
  34
+		void Commit(Commit attempt);
  35
+	}
36 36
 }
17  src/proj/EventStore/IConvertEvents.cs
... ...
@@ -0,0 +1,17 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Linq;
  4
+using System.Text;
  5
+
  6
+namespace EventStore
  7
+{
  8
+    /// <summary>
  9
+    /// Provides the ability to upconvert an event from one type to another.
  10
+    /// </summary>
  11
+    /// <typeparam name="TSource">The event type to convert from</typeparam>
  12
+    /// <typeparam name="TTarget">The event type to convert to</typeparam>
  13
+    public interface IConvertEvents<TSource, TTarget>
  14
+    {
  15
+        TTarget Convert(TSource sourceEvent);
  16
+    }
  17
+}
52  src/proj/EventStore/IPipelineHook.cs
... ...
@@ -1,33 +1,33 @@
1  
-namespace EventStore
  1
+namespace EventStore
2 2
 {
3 3
 	using System;
4 4
 
5  
-	/// <summary>
6  
-	/// Provides the ability to hook into the pipeline of persisting a commit.
  5
+	/// <summary>
  6
+	/// Provides the ability to hook into the pipeline of persisting a commit.
7 7
 	/// </summary>
8 8
 	/// <remarks>
9 9
 	/// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads.
10  
-	/// </remarks>
11  
-	public interface IPipelineHook : IDisposable
12  
-	{
13  
-		/// <summary>
14  
-		/// Hooks into the selection pipeline just prior to the commit being returned to the caller.
15  
-		/// </summary>
16  
-		/// <param name="committed">The commit to be filtered.</param>
17  
-		/// <returns>If successful, returns a populated commit; otherwise returns null.</returns>
18  
-		Commit Select(Commit committed);
19  
-
20  
-		/// <summary>
21  
-		/// Hooks into the commit pipeline prior to persisting the commit to durable storage.
22  
-		/// </summary>
23  
-		/// <param name="attempt">The attempt to be committed.</param>
24  
-		/// <returns>If processing should continue, returns true; otherwise returns false.</returns>
25  
-		bool PreCommit(Commit attempt);
26  
-
27  
-		/// <summary>
28  
-		/// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage.
29  
-		/// </summary>
30  
-		/// <param name="committed">The commit which has been persisted.</param>
31  
-		void PostCommit(Commit committed);
32  
-	}
  10
+	/// </remarks>
  11
+	public interface IPipelineHook : IDisposable
  12
+	{
  13
+		/// <summary>
  14
+		/// Hooks into the selection pipeline just prior to the commit being returned to the caller.
  15
+		/// </summary>
  16
+		/// <param name="committed">The commit to be filtered.</param>
  17
+		/// <returns>If successful, returns a populated commit; otherwise returns null.</returns>
  18
+		Commit Select(Commit committed);
  19
+
  20
+		/// <summary>
  21
+		/// Hooks into the commit pipeline prior to persisting the commit to durable storage.
  22
+		/// </summary>
  23
+		/// <param name="attempt">The attempt to be committed.</param>
  24
+		/// <returns>If processing should continue, returns true; otherwise returns false.</returns>
  25
+		bool PreCommit(Commit attempt);
  26
+
  27
+		/// <summary>
  28
+		/// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage.
  29
+		/// </summary>
  30
+		/// <param name="committed">The commit which has been persisted.</param>
  31
+		void PostCommit(Commit committed);
  32
+	}
33 33
 }
46  src/proj/EventStore/MultipleConvertersFoundException.cs
... ...
@@ -0,0 +1,46 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Linq;
  4
+using System.Runtime.Serialization;
  5
+using System.Text;
  6
+
  7
+namespace EventStore
  8
+{
  9
+    /// <summary>
  10
+    /// Represents the failure that occurs when there are two or more event converters created for the same source type.
  11
+    /// </summary>
  12
+    [Serializable]
  13
+    public class MultipleConvertersFoundException : Exception
  14
+    {
  15
+        /// <summary>
  16
+        /// Initializes a new instance of the MultipleConvertersFoundException class.
  17
+        /// </summary>
  18
+        public MultipleConvertersFoundException()
  19
+        {
  20
+        }
  21
+
  22
+        /// <summary>
  23
+        /// Initializes a new instance of the MultipleConvertersFoundException class.
  24
+        /// </summary>
  25
+        public MultipleConvertersFoundException(string message) 
  26
+            : base(message)
  27
+        {
  28
+        }
  29
+
  30
+        /// <summary>
  31
+        /// Initializes a new instance of the MultipleConvertersFoundException class.
  32
+        /// </summary>
  33
+        public MultipleConvertersFoundException(string message, Exception innerException) 
  34
+            : base(message, innerException)
  35
+        {
  36
+        }
  37
+
  38
+        /// <summary>
  39
+        /// Initializes a new instance of the MultipleConvertersFoundException class.
  40
+        /// </summary>
  41
+        protected MultipleConvertersFoundException(SerializationInfo info, StreamingContext context) 
  42
+            : base(info, context)
  43
+        {
  44
+        }
  45
+    }
  46
+}
2  src/tests/EventStore.Core.UnitTests/EventStore.Core.UnitTests.csproj
@@ -46,6 +46,7 @@
46 46
     </Compile>
47 47
     <Compile Include="DispatchCommitHookTests.cs" />
48 48
     <Compile Include="DispatcherTests\AsynchronousDispatcherTests.cs" />
  49
+    <Compile Include="EventUpconverterPipelineHookTests.cs" />
49 50
     <Compile Include="ExtensionMethods.cs" />
50 51
     <Compile Include="OptimisticCommitHookTests.cs" />
51 52
     <Compile Include="OptimisticEventStoreTests.cs" />
@@ -65,6 +66,7 @@
65 66
       <RequiredTargetFramework>4.0</RequiredTargetFramework>
66 67
       <HintPath>..\..\..\bin\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
67 68
     </Reference>
  69
+    <Reference Include="System" />
68 70
   </ItemGroup>
69 71
   <ItemGroup>
70 72
     <Content Include="..\..\proj\CustomDictionary.xml">
182  src/tests/EventStore.Core.UnitTests/EventUpconverterPipelineHookTests.cs
... ...
@@ -0,0 +1,182 @@
  1
+#pragma warning disable 169
  2
+// ReSharper disable InconsistentNaming
  3
+
  4
+namespace EventStore.Core.UnitTests
  5
+{
  6
+    using System;
  7
+    using System.Linq;
  8
+    using Machine.Specifications;
  9
+    using System.Collections.Generic;
  10
+    using System.Reflection;
  11
+    using It = Machine.Specifications.It;
  12
+
  13
+    [Subject("EventUpconverterPipelineHook")]
  14
+    public class when_opening_a_commit_that_does_not_have_convertible_events : using_event_converter
  15
+    {
  16
+        static readonly Commit commit = new Commit(
  17
+            Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null
  18
+        );
  19
+        static Commit converted;
  20
+
  21
+        Establish context = () =>
  22
+            commit.Events.Add(new EventMessage { Body = new NonConvertingEvent() });
  23
+
  24
+        Because of = () => 
  25
+            converted = EventUpconverter.Select(commit);
  26
+
  27
+        It should_not_be_converted = () =>
  28
+            converted.ShouldBeTheSameAs(commit);
  29
+
  30
+        It should_have_the_same_instance_of_the_event = () =>
  31
+            converted.Events.Single().ShouldEqual(commit.Events.Single());
  32
+    }
  33
+
  34
+    [Subject("EventUpconverterPipelineHook")]
  35
+    public class when_opening_a_commit_that_has_convertible_events : using_event_converter
  36
+    {
  37
+        static readonly Commit commit = new Commit(
  38
+            Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null
  39
+        );
  40
+        static Commit converted;
  41
+        static Guid id = Guid.NewGuid();
  42
+        static readonly EventMessage eventMessage = new EventMessage {
  43
+            Body = new ConvertingEvent(id)
  44
+        };
  45
+
  46
+        Establish context = () =>
  47
+            commit.Events.Add(eventMessage);
  48
+
  49
+        Because of = () =>
  50
+            converted = EventUpconverter.Select(commit);
  51
+
  52
+        It should_be_of_the_converted_type = () =>
  53
+            converted.Events.Single().Body.GetType().ShouldEqual(typeof(ConvertingEvent3));
  54
+
  55
+        It should_have_the_same_id_of_the_commited_event = () =>
  56
+            ((ConvertingEvent3)converted.Events.Single().Body).Id.ShouldEqual(id);
  57
+    }
  58
+
  59
+    [Subject("EventUpconverterPipelineHook")]
  60
+    public class when_an_event_converter_implements_the_IConvertEvents_interface_explicitly : using_event_converter
  61
+    {
  62
+        static readonly Commit commit = new Commit(
  63
+            Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null
  64
+        );
  65
+        static Commit converted;
  66
+        static readonly Guid id = Guid.NewGuid();
  67
+        static readonly EventMessage eventMessage = new EventMessage
  68
+        {
  69
+            Body = new ConvertingEvent2(id, "FooEvent")
  70
+        };
  71
+
  72
+        Establish context = () =>
  73
+            commit.Events.Add(eventMessage);
  74
+
  75
+        Because of = () =>
  76
+            converted = EventUpconverter.Select(commit);
  77
+
  78
+        It should_be_of_the_converted_type = () =>
  79
+            converted.Events.Single().Body.GetType().ShouldEqual(typeof(ConvertingEvent3));
  80
+
  81
+        It should_have_the_same_id_of_the_commited_event = () =>
  82
+            ((ConvertingEvent3)converted.Events.Single().Body).Id.ShouldEqual(id);
  83
+    }
  84
+
  85
+    public class using_event_converter
  86
+    {
  87
+        protected static IEnumerable<Assembly> assemblies;
  88
+        protected static Dictionary<Type, Func<object, object>> converters;
  89
+        protected static EventUpconverterPipelineHook EventUpconverter;
  90
+
  91
+        Establish context = () => {
  92
+            assemblies = getAllAssemblies();
  93
+            converters = GetConverters(assemblies);
  94
+            EventUpconverter = new EventUpconverterPipelineHook(converters);
  95
+        };
  96
+
  97
+        private static Dictionary<Type, Func<object, object>> GetConverters(IEnumerable<Assembly> toScan)
  98
+        {
  99
+            var c = from a in toScan
  100
+                    from t in a.GetTypes()
  101
+                    let i = t.GetInterface(typeof(IConvertEvents<,>).FullName)
  102
+                    where i != null
  103
+                    let sourceType = i.GetGenericArguments().First()
  104
+                    let convertMethod = i.GetMethod("Convert", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)
  105
+                    let instance = Activator.CreateInstance(t)
  106
+                    select new KeyValuePair<Type, Func<object, object>>(
  107
+                        sourceType,
  108
+                        e => convertMethod.Invoke(instance, new[] { e as object })
  109
+                    );
  110
+            try
  111
+            {
  112
+                return c.ToDictionary(x => x.Key, x => x.Value);
  113
+            }
  114
+            catch (ArgumentException ex)
  115
+            {
  116
+                throw new MultipleConvertersFoundException(ex.Message, ex);
  117
+            }
  118
+        }
  119
+
  120
+        private static IEnumerable<Assembly> getAllAssemblies()
  121
+        {
  122
+            return Assembly.GetCallingAssembly()
  123
+                .GetReferencedAssemblies()
  124
+                .Select(Assembly.Load)
  125
+                .Concat(new[] {Assembly.GetCallingAssembly()});
  126
+        }
  127
+    }
  128
+
  129
+    class ConvertingEventConverter : IConvertEvents<ConvertingEvent, ConvertingEvent2>
  130
+    {
  131
+        public ConvertingEvent2 Convert(ConvertingEvent sourceEvent)
  132
+        {
  133
+            return new ConvertingEvent2(sourceEvent.Id, "Temp");
  134
+        }
  135
+    }
  136
+
  137
+    class ExplicitConvertingEventConverter : IConvertEvents<ConvertingEvent2, ConvertingEvent3>
  138
+    {
  139
+        ConvertingEvent3 IConvertEvents<ConvertingEvent2, ConvertingEvent3>.Convert(ConvertingEvent2 sourceEvent)
  140
+        {
  141
+            return new ConvertingEvent3(sourceEvent.Id, "Temp", true);
  142
+        }
  143
+    }
  144
+    
  145
+    class NonConvertingEvent {}
  146
+    class ConvertingEvent
  147
+    {
  148
+        public Guid Id { get; set; }
  149
+
  150
+        public ConvertingEvent(Guid id)
  151
+        {
  152
+            Id = id;
  153
+        }
  154
+    }
  155
+    class ConvertingEvent2
  156
+    {
  157
+        public Guid Id { get; set; }
  158
+        public string Name { get; set; }
  159
+
  160
+        public ConvertingEvent2(Guid id, string name)
  161
+        {
  162
+            Id = id;
  163
+            Name = name;
  164
+        }
  165
+    }
  166
+    class ConvertingEvent3
  167
+    {
  168
+        public Guid Id { get; set; }
  169
+        public string Name { get; set; }
  170
+        public bool ImExplicit { get; set; }
  171
+
  172
+        public ConvertingEvent3(Guid id, string name, bool imExplicit)
  173
+        {
  174
+            Id = id;
  175
+            Name = name;
  176
+            ImExplicit = imExplicit;
  177
+        }
  178
+    }
  179
+}
  180
+
  181
+// ReSharper enable InconsistentNaming
  182
+#pragma warning restore 169
15  src/tests/EventStore.Core.UnitTests/OptimisticEventStoreTests.cs
@@ -382,21 +382,22 @@ public class when_disposing_the_event_store : using_persistence
382 382
 	public abstract class using_persistence
383 383
 	{
384 384
 		protected static Guid streamId = Guid.NewGuid();
385  
-		protected static Mock<IPersistStreams> persistence;
386  
-		protected static OptimisticEventStore store;
387  
-		protected static List<Mock<IPipelineHook>> pipelineHooks;
  385
+	    protected static Mock<IPersistStreams> persistence;
  386
+	    protected static OptimisticEventStore store;
  387
+	    protected static List<Mock<IPipelineHook>> pipelineHooks;
388 388
 
389  
-		Establish context = () =>
  389
+	    Establish context = () =>
390 390
 		{
391 391
 			persistence = new Mock<IPersistStreams>();
392 392
 			pipelineHooks = new List<Mock<IPipelineHook>>();
393  
-			store = new OptimisticEventStore(persistence.Object, pipelineHooks.Select(x => x.Object));
  393
+
  394
+            store = new OptimisticEventStore(persistence.Object, pipelineHooks.Select(x => x.Object));
394 395
 		};
395 396
 
396  
-		Cleanup everything = () =>
  397
+	    Cleanup everything = () =>
397 398
 			streamId = Guid.NewGuid();
398 399
 
399  
-		protected static Commit BuildCommitStub(Guid commitId)
  400
+	    protected static Commit BuildCommitStub(Guid commitId)
400 401
 		{
401 402
 			return new Commit(streamId, 1, commitId, 1, SystemTime.UtcNow, null, null);
402 403
 		}
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.