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
diff --git a/License.md b/License.md
new file mode 100644
index 0000000..c1eb494
--- /dev/null
+++ b/License.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 ASOS
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 6695f5d..3fbbbb8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,87 @@
-# 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
+
+## Persistence Engines
+SES supports the following
+- Azure Cosmos DB
+- In Memory
+
+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
+
+### 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.
+
+## Cosmos DB
+```csharp
+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;
+ })
+ .UseLogging(o =>
+ {
+ o.Success = onSuccessCallback;
+ })
+ .UseTypeMap(new ConfigurableSerializationTypeMap()
+ .RegisterTypes(
+ typeof(OrderCreated).GetTypeInfo().Assembly,
+ t => t.Namespace.EndsWith("Events"),
+ t => t.Name))
+ .Build()
+ .Initialise();
+```
+### CollectionOptions
+Allows 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
+- Bounded Staleness - use this if you need to geo-replicate the database
+
+### 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. 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.
diff --git a/build/build.cake b/build/build.cake
index 8076d0a..5c1d3ce 100644
--- a/build/build.cake
+++ b/build/build.cake
@@ -1,41 +1,101 @@
-#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
+
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
//////////////////////////////////////////////////////////////////////
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==");
+var consistencyLevel = Argument("consistencyLevel", "BoundedStaleness");
+var buildVersion = Argument("buildVersion", "1.0.0");
//////////////////////////////////////////////////////////////////////
// PREPARATION
//////////////////////////////////////////////////////////////////////
// Define directories.
-var solutionFile = "../src/SimpleEventStore/SimpleEventStore.sln";
+var solutionDir = "../src/SimpleEventStore/";
+var solutionFile = solutionDir + "SimpleEventStore.sln";
+var testProjs = GetFiles(solutionDir + "**/*.Tests.csproj");
+var outputDir = "./nuget";
//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////
-Task("Restore-NuGet-Packages")
+Task("Restore-Packages")
.Does(() =>
{
- NuGetRestore(solutionFile);
+ DotNetCoreRestore(solutionDir);
+});
+
+Task("Clean")
+ .Does(() =>
+{
+ CleanDirectory(outputDir);
});
Task("Build")
- .IsDependentOn("Restore-NuGet-Packages")
+ .IsDependentOn("Clean")
+ .IsDependentOn("Restore-Packages")
+ .Does(() =>
+{
+ DotNetCoreBuild(solutionFile, new DotNetCoreBuildSettings {
+ Configuration = configuration,
+ NoIncremental = true,
+ ArgumentCustomization = args => args.Append("/p:BuildVersion=" + buildVersion)
+ });
+});
+
+Task("Transform-Unit-Test-Config")
.Does(() =>
{
- // Use MSBuild
- MSBuild(solutionFile, settings => settings.SetConfiguration(configuration));
+ var documentDbTestConfigFiles = GetFiles(solutionDir + "SimpleEventStore.AzureDocumentDb.Tests/bin/" + configuration + "/**/appsettings.json");
+
+ 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")
.IsDependentOn("Build")
+ .IsDependentOn("Transform-Unit-Test-Config")
.Does(() =>
{
- XUnit2("../src/**/bin/" + configuration + "/*.Tests.dll");
+ foreach(var testPath in testProjs)
+ {
+ DotNetCoreTest(testPath.FullPath, new DotNetCoreTestSettings {
+ Configuration = configuration,
+ NoBuild = true
+ });
+ }
+});
+
+Task("Package")
+ .IsDependentOn("Build")
+ .IsDependentOn("Run-Unit-Tests")
+ .Does(() =>
+{
+ var settings = new DotNetCorePackSettings {
+ Configuration = configuration,
+ NoBuild = true,
+ OutputDirectory = outputDir,
+ ArgumentCustomization = args => args.Append("/p:BuildVersion=" + buildVersion)
+ };
+
+ DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore/", settings);
+ DotNetCorePack("./../src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/", settings);
});
//////////////////////////////////////////////////////////////////////
@@ -49,4 +109,4 @@ Task("Default")
// EXECUTION
//////////////////////////////////////////////////////////////////////
-RunTarget(target);
+RunTarget(target);
\ No newline at end of file
diff --git a/build/build.ps1 b/build/build.ps1
index 44de579..85ed134 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -44,7 +44,14 @@ Param(
[string]$Target = "Default",
[ValidateSet("Release", "Debug")]
[string]$Configuration = "Release",
- [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
+ # TODO: Tidy up and revert back to standard Cake bootstrapper, shouldn't need config in here
+ [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")]
[string]$Verbosity = "Verbose",
[switch]$Experimental,
[Alias("DryRun","Noop")]
@@ -185,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`" $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/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/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/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/AzureDocumentDbEventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs
deleted file mode 100644
index 2affb4b..0000000
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreCatchUpSubscription.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Threading.Tasks;
-using SimpleEventStore.Tests;
-
-namespace SimpleEventStore.AzureDocumentDb.Tests
-{
- public class AzureDocumentDbEventStoreCatchUpSubscription : EventStoreCatchUpSubscription
- {
- protected override Task CreateStorageEngine()
- {
- return StorageEngineFactory.Create("CatchUpSubscriptionTests"); ;
- }
- }
-}
diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs
new file mode 100644
index 0000000..a908e20
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbEventStoreLogging.cs
@@ -0,0 +1,74 @@
+using Microsoft.Azure.Documents;
+using Microsoft.Azure.Documents.Client;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using SimpleEventStore.Tests.Events;
+
+namespace SimpleEventStore.AzureDocumentDb.Tests
+{
+ [TestFixture]
+ public class AzureDocumentDbEventStoreLogging
+ {
+ [Test]
+ 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);
+ 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}");
+ }
+
+ [Test]
+ 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.That(logCount, Is.EqualTo(2));
+ }
+
+ private static async Task CreateStorageEngine(Action onSuccessCallback, string databaseName = "LoggingTests")
+ {
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+ var documentDbUri = config["Uri"];
+ var authKey = config["AuthKey"];
+ var consistencyLevel = config["ConsistencyLevel"];
+
+ if (!Enum.TryParse(consistencyLevel, true, out ConsistencyLevel 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;
+ })
+ .UseLogging(o =>
+ {
+ o.Success = onSuccessCallback;
+ })
+ .Build()
+ .Initialise();
+ }
+ }
+}
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/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/AzureDocumentDbStorageEngineBuilderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs
new file mode 100644
index 0000000..356e070
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/AzureDocumentDbStorageEngineBuilderTests.cs
@@ -0,0 +1,56 @@
+using System;
+using Microsoft.Azure.Documents.Client;
+using NUnit.Framework;
+
+namespace SimpleEventStore.AzureDocumentDb.Tests
+{
+ [TestFixture]
+ public class AzureDocumentDbStorageEngineBuilderTests
+ {
+ [Test]
+ public void when_creating_an_instance_the_document_client_must_be_supplied()
+ {
+ Assert.Throws(() => new AzureDocumentDbStorageEngineBuilder(null, "Test"));
+ }
+
+ [Test]
+ public void when_creating_an_instance_the_database_name_must_be_supplied()
+ {
+ Assert.Throws(() => new AzureDocumentDbStorageEngineBuilder(CreateClient(), null));
+ }
+
+ [Test]
+ public void when_setting_collection_settings_a_callback_must_be_supplied()
+ {
+ var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test");
+ Assert.Throws(() => builder.UseCollection(null));
+ }
+
+ [Test]
+ public void when_setting_subscription_settings_a_callback_must_be_supplied()
+ {
+ var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test");
+ Assert.Throws(() => builder.UseCollection(null));
+ }
+
+ [Test]
+ public void when_setting_logging_settings_a_callback_must_be_supplied()
+ {
+ var builder = new AzureDocumentDbStorageEngineBuilder(CreateClient(), "Test");
+ Assert.Throws(() => builder.UseLogging(null));
+ }
+
+ [Test]
+ 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==");
+ return client;
+ }
+ }
+}
diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs
new file mode 100644
index 0000000..c36e0b8
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ConfigurableTypeMapSerializationBinderTests.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Reflection;
+using NUnit.Framework;
+using SimpleEventStore.Tests.Events;
+
+namespace SimpleEventStore.AzureDocumentDb.Tests
+{
+ [TestFixture]
+ public class ConfigurableTypeMapSerializationBinderTests
+ {
+ [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)));
+ }
+
+ [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));
+ }
+
+ [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));
+ }
+
+ [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));
+ }
+
+ [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));
+ }
+
+ [Test]
+ public void when_registering_a_type_then_the_type_can_be_found()
+ {
+ var sut = new ConfigurableSerializationTypeMap();
+ sut.RegisterType("OrderCreated", typeof(OrderCreated));
+
+ Assert.That(sut.GetTypeFromName("OrderCreated"), Is.EqualTo(typeof(OrderCreated)));
+ }
+
+ [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.That(sut.GetTypeFromName("OrderCreated"), Is.EqualTo(typeof(OrderCreated)));
+ }
+
+ [Test]
+ public void when_registering_a_type_then_the_name_can_be_found()
+ {
+ var sut = new ConfigurableSerializationTypeMap();
+ sut.RegisterType("OrderCreated", typeof(OrderCreated));
+
+ Assert.That(sut.GetNameFromType(typeof(OrderCreated)), Is.EqualTo("OrderCreated"));
+ }
+
+ [Test]
+ 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/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/DocumentDbStorageEventTests.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs
new file mode 100644
index 0000000..87e2cd7
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/DocumentDbStorageEventTests.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reflection;
+using Newtonsoft.Json.Linq;
+using SimpleEventStore.Tests.Events;
+using NUnit.Framework;
+
+namespace SimpleEventStore.AzureDocumentDb.Tests
+{
+ [TestFixture]
+ public class DocumentDbStorageEventTests
+ {
+ [Test]
+ 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 != null && t.Namespace.EndsWith("Events"),
+ t => t.Name);
+ var result = sut.ToStorageEvent(typeMap);
+
+ 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/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/Properties/AssemblyInfo.cs
deleted file mode 100644
index 5ab67ea..0000000
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.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.AzureDocumentDb.Tests")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("SimpleEventStore.AzureDocumentDb.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("205a7f81-a496-4400-9a97-d156f88b7883")]
-
-// 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.Tests/ResponseInformationBuilding.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs
new file mode 100644
index 0000000..4fccf52
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/ResponseInformationBuilding.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Net;
+using Microsoft.Azure.Documents;
+using Microsoft.Azure.Documents.Client;
+using NUnit.Framework;
+
+namespace SimpleEventStore.AzureDocumentDb.Tests
+{
+ [TestFixture]
+ public class ResponseInformationBuilding
+ {
+ [Test]
+ public void when_building_from_a_write_response_all_target_fields_are_mapped()
+ {
+ 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));
+ Assert.That(result.ResponseHeaders, Is.EqualTo(Expected.ResponseHeaders));
+ }
+
+ [Test]
+ public void when_building_from_a_read_response_all_target_fields_are_mapped()
+ {
+ 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));
+ Assert.That(result.ResponseHeaders, Is.EqualTo(Expected.ResponseHeaders));
+ }
+
+ [Test]
+ public void when_building_from_a_subscription_read_response_all_target_fields_are_mapped()
+ {
+ 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));
+ Assert.That(result.ResponseHeaders, Is.EqualTo(Expected.ResponseHeaders));
+ }
+
+ 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;
+ 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 bef89c8..5a2fb20 100644
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/SimpleEventStore.AzureDocumentDb.Tests.csproj
@@ -1,107 +1,32 @@
-
-
+
- Debug
- AnyCPU
- {205A7F81-A496-4400-9A97-D156F88B7883}
- Library
- Properties
- SimpleEventStore.AzureDocumentDb.Tests
- SimpleEventStore.AzureDocumentDb.Tests
- v4.6.1
- 512
-
-
+ netcoreapp2.0;net452
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
- ..\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
-
-
-
-
-
-
-
-
-
-
- ..\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
-
-
-
-
-
-
-
+
+ Always
+
-
+
+
+
+
+
+
+
+
-
- {48c71940-d9b0-446a-9f3d-e6275cd43440}
- SimpleEventStore.AzureDocumentDb
-
-
- {aca6b3ae-fcb9-45f4-9d6b-66196f98f819}
- SimpleEventStore.Tests
-
-
- {73235465-69bf-4762-b8c5-20c8e45795ff}
- SimpleEventStore
-
+
+
+
-
-
-
-
- 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.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 97ec149..1f42e4a 100644
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/StorageEngineFactory.cs
@@ -1,25 +1,45 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+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
{
internal static class StorageEngineFactory
{
- internal static async Task Create(string databaseName)
+ internal static async Task Create(string databaseName, Action collectionOverrides = null)
{
- var documentDbUri = "https://localhost:8081/";
- var authKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
- DocumentClient client = new DocumentClient(new Uri(documentDbUri), authKey);
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
- var storageEngine = new AzureDocumentDbStorageEngine(client, databaseName, new DatabaseOptions(ConsistencyLevel.BoundedStaleness, 400), new SubscriptionOptions(maxItemCount: 1, pollEvery: TimeSpan.FromSeconds(0.5)));
- await storageEngine.Initialise();
+ var consistencyLevel = config["ConsistencyLevel"];
+ ConsistencyLevel consistencyLevelEnum;
- return storageEngine;
+ if(!Enum.TryParse(consistencyLevel, true, out consistencyLevelEnum))
+ {
+ throw new Exception($"The ConsistencyLevel value {consistencyLevel} is not supported");
+ }
+
+ var client = DocumentClientFactory.Create(databaseName);
+
+ return await new AzureDocumentDbStorageEngineBuilder(client, databaseName)
+ .UseCollection(o =>
+ {
+ o.ConsistencyLevel = consistencyLevelEnum;
+ o.CollectionRequestUnits = TestConstants.RequestUnits;
+ if(collectionOverrides != null) collectionOverrides(o);
+ })
+ .UseTypeMap(new ConfigurableSerializationTypeMap()
+ .RegisterTypes(
+ typeof(OrderCreated).GetTypeInfo().Assembly,
+ t => t.Namespace != null && t.Namespace.EndsWith("Events"),
+ t => t.Name))
+ .Build()
+ .Initialise();
}
}
}
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.Tests/appsettings.json b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json
new file mode 100644
index 0000000..d62b638
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb.Tests/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "Uri": "https://localhost:8081/",
+ "AuthKey": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
+ "ConsistencyLevel": "Session"
+}
\ 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/AzureDocumentDbStorageEngine.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs
index 85acca4..ed8b928 100644
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngine.cs
@@ -8,47 +8,51 @@
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;
+ private readonly LoggingOptions loggingOptions;
+ private readonly ISerializationTypeMap typeMap;
- public AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, DatabaseOptions databaseOptions, SubscriptionOptions subscriptionOptions)
+ internal AzureDocumentDbStorageEngine(DocumentClient client, string databaseName, CollectionOptions collectionOptions, LoggingOptions loggingOptions, ISerializationTypeMap typeMap)
{
this.client = client;
this.databaseName = databaseName;
- this.databaseOptions = databaseOptions;
- this.commitsLink = UriFactory.CreateDocumentCollectionUri(databaseName, CommitsCollectionName);
- this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, CommitsCollectionName, AppendStoredProcedureName);
- this.subscriptionOptions = subscriptionOptions;
+ this.collectionOptions = collectionOptions;
+ this.commitsLink = UriFactory.CreateDocumentCollectionUri(databaseName, collectionOptions.CollectionName);
+ this.storedProcLink = UriFactory.CreateStoredProcedureUri(databaseName, collectionOptions.CollectionName, AppendStoredProcedureName);
+ this.loggingOptions = loggingOptions;
+ this.typeMap = typeMap;
}
- public async Task Initialise()
+ public async Task Initialise()
{
await CreateDatabaseIfItDoesNotExist();
await CreateCollectionIfItDoesNotExist();
await CreateAppendStoredProcedureIfItDoesNotExist();
+
+ return this;
}
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
{
var result = await this.client.ExecuteStoredProcedureAsync(
- storedProcLink,
- new RequestOptions { PartitionKey = new PartitionKey(streamId), ConsistencyLevel = this.databaseOptions.ConsistencyLevel },
+ storedProcLink,
+ new RequestOptions { PartitionKey = new PartitionKey(streamId), ConsistencyLevel = this.collectionOptions.ConsistencyLevel },
docs);
+
+ loggingOptions.OnSuccess(ResponseInformation.FromWriteResponse(nameof(AppendToStream), result));
}
catch (DocumentClientException ex)
{
@@ -74,63 +78,41 @@ 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(nameof(ReadStreamForwards), response));
+
+ foreach (var e in response)
{
- events.Add(e.ToStorageEvent());
+ events.Add(e.ToStorageEvent(this.typeMap));
}
}
return events.AsReadOnly();
}
- public void SubscribeToAll(Action, string> onNextEvent, string checkpoint)
- {
- Guard.IsNotNull(nameof(onNextEvent), onNextEvent);
-
- var subscription = new Subscription(this.client, this.commitsLink, onNextEvent, checkpoint, this.subscriptionOptions);
- subscriptions.Add(subscription);
-
- subscription.Start();
- }
-
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 == CommitsCollectionName)
- .Take(1)
- .AsDocumentQuery();
+ 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/*" });
+ collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/metadata/*" });
- if (!(await commitsCollectionQuery.ExecuteNextAsync()).Any())
+ var requestOptions = new RequestOptions
{
- var collection = new DocumentCollection();
- collection.Id = CommitsCollectionName;
- 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 = this.databaseOptions.CollectionRequestUnits
- };
+ OfferThroughput = collectionOptions.CollectionRequestUnits
+ };
- await client.CreateDocumentCollectionAsync(databaseUri, collection, requestOptions);
- }
+ await client.CreateDocumentCollectionIfNotExistsAsync(databaseUri, collection, requestOptions);
}
private async Task CreateAppendStoredProcedureIfItDoesNotExist()
@@ -140,11 +122,11 @@ private async Task CreateAppendStoredProcedureIfItDoesNotExist()
.AsDocumentQuery();
if (!(await query.ExecuteNextAsync()).Any())
- {
+ {
await client.CreateStoredProcedureAsync(commitsLink, new StoredProcedure
{
Id = AppendStoredProcedureName,
- Body = Scripts.appendToStream
+ Body = Resources.GetString("appendToStream.js")
});
}
}
diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs
new file mode 100644
index 0000000..dbdb771
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/AzureDocumentDbStorageEngineBuilder.cs
@@ -0,0 +1,53 @@
+using System;
+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 readonly LoggingOptions loggingOptions = new LoggingOptions();
+ private ISerializationTypeMap typeMap = new DefaultSerializationTypeMap();
+
+ 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 UseLogging(Action action)
+ {
+ Guard.IsNotNull(nameof(action), action);
+
+ action(loggingOptions);
+ 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.loggingOptions, this.typeMap);
+ return engine;
+ }
+ }
+}
diff --git a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs
new file mode 100644
index 0000000..13490b8
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/CollectionOptions.cs
@@ -0,0 +1,23 @@
+using System;
+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; }
+
+ public int? DefaultTimeToLive { get; set; }
+ }
+}
\ No newline at end of file
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/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/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 762cebd..29dbc62 100644
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/DocumentDbStorageEvent.cs
@@ -1,11 +1,13 @@
using System;
+using System.Runtime.Serialization;
+using System.Security.Cryptography.X509Certificates;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace SimpleEventStore.AzureDocumentDb
{
- internal class DocumentDbStorageEvent
+ public class DocumentDbStorageEvent
{
[JsonProperty("id")]
public string Id { get; set; }
@@ -31,17 +33,17 @@ internal 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/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/Properties/AssemblyInfo.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Properties/AssemblyInfo.cs
deleted file mode 100644
index d71c533..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("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[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/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/ResponseInformation.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs
new file mode 100644
index 0000000..7698fb7
--- /dev/null
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/ResponseInformation.cs
@@ -0,0 +1,55 @@
+using System.Collections.Specialized;
+using Microsoft.Azure.Documents;
+using Microsoft.Azure.Documents.Client;
+
+namespace SimpleEventStore.AzureDocumentDb
+{
+ public class ResponseInformation
+ {
+ public string RequestIdentifier { get; private set; }
+
+ 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(string requestIdentifier, IStoredProcedureResponse response)
+ {
+ return new ResponseInformation
+ {
+ RequestIdentifier = requestIdentifier,
+ CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage,
+ MaxResourceQuota = response.MaxResourceQuota,
+ RequestCharge = response.RequestCharge,
+ ResponseHeaders = response.ResponseHeaders
+ };
+ }
+
+ public static ResponseInformation FromReadResponse(string requestIdentifier, IFeedResponse response)
+ {
+ return new ResponseInformation
+ {
+ RequestIdentifier = requestIdentifier,
+ CurrentResourceQuotaUsage = response.CurrentResourceQuotaUsage,
+ MaxResourceQuota = response.MaxResourceQuota,
+ RequestCharge = response.RequestCharge,
+ ResponseHeaders = response.ResponseHeaders
+ };
+ }
+
+ public static ResponseInformation FromSubscriptionReadResponse(string requestIdentifier, IFeedResponse response)
+ {
+ return new ResponseInformation
+ {
+ RequestIdentifier = requestIdentifier,
+ 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/Scripts.Designer.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Scripts.Designer.cs
deleted file mode 100644
index 78b3312..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;
-
-
- ///
- /// 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).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 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]";.
- ///
- 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 762edfb..fc5bf24 100644
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj
+++ b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SimpleEventStore.AzureDocumentDb.csproj
@@ -1,98 +1,32 @@
-
-
+
- Debug
- AnyCPU
- {48C71940-D9B0-446A-9F3D-E6275CD43440}
- Library
- Properties
- SimpleEventStore.AzureDocumentDb
- SimpleEventStore.AzureDocumentDb
- v4.6.1
- 512
-
-
+ netstandard1.6;net452
+ 1.0.0
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
- ..\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
- 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}.
-
-
-
-
+
+ Asos.SimpleEventStore.AzureDocumentDb
+ Provides a DocumentDB storage engine for Simple Event Store (SES)
+ ASOS
+ Copyright ASOS ©2016
+ SimpleEventStore.AzureDocumentDb
+ Asos.SimpleEventStore.AzureDocumentDb
+ ASOS
+ eventsourcing documentdb azure
+ https://github.com/ASOS/SimpleEventStore
+
+ library
+ $(BuildVersion)
+ $(BuildVersion)
+
\ No newline at end of file
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.AzureDocumentDb/Subscription.cs b/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs
deleted file mode 100644
index 8001630..0000000
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/Subscription.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.Azure.Documents;
-using Microsoft.Azure.Documents.Client;
-using Microsoft.Azure.Documents.Linq;
-using Newtonsoft.Json;
-
-namespace SimpleEventStore.AzureDocumentDb
-{
- internal class Subscription
- {
- private readonly DocumentClient client;
- private readonly Uri commitsLink;
- private readonly Action, string> onNextEvent;
- private readonly SubscriptionOptions subscriptionOptions;
- private readonly Dictionary checkpoints;
- private Task workerTask;
-
- public Subscription(DocumentClient client, Uri commitsLink, Action, string> onNextEvent, string checkpoint, SubscriptionOptions subscriptionOptions)
- {
- this.client = client;
- this.commitsLink = commitsLink;
- this.onNextEvent = onNextEvent;
- this.checkpoints = checkpoint == null ? new Dictionary() : JsonConvert.DeserializeObject>(checkpoint);
- this.subscriptionOptions = subscriptionOptions;
- }
-
- // TODO: Configure the retry policy, also allow the subscription to be canclled (use a CancellationToken)
- public void Start()
- {
- workerTask = Task.Run(async () =>
- {
- while (true)
- {
- await ReadEvents();
- await Task.Delay(subscriptionOptions.PollEvery);
- }
- });
- }
-
- private async Task ReadEvents()
- {
- var partitionKeyRanges = new List();
- FeedResponse pkRangesResponse;
-
- do
- {
- pkRangesResponse = await client.ReadPartitionKeyRangeFeedAsync(commitsLink);
- partitionKeyRanges.AddRange(pkRangesResponse);
- }
- while (pkRangesResponse.ResponseContinuation != null);
-
- 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();
- var events = new List();
- string initialCheckpointValue;
-
- foreach (var @event in feedResponse)
- {
- events.Add(DocumentDbStorageEvent.FromDocument(@event).ToStorageEvent());
- }
-
- 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;
- }
- }
- }
- }
- }
-}
\ 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 226c2bd..0000000
--- a/src/SimpleEventStore/SimpleEventStore.AzureDocumentDb/SubscriptionOptions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace SimpleEventStore.AzureDocumentDb
-{
- public class SubscriptionOptions
- {
- public SubscriptionOptions(int maxItemCount, TimeSpan pollEvery)
- {
- this.MaxItemCount = maxItemCount;
- this.PollEvery = pollEvery;
- }
-
- public int MaxItemCount { get; }
-
- public TimeSpan PollEvery { get; }
- }
-}
\ 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/EventDataTests.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs
index ec0bc34..cacd4f5 100644
--- a/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs
+++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventDataTests.cs
@@ -1,14 +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));
}
+
+ [Test]
+ public void when_creating_an_instance_the_properties_are_mapped()
+ {
+ var eventId = Guid.NewGuid();
+ var sut = new EventData(eventId, "BODY", "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 97d4b17..ded8e8d 100644
--- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs
+++ b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreAppending.cs
@@ -1,15 +1,15 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using NUnit.Framework;
using SimpleEventStore.Tests.Events;
-using SimpleEventStore.Tests.Metadata;
-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();
@@ -19,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();
@@ -36,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();
@@ -64,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();
@@ -110,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/EventStoreCatchUpSubscription.cs b/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs
deleted file mode 100644
index 7ecdb8a..0000000
--- a/src/SimpleEventStore/SimpleEventStore.Tests/EventStoreCatchUpSubscription.cs
+++ /dev/null
@@ -1,209 +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;
-
- [Fact]
- public async void 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