diff --git a/OrigoDB.EventStore.nuspec b/OrigoDB.EventStore.nuspec new file mode 100644 index 0000000..6f19a32 --- /dev/null +++ b/OrigoDB.EventStore.nuspec @@ -0,0 +1,25 @@ + + + + OrigoDB.EventStore + OrigoDB EventStore storage module + 0.1.0 + Robert Friberg + Copyright Devrex Labs + OrigoDB command journaling to the EventStore + OrigoDB command journaling to the EventStore + https://github.com/devrexlabs/modules.eventstore + https://github.com/devrexlabs/origodb#license + false + https://github.com/devrexlabs/modules.eventstore/releases + in-memory imdb nosql odbms database imc + + + + + + + + + + diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..a86f99c --- /dev/null +++ b/build.cmd @@ -0,0 +1,18 @@ +@echo off + +:Build +cls + +if not exist tools\Cake\Cake.exe ( + echo Installing Cake... + nuget.exe install Cake -OutputDirectory tools -ExcludeVersion -NonInteractive + echo. +) + +SET BUILDMODE="Release" +IF NOT [%1]==[] (set BUILDMODE="%1") + +echo Starting Cake... +"tools\Cake\Cake.exe" "build.csx" "-verbosity=verbose" "-config=%BUILDMODE%" + +exit /b %errorlevel% diff --git a/build.csx b/build.csx new file mode 100644 index 0000000..55c7141 --- /dev/null +++ b/build.csx @@ -0,0 +1,93 @@ +using System.Text.RegularExpressions; + +var target = Argument("target", "NuGet"); +var config = Argument("config", "Release"); +var output = "./build"; +var version = ParseVersion("./src/OrigoDB.Modules.EventStore/Properties/AssemblyInfo.cs"); + +if(version == null) +{ + // We make sure the version is set. + throw new InvalidOperationException("Could not parse version."); +} + +//////////////////////////////////////////////// +// TASKS +//////////////////////////////////////////////// + +Task("Clean") + .Does(() => +{ + CleanDirectory(output); +}); + + Task("Restore-NuGet-Packages") + .IsDependentOn("Clean") + .Does(() => +{ + NuGetRestore("./src/OrigoDB.Modules.EventStore.sln"); +}); + +Task("Build") + .IsDependentOn("Clean") + .Does(() => +{ + MSBuild("./src/OrigoDB.Modules.EventStore/OrigoDB.Modules.EventStore.csproj", settings => + settings.SetConfiguration(config) + .UseToolVersion(MSBuildToolVersion.VS2012) + .WithTarget("clean") + .WithTarget("build")); +}); + + +Task("Copy") + .IsDependentOn("Build") + .Does(() => +{ + var pattern = "src/OrigoDB.*/bin/" + config + "/OrigoDB.*.dll"; + CopyFiles(pattern, output); +}); + +Task("Zip") + .IsDependentOn("Copy") + .Does(() => +{ + var root = "./build/"; + var output = "./build/OrigoDB.Modules.EventStore.binaries." + version + "-" + config + ".zip"; + var files = root + "/*"; + + // Package the bin folder. + Zip(root, output); +}); + +Task("NuGet") + .IsDependentOn("Zip") + .Does(() => +{ + NuGetPack("./OrigoDB.EventStore.nuspec", new NuGetPackSettings { + Version = version, + OutputDirectory = "./build", + Symbols = true + }); +}); + +//////////////////////////////////////////////// +// RUN TASKS +//////////////////////////////////////////////// + +RunTarget(target); + +//////////////////////////////////////////////// +// UTILITIES +//////////////////////////////////////////////// + +private string ParseVersion(string filename) +{ + var file = FileSystem.GetFile(filename); + using(var reader = new StreamReader(file.OpenRead())) + { + var text = reader.ReadToEnd(); + Regex regex = new Regex(@"AssemblyVersion\(""(?\d+\.\d+\.\d+)""\)"); + return regex.Match(text).Groups["theversionnumber"].Value; + } +} diff --git a/src/OrigoDB.Modules.EventStore.Test/ESCommandStoreTests.cs b/src/OrigoDB.Modules.EventStore.Test/ESCommandStoreTests.cs new file mode 100644 index 0000000..8c635eb --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.Test/ESCommandStoreTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Net; +using NUnit.Framework; +using OrigoDB.Core; +using OrigoDB.Core.Proxy; +using OrigoDB.Core.Test; + +namespace OrigoDB.Modules.EventStore.Test +{ + [TestFixture] + public class ESCommandStoreTests + { + + [Test] + public void SmokeTest2() + { + var config = new EngineConfiguration().ForIsolatedTest(); + var endPoint = new IPEndPoint(IPAddress.Loopback, 1113); + var streamName = "origodb.SmokeTest2---" + Guid.NewGuid(); + config.SetCommandStoreFactory(cfg => new ESCommandStore(cfg, endPoint, streamName)); + var engine = Engine.Create(config); + engine.Execute(new TestCommand()); + engine.Execute(new TestCommand()); + engine.Close(); + + engine = Engine.Load(config); + var db = engine.GetProxy(); + Assert.AreEqual(db.GetState(),2); + var state = engine.Execute(m => m.State); + Assert.AreEqual(2, state); + engine.Close(); + } + + + [Test] + public void SmokeTest() + { + var config = new EngineConfiguration().ForIsolatedTest(); + var endPoint = new IPEndPoint(IPAddress.Loopback, 1113); + var store = new ESCommandStore(config, endPoint, "origodb.SmokeTest-" + Guid.NewGuid().ToString()); + store.Initialize(); + var timeStamp = new DateTime(2000,1,1); + + var appender = JournalAppender.Create(1, store); + + //write 100 entries + for (ulong i = 0; i < 100; i++) + { + appender.Append(new ProxyCommand("fish", null){Timestamp = timeStamp.AddMinutes(i)}); + } + + //read from beginning + int numRead = 0; + foreach (var journalEntry in store.GetJournalEntriesFrom(0)) + { + Assert.IsInstanceOf>(journalEntry); + Assert.AreEqual(journalEntry.Created, timeStamp.AddMinutes(numRead)); + numRead++; + Assert.AreEqual(journalEntry.Id, numRead); + Console.WriteLine(journalEntry.Id); + } + Assert.AreEqual(100, numRead); + } + } +} \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore.Test/OrigoDB.Modules.EventStore.Test.csproj b/src/OrigoDB.Modules.EventStore.Test/OrigoDB.Modules.EventStore.Test.csproj new file mode 100644 index 0000000..a9c3b9f --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.Test/OrigoDB.Modules.EventStore.Test.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {BE04EFB2-BA58-42CC-BB0D-028F4E70490C} + Library + Properties + OrigoDB.Modules.EventStore.Test + OrigoDB.Modules.EventStore.Test + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\OrigoDB.Modules.EventStore\packages\NUnit.2.6.3\lib\nunit.framework.dll + + + False + ..\OrigoDB.Modules.EventStore\packages\OrigoDB.Core.0.15.0\lib\net40\OrigoDB.Core.dll + + + + + + + + + + + + + + + + + + + + + {8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC} + OrigoDB.Modules.EventStore + + + + + \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore.Test/Properties/AssemblyInfo.cs b/src/OrigoDB.Modules.EventStore.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9990b3b --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OrigoDB.Modules.EventStore.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OrigoDB.Modules.EventStore.Test")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bfdb1969-349d-4028-a609-a1eaf327fe51")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/OrigoDB.Modules.EventStore.Test/TestCommand.cs b/src/OrigoDB.Modules.EventStore.Test/TestCommand.cs new file mode 100644 index 0000000..f69ece5 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.Test/TestCommand.cs @@ -0,0 +1,15 @@ +using System; +using OrigoDB.Core; + +namespace OrigoDB.Modules.EventStore.Test +{ + [Serializable] + public class TestCommand : Command + { + + public override void Execute(TestModel model) + { + model.State++; + } + } +} diff --git a/src/OrigoDB.Modules.EventStore.Test/TestModel.cs b/src/OrigoDB.Modules.EventStore.Test/TestModel.cs new file mode 100644 index 0000000..da8fec4 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.Test/TestModel.cs @@ -0,0 +1,21 @@ +using System; +using OrigoDB.Core; + +namespace OrigoDB.Modules.EventStore.Test +{ + [Serializable] + public class TestModel : Model + { + public int State { get; set; } + + public int GetState() + { + return State; + } + + public void SetState(int state) + { + State = state; + } + } +} \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore.Test/packages.config b/src/OrigoDB.Modules.EventStore.Test/packages.config new file mode 100644 index 0000000..4922689 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.Test/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore.sln b/src/OrigoDB.Modules.EventStore.sln new file mode 100644 index 0000000..c68b521 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrigoDB.Modules.EventStore", "OrigoDB.Modules.EventStore\OrigoDB.Modules.EventStore.csproj", "{8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrigoDB.Modules.EventStore.Test", "OrigoDB.Modules.EventStore.Test\OrigoDB.Modules.EventStore.Test.csproj", "{BE04EFB2-BA58-42CC-BB0D-028F4E70490C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC}.Release|Any CPU.Build.0 = Release|Any CPU + {BE04EFB2-BA58-42CC-BB0D-028F4E70490C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE04EFB2-BA58-42CC-BB0D-028F4E70490C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE04EFB2-BA58-42CC-BB0D-028F4E70490C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE04EFB2-BA58-42CC-BB0D-028F4E70490C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/OrigoDB.Modules.EventStore/ESCommandStore.cs b/src/OrigoDB.Modules.EventStore/ESCommandStore.cs new file mode 100644 index 0000000..5a324e8 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore/ESCommandStore.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using EventStore.ClientAPI; +using OrigoDB.Core; +using OrigoDB.Core.Storage; + +namespace OrigoDB.Modules.EventStore +{ + public class ESCommandStore : CommandStore + { + + //Number of events per read from the event store + private const int ReadChunkSize = 200; + + readonly string _streamName; + private readonly IPEndPoint _endPoint; + + public ESCommandStore(EngineConfiguration config, IPEndPoint endPoint, string streamName): base(config) + { + _endPoint = endPoint; + _formatter = config.CreateFormatter(FormatterUsage.Journal); + _streamName = streamName; + } + + public override IEnumerable GetJournalEntriesBeforeOrAt(DateTime pointInTime) + { + return GetJournalEntriesFrom(0).TakeWhile(entry => entry.Created <= pointInTime); + } + + public override IEnumerable GetJournalEntriesFrom(ulong entryId) + { + if (entryId > Int32.MaxValue) throw new InvalidOperationException("EventStore storage event id overflow"); + + using (var connection = EventStoreConnection.Create(_endPoint)) + { + connection.Connect(); + StreamEventsSlice currentSlice; + var nextSliceStart = (int) entryId; + do + { + currentSlice = connection.ReadStreamEventsForward(_streamName, nextSliceStart, ReadChunkSize, false); + //if (currentSlice.Status != SliceReadStatus.StreamNotFound) throw new Exception("Can't read stream: " + currentSlice.Status); + nextSliceStart = currentSlice.NextEventNumber; + foreach (var resolvedEvent in currentSlice.Events) + { + yield return _formatter.FromByteArray(resolvedEvent.Event.Data); + } + + } while (!currentSlice.IsEndOfStream); + } + } + + protected override IJournalWriter CreateStoreSpecificJournalWriter() + { + var connection = EventStoreConnection.Create(_endPoint); + connection.Connect(); + var formatter = _config.CreateFormatter(FormatterUsage.Journal); + return new ESJournalWriter(connection, formatter, _streamName); + } + + public override Stream CreateJournalWriterStream(ulong firstEntryId = 1) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore/ESJournalWriter.cs b/src/OrigoDB.Modules.EventStore/ESJournalWriter.cs new file mode 100644 index 0000000..66ba31a --- /dev/null +++ b/src/OrigoDB.Modules.EventStore/ESJournalWriter.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.Serialization; +using EventStore.ClientAPI; +using OrigoDB.Core; + +namespace OrigoDB.Modules.EventStore +{ + public class ESJournalWriter : IJournalWriter + { + + readonly IEventStoreConnection _connection; + readonly IFormatter _formatter; + readonly string _streamName; + + public ESJournalWriter(IEventStoreConnection conn, IFormatter formatter, string streamName) + { + _connection = conn; + _formatter = formatter; + _streamName = streamName; + } + + public void Close() + { + _connection.Close(); + } + + public void Write(JournalEntry item) + { + var bytes = _formatter.ToByteArray(item); + const bool isJson = false; + var eventData = new EventData(Guid.NewGuid(), "OrigoDB.JournalEntry", isJson, bytes, null); + _connection.AppendToStream(_streamName, ExpectedVersion.Any, eventData); + } + + public void Dispose() + { + _connection.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore/OrigoDB.Modules.EventStore.csproj b/src/OrigoDB.Modules.EventStore/OrigoDB.Modules.EventStore.csproj new file mode 100644 index 0000000..7390765 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore/OrigoDB.Modules.EventStore.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {8BDD533B-0E1A-4BC5-A70C-9DA8D7BEBDFC} + Library + Properties + OrigoDB.Modules.EventStore + OrigoDB.Modules.EventStore + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\EventStore.Client.2.0.2\lib\net40\EventStore.ClientAPI.dll + + + False + ..\packages\OrigoDB.Core.0.15.0\lib\net40\OrigoDB.Core.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/OrigoDB.Modules.EventStore/Properties/AssemblyInfo.cs b/src/OrigoDB.Modules.EventStore/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..479c390 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OrigoDB.Modules.EventStore")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OrigoDB.Modules.EventStore")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("33644590-5d2c-40d3-b7d9-8ef60688707a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.1.0")] +[assembly: AssemblyFileVersion("0.1.0")] diff --git a/src/OrigoDB.Modules.EventStore/packages.config b/src/OrigoDB.Modules.EventStore/packages.config new file mode 100644 index 0000000..e0b3af8 --- /dev/null +++ b/src/OrigoDB.Modules.EventStore/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tools/Cake/Cake.Common.dll b/tools/Cake/Cake.Common.dll new file mode 100644 index 0000000..524922b Binary files /dev/null and b/tools/Cake/Cake.Common.dll differ diff --git a/tools/Cake/Cake.Core.dll b/tools/Cake/Cake.Core.dll new file mode 100644 index 0000000..6db9757 Binary files /dev/null and b/tools/Cake/Cake.Core.dll differ diff --git a/tools/Cake/Cake.exe b/tools/Cake/Cake.exe new file mode 100644 index 0000000..90bac0b Binary files /dev/null and b/tools/Cake/Cake.exe differ diff --git a/tools/Cake/LICENSE b/tools/Cake/LICENSE new file mode 100644 index 0000000..ad36158 --- /dev/null +++ b/tools/Cake/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Patrik Svensson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tools/Cake/NuGet.Core.dll b/tools/Cake/NuGet.Core.dll new file mode 100644 index 0000000..2ec7652 Binary files /dev/null and b/tools/Cake/NuGet.Core.dll differ diff --git a/tools/Cake/Roslyn.Compilers.CSharp.dll b/tools/Cake/Roslyn.Compilers.CSharp.dll new file mode 100644 index 0000000..9da1db2 Binary files /dev/null and b/tools/Cake/Roslyn.Compilers.CSharp.dll differ diff --git a/tools/Cake/Roslyn.Compilers.dll b/tools/Cake/Roslyn.Compilers.dll new file mode 100644 index 0000000..bf7fb3e Binary files /dev/null and b/tools/Cake/Roslyn.Compilers.dll differ diff --git a/tools/nuget/NuGet.exe b/tools/nuget/NuGet.exe new file mode 100644 index 0000000..19efb10 Binary files /dev/null and b/tools/nuget/NuGet.exe differ