From b3b02763905a5c1320ae15061214c5d0b7c14901 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Wed, 25 Jan 2017 04:19:40 +0000 Subject: [PATCH 01/61] Updated README to provide detailed usage scenarios --- README.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6695f5d..538d3db 100644 --- a/README.md +++ b/README.md @@ -1 +1,90 @@ -# SimpleEventStore \ No newline at end of file +# Simple Event Store + +Simple Event Store (SES) provides a lightweight event sourcing abstraction. + +## Key Features +- Full support for async/await for all persistence engines +- Optimistic concurrency for append operations +- Reading streams forward +- Catch up subscriptions + +## Persistence Engines +SES supports the following +- Azure DocumentDB +- In Memory + +All persistence engines run the same test scenarios to ensure feature parity. At present only the DocumentDB persistence engine should be considered for production usage. + +## Usage + +### Creation +```csharp +var storageEngine = new InMemoryStorageEngine(); +var eventStore = new EventStore(storageEngine); +``` +Do not use storage engines directly, only interact with the event store using the EventStore class. There should only be a single instance of the EventStore per process. Creating transient instances will lead to a decrease in performance. + +### Appending Events +```csharp +var expectedRevision = 0; + +await eventStore.AppendToStream(streamId, expectedRevision, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); +``` +The expected stream revision would either be set to 0 for a new stream, or to the expected event number. If the latest event number in the database differs then a concurrency exception will be thrown. + +### Reading Events +```csharp +var events = await eventStore.ReadStreamForwards(streamId, startPosition: 2, numberOfEventsToRead: 1); +// or +var events = await subject.ReadStreamForwards(streamId); +``` +You can either read all events in a stream, or a subset of events. Only read all events if you know the maximum size of a stream is going to be low and that you always need to read all events as part of your workload e.g. replaying events to project current state for a DDD aggregate. + +### Catch up subscriptions +```csharp +eventStore.SubscribeToAll( + (events, checkpoint) => + { + // Process events... + // Then store the checkpoint... + }, + ""); +``` +Catch up subscriptions allow you to subscribe to events after they have been written to the event store. If no previous checkpoint is supplied then the subscription will process all events in the event store. + +Storing the checkpoint allows you to resume a subscription in the case of process failure and should only be done once all events supplied in the callback have been processed. This ensures that events are not missed. + +It is possible to have multiple catch up subscriptions running, for example to +- Publish messages onto a service bus +- Populate a read model + +Each subscription **must** perform an independant task. It is not possible to run multiple instances of the same catch up subscription as it would mean high amounts of duplicate processing as each process instance would read the same checkpoint. + +The number of events received will vary depending on the storage engine configuration. + +## DocumentDB +```csharp +DocumentClient client; // Set this up as required +var databaseName = "MyEventStore"; + +var storageEngine = new AzureDocumentDbStorageEngine( + client, + databaseName, + new DatabaseOptions(ConsistencyLevel.BoundedStaleness, 400), + new SubscriptionOptions( + maxItemCount: 1, + pollEvery: TimeSpan.FromSeconds(0.5))); + +eventStore.Initialise(); +``` +The DatabaseOptions allow you to specify the consistency level of the database along with the default number of RUs. + +Only use one of the following consistency levels +- Strong +- Bounded Staleness - use this if you need to geo-replicate the database + +The SubscriptionOptions allow you to configure how catch up subscriptions work +- Max Item Count - the number of events to pull back in a polling operation +- Poll Every - how long to wait after a polling operation has completed + +Calling the Initialise operation creates the underlying collection based on the DatabaseOptions. This ensures the required stored procedure is present too. It is safe to call this multiple times. \ No newline at end of file From 754d3ae3b614a5d9bf5b4d40b60453ee6399a72e Mon Sep 17 00:00:00 2001 From: "mike.gore" Date: Thu, 9 Mar 2017 11:44:59 +0000 Subject: [PATCH 02/61] Added support for parameterising the build --- build/build.cake | 15 +++++++++++++-- build/build.ps1 | 6 ++++-- .../App.config | 7 +++++++ .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 2 ++ .../StorageEngineFactory.cs | 5 +++-- 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config diff --git a/build/build.cake b/build/build.cake index 8076d0a..2b202c5 100644 --- a/build/build.cake +++ b/build/build.cake @@ -5,6 +5,8 @@ var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); +var uri = Argument("uri", "https://localhost:8081/"); +var authKey = Argument("authKey", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); ////////////////////////////////////////////////////////////////////// // PREPARATION @@ -12,6 +14,7 @@ var configuration = Argument("configuration", "Release"); // Define directories. var solutionFile = "../src/SimpleEventStore/SimpleEventStore.sln"; +var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/SimpleEventStore.AzureDocumentDb.Tests.dll.config"); ////////////////////////////////////////////////////////////////////// // TASKS @@ -27,12 +30,20 @@ Task("Build") .IsDependentOn("Restore-NuGet-Packages") .Does(() => { - // Use MSBuild - MSBuild(solutionFile, settings => settings.SetConfiguration(configuration)); + // Use MSBuild + MSBuild(solutionFile, settings => settings.SetConfiguration(configuration)); +}); + +Task("Transform-Unit-Test-Config") + .Does(() => +{ + XmlPoke(documentDbTestConfigFile, "/configuration/appSettings/add[@key = 'Uri']/@value", uri); + XmlPoke(documentDbTestConfigFile, "/configuration/appSettings/add[@key = 'AuthKey']/@value", authKey); }); Task("Run-Unit-Tests") .IsDependentOn("Build") + .IsDependentOn("Transform-Unit-Test-Config") .Does(() => { XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll"); diff --git a/build/build.ps1 b/build/build.ps1 index 44de579..2c2acc1 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -44,7 +44,9 @@ Param( [string]$Target = "Default", [ValidateSet("Release", "Debug")] [string]$Configuration = "Release", - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Uri, + [string]$AuthKey, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] [string]$Verbosity = "Verbose", [switch]$Experimental, [Alias("DryRun","Noop")] @@ -185,5 +187,5 @@ if (!(Test-Path $CAKE_EXE)) { # Start Cake Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey $UseMono $UseDryRun $UseExperimental $ScriptArgs" exit $LASTEXITCODE \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config new file mode 100644 index 0000000..4fe9479 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index bef89c8..f93476b 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -41,6 +41,7 @@ True + @@ -73,6 +74,7 @@ + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index 97ec149..0c32d29 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -12,8 +13,8 @@ internal static class StorageEngineFactory { internal static async Task Create(string databaseName) { - var documentDbUri = "https://localhost:8081/"; - var authKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; + var documentDbUri = ConfigurationManager.AppSettings["Uri"]; + var authKey = ConfigurationManager.AppSettings["AuthKey"]; DocumentClient client = new DocumentClient(new Uri(documentDbUri), authKey); var storageEngine = new AzureDocumentDbStorageEngine(client, databaseName, new DatabaseOptions(ConsistencyLevel.BoundedStaleness, 400), new SubscriptionOptions(maxItemCount: 1, pollEvery: TimeSpan.FromSeconds(0.5))); From ac92bb745a677111a401a84f874685474f9e8f6d Mon Sep 17 00:00:00 2001 From: "mike.gore" Date: Thu, 9 Mar 2017 12:57:11 +0000 Subject: [PATCH 03/61] Enabled nuget packaging & deploy --- build/build.cake | 29 +++++++++++++++++++ build/build.ps1 | 4 ++- .../Properties/AssemblyInfo.cs | 4 +-- .../SimpleEventStore.AzureDocumentDb.nuspec | 13 +++++++++ .../Properties/AssemblyInfo.cs | 4 +-- .../SimpleEventStore/SimpleEventStore.csproj | 3 ++ .../SimpleEventStore/SimpleEventStore.nuspec | 13 +++++++++ 7 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.nuspec create mode 100644 src/SimpleEventStore/SimpleEventStore/SimpleEventStore.nuspec diff --git a/build/build.cake b/build/build.cake index 2b202c5..662bab6 100644 --- a/build/build.cake +++ b/build/build.cake @@ -49,6 +49,35 @@ Task("Run-Unit-Tests") XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll"); }); +Task("Package") + .IsDependentOn("Run-Unit-Tests") + .Does(() => +{ + NuGetPack("./../src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj", new NuGetPackSettings() { + ArgumentCustomization = args => args.Append("-Prop Configuration=" + configuration) + }); + + NuGetPack("./../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj", new NuGetPackSettings() { + ArgumentCustomization = args => args.Append("-Prop Configuration=" + configuration) + }); +}); + + +Task("Deploy") + .IsDependentOn("Package") + .Does(() => +{ + var nugetSource = Argument("nugetSource"); + var nugetApiKey = Argument("nugetApiKey"); + + var package = GetFiles("./*.nupkg"); + + NuGetPush(package, new NuGetPushSettings { + Source = nugetSource, + ApiKey = nugetApiKey + }); +}); + ////////////////////////////////////////////////////////////////////// // TASK TARGETS ////////////////////////////////////////////////////////////////////// diff --git a/build/build.ps1 b/build/build.ps1 index 2c2acc1..c9c91e7 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -46,6 +46,8 @@ Param( [string]$Configuration = "Release", [string]$Uri, [string]$AuthKey, + [string]$NugetSource, + [string]$NugetApiKeySource, [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] [string]$Verbosity = "Verbose", [switch]$Experimental, @@ -187,5 +189,5 @@ if (!(Test-Path $CAKE_EXE)) { # Start Cake Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey $UseMono $UseDryRun $UseExperimental $ScriptArgs" +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey -nugetSource=$NugetSource -nugetApiKey=$NugetApiKey $UseMono $UseDryRun $UseExperimental $ScriptArgs" exit $LASTEXITCODE \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs index d71c533..7a7b97b 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs @@ -6,9 +6,9 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("SimpleEventStore.AzureDocumentDb")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Provides a DocumentDB storage engine for Simple Event Store (SES)")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("ASOS")] [assembly: AssemblyProduct("SimpleEventStore.AzureDocumentDb")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.nuspec b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.nuspec new file mode 100644 index 0000000..f0c293c --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.nuspec @@ -0,0 +1,13 @@ + + + + Asos.SimpleEventStore.AzureDocumentDb + $version$ + $author$ + $author$ + https://github.com/ASOS/SimpleEventStore + false + $description$ + eventsourcing documentdb azure + + \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs index 3def222..6995b62 100644 --- a/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs +++ b/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs @@ -6,9 +6,9 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("SimpleEventStore")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Simple Event Store (SES) provides a lightweight event sourcing abstraction.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("ASOS")] [assembly: AssemblyProduct("SimpleEventStore")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index 7cae1d4..f608ad1 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -49,6 +49,9 @@ + + + + + SimpleEventStore.AzureDocumentDb.Tests + ASOS + Copyright ASOS ©2016 + SimpleEventStore.AzureDocumentDb.Tests + \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index 7c18aed..ff0e906 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -1,8 +1,9 @@ using System; -using System.Configuration; +using System.IO; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; +using Microsoft.Extensions.Configuration; namespace SimpleEventStore.AzureDocumentDb.Tests { @@ -10,9 +11,14 @@ internal static class StorageEngineFactory { internal static async Task Create(string databaseName) { - var documentDbUri = ConfigurationManager.AppSettings["Uri"]; - var authKey = ConfigurationManager.AppSettings["AuthKey"]; - var consistencyLevel = ConfigurationManager.AppSettings["ConsistencyLevel"]; + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var documentDbUri = config["Uri"]; + var authKey = config["AuthKey"]; + var consistencyLevel = config["ConsistencyLevel"]; ConsistencyLevel consistencyLevelEnum; if(!Enum.TryParse(consistencyLevel, true, out consistencyLevelEnum)) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json new file mode 100644 index 0000000..141db5a --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json @@ -0,0 +1,5 @@ +{ + "Uri": "https://localhost:8082/", + "AuthKey": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", + "ConsistencyLevel": "BoundedStaleness" +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/packages.config b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/packages.config deleted file mode 100644 index e9cbf15..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs deleted file mode 100644 index 7a7b97b..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -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("SimpleEventStore.AzureDocumentDb")] -[assembly: AssemblyDescription("Provides a DocumentDB storage engine for Simple Event Store (SES)")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("ASOS")] -[assembly: AssemblyProduct("SimpleEventStore.AzureDocumentDb")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[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("48c71940-d9b0-446a-9f3d-e6275cd43440")] - -// 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/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs index 78b3312..72bbbe4 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs @@ -10,6 +10,7 @@ namespace SimpleEventStore.AzureDocumentDb { using System; + using System.Reflection; /// @@ -39,7 +40,7 @@ internal Scripts() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SimpleEventStore.AzureDocumentDb.Scripts", typeof(Scripts).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SimpleEventStore.AzureDocumentDb.Scripts", typeof(Scripts).GetTypeInfo().Assembly); resourceMan = temp; } return resourceMan; @@ -62,19 +63,18 @@ internal Scripts() { /// /// Looks up a localized string similar to function appendToStream(documents) { - /// var context = getContext(); - /// var collection = context.getCollection(); - /// var collectionLink = collection.getSelfLink(); - /// - /// var index = 0; - /// - /// createDocument(documents[index]); - /// - /// // NOTE: isAccepted states if the write is going to be processed - /// function createDocument(document) { - /// var accepted = collection.createDocument(collectionLink, document, onDocumentCreated); - /// - /// if (!accepted) throw new Error("Document not accepted for creation."); /// [rest of string was truncated]";. + /// var context = getContext(); + /// var collection = context.getCollection(); + /// var collectionLink = collection.getSelfLink(); + /// var streamId = documents[0].streamId; + /// var expectedEventNumber = documents[0].eventNumber - 1; + /// + /// var concurrencyQuery = { + /// query: "SELECT TOP 1 c.eventNumber FROM Commits c WHERE c.streamId = @streamId ORDER BY c.eventNumber DESC", + /// parameters: [{ name: "@streamId", value: streamId }] + /// }; + /// + /// var accepted [rest of string was truncated]";. /// internal static string appendToStream { get { diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 762edfb..01850aa 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -1,98 +1,39 @@  - - + - Debug - AnyCPU - {48C71940-D9B0-446A-9F3D-E6275CD43440} - Library - Properties - SimpleEventStore.AzureDocumentDb - SimpleEventStore.AzureDocumentDb - v4.6.1 - 512 - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + netstandard1.6 - - ..\packages\Microsoft.Azure.DocumentDB.1.11.1\lib\net45\Microsoft.Azure.Documents.Client.dll - True - - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True - - - - - - - - - + - - - - - - True + + + + True + True Scripts.resx - - - - - - - - - {73235465-69bf-4762-b8c5-20c8e45795ff} - SimpleEventStore - - + ResXFileCodeGenerator Scripts.Designer.cs - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + SimpleEventStore.AzureDocumentDb + Provides a DocumentDB storage engine for Simple Event Store (SES) + ASOS + Copyright ASOS ©2016 + SimpleEventStore.AzureDocumentDb + Asos.SimpleEventStore + + eventsourcing documentdb azure + https://github.com/ASOS/SimpleEventStore + + library + + \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/packages.config b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/packages.config deleted file mode 100644 index 7654e62..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index d6b7c1c..0000000 --- a/src/SimpleEventStore/SimpleEventStore.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -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("SimpleEventStore.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SimpleEventStore.Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[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("aca6b3ae-fcb9-45f4-9d6b-66196f98f819")] - -// 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/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index ab53098..83d5997 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -1,95 +1,19 @@  - - + - Debug - AnyCPU - {ACA6B3AE-FCB9-45F4-9D6B-66196F98F819} - Library - Properties - SimpleEventStore.Tests - SimpleEventStore.Tests - v4.6.1 - 512 - - + netcoreapp1.1 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll - True - - - ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll - True - - - ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - - - - - - - - - - {73235465-69bf-4762-b8c5-20c8e45795ff} - SimpleEventStore - + - + + - - + + SimpleEventStore.Tests + ASOS + Copyright ASOS ©2016 + SimpleEventStore.Tests + \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/packages.config b/src/SimpleEventStore/SimpleEventStore.Tests/packages.config deleted file mode 100644 index d0bd528..0000000 --- a/src/SimpleEventStore/SimpleEventStore.Tests/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/ConcurrencyException.cs b/src/SimpleEventStore/SimpleEventStore/ConcurrencyException.cs index 57aa435..2356b90 100644 --- a/src/SimpleEventStore/SimpleEventStore/ConcurrencyException.cs +++ b/src/SimpleEventStore/SimpleEventStore/ConcurrencyException.cs @@ -1,9 +1,7 @@ using System; -using System.Runtime.Serialization; namespace SimpleEventStore { - [Serializable] public class ConcurrencyException : Exception { // @@ -24,11 +22,5 @@ public ConcurrencyException(string message) : base(message) public ConcurrencyException(string message, Exception inner) : base(message, inner) { } - - protected ConcurrencyException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } } } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs deleted file mode 100644 index 6995b62..0000000 --- a/src/SimpleEventStore/SimpleEventStore/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -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("SimpleEventStore")] -[assembly: AssemblyDescription("Simple Event Store (SES) provides a lightweight event sourcing abstraction.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("ASOS")] -[assembly: AssemblyProduct("SimpleEventStore")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[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("73235465-69bf-4762-b8c5-20c8e45795ff")] - -// 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/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index f608ad1..a3a9434 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -1,63 +1,17 @@  - - + - Debug - AnyCPU - {73235465-69BF-4762-B8C5-20C8E45795FF} - Library - Properties - SimpleEventStore - SimpleEventStore - v4.6.1 - 512 + netstandard1.6 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + + SimpleEventStore + Simple Event Store (SES) provides a lightweight event sourcing abstraction. + ASOS + Copyright ASOS ©2016 + SimpleEventStore + Asos.SimpleEventStore + + eventsourcing documentdb azure + https://github.com/ASOS/SimpleEventStore - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.nuspec b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.nuspec deleted file mode 100644 index 58154e9..0000000 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.nuspec +++ /dev/null @@ -1,13 +0,0 @@ - - - - Asos.SimpleEventStore - $version$ - $author$ - $author$ - https://github.com/ASOS/SimpleEventStore - false - $description$ - eventsourcing documentdb azure - - \ No newline at end of file From 1fb2ccdbd3b0945a86e796421a92fa7a98b7d99e Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 27 Jun 2017 07:01:19 +0100 Subject: [PATCH 14/61] Current WIP on build script - not complete. Need to move to use dotnet CLI. --- build/build.cake | 25 +++++++++---------- build/tools/packages.config | 2 +- ...pleEventStore.AzureDocumentDb.Tests.csproj | 5 ++++ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/build/build.cake b/build/build.cake index 8c91bc8..6b20f45 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,4 +1,6 @@ #tool "nuget:?package=xunit.runner.console" +#addin "Cake.Json" + ////////////////////////////////////////////////////////////////////// // ARGUMENTS ////////////////////////////////////////////////////////////////////// @@ -15,32 +17,28 @@ var consistencyLevel = Argument("consistencyLevel", "BoundedStaleness"); // Define directories. var solutionFile = "../src/SimpleEventStore/SimpleEventStore.sln"; -var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/SimpleEventStore.AzureDocumentDb.Tests.dll.config"); +var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appSettings.json"); ////////////////////////////////////////////////////////////////////// // TASKS ////////////////////////////////////////////////////////////////////// -Task("Restore-NuGet-Packages") - .Does(() => -{ - NuGetRestore(solutionFile); -}); - Task("Build") - .IsDependentOn("Restore-NuGet-Packages") .Does(() => { - // Use MSBuild - MSBuild(solutionFile, settings => settings.SetConfiguration(configuration)); + // TODO: Use dotnet CLI tools + MSBuild(solutionFile, settings => settings.SetConfiguration(configuration).UseToolVersion(MSBuildToolVersion.VS2017)); }); Task("Transform-Unit-Test-Config") .Does(() => { - XmlPoke(documentDbTestConfigFile, "/configuration/appSettings/add[@key = 'Uri']/@value", uri); - XmlPoke(documentDbTestConfigFile, "/configuration/appSettings/add[@key = 'AuthKey']/@value", authKey); - XmlPoke(documentDbTestConfigFile, "/configuration/appSettings/add[@key = 'ConsistencyLevel']/@value", consistencyLevel); + var configJson = ParseJsonFromFile(documentDbTestConfigFile); + configJson["Uri"] = uri; + configJson["AuthKey"] = authKey; + configJson["ConsistencyLevel"] = consistencyLevel; + + SerializeJsonToFile(documentDbTestConfigFile, configJson); }); Task("Run-Unit-Tests") @@ -48,6 +46,7 @@ Task("Run-Unit-Tests") .IsDependentOn("Transform-Unit-Test-Config") .Does(() => { + // TODO: Use dotnet CLI tools XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll"); }); diff --git a/build/tools/packages.config b/build/tools/packages.config index 85fc75c..22b8e0c 100644 --- a/build/tools/packages.config +++ b/build/tools/packages.config @@ -1,4 +1,4 @@ - + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index e8b63e7..287f514 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -3,6 +3,11 @@ netcoreapp1.1 + + + Always + + From 35208947ef2eef7a2facc74689f43bb005c85e6c Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 27 Jun 2017 14:52:38 +0100 Subject: [PATCH 15/61] Migrated build process up to unit test passing and fixed 1. Stored procedure setup (resource file issue) 2. Package naming issue --- build/build.cake | 33 ++++- .../AzureDocumentDbStorageEngine.cs | 3 +- .../Resources.cs | 25 ++++ .../Scripts.Designer.cs | 85 ------------ .../Scripts.resx | 124 ------------------ .../SimpleEventStore.AzureDocumentDb.csproj | 14 +- 6 files changed, 57 insertions(+), 227 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Resources.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.resx diff --git a/build/build.cake b/build/build.cake index 6b20f45..f3275a9 100644 --- a/build/build.cake +++ b/build/build.cake @@ -16,18 +16,28 @@ var consistencyLevel = Argument("consistencyLevel", "BoundedStaleness"); ////////////////////////////////////////////////////////////////////// // Define directories. -var solutionFile = "../src/SimpleEventStore/SimpleEventStore.sln"; -var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appSettings.json"); +var solutionDir = "../src/SimpleEventStore/"; +var solutionFile = solutionDir + "SimpleEventStore.sln"; +var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"); +var testDirs = GetDirectories(solutionDir + "*.Tests"); ////////////////////////////////////////////////////////////////////// // TASKS ////////////////////////////////////////////////////////////////////// +Task("Restore-Packages") + .Does(() => +{ + DotNetCoreRestore(solutionDir); +}); + Task("Build") + .IsDependentOn("Restore-Packages") .Does(() => { - // TODO: Use dotnet CLI tools - MSBuild(solutionFile, settings => settings.SetConfiguration(configuration).UseToolVersion(MSBuildToolVersion.VS2017)); + DotNetCoreBuild(solutionFile, new DotNetCoreBuildSettings { + Configuration = configuration + }); }); Task("Transform-Unit-Test-Config") @@ -46,8 +56,21 @@ Task("Run-Unit-Tests") .IsDependentOn("Transform-Unit-Test-Config") .Does(() => { + foreach(var testPath in testDirs) + { + var returnCode = StartProcess("dotnet", new ProcessSettings { + Arguments = "xunit -c " + configuration + " --no-build", + WorkingDirectory = testPath + }); + + if(returnCode != 0) + { + throw new Exception("Unit test failure for test project " + testPath); + } + } + //DotnetCoreTest(); // TODO: Use dotnet CLI tools - XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll"); + // XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll"); }); Task("Package") diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index 85acca4..21d8eba 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -144,7 +145,7 @@ private async Task CreateAppendStoredProcedureIfItDoesNotExist() await client.CreateStoredProcedureAsync(commitsLink, new StoredProcedure { Id = AppendStoredProcedureName, - Body = Scripts.appendToStream + Body = Resources.GetString("AppendToStream.js") }); } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Resources.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Resources.cs new file mode 100644 index 0000000..3bf2d00 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Resources.cs @@ -0,0 +1,25 @@ + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace SimpleEventStore.AzureDocumentDb +{ + internal static class Resources + { + public static string GetString(string resourceName) + { + using (var reader = new StreamReader(GetStream(resourceName))) + { + return reader.ReadToEnd(); + } + } + + private static Stream GetStream(string resourceName) + { + resourceName = $"{typeof(Resources).FullName}.{resourceName}"; + return typeof(Resources).GetTypeInfo().Assembly.GetManifestResourceStream(resourceName); + } + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs deleted file mode 100644 index 72bbbe4..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SimpleEventStore.AzureDocumentDb { - using System; - using System.Reflection; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Scripts { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Scripts() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SimpleEventStore.AzureDocumentDb.Scripts", typeof(Scripts).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to function appendToStream(documents) { - /// var context = getContext(); - /// var collection = context.getCollection(); - /// var collectionLink = collection.getSelfLink(); - /// var streamId = documents[0].streamId; - /// var expectedEventNumber = documents[0].eventNumber - 1; - /// - /// var concurrencyQuery = { - /// query: "SELECT TOP 1 c.eventNumber FROM Commits c WHERE c.streamId = @streamId ORDER BY c.eventNumber DESC", - /// parameters: [{ name: "@streamId", value: streamId }] - /// }; - /// - /// var accepted [rest of string was truncated]";. - /// - internal static string appendToStream { - get { - return ResourceManager.GetString("appendToStream", resourceCulture); - } - } - } -} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.resx b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.resx deleted file mode 100644 index ee37083..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - Resources\appendToStream.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 - - \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 01850aa..1e262d6 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -10,17 +10,7 @@ - - True - True - Scripts.resx - - - - - ResXFileCodeGenerator - Scripts.Designer.cs - + SimpleEventStore.AzureDocumentDb @@ -28,7 +18,7 @@ ASOS Copyright ASOS ©2016 SimpleEventStore.AzureDocumentDb - Asos.SimpleEventStore + Asos.SimpleEventStore.AzureDocumentDb eventsourcing documentdb azure https://github.com/ASOS/SimpleEventStore From 6f37e00ee0100f6124185dbd90ab1652986cd4f5 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 27 Jun 2017 18:22:08 +0100 Subject: [PATCH 16/61] Removed redundant comments from build script. --- build/build.cake | 3 --- 1 file changed, 3 deletions(-) diff --git a/build/build.cake b/build/build.cake index f3275a9..e35d56f 100644 --- a/build/build.cake +++ b/build/build.cake @@ -68,9 +68,6 @@ Task("Run-Unit-Tests") throw new Exception("Unit test failure for test project " + testPath); } } - //DotnetCoreTest(); - // TODO: Use dotnet CLI tools - // XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll"); }); Task("Package") From f1d418e63d753b4ca224bf5d0e3201010e35586a Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 27 Jun 2017 21:08:57 +0100 Subject: [PATCH 17/61] Altered build process to ensure everything apart from nuget package publishing is using the .NET Core build tools --- build/build.cake | 30 ++++++++++++++----- build/build.ps1 | 3 +- .../SimpleEventStore.AzureDocumentDb.csproj | 3 +- .../SimpleEventStore/SimpleEventStore.csproj | 2 ++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/build/build.cake b/build/build.cake index e35d56f..4c8dc90 100644 --- a/build/build.cake +++ b/build/build.cake @@ -10,6 +10,7 @@ var configuration = Argument("configuration", "Release"); var uri = Argument("uri", "https://localhost:8081/"); var authKey = Argument("authKey", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); var consistencyLevel = Argument("consistencyLevel", "BoundedStaleness"); +var buildVersion = Argument("buildVersion", "1.0.0"); ////////////////////////////////////////////////////////////////////// // PREPARATION @@ -20,6 +21,7 @@ var solutionDir = "../src/SimpleEventStore/"; var solutionFile = solutionDir + "SimpleEventStore.sln"; var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"); var testDirs = GetDirectories(solutionDir + "*.Tests"); +var outputDir = "./nuget"; ////////////////////////////////////////////////////////////////////// // TASKS @@ -31,12 +33,21 @@ Task("Restore-Packages") DotNetCoreRestore(solutionDir); }); +Task("Clean") + .Does(() => +{ + CleanDirectory(outputDir); +}); + Task("Build") + .IsDependentOn("Clean") .IsDependentOn("Restore-Packages") .Does(() => { DotNetCoreBuild(solutionFile, new DotNetCoreBuildSettings { - Configuration = configuration + Configuration = configuration, + NoIncremental = true, + ArgumentCustomization = args => args.Append("/p:Version=" + buildVersion) }); }); @@ -71,16 +82,19 @@ Task("Run-Unit-Tests") }); Task("Package") + .IsDependentOn("Build") .IsDependentOn("Run-Unit-Tests") .Does(() => { - NuGetPack("./../src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj", new NuGetPackSettings() { - ArgumentCustomization = args => args.Append("-Prop Configuration=" + configuration) - }); - - NuGetPack("./../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj", new NuGetPackSettings() { - ArgumentCustomization = args => args.Append("-Prop Configuration=" + configuration) - }); + var settings = new DotNetCorePackSettings { + Configuration = configuration, + NoBuild = true, + OutputDirectory = outputDir, + ArgumentCustomization = args => args.Append("/p:PackageVersion=" + buildVersion) + }; + + DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore/", settings); + DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/", settings); }); diff --git a/build/build.ps1 b/build/build.ps1 index ffbbf40..584e410 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -48,6 +48,7 @@ Param( [string]$Uri = "https://localhost:8081/", [string]$AuthKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", [string]$ConsistencyLevel = "BoundedStaleness", + [string]$BuildVersion = "1.0.0", [string]$NugetSource, [string]$NugetApiKey, [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] @@ -191,5 +192,5 @@ if (!(Test-Path $CAKE_EXE)) { # Start Cake Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey -consistencyLevel=$ConsistencyLevel -nugetSource=$NugetSource -nugetApiKey=$NugetApiKey $UseMono $UseDryRun $UseExperimental $ScriptArgs" +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey -consistencyLevel=$ConsistencyLevel -buildVersion=`"$BuildVersion`" -nugetSource=$NugetSource -nugetApiKey=$NugetApiKey $UseMono $UseDryRun $UseExperimental $ScriptArgs" exit $LASTEXITCODE \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 1e262d6..4accad7 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -24,6 +24,7 @@ https://github.com/ASOS/SimpleEventStore library - + 1.0.0 + 1.0.0 \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index a3a9434..e07cbff 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -13,5 +13,7 @@ eventsourcing documentdb azure https://github.com/ASOS/SimpleEventStore + 1.0.0 + 1.0.0 \ No newline at end of file From 6a6513d587e5b00cc6345f2787b7e0a585d2a11a Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 27 Jun 2017 22:02:35 +0100 Subject: [PATCH 18/61] Fixed package path issue --- build/build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index 4c8dc90..a02f9ca 100644 --- a/build/build.cake +++ b/build/build.cake @@ -105,7 +105,7 @@ Task("Deploy") var nugetSource = Argument("nugetSource"); var nugetApiKey = Argument("nugetApiKey"); - var package = GetFiles("./*.nupkg"); + var package = GetFiles(outputDir + "/*.nupkg"); NuGetPush(package, new NuGetPushSettings { Source = nugetSource, From 3ed7506ac5d980a248d1e975eea59f4507e15eae Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Wed, 28 Jun 2017 06:00:33 +0100 Subject: [PATCH 19/61] Added support for .NET 4.6.1 on top of netstandard 1.6 --- build/build.cake | 20 ++++++++++++------- ...pleEventStore.AzureDocumentDb.Tests.csproj | 6 ++++-- .../SimpleEventStore.AzureDocumentDb.csproj | 5 +++-- .../SimpleEventStore.Tests.csproj | 2 +- .../SimpleEventStore/SimpleEventStore.csproj | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/build/build.cake b/build/build.cake index a02f9ca..7b2966f 100644 --- a/build/build.cake +++ b/build/build.cake @@ -19,7 +19,10 @@ var buildVersion = Argument("buildVersion", "1.0.0"); // Define directories. var solutionDir = "../src/SimpleEventStore/"; var solutionFile = solutionDir + "SimpleEventStore.sln"; -var documentDbTestConfigFile = File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"); +var documentDbTestConfigFiles = new [] { + File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"), + File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/net461/appsettings.json") +}; var testDirs = GetDirectories(solutionDir + "*.Tests"); var outputDir = "./nuget"; @@ -54,12 +57,15 @@ Task("Build") Task("Transform-Unit-Test-Config") .Does(() => { - var configJson = ParseJsonFromFile(documentDbTestConfigFile); - configJson["Uri"] = uri; - configJson["AuthKey"] = authKey; - configJson["ConsistencyLevel"] = consistencyLevel; - - SerializeJsonToFile(documentDbTestConfigFile, configJson); + foreach(var documentDbTestConfigFile in documentDbTestConfigFiles) + { + var configJson = ParseJsonFromFile(documentDbTestConfigFile); + configJson["Uri"] = uri; + configJson["AuthKey"] = authKey; + configJson["ConsistencyLevel"] = consistencyLevel; + + SerializeJsonToFile(documentDbTestConfigFile, configJson); + } }); Task("Run-Unit-Tests") diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index 287f514..906e4ce 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1 + netcoreapp1.1;net461 @@ -9,7 +9,9 @@ - + + + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 4accad7..511d972 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -1,10 +1,11 @@  - netstandard1.6 + netstandard1.6;net461 - + + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index 83d5997..8d6dbad 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1 + netcoreapp1.1;net461 diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index e07cbff..3d07645 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -1,7 +1,7 @@  - netstandard1.6 + netstandard1.6;net461 SimpleEventStore From e3efded37ea6650430bc8bfff92afb78717fa256 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Wed, 28 Jun 2017 06:06:34 +0100 Subject: [PATCH 20/61] Corrected documentation issue #11 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 538d3db..029a59b 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ var storageEngine = new AzureDocumentDbStorageEngine( maxItemCount: 1, pollEvery: TimeSpan.FromSeconds(0.5))); -eventStore.Initialise(); +await storageEngine.Initialise(); ``` The DatabaseOptions allow you to specify the consistency level of the database along with the default number of RUs. From dee9c9453e2aa959bcb33034b18608896a5378b0 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 29 Jun 2017 05:14:38 +0100 Subject: [PATCH 21/61] Implemented a builder for the AzureDocumentDbStorageEngine (#3) and allowed the collection name to be configurable (#12) --- ...zureDocumentDbStorageEngineBuilderTests.cs | 41 +++++++++++++++++ .../StorageEngineFactory.cs | 17 +++++-- .../AzureDocumentDbStorageEngine.cs | 26 +++++------ .../AzureDocumentDbStorageEngineBuilder.cs | 46 +++++++++++++++++++ .../CollectionOptions.cs | 20 ++++++++ .../DatabaseOptions.cs | 17 ------- .../SubscriptionOptions.cs | 10 ++-- .../SimpleEventStore/IStorageEngine.cs | 2 + .../InMemory/InMemoryStorageEngine.cs | 5 ++ 9 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DatabaseOptions.cs diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs new file mode 100644 index 0000000..721ef44 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.Azure.Documents.Client; +using Xunit; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + public class AzureDocumentDbStorageEngineBuilderTests + { + [Fact] + public void when_creating_an_instance_the_document_client_must_be_supplied() + { + Assert.Throws(() => new AzureDocumentDbStorageEngineBuilder(null, "Test")); + } + + [Fact] + public void when_creating_an_instance_the_database_name_must_be_supplied() + { + Assert.Throws(() => new AzureDocumentDbStorageEngineBuilder(CreateClient(), null)); + } + + [Fact] + public void when_setting_collection_settings_a_callback_must_be_supplied() + { + var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); + Assert.Throws(() => builder.UseCollection(null)); + } + + [Fact] + public void when_setting_subscription_settings_a_callback_must_be_supplied() + { + var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); + Assert.Throws(() => builder.UseCollection(null)); + } + + private static DocumentClient CreateClient() + { + var client = new DocumentClient(new Uri("https://localhost:8081/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); + return client; + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index ff0e906..04c0ca0 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -28,10 +28,19 @@ internal static async Task Create(string databaseName) DocumentClient client = new DocumentClient(new Uri(documentDbUri), authKey); - var storageEngine = new AzureDocumentDbStorageEngine(client, databaseName, new DatabaseOptions(consistencyLevelEnum, 400), new SubscriptionOptions(maxItemCount: 1, pollEvery: TimeSpan.FromSeconds(0.5))); - await storageEngine.Initialise(); - - return storageEngine; + return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) + .UseCollection(o => + { + o.ConsistencyLevel = consistencyLevelEnum; + o.CollectionRequestUnits = 400; + }) + .UseSubscriptions(o => + { + o.MaxItemCount = 1; + o.PollEvery = TimeSpan.FromSeconds(0.5); + }) + .Build() + .Initialise(); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index 21d8eba..b1b401b 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -9,35 +8,36 @@ namespace SimpleEventStore.AzureDocumentDb { - public class AzureDocumentDbStorageEngine : IStorageEngine + internal class AzureDocumentDbStorageEngine : IStorageEngine { - private const string CommitsCollectionName = "Commits"; private const string AppendStoredProcedureName = "appendToStream"; private const string ConcurrencyConflictErrorKey = "Concurrency conflict."; private readonly DocumentClient client; private readonly string databaseName; - private readonly DatabaseOptions databaseOptions; + private readonly CollectionOptions collectionOptions; private readonly Uri commitsLink; private readonly Uri storedProcLink; private readonly List subscriptions = new List(); private readonly SubscriptionOptions subscriptionOptions; - public AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, DatabaseOptions databaseOptions, SubscriptionOptions subscriptionOptions) + internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, SubscriptionOptions subscriptionOptions) { this.client = client; this.databaseName = databaseName; - this.databaseOptions = databaseOptions; - this.commitsLink = UriFactory.CreateDocumentCollectionUri(databaseName, CommitsCollectionName); - this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, CommitsCollectionName, AppendStoredProcedureName); + this.collectionOptions = collectionOptions; + this.commitsLink = UriFactory.CreateDocumentCollectionUri(databaseName, collectionOptions.CollectionName); + this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, collectionOptions.CollectionName, AppendStoredProcedureName); this.subscriptionOptions = subscriptionOptions; } - public async Task Initialise() + public async Task Initialise() { await CreateDatabaseIfItDoesNotExist(); await CreateCollectionIfItDoesNotExist(); await CreateAppendStoredProcedureIfItDoesNotExist(); + + return this; } public async Task AppendToStream(string streamId, IEnumerable events) @@ -48,7 +48,7 @@ public async Task AppendToStream(string streamId, IEnumerable even { var result = await this.client.ExecuteStoredProcedureAsync( storedProcLink, - new RequestOptions { PartitionKey = new PartitionKey(streamId), ConsistencyLevel = this.databaseOptions.ConsistencyLevel }, + new RequestOptions { PartitionKey = new PartitionKey(streamId), ConsistencyLevel = this.collectionOptions.ConsistencyLevel }, docs); } catch (DocumentClientException ex) @@ -112,14 +112,14 @@ private async Task CreateCollectionIfItDoesNotExist() var databaseUri = UriFactory.CreateDatabaseUri(databaseName); var commitsCollectionQuery = client.CreateDocumentCollectionQuery(databaseUri) - .Where(x => x.Id == CommitsCollectionName) + .Where(x => x.Id == collectionOptions.CollectionName) .Take(1) .AsDocumentQuery(); if (!(await commitsCollectionQuery.ExecuteNextAsync()).Any()) { var collection = new DocumentCollection(); - collection.Id = CommitsCollectionName; + collection.Id = collectionOptions.CollectionName; collection.PartitionKey.Paths.Add("/streamId"); collection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/body/*"}); @@ -127,7 +127,7 @@ private async Task CreateCollectionIfItDoesNotExist() var requestOptions = new RequestOptions { - OfferThroughput = this.databaseOptions.CollectionRequestUnits + OfferThroughput = collectionOptions.CollectionRequestUnits }; await client.CreateDocumentCollectionAsync(databaseUri, collection, requestOptions); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs new file mode 100644 index 0000000..4cba5bf --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class AzureDocumentDbStorageEngineBuilder + { + private readonly string databaseName; + private readonly DocumentClient client; + private readonly CollectionOptions collectionOptions = new CollectionOptions(); + private SubscriptionOptions subscriptionOptions; + + public AzureDocumentDbStorageEngineBuilder(DocumentClient client, string databaseName) + { + Guard.IsNotNull(nameof(client), client); + Guard.IsNotNullOrEmpty(nameof(databaseName), databaseName); + + this.client = client; + this.databaseName = databaseName; + } + + public AzureDocumentDbStorageEngineBuilder UseCollection(Action action) + { + Guard.IsNotNull(nameof(action), action); + + action(collectionOptions); + return this; + } + + public AzureDocumentDbStorageEngineBuilder UseSubscriptions(Action action) + { + Guard.IsNotNull(nameof(action), action); + + subscriptionOptions = new SubscriptionOptions(); + action(subscriptionOptions); + return this; + } + + public IStorageEngine Build() + { + var engine = new AzureDocumentDbStorageEngine(this.client, databaseName, collectionOptions, subscriptionOptions); + return engine; + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs new file mode 100644 index 0000000..1150ad1 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs @@ -0,0 +1,20 @@ +using Microsoft.Azure.Documents; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class CollectionOptions + { + public CollectionOptions() + { + this.ConsistencyLevel = ConsistencyLevel.Session; + this.CollectionRequestUnits = 400; + this.CollectionName = "Commits"; + } + + public string CollectionName { get; set; } + + public ConsistencyLevel ConsistencyLevel { get; set; } + + public int CollectionRequestUnits { get; set; } + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DatabaseOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DatabaseOptions.cs deleted file mode 100644 index e98094e..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DatabaseOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Azure.Documents; - -namespace SimpleEventStore.AzureDocumentDb -{ - public class DatabaseOptions - { - public DatabaseOptions(ConsistencyLevel consistencyLevel, int collectionRequestUnits) - { - this.ConsistencyLevel = consistencyLevel; - this.CollectionRequestUnits = collectionRequestUnits; - } - - public ConsistencyLevel ConsistencyLevel { get; } - - public int CollectionRequestUnits { get; } - } -} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs index 226c2bd..6a07cce 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs @@ -4,14 +4,14 @@ namespace SimpleEventStore.AzureDocumentDb { public class SubscriptionOptions { - public SubscriptionOptions(int maxItemCount, TimeSpan pollEvery) + public SubscriptionOptions() { - this.MaxItemCount = maxItemCount; - this.PollEvery = pollEvery; + this.MaxItemCount = 100; + this.PollEvery = TimeSpan.FromSeconds(5); } - public int MaxItemCount { get; } + public int MaxItemCount { get; set; } - public TimeSpan PollEvery { get; } + public TimeSpan PollEvery { get; set; } } } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs index 1332cfd..78719c9 100644 --- a/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs @@ -11,5 +11,7 @@ public interface IStorageEngine Task> ReadStreamForwards(string streamId, int startPosition, int numberOfEventsToRead); void SubscribeToAll(Action, string> onNextEvent, string checkpoint); + + Task Initialise(); } } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index 9375002..c09f154 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -56,6 +56,11 @@ public void SubscribeToAll(Action, string> onN subscription.Start(); } + public Task Initialise() + { + return Task.FromResult(this); + } + private class Subscription { private readonly IEnumerable allStream; From c93728ed8200b169ec33a7e4a50cb8ffe690bf23 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 29 Jun 2017 05:38:30 +0100 Subject: [PATCH 22/61] Ensure that when subscription options haven't been specified then an exception is thrown when attempting to create a subscription (#3) --- ...ureDocumentDbEventStoreCatchUpSubscription.cs | 16 ++++++++++++++-- .../AzureDocumentDbStorageEngine.cs | 9 +++++++++ .../SubscriptionsNotConfiguredException.cs | 11 +++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs index 2affb4b..4ff6690 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs @@ -1,5 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; using SimpleEventStore.Tests; +using Xunit; namespace SimpleEventStore.AzureDocumentDb.Tests { @@ -7,7 +10,16 @@ public class AzureDocumentDbEventStoreCatchUpSubscription : EventStoreCatchUpSub { protected override Task CreateStorageEngine() { - return StorageEngineFactory.Create("CatchUpSubscriptionTests"); ; + return StorageEngineFactory.Create("CatchUpSubscriptionTests"); + } + + [Fact] + public void when_subscription_options_have_not_been_supplied_the_subscription_feature_cannot_be_used() + { + var client = new DocumentClient(new Uri("https://localhost:8081/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); + var sut = new AzureDocumentDbStorageEngineBuilder(client, "Test").Build(); + + Assert.Throws(() => sut.SubscribeToAll((e, c) => { }, null)); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index b1b401b..8a35c2e 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -86,6 +86,7 @@ public async Task> ReadStreamForwards(string s public void SubscribeToAll(Action, string> onNextEvent, string checkpoint) { + EnsureSubscriptionsAreEnabled(); Guard.IsNotNull(nameof(onNextEvent), onNextEvent); var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions); @@ -94,6 +95,14 @@ public void SubscribeToAll(Action, string> onN subscription.Start(); } + private void EnsureSubscriptionsAreEnabled() + { + if (subscriptionOptions == null) + { + throw new SubscriptionsNotConfiguredException("Ensure subscription options have been supplied prior to using subscription features."); + } + } + private async Task CreateDatabaseIfItDoesNotExist() { var databaseExistsQuery = client.CreateDatabaseQuery() diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs new file mode 100644 index 0000000..4139840 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs @@ -0,0 +1,11 @@ +using System; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class SubscriptionsNotConfiguredException : Exception + { + public SubscriptionsNotConfiguredException(string message) : base(message) + { + } + } +} From fa799b2f1de008a449e681b9913e7ec4f0110ec3 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 29 Jun 2017 05:48:37 +0100 Subject: [PATCH 23/61] Updated docs due to changes for issue #3 --- README.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 029a59b..4bcf36f 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Simple Event Store (SES) provides a lightweight event sourcing abstraction. ## Persistence Engines SES supports the following -- Azure DocumentDB +- Azure Cosmos DB - In Memory -All persistence engines run the same test scenarios to ensure feature parity. At present only the DocumentDB persistence engine should be considered for production usage. +All persistence engines run the same test scenarios to ensure feature parity. At present only the Cosmos DB persistence engine should be considered for production usage. ## Usage @@ -62,20 +62,25 @@ Each subscription **must** perform an independant task. It is not possible to r The number of events received will vary depending on the storage engine configuration. -## DocumentDB +## Cosmos DB ```csharp DocumentClient client; // Set this up as required -var databaseName = "MyEventStore"; -var storageEngine = new AzureDocumentDbStorageEngine( - client, - databaseName, - new DatabaseOptions(ConsistencyLevel.BoundedStaleness, 400), - new SubscriptionOptions( - maxItemCount: 1, - pollEvery: TimeSpan.FromSeconds(0.5))); - -await storageEngine.Initialise(); +// If UseCollection isn't specified, sensible defaults for development are used. +// If UseSubscriptions isn't supplied the subscription feature is disabled. +return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) + .UseCollection(o => + { + o.ConsistencyLevel = consistencyLevelEnum; + o.CollectionRequestUnits = 400; + }) + .UseSubscriptions(o => + { + o.MaxItemCount = 1; + o.PollEvery = TimeSpan.FromSeconds(0.5); + }) + .Build() + .Initialise(); ``` The DatabaseOptions allow you to specify the consistency level of the database along with the default number of RUs. From 1eac9bee9648457b750b30e5d33a22cabeb8b7e6 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 29 Jun 2017 05:52:00 +0100 Subject: [PATCH 24/61] Corrected docs for #12 #3 --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bcf36f..dfddcdf 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,10 @@ return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) .Build() .Initialise(); ``` -The DatabaseOptions allow you to specify the consistency level of the database along with the default number of RUs. +The CollectionOptions allow you to specify +- The consistency level of the database +- The default number of RUs when the collection is created +- The collection name Only use one of the following consistency levels - Strong From 30c6cf4ab1d68157fee1bf4a2faf70cf003bbd34 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 29 Jun 2017 05:54:04 +0100 Subject: [PATCH 25/61] Corrected doc formatting #12 #3 --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dfddcdf..a6f9930 100644 --- a/README.md +++ b/README.md @@ -69,18 +69,18 @@ DocumentClient client; // Set this up as required // If UseCollection isn't specified, sensible defaults for development are used. // If UseSubscriptions isn't supplied the subscription feature is disabled. return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) - .UseCollection(o => - { - o.ConsistencyLevel = consistencyLevelEnum; - o.CollectionRequestUnits = 400; - }) - .UseSubscriptions(o => - { - o.MaxItemCount = 1; - o.PollEvery = TimeSpan.FromSeconds(0.5); - }) - .Build() - .Initialise(); + .UseCollection(o => + { + o.ConsistencyLevel = consistencyLevelEnum; + o.CollectionRequestUnits = 400; + }) + .UseSubscriptions(o => + { + o.MaxItemCount = 1; + o.PollEvery = TimeSpan.FromSeconds(0.5); + }) + .Build() + .Initialise(); ``` The CollectionOptions allow you to specify - The consistency level of the database From 238cb9c19d8d2ccd551aa5341d906ce7d55fc8a1 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Wed, 5 Jul 2017 07:04:16 +0100 Subject: [PATCH 26/61] Initial implementation for logging improvement for #5. Only supports callbacks for successfull operations at present. --- README.md | 5 + .../App.config | 8 - .../AzureDocumentDbEventStoreLogging.cs | 115 ++++++++++++++ ...zureDocumentDbStorageEngineBuilderTests.cs | 7 + .../ResponseInformationBuilding.cs | 150 ++++++++++++++++++ ...pleEventStore.AzureDocumentDb.Tests.csproj | 2 + .../appsettings.json | 4 +- .../AzureDocumentDbStorageEngine.cs | 14 +- .../AzureDocumentDbStorageEngineBuilder.cs | 12 +- .../DocumentDbStorageEvent.cs | 2 +- .../LoggingOptions.cs | 14 ++ .../ResponseInformation.cs | 50 ++++++ .../Subscription.cs | 5 +- 13 files changed, 371 insertions(+), 17 deletions(-) delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/LoggingOptions.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs diff --git a/README.md b/README.md index a6f9930..988a7a9 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ DocumentClient client; // Set this up as required // If UseCollection isn't specified, sensible defaults for development are used. // If UseSubscriptions isn't supplied the subscription feature is disabled. +// The UseLogging sets up callbacks per Cosmos DB operation performed. return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) .UseCollection(o => { @@ -79,6 +80,10 @@ return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) o.MaxItemCount = 1; o.PollEvery = TimeSpan.FromSeconds(0.5); }) + .UseLogging(o => + { + o.Success = onSuccessCallback; + }) .Build() .Initialise(); ``` diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config deleted file mode 100644 index 71bf081..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/App.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs new file mode 100644 index 0000000..6676fe7 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs @@ -0,0 +1,115 @@ +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Extensions.Configuration; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SimpleEventStore.Tests.Events; +using Xunit; +using Xunit.Abstractions; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + public class AzureDocumentDbEventStoreLogging + { + // TODO: Simplify tests when subscription supports cancellation tokens, should be able to cancel rather than running an if(Interlocked..) statement + + private readonly ITestOutputHelper output; + + public AzureDocumentDbEventStoreLogging(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public async Task when_a_write_operation_is_successful_the_log_callback_is_called() + { + ResponseInformation response = null; + var sut = new EventStore(await CreateStorageEngine(t => response = t)); + var streamId = Guid.NewGuid().ToString(); + + await sut.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated("TEST-ORDER"))); + + Assert.NotNull(response); + output.WriteLine($"Charge: {response.RequestCharge}"); + output.WriteLine($"Quota Usage: {response.CurrentResourceQuotaUsage}"); + output.WriteLine($"Max Resource Quote: {response.MaxResourceQuota}"); + output.WriteLine($"Response headers: {response.ResponseHeaders}"); + } + + [Fact] + public async Task when_a_read_operation_is_successful_the_log_callback_is_called() + { + var logCount = 0; + var sut = new EventStore(await CreateStorageEngine(t => logCount++)); + var streamId = Guid.NewGuid().ToString(); + + await sut.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated("TEST-ORDER"))); + await sut.ReadStreamForwards(streamId); + + Assert.Equal(2, logCount); + } + + [Fact] + public async void when_a_subscription_reads_events_the_log_callback_is_called() + { + var polledOneEvent = 0; + var logCount = 0; + var sut = new EventStore(await CreateStorageEngine(t => Interlocked.Increment(ref logCount))); + var completionSource = new TaskCompletionSource(); + + sut.SubscribeToAll( + (events, checkpoint) => + { + if (Interlocked.Exchange(ref polledOneEvent, 1) == 0) + { + completionSource.SetResult(null); + } + }); + + await sut.AppendToStream(Guid.NewGuid().ToString(), 0, new EventData(Guid.NewGuid(), new OrderCreated("TEST-ORDER"))); + await completionSource.Task; + + Assert.True(logCount > 1); + } + + private static async Task CreateStorageEngine(Action onSuccessCallback, string databaseName = "LoggingTests") + { + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var documentDbUri = config["Uri"]; + var authKey = config["AuthKey"]; + var consistencyLevel = config["ConsistencyLevel"]; + ConsistencyLevel consistencyLevelEnum; + + if (!Enum.TryParse(consistencyLevel, true, out consistencyLevelEnum)) + { + throw new Exception($"The ConsistencyLevel value {consistencyLevel} is not supported"); + } + + DocumentClient client = new DocumentClient(new Uri(documentDbUri), authKey); + + return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) + .UseCollection(o => + { + o.ConsistencyLevel = consistencyLevelEnum; + o.CollectionRequestUnits = 400; + }) + .UseSubscriptions(o => + { + o.MaxItemCount = 1; + o.PollEvery = TimeSpan.FromSeconds(0.5); + }) + .UseLogging(o => + { + o.Success = onSuccessCallback; + }) + .Build() + .Initialise(); + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs index 721ef44..590432a 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs @@ -32,6 +32,13 @@ public void when_setting_subscription_settings_a_callback_must_be_supplied() Assert.Throws(() => builder.UseCollection(null)); } + [Fact] + public void when_setting_logging_settings_a_callback_must_be_supplied() + { + var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); + Assert.Throws(() => builder.UseLogging(null)); + } + private static DocumentClient CreateClient() { var client = new DocumentClient(new Uri("https://localhost:8081/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs new file mode 100644 index 0000000..670f711 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using System.Net; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Xunit; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + public class ResponseInformationBuilding + { + [Fact] + public void when_building_from_a_write_response_all_target_fields_are_mapped() + { + var result = ResponseInformation.FromWriteResponse(new FakeStoredProcedureResponse()); + + Assert.Equal(Expected.CurrentResourceQuotaUsage, result.CurrentResourceQuotaUsage); + Assert.Equal(Expected.MaxResourceQuota, result.MaxResourceQuota); + Assert.Equal(Expected.RequestCharge, result.RequestCharge); + Assert.Equal(Expected.ResponseHeaders, result.ResponseHeaders); + } + + [Fact] + public void when_building_from_a_read_response_all_target_fields_are_mapped() + { + var result = ResponseInformation.FromReadResponse(new FakeFeedResponse()); + + Assert.Equal(Expected.CurrentResourceQuotaUsage, result.CurrentResourceQuotaUsage); + Assert.Equal(Expected.MaxResourceQuota, result.MaxResourceQuota); + Assert.Equal(Expected.RequestCharge, result.RequestCharge); + Assert.Equal(Expected.ResponseHeaders, result.ResponseHeaders); + } + + [Fact] + public void when_building_from_a_subscription_read_response_all_target_fields_are_mapped() + { + var result = ResponseInformation.FromSubscriptionReadResponse(new FakeFeedResponse()); + + Assert.Equal(Expected.CurrentResourceQuotaUsage, result.CurrentResourceQuotaUsage); + Assert.Equal(Expected.MaxResourceQuota, result.MaxResourceQuota); + Assert.Equal(Expected.RequestCharge, result.RequestCharge); + Assert.Equal(Expected.ResponseHeaders, result.ResponseHeaders); + } + + private static class Expected + { + internal const string CurrentResourceQuotaUsage = "TEST-CurrentResourceQuotaUsage"; + internal const string MaxResourceQuota = "TEST-MaxResourceQuota"; + internal const double RequestCharge = 100d; + internal static NameValueCollection ResponseHeaders = new NameValueCollection(); + } + + private class FakeStoredProcedureResponse : IStoredProcedureResponse + { + internal FakeStoredProcedureResponse() + { + CurrentResourceQuotaUsage = Expected.CurrentResourceQuotaUsage; + MaxResourceQuota = Expected.MaxResourceQuota; + RequestCharge = Expected.RequestCharge; + ResponseHeaders = Expected.ResponseHeaders; + } + + public string ActivityId { get; } + + public string CurrentResourceQuotaUsage { get; } + + public string MaxResourceQuota { get; } + + public double RequestCharge { get; } + + public TValue Response { get; } + + public NameValueCollection ResponseHeaders { get; } + + public string SessionToken { get; } + + public string ScriptLog { get; } + + public HttpStatusCode StatusCode { get; } + } + + private class FakeFeedResponse : IFeedResponse + { + internal FakeFeedResponse() + { + CurrentResourceQuotaUsage = Expected.CurrentResourceQuotaUsage; + MaxResourceQuota = Expected.MaxResourceQuota; + RequestCharge = Expected.RequestCharge; + ResponseHeaders = Expected.ResponseHeaders; + } + + public long DatabaseQuota { get; } + + public long DatabaseUsage { get; } + + public long CollectionQuota { get; } + + public long CollectionUsage { get; } + + public long UserQuota { get; } + + public long UserUsage { get; } + + public long PermissionQuota { get; } + + public long PermissionUsage { get; } + + public long CollectionSizeQuota { get; } + + public long CollectionSizeUsage { get; } + + public long StoredProceduresQuota { get; } + + public long StoredProceduresUsage { get; } + + public long TriggersQuota { get; } + + public long TriggersUsage { get; } + + public long UserDefinedFunctionsQuota { get; } + + public long UserDefinedFunctionsUsage { get; } + + public int Count { get; } + + public string MaxResourceQuota { get; } + + public string CurrentResourceQuotaUsage { get; } + + public double RequestCharge { get; } + + public string ActivityId { get; } + + public string ResponseContinuation { get; } + + public string SessionToken { get; } + + public string ContentLocation { get; } + + public NameValueCollection ResponseHeaders { get; } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index 906e4ce..66fbac9 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -15,6 +15,8 @@ + + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json index 141db5a..d62b638 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json @@ -1,5 +1,5 @@ { - "Uri": "https://localhost:8082/", + "Uri": "https://localhost:8081/", "AuthKey": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", - "ConsistencyLevel": "BoundedStaleness" + "ConsistencyLevel": "Session" } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index 8a35c2e..f43efd9 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -20,8 +21,9 @@ internal class AzureDocumentDbStorageEngine : IStorageEngine private readonly Uri storedProcLink; private readonly List subscriptions = new List(); private readonly SubscriptionOptions subscriptionOptions; + private readonly LoggingOptions loggingOptions; - internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, SubscriptionOptions subscriptionOptions) + internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions) { this.client = client; this.databaseName = databaseName; @@ -29,6 +31,7 @@ internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName this.commitsLink = UriFactory.CreateDocumentCollectionUri(databaseName, collectionOptions.CollectionName); this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, collectionOptions.CollectionName, AppendStoredProcedureName); this.subscriptionOptions = subscriptionOptions; + this.loggingOptions = loggingOptions; } public async Task Initialise() @@ -50,6 +53,8 @@ public async Task AppendToStream(string streamId, IEnumerable even storedProcLink, new RequestOptions { PartitionKey = new PartitionKey(streamId), ConsistencyLevel = this.collectionOptions.ConsistencyLevel }, docs); + + loggingOptions.OnSuccess(ResponseInformation.FromWriteResponse(result)); } catch (DocumentClientException ex) { @@ -75,7 +80,10 @@ public async Task> ReadStreamForwards(string s while (eventsQuery.HasMoreResults) { - foreach (var e in await eventsQuery.ExecuteNextAsync()) + var response = await eventsQuery.ExecuteNextAsync(); + loggingOptions.OnSuccess(ResponseInformation.FromReadResponse(response)); + + foreach (var e in response) { events.Add(e.ToStorageEvent()); } @@ -89,7 +97,7 @@ public void SubscribeToAll(Action, string> onN EnsureSubscriptionsAreEnabled(); Guard.IsNotNull(nameof(onNextEvent), onNextEvent); - var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions); + var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions, this.loggingOptions); subscriptions.Add(subscription); subscription.Start(); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs index 4cba5bf..a74ccac 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs @@ -1,5 +1,4 @@ using System; -using System.Threading.Tasks; using Microsoft.Azure.Documents.Client; namespace SimpleEventStore.AzureDocumentDb @@ -9,6 +8,7 @@ public class AzureDocumentDbStorageEngineBuilder private readonly string databaseName; private readonly DocumentClient client; private readonly CollectionOptions collectionOptions = new CollectionOptions(); + private readonly LoggingOptions loggingOptions = new LoggingOptions(); private SubscriptionOptions subscriptionOptions; public AzureDocumentDbStorageEngineBuilder(DocumentClient client, string databaseName) @@ -37,9 +37,17 @@ public AzureDocumentDbStorageEngineBuilder UseSubscriptions(Action action) + { + Guard.IsNotNull(nameof(action), action); + + action(loggingOptions); + return this; + } + public IStorageEngine Build() { - var engine = new AzureDocumentDbStorageEngine(this.client, databaseName, collectionOptions, subscriptionOptions); + var engine = new AzureDocumentDbStorageEngine(this.client, this.databaseName, this.collectionOptions, this.subscriptionOptions, this.loggingOptions); return engine; } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs index 762cebd..fd10ef8 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs @@ -5,7 +5,7 @@ namespace SimpleEventStore.AzureDocumentDb { - internal class DocumentDbStorageEvent + public class DocumentDbStorageEvent { [JsonProperty("id")] public string Id { get; set; } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/LoggingOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/LoggingOptions.cs new file mode 100644 index 0000000..c697363 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/LoggingOptions.cs @@ -0,0 +1,14 @@ +using System; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class LoggingOptions + { + public Action Success { get; set; } + + internal void OnSuccess(ResponseInformation response) + { + Success?.Invoke(response); + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs new file mode 100644 index 0000000..f00b601 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs @@ -0,0 +1,50 @@ +using System.Collections.Specialized; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class ResponseInformation + { + public string CurrentResourceQuotaUsage { get; private set; } + + public string MaxResourceQuota { get; private set; } + + public double RequestCharge { get; private set; } + + public NameValueCollection ResponseHeaders { get; private set; } + + public static ResponseInformation FromWriteResponse(IStoredProcedureResponse response) + { + return new ResponseInformation + { + CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage, + MaxResourceQuota = response.MaxResourceQuota, + RequestCharge = response.RequestCharge, + ResponseHeaders = response.ResponseHeaders + }; + } + + public static ResponseInformation FromReadResponse(IFeedResponse response) + { + return new ResponseInformation + { + CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage, + MaxResourceQuota = response.MaxResourceQuota, + RequestCharge = response.RequestCharge, + ResponseHeaders = response.ResponseHeaders + }; + } + + public static ResponseInformation FromSubscriptionReadResponse(IFeedResponse response) + { + return new ResponseInformation + { + CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage, + MaxResourceQuota = response.MaxResourceQuota, + RequestCharge = response.RequestCharge, + ResponseHeaders = response.ResponseHeaders + }; + } + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs index 9e4fe91..fa0718e 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs @@ -16,15 +16,17 @@ internal class Subscription private readonly Action, string> onNextEvent; private readonly SubscriptionOptions subscriptionOptions; private readonly Dictionary checkpoints; + private readonly LoggingOptions loggingOptions; private Task workerTask; - public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, string checkpoint, SubscriptionOptions subscriptionOptions) + public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions) { this.client = client; this.commitsLink = commitsLink; this.onNextEvent = onNextEvent; this.checkpoints = checkpoint == null ? new Dictionary() : JsonConvert.DeserializeObject>(checkpoint); this.subscriptionOptions = subscriptionOptions; + this.loggingOptions = loggingOptions; } // TODO: Configure the retry policy, also allow the subscription to be canclled (use a CancellationToken) @@ -62,6 +64,7 @@ private async Task ReadEvents() while (query.HasMoreResults) { var feedResponse = await query.ExecuteNextAsync(); + loggingOptions.OnSuccess(ResponseInformation.FromSubscriptionReadResponse(feedResponse)); var events = new List(); string initialCheckpointValue; From deedbe5c30fa63986050bfb96a60010727f9b455 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 7 Jul 2017 21:24:57 +0100 Subject: [PATCH 27/61] Enabled support for custom type names to be specified for the event body and metadata fields in Cosmos DB event documents. Resolves issues #13, #14 --- README.md | 31 ++++++-- ...zureDocumentDbStorageEngineBuilderTests.cs | 7 ++ ...igurableTypeMapSerializationBinderTests.cs | 79 +++++++++++++++++++ .../DocumentDbStorageEventTests.cs | 40 ++++++++++ .../StorageEngineFactory.cs | 7 ++ .../AzureDocumentDbStorageEngine.cs | 11 +-- .../AzureDocumentDbStorageEngineBuilder.cs | 11 ++- .../ConfigurableSerializationTypeMap.cs | 60 ++++++++++++++ .../DefaultSerializationTypeMap.cs | 17 ++++ .../DocumentDbStorageEvent.cs | 14 ++-- .../ISerializationTypeMap.cs | 11 +++ .../Subscription.cs | 6 +- .../EventStoreAppending.cs | 1 - .../Events/TestMetadata.cs | 7 ++ .../Metadata/TestMetadata.cs | 7 -- 15 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ConfigurableSerializationTypeMap.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DefaultSerializationTypeMap.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ISerializationTypeMap.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.Tests/Events/TestMetadata.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.Tests/Metadata/TestMetadata.cs diff --git a/README.md b/README.md index 988a7a9..68e3a76 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,6 @@ DocumentClient client; // Set this up as required // If UseCollection isn't specified, sensible defaults for development are used. // If UseSubscriptions isn't supplied the subscription feature is disabled. -// The UseLogging sets up callbacks per Cosmos DB operation performed. return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) .UseCollection(o => { @@ -81,13 +80,19 @@ return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) o.PollEvery = TimeSpan.FromSeconds(0.5); }) .UseLogging(o => - { - o.Success = onSuccessCallback; - }) + { + o.Success = onSuccessCallback; + }) + .UseTypeMap(new ConfigurableSerializationTypeMap() + .RegisterTypes( + typeof(OrderCreated).GetTypeInfo().Assembly, + t => t.Namespace.EndsWith("Events"), + t => t.Name)) .Build() .Initialise(); ``` -The CollectionOptions allow you to specify +### CollectionOptions +Allows you to specify - The consistency level of the database - The default number of RUs when the collection is created - The collection name @@ -96,8 +101,20 @@ Only use one of the following consistency levels - Strong - Bounded Staleness - use this if you need to geo-replicate the database -The SubscriptionOptions allow you to configure how catch up subscriptions work +### UseSubscriptions +Allows you to configure the following aspects for catch up subscriptions - Max Item Count - the number of events to pull back in a polling operation - Poll Every - how long to wait after a polling operation has completed -Calling the Initialise operation creates the underlying collection based on the DatabaseOptions. This ensures the required stored procedure is present too. It is safe to call this multiple times. \ No newline at end of file +### UseLogging +Sets up callbacks per Cosmos DB operation performed. This is useful if you want to record per call data e.g. RU cost of each operation. + +### UseTypeMap +Allows you to control the event body/metadata type names. Bult in implementations +- DefaultSerializationTypeMap - uses the AssemblyQualifiedName of the type. (default) +- ConfigurableSerializationTypeMap - provides full control. + +While the default implementation is simple, this isn't great for versioning as contract assembly version number changes will render events unreadable. Therefore the configurable implementation or your own implementation is recommended. + +### Initialise +Calling the operation creates the underlying collection based on the DatabaseOptions. This ensures the required stored procedure is present too. It is safe to call this multiple times. \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs index 590432a..dce473a 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs @@ -39,6 +39,13 @@ public void when_setting_logging_settings_a_callback_must_be_supplied() Assert.Throws(() => builder.UseLogging(null)); } + [Fact] + public void when_setting_the_type_map_it_must_be_supplied() + { + var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); + Assert.Throws(() => builder.UseTypeMap(null)); + } + private static DocumentClient CreateClient() { var client = new DocumentClient(new Uri("https://localhost:8081/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs new file mode 100644 index 0000000..993134c --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection; +using Xunit; +using SimpleEventStore.Tests.Events; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + public class ConfigurableTypeMapSerializationBinderTests + { + [Fact] + public void when_registering_a_type_with_a_null_event_type_then_an_exception_is_thrown() + { + var sut = new ConfigurableSerializationTypeMap(); + Assert.Throws(() => sut.RegisterType(null, typeof(OrderCreated))); + } + + [Fact] + public void when_registering_a_type_with_a_null_type_then_an_exception_is_thrown() + { + var sut = new ConfigurableSerializationTypeMap(); + Assert.Throws(() => sut.RegisterType("TEST", null)); + } + + [Fact] + public void when_registering_types_with_a_null_assembly_then_an_exception_is_thrown() + { + var sut = new ConfigurableSerializationTypeMap(); + Assert.Throws(() => sut.RegisterTypes(null, t => true, t => t.Name)); + } + + [Fact] + public void when_registering_events_with_a_null_match_function_then_an_exception_is_thrown() + { + var sut = new ConfigurableSerializationTypeMap(); + Assert.Throws(() => sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, null, t => t.Name)); + } + + [Fact] + public void when_registering_types_with_a_null_naming_function_then_an_exception_is_thrown() + { + var sut = new ConfigurableSerializationTypeMap(); + Assert.Throws(() => sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => true, null)); + } + + [Fact] + public void when_registering_a_type_then_the_type_can_be_found() + { + var sut = new ConfigurableSerializationTypeMap(); + sut.RegisterType("OrderCreated", typeof(OrderCreated)); + + Assert.Equal(typeof(OrderCreated), sut.GetTypeFromName("OrderCreated")); + } + + [Fact] + public void when_registering_multiple_types_then_the_type_can_be_found() + { + var sut = new ConfigurableSerializationTypeMap(); + sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => t.Namespace.EndsWith("Events"), t => t.Name); + + Assert.Equal(typeof(OrderCreated), sut.GetTypeFromName("OrderCreated")); + } + + [Fact] + public void when_registering_a_type_then_the_name_can_be_found() + { + var sut = new ConfigurableSerializationTypeMap(); + sut.RegisterType("OrderCreated", typeof(OrderCreated)); + + Assert.Equal("OrderCreated", sut.GetNameFromType(typeof(OrderCreated))); + } + + [Fact] + public void when_registering_multiple_types_if_no_types_are_found_then_an_exception_is_thrown() + { + var sut = new ConfigurableSerializationTypeMap(); + Assert.Throws(() => sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => false, t => t.Name)); + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs new file mode 100644 index 0000000..e1c0b5b --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Reflection; +using Newtonsoft.Json.Linq; +using SimpleEventStore.Tests.Events; +using Xunit; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + public class DocumentDbStorageEventTests + { + [Fact] + public void when_converting_to_a_storage_event_it_succeeds() + { + var id = Guid.NewGuid(); + var body = new OrderCreated("TEST-ORDER"); + var metadata = new TestMetadata { Value = "TEST-VALUE" }; + var sut = new DocumentDbStorageEvent + { + StreamId = "TEST-STREAM", + Body = JObject.FromObject(body), + BodyType = "OrderCreated", + Metadata = JObject.FromObject(metadata), + MetadataType = "TestMetadata", + EventNumber = 1, + EventId = id + }; + var typeMap = new ConfigurableSerializationTypeMap().RegisterTypes( + typeof(OrderCreated).GetTypeInfo().Assembly, + t => t.Namespace.EndsWith("Events"), + t => t.Name); + var result = sut.ToStorageEvent(typeMap); + + Assert.Equal(sut.StreamId, result.StreamId); + Assert.Equal(body.OrderId, ((OrderCreated)result.EventBody).OrderId); + Assert.Equal(metadata.Value, ((TestMetadata)result.Metadata).Value); + Assert.Equal(sut.EventNumber, result.EventNumber); + Assert.Equal(sut.EventId, result.EventId); + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index 04c0ca0..a561fc3 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -1,9 +1,11 @@ using System; using System.IO; +using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; using Microsoft.Extensions.Configuration; +using SimpleEventStore.Tests.Events; namespace SimpleEventStore.AzureDocumentDb.Tests { @@ -39,6 +41,11 @@ internal static async Task Create(string databaseName) o.MaxItemCount = 1; o.PollEvery = TimeSpan.FromSeconds(0.5); }) + .UseTypeMap(new ConfigurableSerializationTypeMap() + .RegisterTypes( + typeof(OrderCreated).GetTypeInfo().Assembly, + t => t.Namespace.EndsWith("Events"), + t => t.Name)) .Build() .Initialise(); } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index f43efd9..b2b3a87 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -22,8 +21,9 @@ internal class AzureDocumentDbStorageEngine : IStorageEngine private readonly List subscriptions = new List(); private readonly SubscriptionOptions subscriptionOptions; private readonly LoggingOptions loggingOptions; + private readonly ISerializationTypeMap typeMap; - internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions) + internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap typeMap) { this.client = client; this.databaseName = databaseName; @@ -32,6 +32,7 @@ internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, collectionOptions.CollectionName, AppendStoredProcedureName); this.subscriptionOptions = subscriptionOptions; this.loggingOptions = loggingOptions; + this.typeMap = typeMap; } public async Task Initialise() @@ -45,7 +46,7 @@ public async Task Initialise() public async Task AppendToStream(string streamId, IEnumerable events) { - var docs = events.Select(d => DocumentDbStorageEvent.FromStorageEvent(d)).ToList(); + var docs = events.Select(d => DocumentDbStorageEvent.FromStorageEvent(d, this.typeMap)).ToList(); try { @@ -85,7 +86,7 @@ public async Task> ReadStreamForwards(string s foreach (var e in response) { - events.Add(e.ToStorageEvent()); + events.Add(e.ToStorageEvent(this.typeMap)); } } @@ -97,7 +98,7 @@ public void SubscribeToAll(Action, string> onN EnsureSubscriptionsAreEnabled(); Guard.IsNotNull(nameof(onNextEvent), onNextEvent); - var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions, this.loggingOptions); + var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions, this.loggingOptions, this.typeMap); subscriptions.Add(subscription); subscription.Start(); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs index a74ccac..a8b2178 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs @@ -10,6 +10,7 @@ public class AzureDocumentDbStorageEngineBuilder private readonly CollectionOptions collectionOptions = new CollectionOptions(); private readonly LoggingOptions loggingOptions = new LoggingOptions(); private SubscriptionOptions subscriptionOptions; + private ISerializationTypeMap typeMap = new DefaultSerializationTypeMap(); public AzureDocumentDbStorageEngineBuilder(DocumentClient client, string databaseName) { @@ -45,9 +46,17 @@ public AzureDocumentDbStorageEngineBuilder UseLogging(Action act return this; } + public AzureDocumentDbStorageEngineBuilder UseTypeMap(ISerializationTypeMap typeMap) + { + Guard.IsNotNull(nameof(typeMap), typeMap); + this.typeMap = typeMap; + + return this; + } + public IStorageEngine Build() { - var engine = new AzureDocumentDbStorageEngine(this.client, this.databaseName, this.collectionOptions, this.subscriptionOptions, this.loggingOptions); + var engine = new AzureDocumentDbStorageEngine(this.client, this.databaseName, this.collectionOptions, this.subscriptionOptions, this.loggingOptions, this.typeMap); return engine; } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ConfigurableSerializationTypeMap.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ConfigurableSerializationTypeMap.cs new file mode 100644 index 0000000..8ee8173 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ConfigurableSerializationTypeMap.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class ConfigurableSerializationTypeMap : ISerializationTypeMap + { + private readonly Dictionary typeMap = new Dictionary(); + private readonly Dictionary nameMap = new Dictionary(); + + public ConfigurableSerializationTypeMap RegisterType(string eventType, Type type) + { + Guard.IsNotNullOrEmpty(nameof(eventType), eventType); + Guard.IsNotNull(nameof(type), type); + + typeMap.Add(eventType, type); + nameMap.Add(type, eventType); + return this; + } + + public ConfigurableSerializationTypeMap RegisterTypes(Assembly assembly, Func matchFunc, Func namingFunc) + { + Guard.IsNotNull(nameof(assembly), assembly); + Guard.IsNotNull(nameof(matchFunc), matchFunc); + Guard.IsNotNull(nameof(namingFunc), namingFunc); + bool matchesFound = false; + + foreach (var type in assembly.GetTypes().Where(matchFunc)) + { + matchesFound = true; + RegisterType(namingFunc(type), type); + } + + if (!matchesFound) + { + throw new NoTypesFoundException("The matchFunc matched no types in the assembly"); + } + + return this; + } + + public Type GetTypeFromName(string typeName) + { + return typeMap[typeName]; + } + + public string GetNameFromType(Type type) + { + return nameMap[type]; + } + } + + public class NoTypesFoundException : Exception + { + public NoTypesFoundException(string message) : base(message) + { } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DefaultSerializationTypeMap.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DefaultSerializationTypeMap.cs new file mode 100644 index 0000000..a209e28 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DefaultSerializationTypeMap.cs @@ -0,0 +1,17 @@ +using System; + +namespace SimpleEventStore.AzureDocumentDb +{ + public class DefaultSerializationTypeMap : ISerializationTypeMap + { + public string GetNameFromType(Type type) + { + return type.AssemblyQualifiedName; + } + + public Type GetTypeFromName(string typeName) + { + return Type.GetType(typeName); + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs index fd10ef8..29dbc62 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.Serialization; +using System.Security.Cryptography.X509Certificates; using Microsoft.Azure.Documents; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -31,17 +33,17 @@ public class DocumentDbStorageEvent [JsonProperty("eventNumber")] public int EventNumber { get; set; } - public static DocumentDbStorageEvent FromStorageEvent(StorageEvent @event) + public static DocumentDbStorageEvent FromStorageEvent(StorageEvent @event, ISerializationTypeMap typeMap) { var docDbEvent = new DocumentDbStorageEvent(); docDbEvent.Id = $"{@event.StreamId}:{@event.EventNumber}"; docDbEvent.EventId = @event.EventId; docDbEvent.Body = JObject.FromObject(@event.EventBody); - docDbEvent.BodyType = @event.EventBody.GetType().AssemblyQualifiedName; + docDbEvent.BodyType = typeMap.GetNameFromType(@event.EventBody.GetType()); if (@event.Metadata != null) { docDbEvent.Metadata = JObject.FromObject(@event.Metadata); - docDbEvent.MetadataType = @event.Metadata.GetType().AssemblyQualifiedName; + docDbEvent.MetadataType = typeMap.GetNameFromType(@event.Metadata.GetType()); } docDbEvent.StreamId = @event.StreamId; docDbEvent.EventNumber = @event.EventNumber; @@ -66,10 +68,10 @@ public static DocumentDbStorageEvent FromDocument(Document document) return docDbEvent; } - public StorageEvent ToStorageEvent() + public StorageEvent ToStorageEvent(ISerializationTypeMap typeMap) { - object body = Body.ToObject(Type.GetType(BodyType)); - object metadata = Metadata?.ToObject(Type.GetType(MetadataType)); + object body = Body.ToObject(typeMap.GetTypeFromName(BodyType)); + object metadata = Metadata?.ToObject(typeMap.GetTypeFromName(MetadataType)); return new StorageEvent(StreamId, new EventData(EventId, body, metadata), EventNumber); } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ISerializationTypeMap.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ISerializationTypeMap.cs new file mode 100644 index 0000000..9977c3b --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ISerializationTypeMap.cs @@ -0,0 +1,11 @@ +using System; + +namespace SimpleEventStore.AzureDocumentDb +{ + public interface ISerializationTypeMap + { + Type GetTypeFromName(string typeName); + + string GetNameFromType(Type type); + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs index fa0718e..6a49192 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs @@ -17,9 +17,10 @@ internal class Subscription private readonly SubscriptionOptions subscriptionOptions; private readonly Dictionary checkpoints; private readonly LoggingOptions loggingOptions; + private readonly ISerializationTypeMap serializationTypeMap; private Task workerTask; - public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions) + public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap serializationTypeMap) { this.client = client; this.commitsLink = commitsLink; @@ -27,6 +28,7 @@ public Subscription(DocumentClient client, Uri commitsLink, Action() : JsonConvert.DeserializeObject>(checkpoint); this.subscriptionOptions = subscriptionOptions; this.loggingOptions = loggingOptions; + this.serializationTypeMap = serializationTypeMap; } // TODO: Configure the retry policy, also allow the subscription to be canclled (use a CancellationToken) @@ -70,7 +72,7 @@ private async Task ReadEvents() foreach (var @event in feedResponse) { - events.Add(DocumentDbStorageEvent.FromDocument(@event).ToStorageEvent()); + events.Add(DocumentDbStorageEvent.FromDocument(@event).ToStorageEvent(this.serializationTypeMap)); } checkpoints.TryGetValue(pkRange.Id, out initialCheckpointValue); diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs index 97d4b17..d273c87 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Threading.Tasks; using SimpleEventStore.Tests.Events; -using SimpleEventStore.Tests.Metadata; using Xunit; namespace SimpleEventStore.Tests diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/Events/TestMetadata.cs b/src/SimpleEventStore/SimpleEventStore.Tests/Events/TestMetadata.cs new file mode 100644 index 0000000..2cedcaa --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.Tests/Events/TestMetadata.cs @@ -0,0 +1,7 @@ +namespace SimpleEventStore.Tests.Events +{ + public class TestMetadata + { + public string Value { get; set; } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/Metadata/TestMetadata.cs b/src/SimpleEventStore/SimpleEventStore.Tests/Metadata/TestMetadata.cs deleted file mode 100644 index 416cc6f..0000000 --- a/src/SimpleEventStore/SimpleEventStore.Tests/Metadata/TestMetadata.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SimpleEventStore.Tests.Metadata -{ - internal class TestMetadata - { - public string Value { get; set; } - } -} \ No newline at end of file From 6be7c8e0e0d0f19083a53aca3bcffb094a2c4de3 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Wed, 12 Jul 2017 20:51:49 +0100 Subject: [PATCH 28/61] Corrected tests so they return a Task rather than void --- .../SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs index 7ecdb8a..27174b9 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs @@ -11,7 +11,7 @@ public abstract class EventStoreCatchUpSubscription : EventStoreTestBase private const int NumberOfStreamsToCreate = 10; [Fact] - public async void when_a_subscription_is_started_with_no_checkpoint_token_all_stored_events_are_read_in_stream_order() + public async Task when_a_subscription_is_started_with_no_checkpoint_token_all_stored_events_are_read_in_stream_order() { var sut = await GetEventStore(); var streams = new Dictionary>(); @@ -50,7 +50,7 @@ public async void when_a_subscription_is_started_with_no_checkpoint_token_all_st } [Fact] - public async void when_a_subscription_is_started_with_no_checkpoint_token_new_events_written_are_read_in_stream_order() + public async Task when_a_subscription_is_started_with_no_checkpoint_token_new_events_written_are_read_in_stream_order() { var sut = await GetEventStore(); var streams = new Dictionary>(); From 16411f610b4522cfc4efc49753d8eeefa83e8061 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Wed, 12 Jul 2017 22:04:00 +0100 Subject: [PATCH 29/61] Initial commit to support issues #4 & #9. Need to add missing tests before the enhancements are complete. --- ...DocumentDbEventStoreCatchUpSubscription.cs | 2 +- .../AzureDocumentDbStorageEngine.cs | 5 +- .../Subscription.cs | 33 ++++++++++--- .../EventStoreCatchUpSubscription.cs | 32 +++++++++++++ .../SimpleEventStore/EventStore.cs | 4 +- .../SimpleEventStore/IStorageEngine.cs | 2 +- .../SimpleEventStore/ISubscription.cs | 9 ++++ .../InMemory/InMemoryStorageEngine.cs | 46 ++++++++++++++----- 8 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore/ISubscription.cs diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs index 4ff6690..ca297a1 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs @@ -19,7 +19,7 @@ public void when_subscription_options_have_not_been_supplied_the_subscription_fe var client = new DocumentClient(new Uri("https://localhost:8081/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); var sut = new AzureDocumentDbStorageEngineBuilder(client, "Test").Build(); - Assert.Throws(() => sut.SubscribeToAll((e, c) => { }, null)); + Assert.Throws(() => sut.SubscribeToAll((e, c) => { }, null, null)); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index b2b3a87..13f30bd 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -93,15 +93,16 @@ public async Task> ReadStreamForwards(string s return events.AsReadOnly(); } - public void SubscribeToAll(Action, string> onNextEvent, string checkpoint) + public ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped, string checkpoint) { EnsureSubscriptionsAreEnabled(); Guard.IsNotNull(nameof(onNextEvent), onNextEvent); - var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions, this.loggingOptions, this.typeMap); + var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, onStopped, checkpoint, this.subscriptionOptions, this.loggingOptions, this.typeMap); subscriptions.Add(subscription); subscription.Start(); + return subscription; } private void EnsureSubscriptionsAreEnabled() diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs index 6a49192..fa73435 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -9,39 +10,57 @@ namespace SimpleEventStore.AzureDocumentDb { - internal class Subscription + public class Subscription : ISubscription { private readonly DocumentClient client; private readonly Uri commitsLink; private readonly Action, string> onNextEvent; + private Action onStopped; private readonly SubscriptionOptions subscriptionOptions; private readonly Dictionary checkpoints; private readonly LoggingOptions loggingOptions; private readonly ISerializationTypeMap serializationTypeMap; private Task workerTask; + private CancellationTokenSource cancellationSource; - public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap serializationTypeMap) + public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, Action onStopped, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap serializationTypeMap) { this.client = client; this.commitsLink = commitsLink; this.onNextEvent = onNextEvent; + this.onStopped = onStopped; this.checkpoints = checkpoint == null ? new Dictionary() : JsonConvert.DeserializeObject>(checkpoint); this.subscriptionOptions = subscriptionOptions; this.loggingOptions = loggingOptions; this.serializationTypeMap = serializationTypeMap; } - // TODO: Configure the retry policy, also allow the subscription to be canclled (use a CancellationToken) public void Start() { + cancellationSource = new CancellationTokenSource(); + workerTask = Task.Run(async () => { - while (true) + try + { + while (!cancellationSource.Token.IsCancellationRequested) + { + await ReadEvents(); + await Task.Delay(subscriptionOptions.PollEvery); + } + } + catch (Exception e) { - await ReadEvents(); - await Task.Delay(subscriptionOptions.PollEvery); + this.onStopped?.Invoke(this, e); } - }); + + this.onStopped?.Invoke(this, null); + }, cancellationSource.Token); + } + + public void Stop() + { + this.cancellationSource.Cancel(); } private async Task ReadEvents() diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs index 27174b9..b07aa33 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs @@ -173,6 +173,7 @@ await sut.AppendToStream( } } }, + null, checkpoint); await resumedEventRead.Task; @@ -181,6 +182,37 @@ await sut.AppendToStream( Assert.IsType(resumedEventRead.Task.Result.EventBody); } + [Fact] + public async Task when_a_subscription_is_started_it_can_be_stopped_and_no_more_events_are_processed() + { + var eventStore = await GetEventStore(); + var callbackCompletionSource = new TaskCompletionSource(); + + var subscription = eventStore.SubscribeToAll((events, c) => { }, (sub, exception) => callbackCompletionSource.SetResult(exception)); + subscription.Stop(); + + callbackCompletionSource.Task.Wait(TimeSpan.FromSeconds(5)); + Assert.Null(callbackCompletionSource.Task.Result); + } + + [Fact] + public async Task when_a_subscription_throws_an_exception_it_is_stopped_and_the_exception_is_supplied() + { + var streamId = Guid.NewGuid().ToString(); + var eventStore = await GetEventStore(); + var callbackCompletionSource = new TaskCompletionSource(); + await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); + + eventStore.SubscribeToAll((events, c) => throw new Exception("TEST"), (sub, exception) => callbackCompletionSource.SetResult(exception)); + + callbackCompletionSource.Task.Wait(TimeSpan.FromSeconds(5)); + Assert.NotNull(callbackCompletionSource.Task.Result); + } + + // TODO: Add test to prove a subscription can be restarted... + // TODO: Add test to prove starting a running subscription has no side effects... + // TODO: Add test to prove stopping a stopped subscription has no side effects... + private static async Task CreateStreams(Dictionary> streams, EventStore sut) { var streamsToCommit = new Dictionary(); diff --git a/src/SimpleEventStore/SimpleEventStore/EventStore.cs b/src/SimpleEventStore/SimpleEventStore/EventStore.cs index 5d85d7b..57732fa 100644 --- a/src/SimpleEventStore/SimpleEventStore/EventStore.cs +++ b/src/SimpleEventStore/SimpleEventStore/EventStore.cs @@ -42,9 +42,9 @@ public Task> ReadStreamForwards(string streamI return engine.ReadStreamForwards(streamId, startPosition, numberOfEventsToRead); } - public void SubscribeToAll(Action, string> onNextEvent, string checkpoint = null) + public ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped = null, string checkpoint = null) { - engine.SubscribeToAll(onNextEvent, checkpoint); + return engine.SubscribeToAll(onNextEvent, onStopped, checkpoint); } } } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs index 78719c9..9609a1b 100644 --- a/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs @@ -10,7 +10,7 @@ public interface IStorageEngine Task> ReadStreamForwards(string streamId, int startPosition, int numberOfEventsToRead); - void SubscribeToAll(Action, string> onNextEvent, string checkpoint); + ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped, string checkpoint); Task Initialise(); } diff --git a/src/SimpleEventStore/SimpleEventStore/ISubscription.cs b/src/SimpleEventStore/SimpleEventStore/ISubscription.cs new file mode 100644 index 0000000..8da9783 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore/ISubscription.cs @@ -0,0 +1,9 @@ +namespace SimpleEventStore +{ + public interface ISubscription + { + void Start(); + + void Stop(); + } +} diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index c09f154..29f32b8 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace SimpleEventStore.InMemory @@ -47,13 +48,15 @@ public Task> ReadStreamForwards(string streamI return Task.FromResult(result); } - public void SubscribeToAll(Action, string> onNextEvent, string checkpoint) + public ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped, string checkpoint) { Guard.IsNotNull(nameof(onNextEvent), onNextEvent); - var subscription = new Subscription(this.allEvents, onNextEvent, checkpoint); + var subscription = new Subscription(this.allEvents, onNextEvent, onStopped, checkpoint); this.subscriptions.Add(subscription); + subscription.Start(); + return subscription; } public Task Initialise() @@ -61,31 +64,50 @@ public Task Initialise() return Task.FromResult(this); } - private class Subscription + public class Subscription : ISubscription { private readonly IEnumerable allStream; - private readonly Action, string> onNewEvent; - private string initialCheckpoint; + private readonly Action, string> onNextEvent; + private readonly Action onStopped; + private readonly string initialCheckpoint; private int currentPosition; private Task workerTask; + private CancellationTokenSource cancellationSource; - public Subscription(IEnumerable allStream, Action, string> onNewEvent, string checkpoint) + public Subscription(IEnumerable allStream, Action, string> onNextEvent, Action onStopped, string checkpoint) { this.allStream = allStream; - this.onNewEvent = onNewEvent; + this.onNextEvent = onNextEvent; + this.onStopped = onStopped; this.initialCheckpoint = checkpoint; } public void Start() { + cancellationSource = new CancellationTokenSource(); + workerTask = Task.Run(async () => { - while (true) + try + { + while (!cancellationSource.Token.IsCancellationRequested) + { + ReadEvents(); + await Task.Delay(500); + } + } + catch (Exception e) { - ReadEvents(); - await Task.Delay(500); + this.onStopped?.Invoke(this, e); } - }); + + this.onStopped?.Invoke(this, null); + }, cancellationSource.Token); + } + + public void Stop() + { + this.cancellationSource.Cancel(); } private void ReadEvents() @@ -103,7 +125,7 @@ private void ReadEvents() if(dispatchEvents) { - this.onNewEvent(new[] { @event }, @event.EventId.ToString()); + this.onNextEvent(new[] { @event }, @event.EventId.ToString()); this.currentPosition++; } } From 31a8758507b1bb1aa3a9671748c24ae70604c13f Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 14 Jul 2017 15:34:49 +0100 Subject: [PATCH 30/61] Added missing tests to check subscription stop & start work as intended. --- .../Subscription.cs | 14 +++- .../EventStoreCatchUpSubscription.cs | 72 +++++++++++++++---- .../InMemory/InMemoryStorageEngine.cs | 14 +++- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs index fa73435..27f1311 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs @@ -22,6 +22,7 @@ public class Subscription : ISubscription private readonly ISerializationTypeMap serializationTypeMap; private Task workerTask; private CancellationTokenSource cancellationSource; + private bool running = false; public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, Action onStopped, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap serializationTypeMap) { @@ -37,8 +38,12 @@ public Subscription(DocumentClient client, Uri commitsLink, Action { try @@ -56,11 +61,16 @@ public void Start() this.onStopped?.Invoke(this, null); }, cancellationSource.Token); + running = true; } public void Stop() { - this.cancellationSource.Cancel(); + if (running) + { + this.cancellationSource.Cancel(); + running = false; + } } private async Task ReadEvents() diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs index b07aa33..d1d2245 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs @@ -9,6 +9,7 @@ namespace SimpleEventStore.Tests public abstract class EventStoreCatchUpSubscription : EventStoreTestBase { private const int NumberOfStreamsToCreate = 10; + private static TimeSpan TestMaxTimeout = TimeSpan.FromSeconds(30); [Fact] public async Task when_a_subscription_is_started_with_no_checkpoint_token_all_stored_events_are_read_in_stream_order() @@ -44,8 +45,7 @@ public async Task when_a_subscription_is_started_with_no_checkpoint_token_all_st } }); - await completionSource.Task; - + Assert.True(completionSource.Task.Wait(TestMaxTimeout)); Assert.Equal(0, streams.Count); } @@ -82,8 +82,8 @@ public async Task when_a_subscription_is_started_with_no_checkpoint_token_new_ev }); await CreateStreams(streams, sut); - await completionSource.Task; + Assert.True(completionSource.Task.Wait(TestMaxTimeout)); Assert.Equal(0, streams.Count); } @@ -121,9 +121,7 @@ public async Task when_multiple_subscriptions_are_created_they_receive_events() var streamId = Guid.NewGuid().ToString(); await sut.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); - Task.WaitAll(subscription1Called.Task, subscription2Called.Task); - Assert.True(subscription1Called.Task.Result); - Assert.True(subscription2Called.Task.Result); + Assert.True(Task.WaitAll(new [] { subscription1Called.Task, subscription2Called.Task }, TestMaxTimeout)); } [Fact] @@ -176,8 +174,7 @@ await sut.AppendToStream( null, checkpoint); - await resumedEventRead.Task; - + Assert.True(resumedEventRead.Task.Wait(TestMaxTimeout)); Assert.NotNull(resumedEventRead.Task.Result); Assert.IsType(resumedEventRead.Task.Result.EventBody); } @@ -191,7 +188,7 @@ public async Task when_a_subscription_is_started_it_can_be_stopped_and_no_more_e var subscription = eventStore.SubscribeToAll((events, c) => { }, (sub, exception) => callbackCompletionSource.SetResult(exception)); subscription.Stop(); - callbackCompletionSource.Task.Wait(TimeSpan.FromSeconds(5)); + Assert.True(callbackCompletionSource.Task.Wait(TestMaxTimeout)); Assert.Null(callbackCompletionSource.Task.Result); } @@ -203,15 +200,62 @@ public async Task when_a_subscription_throws_an_exception_it_is_stopped_and_the_ var callbackCompletionSource = new TaskCompletionSource(); await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); - eventStore.SubscribeToAll((events, c) => throw new Exception("TEST"), (sub, exception) => callbackCompletionSource.SetResult(exception)); + eventStore.SubscribeToAll( + (events, c) => { + throw new Exception("TEST"); + }, + (sub, exception) => + { + callbackCompletionSource.SetResult(exception); + }); - callbackCompletionSource.Task.Wait(TimeSpan.FromSeconds(5)); + Assert.True(callbackCompletionSource.Task.Wait(TestMaxTimeout)); Assert.NotNull(callbackCompletionSource.Task.Result); } - // TODO: Add test to prove a subscription can be restarted... - // TODO: Add test to prove starting a running subscription has no side effects... - // TODO: Add test to prove stopping a stopped subscription has no side effects... + [Fact] + public async Task when_a_subscription_stops_it_can_be_restarted() + { + var eventStore = await GetEventStore(); + var ignoreEvents = true; + var processedEvents = new TaskCompletionSource(); + + var subscription = eventStore.SubscribeToAll( + (events, c) => + { + if (!ignoreEvents) + { + processedEvents.SetResult(true); + } + }); + + subscription.Stop(); + ignoreEvents = false; + subscription.Start(); + + var streamId = Guid.NewGuid().ToString(); + await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); + + Assert.True(processedEvents.Task.Wait(TestMaxTimeout)); + } + + [Fact] + public async Task when_a_subscription_is_stopped_another_stop_does_not_throw_an_exception() + { + var eventStore = await GetEventStore(); + var subscription = eventStore.SubscribeToAll((e, c) => { }); + subscription.Stop(); + subscription.Stop(); + } + + [Fact] + public async Task when_a_subscription_is_started_another_start_does_not_throw_an_exception() + { + var eventStore = await GetEventStore(); + var subscription = eventStore.SubscribeToAll((e, c) => { }); + subscription.Start(); + subscription.Start(); + } private static async Task CreateStreams(Dictionary> streams, EventStore sut) { diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index 29f32b8..b8ea5b1 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -73,6 +73,7 @@ public class Subscription : ISubscription private int currentPosition; private Task workerTask; private CancellationTokenSource cancellationSource; + private bool running = false; public Subscription(IEnumerable allStream, Action, string> onNextEvent, Action onStopped, string checkpoint) { @@ -84,8 +85,12 @@ public Subscription(IEnumerable allStream, Action { try @@ -103,11 +108,16 @@ public void Start() this.onStopped?.Invoke(this, null); }, cancellationSource.Token); + running = true; } public void Stop() { - this.cancellationSource.Cancel(); + if (running) + { + this.cancellationSource.Cancel(); + running = false; + } } private void ReadEvents() From d6aaa5da03237d97103a849c0735308e7bede5d1 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sat, 15 Jul 2017 17:49:41 +0100 Subject: [PATCH 31/61] Added missing package references for VS/Rider unit test debugging. --- .../SimpleEventStore.Tests/SimpleEventStore.Tests.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index 8d6dbad..3a96d91 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -8,6 +8,8 @@ + + From 6a52b93231b628b448d9ef180a04c3fa570ba5e1 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sat, 15 Jul 2017 18:08:07 +0100 Subject: [PATCH 32/61] Fixed failing tests due to auto-generated proxy types. --- .../ConfigurableTypeMapSerializationBinderTests.cs | 2 +- .../DocumentDbStorageEventTests.cs | 2 +- .../StorageEngineFactory.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs index 993134c..6afaa99 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs @@ -55,7 +55,7 @@ public void when_registering_a_type_then_the_type_can_be_found() public void when_registering_multiple_types_then_the_type_can_be_found() { var sut = new ConfigurableSerializationTypeMap(); - sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => t.Namespace.EndsWith("Events"), t => t.Name); + sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => t.Namespace != null && t.Namespace.EndsWith("Events"), t => t.Name); Assert.Equal(typeof(OrderCreated), sut.GetTypeFromName("OrderCreated")); } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs index e1c0b5b..b5194ff 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs @@ -26,7 +26,7 @@ public void when_converting_to_a_storage_event_it_succeeds() }; var typeMap = new ConfigurableSerializationTypeMap().RegisterTypes( typeof(OrderCreated).GetTypeInfo().Assembly, - t => t.Namespace.EndsWith("Events"), + t => t.Namespace != null && t.Namespace.EndsWith("Events"), t => t.Name); var result = sut.ToStorageEvent(typeMap); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index a561fc3..22ec3f6 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -44,7 +44,7 @@ internal static async Task Create(string databaseName) .UseTypeMap(new ConfigurableSerializationTypeMap() .RegisterTypes( typeof(OrderCreated).GetTypeInfo().Assembly, - t => t.Namespace.EndsWith("Events"), + t => t.Namespace != null && t.Namespace.EndsWith("Events"), t => t.Name)) .Build() .Initialise(); From f4c6fd1c6083f32eca52e27f17f4f2724e83d200 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sat, 15 Jul 2017 21:13:38 +0100 Subject: [PATCH 33/61] Ensured that subcription tasks don't cancel before they can start. This fixes the failing unit tests. --- .../AzureDocumentDbStorageEngine.cs | 3 --- .../SimpleEventStore.AzureDocumentDb/Subscription.cs | 2 +- .../EventStoreCatchUpSubscription.cs | 10 +++++----- .../SimpleEventStore/InMemory/InMemoryStorageEngine.cs | 4 +--- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index 13f30bd..cc39550 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -18,7 +18,6 @@ internal class AzureDocumentDbStorageEngine : IStorageEngine private readonly CollectionOptions collectionOptions; private readonly Uri commitsLink; private readonly Uri storedProcLink; - private readonly List subscriptions = new List(); private readonly SubscriptionOptions subscriptionOptions; private readonly LoggingOptions loggingOptions; private readonly ISerializationTypeMap typeMap; @@ -99,8 +98,6 @@ public ISubscription SubscribeToAll(Action, st Guard.IsNotNull(nameof(onNextEvent), onNextEvent); var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, onStopped, checkpoint, this.subscriptionOptions, this.loggingOptions, this.typeMap); - subscriptions.Add(subscription); - subscription.Start(); return subscription; } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs index 27f1311..86d26a2 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs @@ -60,7 +60,7 @@ public void Start() } this.onStopped?.Invoke(this, null); - }, cancellationSource.Token); + }); running = true; } diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs index d1d2245..9116bb9 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs @@ -9,7 +9,8 @@ namespace SimpleEventStore.Tests public abstract class EventStoreCatchUpSubscription : EventStoreTestBase { private const int NumberOfStreamsToCreate = 10; - private static TimeSpan TestMaxTimeout = TimeSpan.FromSeconds(30); + private static readonly TimeSpan TestMaxTimeout = TimeSpan.FromSeconds(10); + [Fact] public async Task when_a_subscription_is_started_with_no_checkpoint_token_all_stored_events_are_read_in_stream_order() @@ -182,14 +183,13 @@ await sut.AppendToStream( [Fact] public async Task when_a_subscription_is_started_it_can_be_stopped_and_no_more_events_are_processed() { - var eventStore = await GetEventStore(); var callbackCompletionSource = new TaskCompletionSource(); - - var subscription = eventStore.SubscribeToAll((events, c) => { }, (sub, exception) => callbackCompletionSource.SetResult(exception)); + var eventStore = await GetEventStore(); + + var subscription = eventStore.SubscribeToAll((events, c) => { }, (sub, exception) => callbackCompletionSource.SetResult(exception)); subscription.Stop(); Assert.True(callbackCompletionSource.Task.Wait(TestMaxTimeout)); - Assert.Null(callbackCompletionSource.Task.Result); } [Fact] diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index b8ea5b1..f38f8c9 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -11,7 +11,6 @@ public class InMemoryStorageEngine : IStorageEngine { private readonly ConcurrentDictionary> streams = new ConcurrentDictionary>(); private readonly List allEvents = new List(); - private readonly List subscriptions = new List(); public Task AppendToStream(string streamId, IEnumerable events) { @@ -53,7 +52,6 @@ public ISubscription SubscribeToAll(Action, st Guard.IsNotNull(nameof(onNextEvent), onNextEvent); var subscription = new Subscription(this.allEvents, onNextEvent, onStopped, checkpoint); - this.subscriptions.Add(subscription); subscription.Start(); return subscription; @@ -107,7 +105,7 @@ public void Start() } this.onStopped?.Invoke(this, null); - }, cancellationSource.Token); + }); running = true; } From 076b57841fb83698f22932e1a34d2bd78ae8114d Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 11 Aug 2017 11:45:30 +0100 Subject: [PATCH 34/61] Added support for .NET Framework 4.5.2 upwards --- .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 6 +++--- .../SimpleEventStore.AzureDocumentDb.csproj | 4 ++-- .../SimpleEventStore.Tests/SimpleEventStore.Tests.csproj | 2 +- .../SimpleEventStore/SimpleEventStore.csproj | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index 66fbac9..526aa30 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1;net461 + netstandard1.6;net452 @@ -10,8 +10,8 @@ - - + + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 511d972..730a312 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -1,11 +1,11 @@  - netstandard1.6;net461 + netstandard1.6;net452 - + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index 3a96d91..cea79ce 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1;net461 + netstandard1.6;net452 diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index 3d07645..a286ddc 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -1,7 +1,7 @@  - netstandard1.6;net461 + netstandard1.6;net452 SimpleEventStore From 3230970a3e030d888f100c12606dd3c2fec11cb5 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 11 Aug 2017 12:03:50 +0100 Subject: [PATCH 35/61] Fixed build script and targeting for tests --- build/build.cake | 2 +- .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 2 +- .../SimpleEventStore.Tests/SimpleEventStore.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/build.cake b/build/build.cake index 7b2966f..1a1ef1f 100644 --- a/build/build.cake +++ b/build/build.cake @@ -21,7 +21,7 @@ var solutionDir = "../src/SimpleEventStore/"; var solutionFile = solutionDir + "SimpleEventStore.sln"; var documentDbTestConfigFiles = new [] { File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"), - File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/net461/appsettings.json") + File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/net452/appsettings.json") }; var testDirs = GetDirectories(solutionDir + "*.Tests"); var outputDir = "./nuget"; diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index 526aa30..bd1dee2 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -1,7 +1,7 @@  - netstandard1.6;net452 + netcoreapp1.1;net452 diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index cea79ce..ec570bf 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -1,7 +1,7 @@  - netstandard1.6;net452 + netcoreapp1.1;net452 From 1de8ba3c827a5c5437726fe2d807016d28211f90 Mon Sep 17 00:00:00 2001 From: Gordon Barrs Date: Wed, 30 Aug 2017 14:26:16 +0100 Subject: [PATCH 36/61] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68e3a76..74f89fe 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,11 @@ Allows you to configure the following aspects for catch up subscriptions Sets up callbacks per Cosmos DB operation performed. This is useful if you want to record per call data e.g. RU cost of each operation. ### UseTypeMap -Allows you to control the event body/metadata type names. Bult in implementations +Allows you to control the event body/metadata type names. Built in implementations - DefaultSerializationTypeMap - uses the AssemblyQualifiedName of the type. (default) - ConfigurableSerializationTypeMap - provides full control. While the default implementation is simple, this isn't great for versioning as contract assembly version number changes will render events unreadable. Therefore the configurable implementation or your own implementation is recommended. ### Initialise -Calling the operation creates the underlying collection based on the DatabaseOptions. This ensures the required stored procedure is present too. It is safe to call this multiple times. \ No newline at end of file +Calling the operation creates the underlying collection based on the DatabaseOptions. This ensures the required stored procedure is present too. It is safe to call this multiple times. From b926be6d2966ee11bffb2574b01c47d9fcbda024 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 19 Sep 2017 06:24:05 +0100 Subject: [PATCH 37/61] Removed support for subscriptions --- README.md | 27 -- ...DocumentDbEventStoreCatchUpSubscription.cs | 25 -- .../AzureDocumentDbEventStoreLogging.cs | 28 -- .../StorageEngineFactory.cs | 5 - .../AzureDocumentDbStorageEngine.cs | 22 +- .../AzureDocumentDbStorageEngineBuilder.cs | 12 +- .../Subscription.cs | 146 --------- .../SubscriptionOptions.cs | 17 -- .../SubscriptionsNotConfiguredException.cs | 11 - .../EventStoreCatchUpSubscription.cs | 285 ------------------ .../InMemoryEventStoreCatchUpSubscription.cs | 13 - .../SimpleEventStore/EventStore.cs | 5 - .../SimpleEventStore/IStorageEngine.cs | 2 - .../SimpleEventStore/ISubscription.cs | 9 - .../InMemory/InMemoryStorageEngine.cs | 88 ------ 15 files changed, 2 insertions(+), 693 deletions(-) delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreCatchUpSubscription.cs delete mode 100644 src/SimpleEventStore/SimpleEventStore/ISubscription.cs diff --git a/README.md b/README.md index 68e3a76..5d76016 100644 --- a/README.md +++ b/README.md @@ -40,28 +40,6 @@ var events = await subject.ReadStreamForwards(streamId); ``` You can either read all events in a stream, or a subset of events. Only read all events if you know the maximum size of a stream is going to be low and that you always need to read all events as part of your workload e.g. replaying events to project current state for a DDD aggregate. -### Catch up subscriptions -```csharp -eventStore.SubscribeToAll( - (events, checkpoint) => - { - // Process events... - // Then store the checkpoint... - }, - ""); -``` -Catch up subscriptions allow you to subscribe to events after they have been written to the event store. If no previous checkpoint is supplied then the subscription will process all events in the event store. - -Storing the checkpoint allows you to resume a subscription in the case of process failure and should only be done once all events supplied in the callback have been processed. This ensures that events are not missed. - -It is possible to have multiple catch up subscriptions running, for example to -- Publish messages onto a service bus -- Populate a read model - -Each subscription **must** perform an independant task. It is not possible to run multiple instances of the same catch up subscription as it would mean high amounts of duplicate processing as each process instance would read the same checkpoint. - -The number of events received will vary depending on the storage engine configuration. - ## Cosmos DB ```csharp DocumentClient client; // Set this up as required @@ -74,11 +52,6 @@ return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) o.ConsistencyLevel = consistencyLevelEnum; o.CollectionRequestUnits = 400; }) - .UseSubscriptions(o => - { - o.MaxItemCount = 1; - o.PollEvery = TimeSpan.FromSeconds(0.5); - }) .UseLogging(o => { o.Success = onSuccessCallback; diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs deleted file mode 100644 index ca297a1..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; -using SimpleEventStore.Tests; -using Xunit; - -namespace SimpleEventStore.AzureDocumentDb.Tests -{ - public class AzureDocumentDbEventStoreCatchUpSubscription : EventStoreCatchUpSubscription - { - protected override Task CreateStorageEngine() - { - return StorageEngineFactory.Create("CatchUpSubscriptionTests"); - } - - [Fact] - public void when_subscription_options_have_not_been_supplied_the_subscription_feature_cannot_be_used() - { - var client = new DocumentClient(new Uri("https://localhost:8081/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="); - var sut = new AzureDocumentDbStorageEngineBuilder(client, "Test").Build(); - - Assert.Throws(() => sut.SubscribeToAll((e, c) => { }, null, null)); - } - } -} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs index 6676fe7..b77311e 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs @@ -51,29 +51,6 @@ public async Task when_a_read_operation_is_successful_the_log_callback_is_called Assert.Equal(2, logCount); } - [Fact] - public async void when_a_subscription_reads_events_the_log_callback_is_called() - { - var polledOneEvent = 0; - var logCount = 0; - var sut = new EventStore(await CreateStorageEngine(t => Interlocked.Increment(ref logCount))); - var completionSource = new TaskCompletionSource(); - - sut.SubscribeToAll( - (events, checkpoint) => - { - if (Interlocked.Exchange(ref polledOneEvent, 1) == 0) - { - completionSource.SetResult(null); - } - }); - - await sut.AppendToStream(Guid.NewGuid().ToString(), 0, new EventData(Guid.NewGuid(), new OrderCreated("TEST-ORDER"))); - await completionSource.Task; - - Assert.True(logCount > 1); - } - private static async Task CreateStorageEngine(Action onSuccessCallback, string databaseName = "LoggingTests") { var config = new ConfigurationBuilder() @@ -99,11 +76,6 @@ private static async Task CreateStorageEngine(Action - { - o.MaxItemCount = 1; - o.PollEvery = TimeSpan.FromSeconds(0.5); - }) .UseLogging(o => { o.Success = onSuccessCallback; diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index 22ec3f6..249d050 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -36,11 +36,6 @@ internal static async Task Create(string databaseName) o.ConsistencyLevel = consistencyLevelEnum; o.CollectionRequestUnits = 400; }) - .UseSubscriptions(o => - { - o.MaxItemCount = 1; - o.PollEvery = TimeSpan.FromSeconds(0.5); - }) .UseTypeMap(new ConfigurableSerializationTypeMap() .RegisterTypes( typeof(OrderCreated).GetTypeInfo().Assembly, diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index cc39550..f58da4c 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -18,18 +18,16 @@ internal class AzureDocumentDbStorageEngine : IStorageEngine private readonly CollectionOptions collectionOptions; private readonly Uri commitsLink; private readonly Uri storedProcLink; - private readonly SubscriptionOptions subscriptionOptions; private readonly LoggingOptions loggingOptions; private readonly ISerializationTypeMap typeMap; - internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap typeMap) + internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, LoggingOptions loggingOptions, ISerializationTypeMap typeMap) { this.client = client; this.databaseName = databaseName; this.collectionOptions = collectionOptions; this.commitsLink = UriFactory.CreateDocumentCollectionUri(databaseName, collectionOptions.CollectionName); this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, collectionOptions.CollectionName, AppendStoredProcedureName); - this.subscriptionOptions = subscriptionOptions; this.loggingOptions = loggingOptions; this.typeMap = typeMap; } @@ -92,24 +90,6 @@ public async Task> ReadStreamForwards(string s return events.AsReadOnly(); } - public ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped, string checkpoint) - { - EnsureSubscriptionsAreEnabled(); - Guard.IsNotNull(nameof(onNextEvent), onNextEvent); - - var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, onStopped, checkpoint, this.subscriptionOptions, this.loggingOptions, this.typeMap); - subscription.Start(); - return subscription; - } - - private void EnsureSubscriptionsAreEnabled() - { - if (subscriptionOptions == null) - { - throw new SubscriptionsNotConfiguredException("Ensure subscription options have been supplied prior to using subscription features."); - } - } - private async Task CreateDatabaseIfItDoesNotExist() { var databaseExistsQuery = client.CreateDatabaseQuery() diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs index a8b2178..dbdb771 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs @@ -9,7 +9,6 @@ public class AzureDocumentDbStorageEngineBuilder private readonly DocumentClient client; private readonly CollectionOptions collectionOptions = new CollectionOptions(); private readonly LoggingOptions loggingOptions = new LoggingOptions(); - private SubscriptionOptions subscriptionOptions; private ISerializationTypeMap typeMap = new DefaultSerializationTypeMap(); public AzureDocumentDbStorageEngineBuilder(DocumentClient client, string databaseName) @@ -29,15 +28,6 @@ public AzureDocumentDbStorageEngineBuilder UseCollection(Action action) - { - Guard.IsNotNull(nameof(action), action); - - subscriptionOptions = new SubscriptionOptions(); - action(subscriptionOptions); - return this; - } - public AzureDocumentDbStorageEngineBuilder UseLogging(Action action) { Guard.IsNotNull(nameof(action), action); @@ -56,7 +46,7 @@ public AzureDocumentDbStorageEngineBuilder UseTypeMap(ISerializationTypeMap type public IStorageEngine Build() { - var engine = new AzureDocumentDbStorageEngine(this.client, this.databaseName, this.collectionOptions, this.subscriptionOptions, this.loggingOptions, this.typeMap); + var engine = new AzureDocumentDbStorageEngine(this.client, this.databaseName, this.collectionOptions, this.loggingOptions, this.typeMap); return engine; } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs deleted file mode 100644 index 86d26a2..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Microsoft.Azure.Documents.Linq; -using Newtonsoft.Json; - -namespace SimpleEventStore.AzureDocumentDb -{ - public class Subscription : ISubscription - { - private readonly DocumentClient client; - private readonly Uri commitsLink; - private readonly Action, string> onNextEvent; - private Action onStopped; - private readonly SubscriptionOptions subscriptionOptions; - private readonly Dictionary checkpoints; - private readonly LoggingOptions loggingOptions; - private readonly ISerializationTypeMap serializationTypeMap; - private Task workerTask; - private CancellationTokenSource cancellationSource; - private bool running = false; - - public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, Action onStopped, string checkpoint, SubscriptionOptions subscriptionOptions, LoggingOptions loggingOptions, ISerializationTypeMap serializationTypeMap) - { - this.client = client; - this.commitsLink = commitsLink; - this.onNextEvent = onNextEvent; - this.onStopped = onStopped; - this.checkpoints = checkpoint == null ? new Dictionary() : JsonConvert.DeserializeObject>(checkpoint); - this.subscriptionOptions = subscriptionOptions; - this.loggingOptions = loggingOptions; - this.serializationTypeMap = serializationTypeMap; - } - - public void Start() - { - if (running) - { - return; - } - - cancellationSource = new CancellationTokenSource(); - workerTask = Task.Run(async () => - { - try - { - while (!cancellationSource.Token.IsCancellationRequested) - { - await ReadEvents(); - await Task.Delay(subscriptionOptions.PollEvery); - } - } - catch (Exception e) - { - this.onStopped?.Invoke(this, e); - } - - this.onStopped?.Invoke(this, null); - }); - running = true; - } - - public void Stop() - { - if (running) - { - this.cancellationSource.Cancel(); - running = false; - } - } - - private async Task ReadEvents() - { - var partitionKeyRanges = await GetPartitionKeyRanges(); - - foreach (var pkRange in partitionKeyRanges) - { - string continuation; - checkpoints.TryGetValue(pkRange.Id, out continuation); - - IDocumentQuery query = client.CreateDocumentChangeFeedQuery( - commitsLink, - new ChangeFeedOptions - { - PartitionKeyRangeId = pkRange.Id, - StartFromBeginning = true, - RequestContinuation = continuation, - MaxItemCount = subscriptionOptions.MaxItemCount - }); - - while (query.HasMoreResults) - { - var feedResponse = await query.ExecuteNextAsync(); - loggingOptions.OnSuccess(ResponseInformation.FromSubscriptionReadResponse(feedResponse)); - var events = new List(); - string initialCheckpointValue; - - foreach (var @event in feedResponse) - { - events.Add(DocumentDbStorageEvent.FromDocument(@event).ToStorageEvent(this.serializationTypeMap)); - } - - checkpoints.TryGetValue(pkRange.Id, out initialCheckpointValue); - - try - { - checkpoints[pkRange.Id] = feedResponse.ResponseContinuation; - this.onNextEvent(events.AsReadOnly(), JsonConvert.SerializeObject(checkpoints)); - } - catch(Exception) - { - if (initialCheckpointValue != null) - { - checkpoints[pkRange.Id] = initialCheckpointValue; - } - throw; - } - } - } - } - - private async Task> GetPartitionKeyRanges() - { - var partitionKeyRanges = new List(); - FeedResponse pkRangesResponse; - string continuationToken = null; - - do - { - pkRangesResponse = await client.ReadPartitionKeyRangeFeedAsync(commitsLink, new FeedOptions - { - RequestContinuation = continuationToken - }); - partitionKeyRanges.AddRange(pkRangesResponse); - continuationToken = pkRangesResponse.ResponseContinuation; - } - while (continuationToken != null); - - return partitionKeyRanges; - } - } -} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs deleted file mode 100644 index 6a07cce..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace SimpleEventStore.AzureDocumentDb -{ - public class SubscriptionOptions - { - public SubscriptionOptions() - { - this.MaxItemCount = 100; - this.PollEvery = TimeSpan.FromSeconds(5); - } - - public int MaxItemCount { get; set; } - - public TimeSpan PollEvery { get; set; } - } -} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs deleted file mode 100644 index 4139840..0000000 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionsNotConfiguredException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace SimpleEventStore.AzureDocumentDb -{ - public class SubscriptionsNotConfiguredException : Exception - { - public SubscriptionsNotConfiguredException(string message) : base(message) - { - } - } -} diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs deleted file mode 100644 index 9116bb9..0000000 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using SimpleEventStore.Tests.Events; -using Xunit; - -namespace SimpleEventStore.Tests -{ - public abstract class EventStoreCatchUpSubscription : EventStoreTestBase - { - private const int NumberOfStreamsToCreate = 10; - private static readonly TimeSpan TestMaxTimeout = TimeSpan.FromSeconds(10); - - - [Fact] - public async Task when_a_subscription_is_started_with_no_checkpoint_token_all_stored_events_are_read_in_stream_order() - { - var sut = await GetEventStore(); - var streams = new Dictionary>(); - var completionSource = new TaskCompletionSource(); - - await CreateStreams(streams, sut); - - sut.SubscribeToAll( - (events, checkpoint) => - { - foreach (var @event in events) - { - if (streams.ContainsKey(@event.StreamId)) - { - var stream = streams[@event.StreamId]; - - Assert.Equal(stream.Peek().EventId, @event.EventId); - stream.Dequeue(); - - if (stream.Count == 0) - { - streams.Remove(@event.StreamId); - } - - if (streams.Count == 0) - { - completionSource.SetResult(null); - } - } - } - }); - - Assert.True(completionSource.Task.Wait(TestMaxTimeout)); - Assert.Equal(0, streams.Count); - } - - [Fact] - public async Task when_a_subscription_is_started_with_no_checkpoint_token_new_events_written_are_read_in_stream_order() - { - var sut = await GetEventStore(); - var streams = new Dictionary>(); - var completionSource = new TaskCompletionSource(); - - sut.SubscribeToAll( - (events, checkpoint) => - { - foreach (var @event in events) - { - if (streams.ContainsKey(@event.StreamId)) - { - var stream = streams[@event.StreamId]; - - Assert.Equal(stream.Peek().EventId, @event.EventId); - stream.Dequeue(); - - if (stream.Count == 0) - { - streams.Remove(@event.StreamId); - } - - if (streams.Count == 0) - { - completionSource.SetResult(null); - } - } - } - }); - - await CreateStreams(streams, sut); - - Assert.True(completionSource.Task.Wait(TestMaxTimeout)); - Assert.Equal(0, streams.Count); - } - - [Fact] - public async Task when_a_subscription_is_started_a_next_event_function_must_be_supplied() - { - var sut = await GetEventStore(); - Assert.Throws(() => sut.SubscribeToAll(null)); - } - - [Fact] - public async Task when_multiple_subscriptions_are_created_they_receive_events() - { - var subscription1Called = new TaskCompletionSource(false); - var subscription2Called = new TaskCompletionSource(false); - - var sut = await GetEventStore(); - sut.SubscribeToAll( - (events, checkpoint) => - { - if (!subscription1Called.Task.IsCompleted) - { - subscription1Called.SetResult(true); - } - }); - sut.SubscribeToAll( - (events, checkpoint) => - { - if (!subscription2Called.Task.IsCompleted) - { - subscription2Called.SetResult(true); - } - }); - - var streamId = Guid.NewGuid().ToString(); - await sut.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); - - Assert.True(Task.WaitAll(new [] { subscription1Called.Task, subscription2Called.Task }, TestMaxTimeout)); - } - - [Fact] - public async Task when_a_subscription_is_started_with_a_checkpoint_only_events_after_the_checkpoint_are_received() - { - var initialCheckpointObtained = new TaskCompletionSource(); - var resumedEventRead = new TaskCompletionSource(); - var streamId = Guid.NewGuid().ToString(); - var sut = await GetEventStore(); - var orderCreatedId = Guid.NewGuid(); - - await sut.AppendToStream( - streamId, - 0, - new EventData(orderCreatedId, new OrderCreated(streamId)) - ); - - await sut.AppendToStream( - streamId, - 1, - new EventData(Guid.NewGuid(), new OrderDispatched(streamId)) - ); - - sut.SubscribeToAll( - (events, c) => - { - foreach (var e in events) - { - if (e.EventId == orderCreatedId) - { - initialCheckpointObtained.SetResult(c); - } - } - }); - - await initialCheckpointObtained.Task; - var checkpoint = initialCheckpointObtained.Task.Result; - - sut.SubscribeToAll( - (events, c) => - { - foreach (var e in events) - { - if (!resumedEventRead.Task.IsCompleted && e.StreamId == streamId) - { - resumedEventRead.SetResult(e); - } - } - }, - null, - checkpoint); - - Assert.True(resumedEventRead.Task.Wait(TestMaxTimeout)); - Assert.NotNull(resumedEventRead.Task.Result); - Assert.IsType(resumedEventRead.Task.Result.EventBody); - } - - [Fact] - public async Task when_a_subscription_is_started_it_can_be_stopped_and_no_more_events_are_processed() - { - var callbackCompletionSource = new TaskCompletionSource(); - var eventStore = await GetEventStore(); - - var subscription = eventStore.SubscribeToAll((events, c) => { }, (sub, exception) => callbackCompletionSource.SetResult(exception)); - subscription.Stop(); - - Assert.True(callbackCompletionSource.Task.Wait(TestMaxTimeout)); - } - - [Fact] - public async Task when_a_subscription_throws_an_exception_it_is_stopped_and_the_exception_is_supplied() - { - var streamId = Guid.NewGuid().ToString(); - var eventStore = await GetEventStore(); - var callbackCompletionSource = new TaskCompletionSource(); - await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); - - eventStore.SubscribeToAll( - (events, c) => { - throw new Exception("TEST"); - }, - (sub, exception) => - { - callbackCompletionSource.SetResult(exception); - }); - - Assert.True(callbackCompletionSource.Task.Wait(TestMaxTimeout)); - Assert.NotNull(callbackCompletionSource.Task.Result); - } - - [Fact] - public async Task when_a_subscription_stops_it_can_be_restarted() - { - var eventStore = await GetEventStore(); - var ignoreEvents = true; - var processedEvents = new TaskCompletionSource(); - - var subscription = eventStore.SubscribeToAll( - (events, c) => - { - if (!ignoreEvents) - { - processedEvents.SetResult(true); - } - }); - - subscription.Stop(); - ignoreEvents = false; - subscription.Start(); - - var streamId = Guid.NewGuid().ToString(); - await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId))); - - Assert.True(processedEvents.Task.Wait(TestMaxTimeout)); - } - - [Fact] - public async Task when_a_subscription_is_stopped_another_stop_does_not_throw_an_exception() - { - var eventStore = await GetEventStore(); - var subscription = eventStore.SubscribeToAll((e, c) => { }); - subscription.Stop(); - subscription.Stop(); - } - - [Fact] - public async Task when_a_subscription_is_started_another_start_does_not_throw_an_exception() - { - var eventStore = await GetEventStore(); - var subscription = eventStore.SubscribeToAll((e, c) => { }); - subscription.Start(); - subscription.Start(); - } - - private static async Task CreateStreams(Dictionary> streams, EventStore sut) - { - var streamsToCommit = new Dictionary(); - - for (int i = 0; i < NumberOfStreamsToCreate; i++) - { - var streamId = Guid.NewGuid().ToString(); - var createdEvent = new EventData(Guid.NewGuid(), new OrderCreated(streamId), null); - var dispatchedEvent = new EventData(Guid.NewGuid(), new OrderDispatched(streamId), null); - var streamOrder = new Queue(); - - streamOrder.Enqueue(createdEvent); - streamOrder.Enqueue(dispatchedEvent); - - streams.Add(streamId, streamOrder); - - streamsToCommit.Add(streamId, new [] { createdEvent, dispatchedEvent }); - } - - foreach (var stream in streamsToCommit) - { - await sut.AppendToStream(stream.Key, 0, stream.Value); - } - } - } -} diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreCatchUpSubscription.cs deleted file mode 100644 index 97bc3cc..0000000 --- a/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreCatchUpSubscription.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using SimpleEventStore.InMemory; - -namespace SimpleEventStore.Tests.InMemory -{ - public class InMemoryEventStoreCatchUpSubscription : EventStoreCatchUpSubscription - { - protected override Task CreateStorageEngine() - { - return Task.FromResult((IStorageEngine)new InMemoryStorageEngine()); - } - } -} diff --git a/src/SimpleEventStore/SimpleEventStore/EventStore.cs b/src/SimpleEventStore/SimpleEventStore/EventStore.cs index 57732fa..695cf55 100644 --- a/src/SimpleEventStore/SimpleEventStore/EventStore.cs +++ b/src/SimpleEventStore/SimpleEventStore/EventStore.cs @@ -41,10 +41,5 @@ public Task> ReadStreamForwards(string streamI return engine.ReadStreamForwards(streamId, startPosition, numberOfEventsToRead); } - - public ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped = null, string checkpoint = null) - { - return engine.SubscribeToAll(onNextEvent, onStopped, checkpoint); - } } } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs index 9609a1b..86b89c4 100644 --- a/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/IStorageEngine.cs @@ -10,8 +10,6 @@ public interface IStorageEngine Task> ReadStreamForwards(string streamId, int startPosition, int numberOfEventsToRead); - ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped, string checkpoint); - Task Initialise(); } } \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/ISubscription.cs b/src/SimpleEventStore/SimpleEventStore/ISubscription.cs deleted file mode 100644 index 8da9783..0000000 --- a/src/SimpleEventStore/SimpleEventStore/ISubscription.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SimpleEventStore -{ - public interface ISubscription - { - void Start(); - - void Stop(); - } -} diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index f38f8c9..f4045f5 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -47,97 +47,9 @@ public Task> ReadStreamForwards(string streamI return Task.FromResult(result); } - public ISubscription SubscribeToAll(Action, string> onNextEvent, Action onStopped, string checkpoint) - { - Guard.IsNotNull(nameof(onNextEvent), onNextEvent); - - var subscription = new Subscription(this.allEvents, onNextEvent, onStopped, checkpoint); - - subscription.Start(); - return subscription; - } - public Task Initialise() { return Task.FromResult(this); } - - public class Subscription : ISubscription - { - private readonly IEnumerable allStream; - private readonly Action, string> onNextEvent; - private readonly Action onStopped; - private readonly string initialCheckpoint; - private int currentPosition; - private Task workerTask; - private CancellationTokenSource cancellationSource; - private bool running = false; - - public Subscription(IEnumerable allStream, Action, string> onNextEvent, Action onStopped, string checkpoint) - { - this.allStream = allStream; - this.onNextEvent = onNextEvent; - this.onStopped = onStopped; - this.initialCheckpoint = checkpoint; - } - - public void Start() - { - if (running) - { - return; - } - - cancellationSource = new CancellationTokenSource(); - workerTask = Task.Run(async () => - { - try - { - while (!cancellationSource.Token.IsCancellationRequested) - { - ReadEvents(); - await Task.Delay(500); - } - } - catch (Exception e) - { - this.onStopped?.Invoke(this, e); - } - - this.onStopped?.Invoke(this, null); - }); - running = true; - } - - public void Stop() - { - if (running) - { - this.cancellationSource.Cancel(); - running = false; - } - } - - private void ReadEvents() - { - var snapshot = allStream.Skip(this.currentPosition).ToList(); - - foreach (var @event in snapshot) - { - bool dispatchEvents = true; - - if (this.initialCheckpoint == null || this.initialCheckpoint == @event.EventId.ToString()) - { - dispatchEvents = this.initialCheckpoint == null; - } - - if(dispatchEvents) - { - this.onNextEvent(new[] { @event }, @event.EventId.ToString()); - this.currentPosition++; - } - } - } - } } } \ No newline at end of file From 2cafc27e0274b2bc75f465946a0edf0177883ad2 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 19 Sep 2017 08:26:37 +0100 Subject: [PATCH 38/61] Changed package name to include Asos as a prefix --- .../SimpleEventStore.AzureDocumentDb.csproj | 2 +- src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 730a312..e177cef 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -14,7 +14,7 @@ - SimpleEventStore.AzureDocumentDb + Asos.SimpleEventStore.AzureDocumentDb Provides a DocumentDB storage engine for Simple Event Store (SES) ASOS Copyright ASOS ©2016 diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index a286ddc..e9ea262 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -4,7 +4,7 @@ netstandard1.6;net452 - SimpleEventStore + ASOS.SimpleEventStore Simple Event Store (SES) provides a lightweight event sourcing abstraction. ASOS Copyright ASOS ©2016 From 30523140882c18188103e3c35efde3294e74697e Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 19 Sep 2017 08:37:38 +0100 Subject: [PATCH 39/61] Removed references to subscriptions in the README --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 5d76016..c0383d8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ Simple Event Store (SES) provides a lightweight event sourcing abstraction. - Full support for async/await for all persistence engines - Optimistic concurrency for append operations - Reading streams forward -- Catch up subscriptions ## Persistence Engines SES supports the following @@ -74,11 +73,6 @@ Only use one of the following consistency levels - Strong - Bounded Staleness - use this if you need to geo-replicate the database -### UseSubscriptions -Allows you to configure the following aspects for catch up subscriptions -- Max Item Count - the number of events to pull back in a polling operation -- Poll Every - how long to wait after a polling operation has completed - ### UseLogging Sets up callbacks per Cosmos DB operation performed. This is useful if you want to record per call data e.g. RU cost of each operation. From 061c4b1568cf93371171798338160636602dd6b3 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 19 Sep 2017 10:23:17 +0100 Subject: [PATCH 40/61] Updated build script so nuget settings are passed in as strings --- build/build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.ps1 b/build/build.ps1 index 584e410..85ed134 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -192,5 +192,5 @@ if (!(Test-Path $CAKE_EXE)) { # Start Cake Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey -consistencyLevel=$ConsistencyLevel -buildVersion=`"$BuildVersion`" -nugetSource=$NugetSource -nugetApiKey=$NugetApiKey $UseMono $UseDryRun $UseExperimental $ScriptArgs" +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" -uri=$Uri -authKey=$AuthKey -consistencyLevel=$ConsistencyLevel -buildVersion=`"$BuildVersion`" -nugetSource=`"$NugetSource`" -nugetApiKey=`"$NugetApiKey`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" exit $LASTEXITCODE \ No newline at end of file From 65c8a74ca1fff0253ac472224bc2754fddc13385 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Tue, 19 Sep 2017 11:03:13 +0100 Subject: [PATCH 41/61] Updated csproj to add ASOS as the nuget package author --- .../SimpleEventStore.AzureDocumentDb.csproj | 2 +- src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index e177cef..b3f7065 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -20,7 +20,7 @@ Copyright ASOS ©2016 SimpleEventStore.AzureDocumentDb Asos.SimpleEventStore.AzureDocumentDb - + ASOS eventsourcing documentdb azure https://github.com/ASOS/SimpleEventStore diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index e9ea262..1d9d082 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -10,7 +10,7 @@ Copyright ASOS ©2016 SimpleEventStore Asos.SimpleEventStore - + ASOS eventsourcing documentdb azure https://github.com/ASOS/SimpleEventStore 1.0.0 From dc77f3f9e65bb1dfd5a20ce8e59aba08131fdfbf Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 21 Sep 2017 16:29:58 +0100 Subject: [PATCH 42/61] Altered build script to ensure assembly versions match package versions. Fixes #20 --- build/build.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.cake b/build/build.cake index 1a1ef1f..6683773 100644 --- a/build/build.cake +++ b/build/build.cake @@ -50,7 +50,7 @@ Task("Build") DotNetCoreBuild(solutionFile, new DotNetCoreBuildSettings { Configuration = configuration, NoIncremental = true, - ArgumentCustomization = args => args.Append("/p:Version=" + buildVersion) + ArgumentCustomization = args => args.Append("/p:Version=" + buildVersion + " /p:PackageVersion=" + buildVersion) }); }); @@ -96,7 +96,7 @@ Task("Package") Configuration = configuration, NoBuild = true, OutputDirectory = outputDir, - ArgumentCustomization = args => args.Append("/p:PackageVersion=" + buildVersion) + ArgumentCustomization = args => args.Append("/p:PackageVersion=" + buildVersion + " /p:PackageVersion=" + buildVersion) }; DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore/", settings); From 3548046892b0b2e5e18f34d406cbec8b131a85a6 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 22 Sep 2017 08:01:09 +0100 Subject: [PATCH 43/61] Corrected build script issue where PackageVersion was used twice rather than Version. Relates to #20 --- build/build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index 6683773..bc94676 100644 --- a/build/build.cake +++ b/build/build.cake @@ -96,7 +96,7 @@ Task("Package") Configuration = configuration, NoBuild = true, OutputDirectory = outputDir, - ArgumentCustomization = args => args.Append("/p:PackageVersion=" + buildVersion + " /p:PackageVersion=" + buildVersion) + ArgumentCustomization = args => args.Append("/p:Version=" + buildVersion + " /p:PackageVersion=" + buildVersion) }; DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore/", settings); From 5f6e016f792a402907e79babed9aafb546d2e4d0 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 22 Sep 2017 10:09:22 +0100 Subject: [PATCH 44/61] Altered build script to see if it fixes Version override issues on the build server --- build/build.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.cake b/build/build.cake index bc94676..e554346 100644 --- a/build/build.cake +++ b/build/build.cake @@ -50,7 +50,7 @@ Task("Build") DotNetCoreBuild(solutionFile, new DotNetCoreBuildSettings { Configuration = configuration, NoIncremental = true, - ArgumentCustomization = args => args.Append("/p:Version=" + buildVersion + " /p:PackageVersion=" + buildVersion) + ArgumentCustomization = args => args.Append("/p:Version=\"" + buildVersion + "\" /p:PackageVersion=\"" + buildVersion + "\"") }); }); @@ -96,7 +96,7 @@ Task("Package") Configuration = configuration, NoBuild = true, OutputDirectory = outputDir, - ArgumentCustomization = args => args.Append("/p:Version=" + buildVersion + " /p:PackageVersion=" + buildVersion) + ArgumentCustomization = args => args.Append("/p:Version=\"" + buildVersion + "\" /p:PackageVersion=\"" + buildVersion + "\"") }; DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore/", settings); From c41f987d678fd1f9e1aebe31e3dca3760262359a Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Fri, 22 Sep 2017 10:31:44 +0100 Subject: [PATCH 45/61] Altered build scripts and projects so we pass in the PackageVersion and Version via separate arguments. Hopefully this fixes the CI/CD issue that we are seeing in issue #20 --- build/build.cake | 4 ++-- .../SimpleEventStore.AzureDocumentDb.csproj | 5 +++-- .../SimpleEventStore/SimpleEventStore.csproj | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build/build.cake b/build/build.cake index e554346..8b31de8 100644 --- a/build/build.cake +++ b/build/build.cake @@ -50,7 +50,7 @@ Task("Build") DotNetCoreBuild(solutionFile, new DotNetCoreBuildSettings { Configuration = configuration, NoIncremental = true, - ArgumentCustomization = args => args.Append("/p:Version=\"" + buildVersion + "\" /p:PackageVersion=\"" + buildVersion + "\"") + ArgumentCustomization = args => args.Append("/p:BuildVersion=" + buildVersion) }); }); @@ -96,7 +96,7 @@ Task("Package") Configuration = configuration, NoBuild = true, OutputDirectory = outputDir, - ArgumentCustomization = args => args.Append("/p:Version=\"" + buildVersion + "\" /p:PackageVersion=\"" + buildVersion + "\"") + ArgumentCustomization = args => args.Append("/p:BuildVersion=" + buildVersion) }; DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore/", settings); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index b3f7065..3537503 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -2,6 +2,7 @@ netstandard1.6;net452 + 1.0.0 @@ -25,7 +26,7 @@ https://github.com/ASOS/SimpleEventStore library - 1.0.0 - 1.0.0 + $(BuildVersion) + $(BuildVersion) \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj index 1d9d082..75c4429 100644 --- a/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj +++ b/src/SimpleEventStore/SimpleEventStore/SimpleEventStore.csproj @@ -2,6 +2,7 @@ netstandard1.6;net452 + 1.0.0 ASOS.SimpleEventStore @@ -13,7 +14,7 @@ ASOS eventsourcing documentdb azure https://github.com/ASOS/SimpleEventStore - 1.0.0 - 1.0.0 + $(BuildVersion) + $(BuildVersion) \ No newline at end of file From 98205f5a0186a2828c127a2a331a99cda03b61ed Mon Sep 17 00:00:00 2001 From: Ralph Willgoss Date: Wed, 11 Oct 2017 21:24:37 +0000 Subject: [PATCH 46/61] Fix KeyNotFoundException when reading an empty stream Fix KeyNotFoundException when reading an empty stream using the InMemoryStorageEngine --- .../SimpleEventStore.Tests/EventStoreReading.cs | 11 +++++++++++ .../InMemory/InMemoryStorageEngine.cs | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs index cb00cb0..d1a7971 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs @@ -12,6 +12,17 @@ namespace SimpleEventStore.Tests public abstract class EventStoreReading : EventStoreTestBase { + [Fact] + public async Task when_reading_a_stream_which_has_no_events_an_empty_list_is_returned() + { + var streamId = Guid.NewGuid().ToString(); + var subject = await GetEventStore(); + + var events = await subject.ReadStreamForwards(streamId); + + Assert.Equal(0, events.Count()); + } + [Fact] public async Task when_reading_a_stream_all_events_are_returned() { diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index f4045f5..e4b8cf0 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -43,7 +43,16 @@ private void AddEventsToAllStream(IEnumerable events) public Task> ReadStreamForwards(string streamId, int startPosition, int numberOfEventsToRead) { - IReadOnlyCollection result = streams[streamId].Skip(startPosition - 1).Take(numberOfEventsToRead).ToList().AsReadOnly(); + IReadOnlyCollection result; + if (!streams.ContainsKey(streamId)) + { + result = new List(0).AsReadOnly(); + } + else + { + result = streams[streamId].Skip(startPosition - 1).Take(numberOfEventsToRead).ToList().AsReadOnly(); + } + return Task.FromResult(result); } From 9b6bc72ff5ced21a91bb630239d66d123b032b98 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sun, 15 Oct 2017 15:20:34 +0100 Subject: [PATCH 47/61] Simplied code to initalise Cosmos database & collection --- .../AzureDocumentDbStorageEngine.cs | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index f58da4c..c2fb89e 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -92,42 +92,26 @@ public async Task> ReadStreamForwards(string s private async Task CreateDatabaseIfItDoesNotExist() { - var databaseExistsQuery = client.CreateDatabaseQuery() - .Where(x => x.Id == databaseName) - .Take(1) - .AsDocumentQuery(); - - if (!(await databaseExistsQuery.ExecuteNextAsync()).Any()) - { - await client.CreateDatabaseAsync(new Database {Id = databaseName}); - } + await client.CreateDatabaseIfNotExistsAsync(new Database { Id = databaseName }); } private async Task CreateCollectionIfItDoesNotExist() { var databaseUri = UriFactory.CreateDatabaseUri(databaseName); - var commitsCollectionQuery = client.CreateDocumentCollectionQuery(databaseUri) - .Where(x => x.Id == collectionOptions.CollectionName) - .Take(1) - .AsDocumentQuery(); + var collection = new DocumentCollection(); + collection.Id = collectionOptions.CollectionName; + collection.PartitionKey.Paths.Add("/streamId"); + collection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); + collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/body/*"}); + collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/metadata/*" }); - if (!(await commitsCollectionQuery.ExecuteNextAsync()).Any()) + var requestOptions = new RequestOptions { - var collection = new DocumentCollection(); - collection.Id = collectionOptions.CollectionName; - collection.PartitionKey.Paths.Add("/streamId"); - collection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); - collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/body/*"}); - collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/metadata/*" }); - - var requestOptions = new RequestOptions - { - OfferThroughput = collectionOptions.CollectionRequestUnits - }; + OfferThroughput = collectionOptions.CollectionRequestUnits + }; - await client.CreateDocumentCollectionAsync(databaseUri, collection, requestOptions); - } + await client.CreateDocumentCollectionIfNotExistsAsync(databaseUri, collection, requestOptions); } private async Task CreateAppendStoredProcedureIfItDoesNotExist() From 58ae1a05af535015ea0b9206e5998a5248922184 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sun, 15 Oct 2017 15:41:14 +0100 Subject: [PATCH 48/61] Simplified in-memory storage engine --- .../InMemory/InMemoryStorageEngine.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs index e4b8cf0..b7926a8 100644 --- a/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore/InMemory/InMemoryStorageEngine.cs @@ -1,14 +1,14 @@ -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace SimpleEventStore.InMemory { public class InMemoryStorageEngine : IStorageEngine { + private static readonly IReadOnlyCollection EmptyStream = new StorageEvent[0]; + private readonly ConcurrentDictionary> streams = new ConcurrentDictionary>(); private readonly List allEvents = new List(); @@ -25,7 +25,7 @@ public Task AppendToStream(string streamId, IEnumerable events) if (firstEvent.EventNumber - 1 != streams[streamId].Count) { - throw new ConcurrencyException($"Concurrency conflict when appending to stream {@streamId}. Expected revision {firstEvent.EventNumber} : Actual revision {streams[streamId].Count}"); + throw new ConcurrencyException($"Concurrency conflict when appending to stream {streamId}. Expected revision {firstEvent.EventNumber} : Actual revision {streams[streamId].Count}"); } streams[streamId].AddRange(events); @@ -43,17 +43,13 @@ private void AddEventsToAllStream(IEnumerable events) public Task> ReadStreamForwards(string streamId, int startPosition, int numberOfEventsToRead) { - IReadOnlyCollection result; if (!streams.ContainsKey(streamId)) { - result = new List(0).AsReadOnly(); - } - else - { - result = streams[streamId].Skip(startPosition - 1).Take(numberOfEventsToRead).ToList().AsReadOnly(); + return Task.FromResult(EmptyStream); } - - return Task.FromResult(result); + + IReadOnlyCollection stream = streams[streamId].Skip(startPosition - 1).Take(numberOfEventsToRead).ToList().AsReadOnly(); + return Task.FromResult(stream); } public Task Initialise() From 622612dd7309069935145a3b4a363b50251a9a7b Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Mon, 16 Oct 2017 09:47:05 +0100 Subject: [PATCH 49/61] Updated build script to use explicit versions for addins --- build/build.cake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index 8b31de8..e299c15 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,5 +1,6 @@ #tool "nuget:?package=xunit.runner.console" -#addin "Cake.Json" +#addin nuget:?package=Cake.Json&version=1.0.2.13 +#addin nuget:?package=Newtonsoft.Json&version=9.0.1 ////////////////////////////////////////////////////////////////////// // ARGUMENTS From 7aaa4c3d87b890fd3f3564187ea7e33b589cbf39 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Mon, 16 Oct 2017 09:49:16 +0100 Subject: [PATCH 50/61] Build uses explicit versions for add-ins --- build/build.cake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index 8b31de8..e299c15 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,5 +1,6 @@ #tool "nuget:?package=xunit.runner.console" -#addin "Cake.Json" +#addin nuget:?package=Cake.Json&version=1.0.2.13 +#addin nuget:?package=Newtonsoft.Json&version=9.0.1 ////////////////////////////////////////////////////////////////////// // ARGUMENTS From a9b3844ac658ec81b52b1132487bae8a3e5cacc7 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sat, 21 Oct 2017 08:41:43 +0100 Subject: [PATCH 51/61] Updated XUnit for .NET Core SDK 2 only build agents --- .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 6 +++--- .../SimpleEventStore.Tests/SimpleEventStore.Tests.csproj | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index bd1dee2..37e958b 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -14,10 +14,10 @@ - - + + - + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index ec570bf..5c5219e 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -7,10 +7,10 @@ - - + + - + SimpleEventStore.Tests From eb8610160e2d69deea6b30b4b2aa3494bc0e48e0 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sat, 21 Oct 2017 20:25:03 +0100 Subject: [PATCH 52/61] Revert "Updated XUnit for .NET Core SDK 2 only build agents" This reverts commit a9b3844ac658ec81b52b1132487bae8a3e5cacc7. --- .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 6 +++--- .../SimpleEventStore.Tests/SimpleEventStore.Tests.csproj | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index 37e958b..bd1dee2 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -14,10 +14,10 @@ - - + + - + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index 5c5219e..ec570bf 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -7,10 +7,10 @@ - - + + - + SimpleEventStore.Tests From 1db527f2b95bc03ac39736356c4bd7c26e676209 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sun, 22 Oct 2017 04:52:56 +0100 Subject: [PATCH 53/61] Tests now use NUnit. All packages have been upgraded. --- build/build.cake | 42 ++++++------ .../AzureDocumentDbEventStoreAppending.cs | 4 +- .../AzureDocumentDbEventStoreLogging.cs | 33 +++------ .../AzureDocumentDbEventStoreReading.cs | 2 + ...zureDocumentDbStorageEngineBuilderTests.cs | 15 +++-- ...igurableTypeMapSerializationBinderTests.cs | 27 ++++---- .../DocumentDbStorageEventTests.cs | 15 +++-- .../ResponseInformationBuilding.cs | 34 +++++----- ...pleEventStore.AzureDocumentDb.Tests.csproj | 11 ++- .../StorageEngineFactory.cs | 2 - .../SimpleEventStore.AzureDocumentDb.csproj | 4 +- .../SimpleEventStore.Tests/EventDataTests.cs | 13 ++-- .../EventStoreAppending.cs | 67 ++++++++++--------- .../EventStoreReading.cs | 32 ++++----- .../InMemory/InMemoryEventStoreAppending.cs | 2 + .../InMemory/InMemoryEventStoreReading.cs | 2 + .../SimpleEventStore.Tests.csproj | 7 +- .../StorageEventTests.cs | 15 +++-- 18 files changed, 160 insertions(+), 167 deletions(-) diff --git a/build/build.cake b/build/build.cake index e299c15..93648cc 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,4 +1,3 @@ -#tool "nuget:?package=xunit.runner.console" #addin nuget:?package=Cake.Json&version=1.0.2.13 #addin nuget:?package=Newtonsoft.Json&version=9.0.1 @@ -21,10 +20,10 @@ var buildVersion = Argument("buildVersion", "1.0.0"); var solutionDir = "../src/SimpleEventStore/"; var solutionFile = solutionDir + "SimpleEventStore.sln"; var documentDbTestConfigFiles = new [] { - File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"), - File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/net452/appsettings.json") + File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"), + File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/net452/appsettings.json") }; -var testDirs = GetDirectories(solutionDir + "*.Tests"); +var testProjs = GetFiles(solutionDir + "**/*.Tests.csproj"); var outputDir = "./nuget"; ////////////////////////////////////////////////////////////////////// @@ -58,15 +57,17 @@ Task("Build") Task("Transform-Unit-Test-Config") .Does(() => { - foreach(var documentDbTestConfigFile in documentDbTestConfigFiles) - { - var configJson = ParseJsonFromFile(documentDbTestConfigFile); - configJson["Uri"] = uri; - configJson["AuthKey"] = authKey; - configJson["ConsistencyLevel"] = consistencyLevel; - - SerializeJsonToFile(documentDbTestConfigFile, configJson); - } + foreach(var documentDbTestConfigFile in documentDbTestConfigFiles) + { + var configJson = ParseJsonFromFile(documentDbTestConfigFile); + configJson["Uri"] = uri; + configJson["AuthKey"] = authKey; + configJson["ConsistencyLevel"] = consistencyLevel; + + SerializeJsonToFile(documentDbTestConfigFile, configJson); + + Information("Transformed " + documentDbTestConfigFile); + } }); Task("Run-Unit-Tests") @@ -74,17 +75,12 @@ Task("Run-Unit-Tests") .IsDependentOn("Transform-Unit-Test-Config") .Does(() => { - foreach(var testPath in testDirs) + foreach(var testPath in testProjs) { - var returnCode = StartProcess("dotnet", new ProcessSettings { - Arguments = "xunit -c " + configuration + " --no-build", - WorkingDirectory = testPath + DotNetCoreTest(testPath.FullPath, new DotNetCoreTestSettings { + Configuration = configuration, + NoBuild = true }); - - if(returnCode != 0) - { - throw new Exception("Unit test failure for test project " + testPath); - } } }); @@ -131,4 +127,4 @@ Task("Default") // EXECUTION ////////////////////////////////////////////////////////////////////// -RunTarget(target); +RunTarget(target); \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreAppending.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreAppending.cs index 199642d..af9ede5 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreAppending.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreAppending.cs @@ -1,13 +1,15 @@ using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.Tests; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class AzureDocumentDbEventStoreAppending : EventStoreAppending { protected override Task CreateStorageEngine() { - return StorageEngineFactory.Create("AppendingTests"); ; + return StorageEngineFactory.Create("AppendingTests"); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs index b77311e..a908e20 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs @@ -2,27 +2,16 @@ using Microsoft.Azure.Documents.Client; using Microsoft.Extensions.Configuration; using System; -using System.IO; -using System.Threading; using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.Tests.Events; -using Xunit; -using Xunit.Abstractions; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class AzureDocumentDbEventStoreLogging { - // TODO: Simplify tests when subscription supports cancellation tokens, should be able to cancel rather than running an if(Interlocked..) statement - - private readonly ITestOutputHelper output; - - public AzureDocumentDbEventStoreLogging(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] + [Test] public async Task when_a_write_operation_is_successful_the_log_callback_is_called() { ResponseInformation response = null; @@ -32,13 +21,13 @@ public async Task when_a_write_operation_is_successful_the_log_callback_is_calle await sut.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated("TEST-ORDER"))); Assert.NotNull(response); - output.WriteLine($"Charge: {response.RequestCharge}"); - output.WriteLine($"Quota Usage: {response.CurrentResourceQuotaUsage}"); - output.WriteLine($"Max Resource Quote: {response.MaxResourceQuota}"); - output.WriteLine($"Response headers: {response.ResponseHeaders}"); + TestContext.Out.WriteLine($"Charge: {response.RequestCharge}"); + TestContext.Out.WriteLine($"Quota Usage: {response.CurrentResourceQuotaUsage}"); + TestContext.Out.WriteLine($"Max Resource Quote: {response.MaxResourceQuota}"); + TestContext.Out.WriteLine($"Response headers: {response.ResponseHeaders}"); } - [Fact] + [Test] public async Task when_a_read_operation_is_successful_the_log_callback_is_called() { var logCount = 0; @@ -48,22 +37,20 @@ public async Task when_a_read_operation_is_successful_the_log_callback_is_called await sut.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated("TEST-ORDER"))); await sut.ReadStreamForwards(streamId); - Assert.Equal(2, logCount); + Assert.That(logCount, Is.EqualTo(2)); } private static async Task CreateStorageEngine(Action onSuccessCallback, string databaseName = "LoggingTests") { var config = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); var documentDbUri = config["Uri"]; var authKey = config["AuthKey"]; var consistencyLevel = config["ConsistencyLevel"]; - ConsistencyLevel consistencyLevelEnum; - if (!Enum.TryParse(consistencyLevel, true, out consistencyLevelEnum)) + if (!Enum.TryParse(consistencyLevel, true, out ConsistencyLevel consistencyLevelEnum)) { throw new Exception($"The ConsistencyLevel value {consistencyLevel} is not supported"); } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReading.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReading.cs index a38f713..89c51e8 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReading.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReading.cs @@ -1,8 +1,10 @@ using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.Tests; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class AzureDocumentDbEventStoreReading : EventStoreReading { protected override Task CreateStorageEngine() diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs index dce473a..356e070 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs @@ -1,45 +1,46 @@ using System; using Microsoft.Azure.Documents.Client; -using Xunit; +using NUnit.Framework; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class AzureDocumentDbStorageEngineBuilderTests { - [Fact] + [Test] public void when_creating_an_instance_the_document_client_must_be_supplied() { Assert.Throws(() => new AzureDocumentDbStorageEngineBuilder(null, "Test")); } - [Fact] + [Test] public void when_creating_an_instance_the_database_name_must_be_supplied() { Assert.Throws(() => new AzureDocumentDbStorageEngineBuilder(CreateClient(), null)); } - [Fact] + [Test] public void when_setting_collection_settings_a_callback_must_be_supplied() { var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); Assert.Throws(() => builder.UseCollection(null)); } - [Fact] + [Test] public void when_setting_subscription_settings_a_callback_must_be_supplied() { var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); Assert.Throws(() => builder.UseCollection(null)); } - [Fact] + [Test] public void when_setting_logging_settings_a_callback_must_be_supplied() { var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); Assert.Throws(() => builder.UseLogging(null)); } - [Fact] + [Test] public void when_setting_the_type_map_it_must_be_supplied() { var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test"); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs index 6afaa99..c36e0b8 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs @@ -1,75 +1,76 @@ using System; using System.Reflection; -using Xunit; +using NUnit.Framework; using SimpleEventStore.Tests.Events; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class ConfigurableTypeMapSerializationBinderTests { - [Fact] + [Test] public void when_registering_a_type_with_a_null_event_type_then_an_exception_is_thrown() { var sut = new ConfigurableSerializationTypeMap(); Assert.Throws(() => sut.RegisterType(null, typeof(OrderCreated))); } - [Fact] + [Test] public void when_registering_a_type_with_a_null_type_then_an_exception_is_thrown() { var sut = new ConfigurableSerializationTypeMap(); Assert.Throws(() => sut.RegisterType("TEST", null)); } - [Fact] + [Test] public void when_registering_types_with_a_null_assembly_then_an_exception_is_thrown() { var sut = new ConfigurableSerializationTypeMap(); Assert.Throws(() => sut.RegisterTypes(null, t => true, t => t.Name)); } - [Fact] + [Test] public void when_registering_events_with_a_null_match_function_then_an_exception_is_thrown() { var sut = new ConfigurableSerializationTypeMap(); Assert.Throws(() => sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, null, t => t.Name)); } - [Fact] + [Test] public void when_registering_types_with_a_null_naming_function_then_an_exception_is_thrown() { var sut = new ConfigurableSerializationTypeMap(); Assert.Throws(() => sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => true, null)); } - [Fact] + [Test] public void when_registering_a_type_then_the_type_can_be_found() { var sut = new ConfigurableSerializationTypeMap(); sut.RegisterType("OrderCreated", typeof(OrderCreated)); - Assert.Equal(typeof(OrderCreated), sut.GetTypeFromName("OrderCreated")); + Assert.That(sut.GetTypeFromName("OrderCreated"), Is.EqualTo(typeof(OrderCreated))); } - [Fact] + [Test] public void when_registering_multiple_types_then_the_type_can_be_found() { var sut = new ConfigurableSerializationTypeMap(); sut.RegisterTypes(typeof(OrderCreated).GetTypeInfo().Assembly, t => t.Namespace != null && t.Namespace.EndsWith("Events"), t => t.Name); - Assert.Equal(typeof(OrderCreated), sut.GetTypeFromName("OrderCreated")); + Assert.That(sut.GetTypeFromName("OrderCreated"), Is.EqualTo(typeof(OrderCreated))); } - [Fact] + [Test] public void when_registering_a_type_then_the_name_can_be_found() { var sut = new ConfigurableSerializationTypeMap(); sut.RegisterType("OrderCreated", typeof(OrderCreated)); - Assert.Equal("OrderCreated", sut.GetNameFromType(typeof(OrderCreated))); + Assert.That(sut.GetNameFromType(typeof(OrderCreated)), Is.EqualTo("OrderCreated")); } - [Fact] + [Test] public void when_registering_multiple_types_if_no_types_are_found_then_an_exception_is_thrown() { var sut = new ConfigurableSerializationTypeMap(); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs index b5194ff..87e2cd7 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs @@ -2,13 +2,14 @@ using System.Reflection; using Newtonsoft.Json.Linq; using SimpleEventStore.Tests.Events; -using Xunit; +using NUnit.Framework; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class DocumentDbStorageEventTests { - [Fact] + [Test] public void when_converting_to_a_storage_event_it_succeeds() { var id = Guid.NewGuid(); @@ -30,11 +31,11 @@ public void when_converting_to_a_storage_event_it_succeeds() t => t.Name); var result = sut.ToStorageEvent(typeMap); - Assert.Equal(sut.StreamId, result.StreamId); - Assert.Equal(body.OrderId, ((OrderCreated)result.EventBody).OrderId); - Assert.Equal(metadata.Value, ((TestMetadata)result.Metadata).Value); - Assert.Equal(sut.EventNumber, result.EventNumber); - Assert.Equal(sut.EventId, result.EventId); + Assert.That(result.StreamId, Is.EqualTo(sut.StreamId)); + Assert.That(((OrderCreated)result.EventBody).OrderId, Is.EqualTo(body.OrderId)); + Assert.That(((TestMetadata)result.Metadata).Value, Is.EqualTo(metadata.Value)); + Assert.That(result.EventNumber, Is.EqualTo(sut.EventNumber)); + Assert.That(result.EventId, Is.EqualTo(sut.EventId)); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs index 670f711..83b964b 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs @@ -1,47 +1,47 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Text; using System.Net; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; -using Xunit; +using NUnit.Framework; namespace SimpleEventStore.AzureDocumentDb.Tests { + [TestFixture] public class ResponseInformationBuilding { - [Fact] + [Test] public void when_building_from_a_write_response_all_target_fields_are_mapped() { var result = ResponseInformation.FromWriteResponse(new FakeStoredProcedureResponse()); - Assert.Equal(Expected.CurrentResourceQuotaUsage, result.CurrentResourceQuotaUsage); - Assert.Equal(Expected.MaxResourceQuota, result.MaxResourceQuota); - Assert.Equal(Expected.RequestCharge, result.RequestCharge); - Assert.Equal(Expected.ResponseHeaders, result.ResponseHeaders); + Assert.That(result.CurrentResourceQuotaUsage, Is.EqualTo(Expected.CurrentResourceQuotaUsage)); + Assert.That(result.MaxResourceQuota, Is.EqualTo(Expected.MaxResourceQuota)); + Assert.That(result.RequestCharge, Is.EqualTo(Expected.RequestCharge)); + Assert.That(result.ResponseHeaders, Is.EqualTo(Expected.ResponseHeaders)); } - [Fact] + [Test] public void when_building_from_a_read_response_all_target_fields_are_mapped() { var result = ResponseInformation.FromReadResponse(new FakeFeedResponse()); - Assert.Equal(Expected.CurrentResourceQuotaUsage, result.CurrentResourceQuotaUsage); - Assert.Equal(Expected.MaxResourceQuota, result.MaxResourceQuota); - Assert.Equal(Expected.RequestCharge, result.RequestCharge); - Assert.Equal(Expected.ResponseHeaders, result.ResponseHeaders); + Assert.That(result.CurrentResourceQuotaUsage, Is.EqualTo(Expected.CurrentResourceQuotaUsage)); + Assert.That(result.MaxResourceQuota, Is.EqualTo(Expected.MaxResourceQuota)); + Assert.That(result.RequestCharge, Is.EqualTo(Expected.RequestCharge)); + Assert.That(result.ResponseHeaders, Is.EqualTo(Expected.ResponseHeaders)); } - [Fact] + [Test] public void when_building_from_a_subscription_read_response_all_target_fields_are_mapped() { var result = ResponseInformation.FromSubscriptionReadResponse(new FakeFeedResponse()); - Assert.Equal(Expected.CurrentResourceQuotaUsage, result.CurrentResourceQuotaUsage); - Assert.Equal(Expected.MaxResourceQuota, result.MaxResourceQuota); - Assert.Equal(Expected.RequestCharge, result.RequestCharge); - Assert.Equal(Expected.ResponseHeaders, result.ResponseHeaders); + Assert.That(result.CurrentResourceQuotaUsage, Is.EqualTo(Expected.CurrentResourceQuotaUsage)); + Assert.That(result.MaxResourceQuota, Is.EqualTo(Expected.MaxResourceQuota)); + Assert.That(result.RequestCharge, Is.EqualTo(Expected.RequestCharge)); + Assert.That(result.ResponseHeaders, Is.EqualTo(Expected.ResponseHeaders)); } private static class Expected diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index bd1dee2..a5a1d05 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -9,15 +9,14 @@ - - + + - - - - + + + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index 249d050..92ddf80 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.Documents; @@ -14,7 +13,6 @@ internal static class StorageEngineFactory internal static async Task Create(string databaseName) { var config = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 3537503..21a7665 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -5,8 +5,8 @@ 1.0.0 - - + + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs index bb4ed84..cacd4f5 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs @@ -1,25 +1,26 @@ using System; -using Xunit; +using NUnit.Framework; namespace SimpleEventStore.Tests { + [TestFixture] public class EventDataTests { - [Fact] + [Test] public void when_creating_an_instance_the_event_body_must_be_supplied() { Assert.Throws(() => new EventData(Guid.NewGuid(), null)); } - [Fact] + [Test] public void when_creating_an_instance_the_properties_are_mapped() { var eventId = Guid.NewGuid(); var sut = new EventData(eventId, "BODY", "METADATA"); - Assert.Equal(eventId, sut.EventId); - Assert.Equal("BODY", sut.Body); - Assert.Equal("METADATA", sut.Metadata); + Assert.That(sut.EventId, Is.EqualTo(eventId)); + Assert.That(sut.Body, Is.EqualTo("BODY")); + Assert.That(sut.Metadata, Is.EqualTo("METADATA")); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs index d273c87..ded8e8d 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs @@ -1,14 +1,15 @@ using System; using System.Linq; using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.Tests.Events; -using Xunit; namespace SimpleEventStore.Tests { + [TestFixture] public abstract class EventStoreAppending : EventStoreTestBase { - [Fact] + [Test] public async Task when_appending_to_a_new_stream_the_event_is_saved() { var streamId = Guid.NewGuid().ToString(); @@ -18,13 +19,13 @@ public async Task when_appending_to_a_new_stream_the_event_is_saved() await subject.AppendToStream(streamId, 0, @event); var stream = await subject.ReadStreamForwards(streamId); - Assert.Equal(1, stream.Count()); - Assert.Equal(streamId, stream.Single().StreamId); - Assert.Equal(@event.EventId, stream.Single().EventId); - Assert.Equal(1, stream.Single().EventNumber); + Assert.That(stream.Count, Is.EqualTo(1)); + Assert.That(stream.Single().StreamId, Is.EqualTo(streamId)); + Assert.That(stream.Single().EventId, Is.EqualTo(@event.EventId)); + Assert.That(stream.Single().EventNumber, Is.EqualTo(1)); } - [Fact] + [Test] public async Task when_appending_to_an_existing_stream_the_event_is_saved() { var streamId = Guid.NewGuid().ToString(); @@ -35,26 +36,26 @@ public async Task when_appending_to_an_existing_stream_the_event_is_saved() await subject.AppendToStream(streamId, 1, @event); var stream = await subject.ReadStreamForwards(streamId); - Assert.Equal(2, stream.Count()); - Assert.Equal(@event.EventId, stream.Skip(1).Single().EventId); - Assert.Equal(2, stream.Skip(1).Single().EventNumber); + Assert.That(stream.Count, Is.EqualTo(2)); + Assert.That(stream.Skip(1).Single().EventId, Is.EqualTo(@event.EventId)); + Assert.That(stream.Skip(1).Single().EventNumber, Is.EqualTo(2)); } - [Theory] - [InlineData(-1)] - [InlineData(1)] + [Test] + [TestCase(-1)] + [TestCase(1)] public async Task when_appending_to_a_new_stream_with_an_unexpected_version__a_concurrency_error_is_thrown(int expectedVersion) { var streamId = Guid.NewGuid().ToString(); var subject = await GetEventStore(); var @event = new EventData(Guid.NewGuid(), new OrderDispatched(streamId)); - await Assert.ThrowsAsync(async () => await subject.AppendToStream(streamId, expectedVersion, @event)); + Assert.ThrowsAsync(async () => await subject.AppendToStream(streamId, expectedVersion, @event)); } - [Theory] - [InlineData(0)] - [InlineData(2)] + [Test] + [TestCase(0)] + [TestCase(2)] public async Task when_appending_to_an_existing_stream_with_an_unexpected_version_a_concurrency_error_is_thrown(int expectedVersion) { var streamId = Guid.NewGuid().ToString(); @@ -63,42 +64,42 @@ public async Task when_appending_to_an_existing_stream_with_an_unexpected_versio var @event = new EventData(Guid.NewGuid(), new OrderDispatched(streamId)); - await Assert.ThrowsAsync(async () => await subject.AppendToStream(streamId, expectedVersion, @event)); + Assert.ThrowsAsync(async () => await subject.AppendToStream(streamId, expectedVersion, @event)); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] public async Task when_appending_to_an_invalid_stream_id_an_argument_error_is_thrown(string streamId) { var eventStore = await GetEventStore(); - await Assert.ThrowsAsync(async () => eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId)))); + Assert.ThrowsAsync(async () => await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId)))); } - [Fact] + [Test] public async Task when_appending_to_a_new_stream_with_multiple_events_then_they_are_saved() { var streamId = Guid.NewGuid().ToString(); var subject = await GetEventStore(); - var @events = new [] + var events = new [] { new EventData(Guid.NewGuid(), new OrderCreated(streamId)), new EventData(Guid.NewGuid(), new OrderDispatched(streamId)) }; - await subject.AppendToStream(streamId, 0, @events); + await subject.AppendToStream(streamId, 0, events); var savedEvents = await subject.ReadStreamForwards(streamId); - Assert.Equal(2, savedEvents.Count()); - Assert.Equal(streamId, savedEvents.First().StreamId); - Assert.Equal(1, savedEvents.First().EventNumber); - Assert.Equal(streamId, savedEvents.Skip(1).Single().StreamId); - Assert.Equal(2, savedEvents.Skip(1).Single().EventNumber); + Assert.That(savedEvents.Count, Is.EqualTo(2)); + Assert.That(savedEvents.First().StreamId, Is.EqualTo(streamId)); + Assert.That(savedEvents.First().EventNumber, Is.EqualTo(1)); + Assert.That(savedEvents.Skip(1).Single().StreamId, Is.EqualTo(streamId)); + Assert.That(savedEvents.Skip(1).Single().EventNumber, Is.EqualTo(2)); } - [Fact] + [Test] public async Task when_appending_to_a_new_stream_the_event_metadata_is_saved() { var streamId = Guid.NewGuid().ToString(); @@ -109,7 +110,7 @@ public async Task when_appending_to_a_new_stream_the_event_metadata_is_saved() await subject.AppendToStream(streamId, 0, @event); var stream = await subject.ReadStreamForwards(streamId); - Assert.Equal(metadata.Value, ((TestMetadata)stream.Single().Metadata).Value); + Assert.That(((TestMetadata)stream.Single().Metadata).Value, Is.EqualTo(metadata.Value)); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs index d1a7971..0ad80c5 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreReading.cs @@ -1,18 +1,18 @@ using System; using System.Linq; using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.Tests.Events; -using Xunit; namespace SimpleEventStore.Tests { // TODOs // 1. Make partioning support configurable // 2. Allow for lower levels of consistency than just strong - + [TestFixture] public abstract class EventStoreReading : EventStoreTestBase { - [Fact] + [Test] public async Task when_reading_a_stream_which_has_no_events_an_empty_list_is_returned() { var streamId = Guid.NewGuid().ToString(); @@ -20,10 +20,10 @@ public async Task when_reading_a_stream_which_has_no_events_an_empty_list_is_ret var events = await subject.ReadStreamForwards(streamId); - Assert.Equal(0, events.Count()); + Assert.That(events.Count, Is.EqualTo(0)); } - [Fact] + [Test] public async Task when_reading_a_stream_all_events_are_returned() { var streamId = Guid.NewGuid().ToString(); @@ -34,22 +34,22 @@ public async Task when_reading_a_stream_all_events_are_returned() var events = await subject.ReadStreamForwards(streamId); - Assert.Equal(2, events.Count()); - Assert.IsType(events.First().EventBody); - Assert.IsType(events.Skip(1).Single().EventBody); + Assert.That(events.Count, Is.EqualTo(2)); + Assert.That(events.First().EventBody, Is.InstanceOf()); + Assert.That(events.Skip(1).Single().EventBody, Is.InstanceOf()); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] public async Task when_reading_from_an_invalid_stream_id_an_argument_error_is_thrown(string streamId) { var eventStore = await GetEventStore(); - await Assert.ThrowsAsync(async () => await eventStore.ReadStreamForwards(streamId)); + Assert.ThrowsAsync(async () => await eventStore.ReadStreamForwards(streamId)); } - [Fact] + [Test] public async Task when_reading_a_stream_only_the_required_events_are_returned() { var streamId = Guid.NewGuid().ToString(); @@ -60,8 +60,8 @@ public async Task when_reading_a_stream_only_the_required_events_are_returned() var events = await subject.ReadStreamForwards(streamId, startPosition: 2, numberOfEventsToRead: 1); - Assert.Equal(1, events.Count()); - Assert.IsType(events.First().EventBody); + Assert.That(events.Count, Is.EqualTo(1)); + Assert.That(events.First().EventBody, Is.InstanceOf()); } } } diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreAppending.cs b/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreAppending.cs index 15bdcc1..9258434 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreAppending.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreAppending.cs @@ -1,8 +1,10 @@ using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.InMemory; namespace SimpleEventStore.Tests.InMemory { + [TestFixture] public class InMemoryEventStoreAppending : EventStoreAppending { protected override Task CreateStorageEngine() diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreReading.cs b/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreReading.cs index bfc9675..78ac28f 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreReading.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/InMemory/InMemoryEventStoreReading.cs @@ -1,8 +1,10 @@ using System.Threading.Tasks; +using NUnit.Framework; using SimpleEventStore.InMemory; namespace SimpleEventStore.Tests.InMemory { + [TestFixture] public class InMemoryEventStoreReading : EventStoreReading { protected override Task CreateStorageEngine() diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index ec570bf..9ed014b 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -7,10 +7,9 @@ - - - - + + + SimpleEventStore.Tests diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/StorageEventTests.cs b/src/SimpleEventStore/SimpleEventStore.Tests/StorageEventTests.cs index 990e422..6e10afb 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/StorageEventTests.cs +++ b/src/SimpleEventStore/SimpleEventStore.Tests/StorageEventTests.cs @@ -1,11 +1,12 @@ using System; -using Xunit; +using NUnit.Framework; namespace SimpleEventStore.Tests { + [TestFixture] public class StorageEventTests { - [Fact] + [Test] public void when_creating_a_new_instance_the_properties_are_mapped() { var eventId = Guid.NewGuid(); @@ -13,11 +14,11 @@ public void when_creating_a_new_instance_the_properties_are_mapped() var sut = new StorageEvent("STREAMID", @event, 1); - Assert.Equal("STREAMID", sut.StreamId); - Assert.Equal("BODY", sut.EventBody); - Assert.Equal("METADATA", sut.Metadata); - Assert.Equal(1, sut.EventNumber); - Assert.Equal(eventId, sut.EventId); + Assert.That(sut.StreamId, Is.EqualTo("STREAMID")); + Assert.That(sut.EventBody, Is.EqualTo("BODY")); + Assert.That(sut.Metadata, Is.EqualTo("METADATA")); + Assert.That(sut.EventNumber, Is.EqualTo(1)); + Assert.That(sut.EventId, Is.EqualTo(eventId)); } } } From 6dfea14ddac7d6394af057de10e387c267c42f5e Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sun, 22 Oct 2017 05:24:47 +0100 Subject: [PATCH 54/61] Test projects now target .NET Core 2.0 --- build/build.cake | 6 ++---- .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 4 ++-- .../SimpleEventStore.Tests/SimpleEventStore.Tests.csproj | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/build/build.cake b/build/build.cake index 93648cc..41e126b 100644 --- a/build/build.cake +++ b/build/build.cake @@ -19,10 +19,6 @@ var buildVersion = Argument("buildVersion", "1.0.0"); // Define directories. var solutionDir = "../src/SimpleEventStore/"; var solutionFile = solutionDir + "SimpleEventStore.sln"; -var documentDbTestConfigFiles = new [] { - File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/netcoreapp1.1/appsettings.json"), - File("../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/net452/appsettings.json") -}; var testProjs = GetFiles(solutionDir + "**/*.Tests.csproj"); var outputDir = "./nuget"; @@ -57,6 +53,8 @@ Task("Build") Task("Transform-Unit-Test-Config") .Does(() => { + var documentDbTestConfigFiles = GetFiles(solutionDir + "SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/**/appsettings.json"); + foreach(var documentDbTestConfigFile in documentDbTestConfigFiles) { var configJson = ParseJsonFromFile(documentDbTestConfigFile); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index a5a1d05..e78f70d 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1;net452 + netcoreapp2.0;net452 @@ -9,7 +9,7 @@ - + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index 9ed014b..f78ad2c 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1;net452 + netcoreapp2.0;net452 From 36010db57daaab5a03b8a14b5ec23f1cb2f62189 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Sun, 22 Oct 2017 07:43:28 +0100 Subject: [PATCH 55/61] Added VS code launch & tasks files --- .vscode/launch.json | 14 ++++++++++++++ .vscode/tasks.json | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e21a627 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..cc16e88 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,22 @@ +{ + "version": "0.1.0", + "command": "dotnet", + "isShellCommand": true, + "args": [], + "tasks": [ + { + "taskName": "build", + "command": "powershell", + "options": { + "cwd": "${workspaceRoot}/build" + }, + "args": [ + "./build.ps1", + "-ConsistencyLevel", + "Session" + ], + "isBuildCommand": true, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file From 086b11b371cda103e1e8574286f4e4553c5bf6b6 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Thu, 9 Nov 2017 06:39:00 +0000 Subject: [PATCH 56/61] Collection level time to live support. Resolves #23 --- .../AzureDocumentDBEventStoreInitializing.cs | 71 +++++++++++++++++++ ...ventStoreReadingPartiallyDeletedStreams.cs | 41 +++++++++++ .../DocumentClientFactory.cs | 21 ++++++ .../StorageEngineFactory.cs | 9 ++- .../TestConstants.cs | 8 +++ .../AzureDocumentDbStorageEngine.cs | 1 + .../CollectionOptions.cs | 3 + 7 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDBEventStoreInitializing.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReadingPartiallyDeletedStreams.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentClientFactory.cs create mode 100644 src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/TestConstants.cs diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDBEventStoreInitializing.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDBEventStoreInitializing.cs new file mode 100644 index 0000000..e367b45 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDBEventStoreInitializing.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; +using NUnit.Framework; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + [TestFixture] + public class AzureDocumentDBEventStoreInitializing + { + private const string DatabaseName = "InitializeTests"; + + [OneTimeTearDown] + public async Task TearDownDatabase() + { + var client = DocumentClientFactory.Create(DatabaseName); + await client.DeleteDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseName)); + } + + [Test] + public async Task when_initializing_all_expected_resources_are_created() + { + var client = DocumentClientFactory.Create(DatabaseName); + var collectionName = "AllExpectedResourcesAreCreated_" + Guid.NewGuid(); + var storageEngine = await StorageEngineFactory.Create(DatabaseName, o => o.CollectionName = collectionName); + + await storageEngine.Initialise(); + + var database = (await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseName))).Resource; + var collection = (await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName))).Resource; + var storedProcedure = (await client.ReadStoredProcedureAsync(UriFactory.CreateStoredProcedureUri(DatabaseName, collectionName, TestConstants.AppendStoredProcedureName))).Resource; + var offer = client.CreateOfferQuery() + .Where(r => r.ResourceLink == collection.SelfLink) + .AsEnumerable() + .OfType() + .Single(); + + Assert.That(offer.Content.OfferThroughput, Is.EqualTo(TestConstants.RequestUnits)); + Assert.That(collection.DefaultTimeToLive, Is.Null); + Assert.That(collection.PartitionKey.Paths.Count, Is.EqualTo(1)); + Assert.That(collection.PartitionKey.Paths.Single(), Is.EqualTo("/streamId")); + Assert.That(collection.IndexingPolicy.IncludedPaths.Count, Is.EqualTo(1)); + Assert.That(collection.IndexingPolicy.IncludedPaths[0].Path, Is.EqualTo("/*")); + Assert.That(collection.IndexingPolicy.ExcludedPaths.Count, Is.EqualTo(2)); + Assert.That(collection.IndexingPolicy.ExcludedPaths[0].Path, Is.EqualTo("/body/*")); + Assert.That(collection.IndexingPolicy.ExcludedPaths[1].Path, Is.EqualTo("/metadata/*")); + } + + [Test] + public async Task when_initializing_with_a_time_to_live_it_is_set() + { + var ttl = 60; + var collectionName = "TimeToLiveIsSet_" + Guid.NewGuid(); + var client = DocumentClientFactory.Create(DatabaseName); + var storageEngine = await StorageEngineFactory.Create(DatabaseName, o => + { + o.CollectionName = collectionName; + o.DefaultTimeToLive = ttl; + }); + + await storageEngine.Initialise(); + + var collection = (await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName))).Resource; + Assert.That(collection.DefaultTimeToLive, Is.EqualTo(ttl)); + } + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReadingPartiallyDeletedStreams.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReadingPartiallyDeletedStreams.cs new file mode 100644 index 0000000..9f2305d --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreReadingPartiallyDeletedStreams.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using NUnit.Framework; +using SimpleEventStore.Tests.Events; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + [TestFixture] + public class AzureDocumentDbEventStoreReadingPartiallyDeletedStreams + { + [Test] + public async Task when_reading_a_stream_that_has_deleted_events_the_stream_can_still_be_read() + { + const string databaseName = "ReadingPartialStreamTests"; + const string collectionName = "Commits"; + + var client = DocumentClientFactory.Create(databaseName); + var storageEngine = await StorageEngineFactory.Create(databaseName, o => o.CollectionName = collectionName); + var eventStore = new EventStore(storageEngine); + var streamId = Guid.NewGuid().ToString(); + await eventStore.AppendToStream(streamId, 0, new EventData(Guid.NewGuid(), new OrderCreated(streamId)), new EventData(Guid.NewGuid(), new OrderDispatched(streamId))); + await SimulateTimeToLiveExpiration(databaseName, collectionName, client, streamId); + + var stream = await eventStore.ReadStreamForwards(streamId); + + Assert.That(stream.Count, Is.EqualTo(1)); + Assert.That(stream.First().EventBody, Is.InstanceOf()); + Assert.That(stream.First().EventNumber, Is.EqualTo(2)); + } + + private static async Task SimulateTimeToLiveExpiration(string databaseName, string collectionName, DocumentClient client, string streamId) + { + await client.DeleteDocumentAsync( + UriFactory.CreateDocumentUri(databaseName, collectionName, $"{streamId}:1"), + new RequestOptions() { PartitionKey = new PartitionKey(streamId) }); + } + } +} diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentClientFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentClientFactory.cs new file mode 100644 index 0000000..05746c0 --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentClientFactory.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Azure.Documents.Client; +using Microsoft.Extensions.Configuration; + +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + internal static class DocumentClientFactory + { + internal static DocumentClient Create(string databaseName) + { + var config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + + var documentDbUri = config["Uri"]; + var authKey = config["AuthKey"]; + + return new DocumentClient(new Uri(documentDbUri), authKey); + } + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs index 92ddf80..1f42e4a 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs @@ -10,14 +10,12 @@ namespace SimpleEventStore.AzureDocumentDb.Tests { internal static class StorageEngineFactory { - internal static async Task Create(string databaseName) + internal static async Task Create(string databaseName, Action collectionOverrides = null) { var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); - var documentDbUri = config["Uri"]; - var authKey = config["AuthKey"]; var consistencyLevel = config["ConsistencyLevel"]; ConsistencyLevel consistencyLevelEnum; @@ -26,13 +24,14 @@ internal static async Task Create(string databaseName) throw new Exception($"The ConsistencyLevel value {consistencyLevel} is not supported"); } - DocumentClient client = new DocumentClient(new Uri(documentDbUri), authKey); + var client = DocumentClientFactory.Create(databaseName); return await new AzureDocumentDbStorageEngineBuilder(client, databaseName) .UseCollection(o => { o.ConsistencyLevel = consistencyLevelEnum; - o.CollectionRequestUnits = 400; + o.CollectionRequestUnits = TestConstants.RequestUnits; + if(collectionOverrides != null) collectionOverrides(o); }) .UseTypeMap(new ConfigurableSerializationTypeMap() .RegisterTypes( diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/TestConstants.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/TestConstants.cs new file mode 100644 index 0000000..3d3a2ae --- /dev/null +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/TestConstants.cs @@ -0,0 +1,8 @@ +namespace SimpleEventStore.AzureDocumentDb.Tests +{ + internal static class TestConstants + { + internal const int RequestUnits = 400; + internal const string AppendStoredProcedureName = "appendToStream"; + } +} \ No newline at end of file diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index c2fb89e..e1ee0a5 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -101,6 +101,7 @@ private async Task CreateCollectionIfItDoesNotExist() var collection = new DocumentCollection(); collection.Id = collectionOptions.CollectionName; + collection.DefaultTimeToLive = collectionOptions.DefaultTimeToLive; collection.PartitionKey.Paths.Add("/streamId"); collection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/body/*"}); diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs index 1150ad1..13490b8 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Azure.Documents; namespace SimpleEventStore.AzureDocumentDb @@ -16,5 +17,7 @@ public CollectionOptions() public ConsistencyLevel ConsistencyLevel { get; set; } public int CollectionRequestUnits { get; set; } + + public int? DefaultTimeToLive { get; set; } } } \ No newline at end of file From 41fed8f69a9df11d97c8699e8461944215523df9 Mon Sep 17 00:00:00 2001 From: Craig Bendell Date: Mon, 6 Aug 2018 13:00:21 +0100 Subject: [PATCH 57/61] Match file case for appendToStream.js With this change everything builds without issue on a case-sensitive file system. --- .../SimpleEventStore.AzureDocumentDb.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 21a7665..2ee5d14 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -12,7 +12,7 @@ - + Asos.SimpleEventStore.AzureDocumentDb From 3b0b7a261c0c4dfc31a8ea76323fb9e6b3c54c5c Mon Sep 17 00:00:00 2001 From: Craig Bendell Date: Fri, 7 Sep 2018 12:36:18 +0100 Subject: [PATCH 58/61] Match case when retrieving appendToStream.js via Resources --- .../AzureDocumentDbStorageEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index e1ee0a5..ad0bd3a 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -126,7 +126,7 @@ private async Task CreateAppendStoredProcedureIfItDoesNotExist() await client.CreateStoredProcedureAsync(commitsLink, new StoredProcedure { Id = AppendStoredProcedureName, - Body = Resources.GetString("AppendToStream.js") + Body = Resources.GetString("appendToStream.js") }); } } From ff59f4fdc89fbb1ecd37b8973d07ef05489f1e99 Mon Sep 17 00:00:00 2001 From: Tony Gamble Date: Thu, 13 Sep 2018 13:16:21 +0100 Subject: [PATCH 59/61] Ability to log response metrics by action performed so can differentiate metrics per operation Fix case change Set request identifier in ResponseInformation factory method --- .../ResponseInformationBuilding.cs | 10 +++++++--- .../AzureDocumentDbStorageEngine.cs | 10 +++++----- .../ResponseInformation.cs | 11 ++++++++--- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs index 83b964b..4fccf52 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs @@ -14,8 +14,9 @@ public class ResponseInformationBuilding [Test] public void when_building_from_a_write_response_all_target_fields_are_mapped() { - var result = ResponseInformation.FromWriteResponse(new FakeStoredProcedureResponse()); + var result = ResponseInformation.FromWriteResponse(Expected.RequestIdentifier, new FakeStoredProcedureResponse()); + Assert.That(result.RequestIdentifier, Is.EqualTo(Expected.RequestIdentifier)); Assert.That(result.CurrentResourceQuotaUsage, Is.EqualTo(Expected.CurrentResourceQuotaUsage)); Assert.That(result.MaxResourceQuota, Is.EqualTo(Expected.MaxResourceQuota)); Assert.That(result.RequestCharge, Is.EqualTo(Expected.RequestCharge)); @@ -25,8 +26,9 @@ public void when_building_from_a_write_response_all_target_fields_are_mapped() [Test] public void when_building_from_a_read_response_all_target_fields_are_mapped() { - var result = ResponseInformation.FromReadResponse(new FakeFeedResponse()); + var result = ResponseInformation.FromReadResponse(Expected.RequestIdentifier, new FakeFeedResponse()); + Assert.That(result.RequestIdentifier, Is.EqualTo(Expected.RequestIdentifier)); Assert.That(result.CurrentResourceQuotaUsage, Is.EqualTo(Expected.CurrentResourceQuotaUsage)); Assert.That(result.MaxResourceQuota, Is.EqualTo(Expected.MaxResourceQuota)); Assert.That(result.RequestCharge, Is.EqualTo(Expected.RequestCharge)); @@ -36,8 +38,9 @@ public void when_building_from_a_read_response_all_target_fields_are_mapped() [Test] public void when_building_from_a_subscription_read_response_all_target_fields_are_mapped() { - var result = ResponseInformation.FromSubscriptionReadResponse(new FakeFeedResponse()); + var result = ResponseInformation.FromSubscriptionReadResponse(Expected.RequestIdentifier, new FakeFeedResponse()); + Assert.That(result.RequestIdentifier, Is.EqualTo(Expected.RequestIdentifier)); Assert.That(result.CurrentResourceQuotaUsage, Is.EqualTo(Expected.CurrentResourceQuotaUsage)); Assert.That(result.MaxResourceQuota, Is.EqualTo(Expected.MaxResourceQuota)); Assert.That(result.RequestCharge, Is.EqualTo(Expected.RequestCharge)); @@ -46,6 +49,7 @@ public void when_building_from_a_subscription_read_response_all_target_fields_ar private static class Expected { + internal const string RequestIdentifier = "TEST-Identifier"; internal const string CurrentResourceQuotaUsage = "TEST-CurrentResourceQuotaUsage"; internal const string MaxResourceQuota = "TEST-MaxResourceQuota"; internal const double RequestCharge = 100d; diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs index ad0bd3a..ed8b928 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs @@ -48,11 +48,11 @@ public async Task AppendToStream(string streamId, IEnumerable even try { var result = await this.client.ExecuteStoredProcedureAsync( - storedProcLink, + storedProcLink, new RequestOptions { PartitionKey = new PartitionKey(streamId), ConsistencyLevel = this.collectionOptions.ConsistencyLevel }, docs); - loggingOptions.OnSuccess(ResponseInformation.FromWriteResponse(result)); + loggingOptions.OnSuccess(ResponseInformation.FromWriteResponse(nameof(AppendToStream), result)); } catch (DocumentClientException ex) { @@ -79,7 +79,7 @@ public async Task> ReadStreamForwards(string s while (eventsQuery.HasMoreResults) { var response = await eventsQuery.ExecuteNextAsync(); - loggingOptions.OnSuccess(ResponseInformation.FromReadResponse(response)); + loggingOptions.OnSuccess(ResponseInformation.FromReadResponse(nameof(ReadStreamForwards), response)); foreach (var e in response) { @@ -104,7 +104,7 @@ private async Task CreateCollectionIfItDoesNotExist() collection.DefaultTimeToLive = collectionOptions.DefaultTimeToLive; collection.PartitionKey.Paths.Add("/streamId"); collection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); - collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/body/*"}); + collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/body/*" }); collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/metadata/*" }); var requestOptions = new RequestOptions @@ -122,7 +122,7 @@ private async Task CreateAppendStoredProcedureIfItDoesNotExist() .AsDocumentQuery(); if (!(await query.ExecuteNextAsync()).Any()) - { + { await client.CreateStoredProcedureAsync(commitsLink, new StoredProcedure { Id = AppendStoredProcedureName, diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs index f00b601..7698fb7 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs @@ -6,6 +6,8 @@ namespace SimpleEventStore.AzureDocumentDb { public class ResponseInformation { + public string RequestIdentifier { get; private set; } + public string CurrentResourceQuotaUsage { get; private set; } public string MaxResourceQuota { get; private set; } @@ -14,10 +16,11 @@ public class ResponseInformation public NameValueCollection ResponseHeaders { get; private set; } - public static ResponseInformation FromWriteResponse(IStoredProcedureResponse response) + public static ResponseInformation FromWriteResponse(string requestIdentifier, IStoredProcedureResponse response) { return new ResponseInformation { + RequestIdentifier = requestIdentifier, CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage, MaxResourceQuota = response.MaxResourceQuota, RequestCharge = response.RequestCharge, @@ -25,10 +28,11 @@ public static ResponseInformation FromWriteResponse(IStoredProcedureResponse response) + public static ResponseInformation FromReadResponse(string requestIdentifier, IFeedResponse response) { return new ResponseInformation { + RequestIdentifier = requestIdentifier, CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage, MaxResourceQuota = response.MaxResourceQuota, RequestCharge = response.RequestCharge, @@ -36,10 +40,11 @@ public static ResponseInformation FromReadResponse(IFeedResponse response) + public static ResponseInformation FromSubscriptionReadResponse(string requestIdentifier, IFeedResponse response) { return new ResponseInformation { + RequestIdentifier = requestIdentifier, CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage, MaxResourceQuota = response.MaxResourceQuota, RequestCharge = response.RequestCharge, From 4f04f76ebdb0668ad8da467395412b2d7b362e73 Mon Sep 17 00:00:00 2001 From: Dave Thompson Date: Thu, 4 Oct 2018 16:57:53 +0100 Subject: [PATCH 60/61] Update to latest CosmosDB package version N.B.: Very old installations of the local CosmosDB emulator will need to be upgraded on development machines before tests execute. To do this, either update to the latest Docker image, or (if using the MSI package), uninstall the current version first (important!) and then install the latest emulator version from https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator. --- .../SimpleEventStore.AzureDocumentDb.Tests.csproj | 10 +++++----- .../SimpleEventStore.AzureDocumentDb.csproj | 4 ++-- .../SimpleEventStore.Tests.csproj | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj index e78f70d..5a2fb20 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj @@ -9,14 +9,14 @@ - - + + - - - + + + diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj index 2ee5d14..fc5bf24 100644 --- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj +++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj @@ -5,8 +5,8 @@ 1.0.0 - - + + diff --git a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj index f78ad2c..2e6fb46 100644 --- a/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj +++ b/src/SimpleEventStore/SimpleEventStore.Tests/SimpleEventStore.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + SimpleEventStore.Tests From 19db7ef7e72971bea76e1e5b0ab586b7cd5640e0 Mon Sep 17 00:00:00 2001 From: Mike Gore Date: Mon, 3 Dec 2018 12:32:19 +0000 Subject: [PATCH 61/61] Removed publishing to nuget task --- build/build.cake | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/build/build.cake b/build/build.cake index 41e126b..5c1d3ce 100644 --- a/build/build.cake +++ b/build/build.cake @@ -98,22 +98,6 @@ Task("Package") DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/", settings); }); - -Task("Deploy") - .IsDependentOn("Package") - .Does(() => -{ - var nugetSource = Argument("nugetSource"); - var nugetApiKey = Argument("nugetApiKey"); - - var package = GetFiles(outputDir + "/*.nupkg"); - - NuGetPush(package, new NuGetPushSettings { - Source = nugetSource, - ApiKey = nugetApiKey - }); -}); - ////////////////////////////////////////////////////////////////////// // TASK TARGETS //////////////////////////////////////////////////////////////////////