From 55f71726fe0f9b42fe8133e0a885e8ef8407899f Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Fri, 19 Jul 2019 14:08:06 +0200 Subject: [PATCH 1/9] Added CosmosDbAdapter --- .../CosmosDb.cs | 24 +++++ .../CosmosDbClient.cs | 99 +++++++++++++++++++ .../CosmosDbEventRepository.cs | 82 +++++++++++++++ .../ICosmosDb.cs | 10 ++ .../ICosmosDbClient.cs | 20 ++++ ...ve.Eventstores.Persistence.CosmosDb.csproj | 15 +++ Microwave.Domain/IDatabaseConfiguration.cs | 5 +- Microwave.Domain/Identities/GuidIdentity.cs | 2 +- Microwave.Domain/Identities/Identity.cs | 5 + .../CosmosDbEventRepositoryTests.cs | 76 ++++++++++++++ ...wave.Persistence.CosmosDb.UnitTests.csproj | 20 ++++ Microwave.sln | 44 +++++---- Microwave/DatabaseConfiguration.cs | 1 + 13 files changed, 383 insertions(+), 20 deletions(-) create mode 100644 CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs create mode 100644 CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs create mode 100644 CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs create mode 100644 CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs create mode 100644 CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs create mode 100644 CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj create mode 100644 Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs create mode 100644 Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs new file mode 100644 index 00000000..2452dd8e --- /dev/null +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microwave.Domain; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDb : ICosmosDb + { + + private readonly IMicrowaveConfiguration _configuration; + + public CosmosDb(IMicrowaveConfiguration configuration) + { + _configuration = configuration; + } + + public DocumentClient GetCosmosDbClient() + { + return new DocumentClient(new Uri(_configuration.DatabaseConfiguration.ConnectionString), + _configuration.DatabaseConfiguration.PrimaryKey); + } + } +} \ No newline at end of file diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs new file mode 100644 index 00000000..28d9fad8 --- /dev/null +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDbClient : ICosmosDbClient + { + private readonly DocumentClient _client; + private IEnumerable _domainEventTypes; + private const string DatabaseId = "Eventstore"; + private const string CollectionId = "DomainEvents"; + + public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) + { + _client = cosmosDb.GetCosmosDbClient(); + var type = typeof(IDomainEvent); + _domainEventTypes = assemblies + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p)); + + } + + public async Task InitializeCosmosDbAsync() + { + var database = await _client.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId }); + var collection = await _client.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri(DatabaseId), + new DocumentCollection { Id = CollectionId }); + if (database == null || collection == null) + { + throw new ArgumentException("Could not create Database or Collection with given CosmosDb Configuration Parameters!"); + } + } + + + public async Task CreateDomainEventAsync(IDomainEvent domainEvent) + { + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + await _client.CreateDocumentAsync(uri, domainEvent); + + } + + + public async Task> GetDomainEventsAsync(Identity identity) + { + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.DomainEvent.EntityId == identity) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = wrappedEvents.Select(e => JsonConvert.DeserializeObject(e.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == e.GetValue("DomainEventType").ToString()))).ToList(); + return new List(); + } + + + public async Task>> GetDomainEventsAsync(DateTimeOffset tickSince) + { + FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + var query = _client.CreateDocumentQuery(uri, queryOptions) + .Where(e => e.Created > tickSince); + return Result>.Ok(query.ToList()); + } + + public async Task CreateItemAsync(DomainEventWrapper domainEvent) + { + return await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), domainEvent); + } + + public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince) + { + FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + var query = _client.CreateDocumentQuery(uri, queryOptions) + .Where(e => e.DomainEventType == eventType); + return Result>.Ok(query.ToList()); + } + } + +} \ No newline at end of file diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs new file mode 100644 index 00000000..d31e1c1b --- /dev/null +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +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 Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; +using Microwave.EventStores.Ports; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDbEventRepository : IEventRepository + { + private readonly ICosmosDbClient _cosmosDbClient; + + + public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient) + { + _cosmosDbClient = cosmosDbClient; + } + + public async Task>> LoadEventsByEntity(Identity entityId, long @from = 0) + { + throw new NotImplementedException(); + //var uri = CreateUriForCosmosDb(entityId); + //var domainEvents = (await _client.ReadDocumentAsync>(uri)).Document; + //return new EventStoreResult>(domainEvents, domainEvents.Max(e => e.Version)); + } + + public async Task AppendAsync(IEnumerable domainEvents, long currentEntityVersion) + { + foreach (var domainEvent in domainEvents) + { + + await _cosmosDbClient.CreateDomainEventAsync(domainEvent); + } + + return Result.Ok(); + } + + public async Task>> LoadEvents(DateTimeOffset tickSince = default(DateTimeOffset)) + { + var result = await _cosmosDbClient.GetDomainEventsAsync(tickSince); + if (result.Value.Any()) + { + return Result>.Ok(result.Value); + } + else + { + return Result>.NotFound(null); + } + } + + public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince = default(DateTimeOffset)) + { + var result = _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); + return Result>.Ok(result.Result.Value); + } + + public async Task> GetLastEventOccuredOn(string domainEventType) + { + throw new NotImplementedException(); + //FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + //var uri = UriFactory.CreateDocumentCollectionUri(DatabaseName, CollectionId); + //var query = _client.CreateDocumentQuery(uri, queryOptions).ToList(); + //var latestEventTime = query.Max(e => e.Created); + + //return Result.Ok(latestEventTime); + } + + private Uri CreateUriForCosmosDb(Identity identity) + { + //return UriFactory.CreateDocumentUri(DatabaseName, CollectionId, identity.Id); + return null; + } + } +} diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs new file mode 100644 index 00000000..764f5442 --- /dev/null +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public interface ICosmosDb + { + DocumentClient GetCosmosDbClient(); + } +} \ No newline at end of file diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs new file mode 100644 index 00000000..3dc3755f --- /dev/null +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public interface ICosmosDbClient + { + Task CreateDomainEventAsync(IDomainEvent domainEvent); + Task>> GetDomainEventsAsync(DateTimeOffset tickSince); + Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); + Task CreateItemAsync(DomainEventWrapper domainEvent); + Task> GetDomainEventsAsync(Identity identity); + } +} \ No newline at end of file diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj new file mode 100644 index 00000000..847a46ac --- /dev/null +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.1 + + + + + + + + + + + diff --git a/Microwave.Domain/IDatabaseConfiguration.cs b/Microwave.Domain/IDatabaseConfiguration.cs index 941ee353..0b9fc31f 100644 --- a/Microwave.Domain/IDatabaseConfiguration.cs +++ b/Microwave.Domain/IDatabaseConfiguration.cs @@ -2,7 +2,8 @@ namespace Microwave.Domain { public interface IDatabaseConfiguration { - string ConnectionString { get; } - string DatabaseName { get; } + string ConnectionString { get; set; } + string DatabaseName { get; set; } + string PrimaryKey { get; set; } } } \ No newline at end of file diff --git a/Microwave.Domain/Identities/GuidIdentity.cs b/Microwave.Domain/Identities/GuidIdentity.cs index bd67aedd..02224c9f 100644 --- a/Microwave.Domain/Identities/GuidIdentity.cs +++ b/Microwave.Domain/Identities/GuidIdentity.cs @@ -4,7 +4,7 @@ namespace Microwave.Domain.Identities { public class GuidIdentity : Identity { - private GuidIdentity(string id) + public GuidIdentity(string id) { Id = id; } diff --git a/Microwave.Domain/Identities/Identity.cs b/Microwave.Domain/Identities/Identity.cs index 0e0c2bf2..74a6ef68 100644 --- a/Microwave.Domain/Identities/Identity.cs +++ b/Microwave.Domain/Identities/Identity.cs @@ -4,6 +4,11 @@ namespace Microwave.Domain.Identities { public abstract class Identity : IEquatable { + + public Identity() + { + } + public string Id { get; protected set; } public static bool operator== (Identity id1, Identity id2) diff --git a/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs new file mode 100644 index 00000000..255c487a --- /dev/null +++ b/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.EventStores; +using Microwave.Eventstores.Persistence.CosmosDb; + +namespace Microwave.Persistence.CosmosDb.UnitTests +{ + [TestClass] + public class CosmosDbEventRepositoryTests + { + [TestMethod] + public async Task DomainEventIsAppendedCorrectly() + { + var databaseConfig = new DatabaseConfiguration(); + databaseConfig.ConnectionString = "https://spoppinga.documents.azure.com:443/"; + databaseConfig.PrimaryKey = + "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA=="; + var config = new MicrowaveConfiguration(); + config.DatabaseConfiguration = databaseConfig; + + var cosmosDatabse = new Eventstores.Persistence.CosmosDb.CosmosDb(config); + var cosmosDbClient = new CosmosDbClient(cosmosDatabse, new List { Assembly.GetAssembly(typeof(UserCreatedEvent)) }); + await cosmosDbClient.InitializeCosmosDbAsync(); + + var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + var domainEvent = new UserCreatedEvent(GuidIdentity.Create(Guid.NewGuid()), "Hans Wurst"); + await cosmosDbClient.CreateItemAsync(new DomainEventWrapper + { + Created = DateTimeOffset.Now, + Version = 0, + DomainEvent = domainEvent + }); + + } + + [TestMethod] + public async Task DomainEventsAreGettedCorrectly() + { + var databaseConfig = new DatabaseConfiguration(); + databaseConfig.ConnectionString = "https://spoppinga.documents.azure.com:443/"; + databaseConfig.PrimaryKey = + "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA=="; + var config = new MicrowaveConfiguration(); + config.DatabaseConfiguration = databaseConfig; + + var cosmosDatabse = new Eventstores.Persistence.CosmosDb.CosmosDb(config); + var cosmosDbClient = new CosmosDbClient(cosmosDatabse , new List{Assembly.GetAssembly(typeof(UserCreatedEvent))}); + await cosmosDbClient.InitializeCosmosDbAsync(); + + var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + var result = await cosmosDbClient.GetDomainEventsAsync(Identity.Create(Guid.Parse("19cf121a-44cd-40bb-a3b5-ea9deb11d4f5"))); + + } + + + } + + public class UserCreatedEvent : IDomainEvent + { + public Identity EntityId { get; } + public string Username { get; } + + + + public UserCreatedEvent(GuidIdentity entityId, string name) + { + EntityId = entityId; + Username = name; + } + } +} diff --git a/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj b/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj new file mode 100644 index 00000000..34dc4ef5 --- /dev/null +++ b/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + diff --git a/Microwave.sln b/Microwave.sln index ad4dbb96..a95cd092 100644 --- a/Microwave.sln +++ b/Microwave.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2026 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28822.285 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Domain", "Microwave.Domain\Microwave.Domain.csproj", "{9A1885D9-77BB-45D4-99E5-A9FC15F9B6A5}" EndProject @@ -19,33 +19,37 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave", "Microwave\Micr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UnitTests", "Microwave.UnitTests\Microwave.UnitTests.csproj", "{6DE4BD27-1E59-43F3-AF52-5925CEA1E9CA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Domain.UnitTests", "Microwave.Domain.UnitTests\Microwave.Domain.UnitTests.csproj", "{306D2634-E1A9-4C72-AEA5-AEB9FA26110F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Domain.UnitTests", "Microwave.Domain.UnitTests\Microwave.Domain.UnitTests.csproj", "{306D2634-E1A9-4C72-AEA5-AEB9FA26110F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.WebApi.UnitTests", "Microwave.WebApi.UnitTests\Microwave.WebApi.UnitTests.csproj", "{3622487E-A894-49FD-A8EF-5D11DAA01B62}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.WebApi.UnitTests", "Microwave.WebApi.UnitTests\Microwave.WebApi.UnitTests.csproj", "{3622487E-A894-49FD-A8EF-5D11DAA01B62}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Eventstores.UnitTests.DomainEvents", "Microwave.Eventstores.UnitTests.DomainEvents\Microwave.Eventstores.UnitTests.DomainEvents.csproj", "{AA1064F6-6A41-405B-9AEB-BDFD5670506C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Eventstores.UnitTests.DomainEvents", "Microwave.Eventstores.UnitTests.DomainEvents\Microwave.Eventstores.UnitTests.DomainEvents.csproj", "{AA1064F6-6A41-405B-9AEB-BDFD5670506C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.UnitTests.PublishedEventsDll", "Microwave.UnitTests.PublishedEventsDll\Microwave.UnitTests.PublishedEventsDll.csproj", "{5A17AEC8-F492-48F4-8C6F-4063489DA58F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UnitTests.PublishedEventsDll", "Microwave.UnitTests.PublishedEventsDll\Microwave.UnitTests.PublishedEventsDll.csproj", "{5A17AEC8-F492-48F4-8C6F-4063489DA58F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Eventstores.Persistence.MongoDb", "Microwave.Eventstores.Persistence.MongoDb\Microwave.Eventstores.Persistence.MongoDb.csproj", "{6394F395-66C1-483D-B4BC-8B19007F7785}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Eventstores.Persistence.MongoDb", "Microwave.Eventstores.Persistence.MongoDb\Microwave.Eventstores.Persistence.MongoDb.csproj", "{6394F395-66C1-483D-B4BC-8B19007F7785}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.MongoDb.UnitTests", "Microwave.Persistence.MongoDb.UnitTests\Microwave.Persistence.MongoDb.UnitTests.csproj", "{52EC4F8C-6145-4E15-8FE3-388431BEF5FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.MongoDb.UnitTests", "Microwave.Persistence.MongoDb.UnitTests\Microwave.Persistence.MongoDb.UnitTests.csproj", "{52EC4F8C-6145-4E15-8FE3-388431BEF5FA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Discovery", "Microwave.Discovery\Microwave.Discovery.csproj", "{80A57BBD-8D51-4638-9D74-E63785754CC7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Discovery", "Microwave.Discovery\Microwave.Discovery.csproj", "{80A57BBD-8D51-4638-9D74-E63785754CC7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Discovery.UnitTests", "Microwave.Discovery.UnitTests\Microwave.Discovery.UnitTests.csproj", "{D45D3575-38B3-408E-8693-A1E67D6406EA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Discovery.UnitTests", "Microwave.Discovery.UnitTests\Microwave.Discovery.UnitTests.csproj", "{D45D3575-38B3-408E-8693-A1E67D6406EA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestServices", "TestServices", "{4DA39CBA-8A2D-4C97-9B79-B118325D7D04}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.UI.UnitTests", "Microwave.UI.UnitTests\Microwave.UI.UnitTests.csproj", "{10F2D68D-5EE4-4DC4-81AB-DAA30A6BF951}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UI.UnitTests", "Microwave.UI.UnitTests\Microwave.UI.UnitTests.csproj", "{10F2D68D-5EE4-4DC4-81AB-DAA30A6BF951}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Queries.Persistence.MongoDb", "Microwave.Queries.Persistence.MongoDb\Microwave.Queries.Persistence.MongoDb.csproj", "{E245AB65-88DD-4CAC-BDCE-E923CB87067B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Queries.Persistence.MongoDb", "Microwave.Queries.Persistence.MongoDb\Microwave.Queries.Persistence.MongoDb.csproj", "{E245AB65-88DD-4CAC-BDCE-E923CB87067B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Configuration.MongoDb", "Microwave.Configuration.MongoDb\Microwave.Configuration.MongoDb.csproj", "{6F7E86A7-2124-4995-A748-0BD78D98576F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Configuration.MongoDb", "Microwave.Configuration.MongoDb\Microwave.Configuration.MongoDb.csproj", "{6F7E86A7-2124-4995-A748-0BD78D98576F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.UI", "Microwave.UI\Microwave.UI.csproj", "{446A53A9-B95A-4B3E-B067-6FBC126173E4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UI", "Microwave.UI\Microwave.UI.csproj", "{446A53A9-B95A-4B3E-B067-6FBC126173E4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.MongoDb.Extensions", "Microwave.Persistence.MongoDb.Extensions\Microwave.Persistence.MongoDb.Extensions.csproj", "{BF34D55C-37E7-4FB8-B051-4FB0C56B4D64}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.MongoDb.Extensions", "Microwave.Persistence.MongoDb.Extensions\Microwave.Persistence.MongoDb.Extensions.csproj", "{BF34D55C-37E7-4FB8-B051-4FB0C56B4D64}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Eventstores.Persistence.CosmosDb", "CosmosDb\Microwave.Eventstores.Persistence.CosmosDb\Microwave.Eventstores.Persistence.CosmosDb.csproj", "{1E1F3B08-CB8F-4655-92A6-3C272CD56DDD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.CosmosDb.UnitTests", "Microwave.Persistence.CosmosDb.UnitTests\Microwave.Persistence.CosmosDb.UnitTests.csproj", "{DE66D731-3454-437D-A9DC-10EF930796CE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -137,6 +141,14 @@ Global {BF34D55C-37E7-4FB8-B051-4FB0C56B4D64}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF34D55C-37E7-4FB8-B051-4FB0C56B4D64}.Release|Any CPU.ActiveCfg = Release|Any CPU {BF34D55C-37E7-4FB8-B051-4FB0C56B4D64}.Release|Any CPU.Build.0 = Release|Any CPU + {1E1F3B08-CB8F-4655-92A6-3C272CD56DDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E1F3B08-CB8F-4655-92A6-3C272CD56DDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E1F3B08-CB8F-4655-92A6-3C272CD56DDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E1F3B08-CB8F-4655-92A6-3C272CD56DDD}.Release|Any CPU.Build.0 = Release|Any CPU + {DE66D731-3454-437D-A9DC-10EF930796CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE66D731-3454-437D-A9DC-10EF930796CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE66D731-3454-437D-A9DC-10EF930796CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE66D731-3454-437D-A9DC-10EF930796CE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -144,6 +156,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {10681780-EDC2-4AAC-8551-A22B38DC45CE} EndGlobalSection - GlobalSection(NestedProjects) = preSolution - EndGlobalSection EndGlobal diff --git a/Microwave/DatabaseConfiguration.cs b/Microwave/DatabaseConfiguration.cs index efb9b3ef..2813c310 100644 --- a/Microwave/DatabaseConfiguration.cs +++ b/Microwave/DatabaseConfiguration.cs @@ -6,5 +6,6 @@ public class DatabaseConfiguration : IDatabaseConfiguration { public string ConnectionString { get; set; } = "mongodb://localhost:27017/"; public string DatabaseName { get; set; } = "MicrowaveDb"; + public string PrimaryKey { get; set; } } } \ No newline at end of file From 90bce306baeea34fafe71ed4224fed9b00269fc4 Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Wed, 24 Jul 2019 15:23:56 +0200 Subject: [PATCH 2/9] Added CosmosDbAdapter --- ...ve.Eventstores.Persistence.CosmosDb.csproj | 1 + Microwave.Domain/Identities/StringIdentity.cs | 2 +- .../Microwave.Persistence.CosmosDb.csproj | 17 ++++ Microwave.Persistence.CosmosDb/CosmosDb.cs | 24 +++++ .../CosmosDbClient.cs | 99 +++++++++++++++++++ .../CosmosDbEventRepository.cs | 82 +++++++++++++++ Microwave.Persistence.CosmosDb/ICosmosDb.cs | 10 ++ .../ICosmosDbClient.cs | 20 ++++ .../Microwave.Persistence.CosmosDb.csproj | 17 ++++ Microwave.sln | 65 +++++++----- .../CosmosDbEventRepositoryTests.cs | 0 ...wave.Persistence.CosmosDb.UnitTests.csproj | 5 +- 12 files changed, 312 insertions(+), 30 deletions(-) create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj create mode 100644 Microwave.Persistence.CosmosDb/CosmosDb.cs create mode 100644 Microwave.Persistence.CosmosDb/CosmosDbClient.cs create mode 100644 Microwave.Persistence.CosmosDb/CosmosDbEventRepository.cs create mode 100644 Microwave.Persistence.CosmosDb/ICosmosDb.cs create mode 100644 Microwave.Persistence.CosmosDb/ICosmosDbClient.cs create mode 100644 Microwave.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj rename {Microwave.Persistence.CosmosDb.UnitTests => TestProjects/Microwave.Persistence.CosmosDb.UnitTests}/CosmosDbEventRepositoryTests.cs (100%) rename {Microwave.Persistence.CosmosDb.UnitTests => TestProjects/Microwave.Persistence.CosmosDb.UnitTests}/Microwave.Persistence.CosmosDb.UnitTests.csproj (63%) diff --git a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj index 847a46ac..2cf43180 100644 --- a/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj +++ b/CosmosDb/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj @@ -10,6 +10,7 @@ + diff --git a/Microwave.Domain/Identities/StringIdentity.cs b/Microwave.Domain/Identities/StringIdentity.cs index 7ac423b5..97a95973 100644 --- a/Microwave.Domain/Identities/StringIdentity.cs +++ b/Microwave.Domain/Identities/StringIdentity.cs @@ -2,7 +2,7 @@ namespace Microwave.Domain.Identities { public class StringIdentity : Identity { - private StringIdentity(string id) + public StringIdentity(string id) { Id = id; } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj new file mode 100644 index 00000000..fb9d9915 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + Microwave.Persistence.CosmosDb + Microwave.Persistence.CosmosDb + + + + + + + + + + + diff --git a/Microwave.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Persistence.CosmosDb/CosmosDb.cs new file mode 100644 index 00000000..2452dd8e --- /dev/null +++ b/Microwave.Persistence.CosmosDb/CosmosDb.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microwave.Domain; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDb : ICosmosDb + { + + private readonly IMicrowaveConfiguration _configuration; + + public CosmosDb(IMicrowaveConfiguration configuration) + { + _configuration = configuration; + } + + public DocumentClient GetCosmosDbClient() + { + return new DocumentClient(new Uri(_configuration.DatabaseConfiguration.ConnectionString), + _configuration.DatabaseConfiguration.PrimaryKey); + } + } +} \ No newline at end of file diff --git a/Microwave.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Persistence.CosmosDb/CosmosDbClient.cs new file mode 100644 index 00000000..28d9fad8 --- /dev/null +++ b/Microwave.Persistence.CosmosDb/CosmosDbClient.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDbClient : ICosmosDbClient + { + private readonly DocumentClient _client; + private IEnumerable _domainEventTypes; + private const string DatabaseId = "Eventstore"; + private const string CollectionId = "DomainEvents"; + + public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) + { + _client = cosmosDb.GetCosmosDbClient(); + var type = typeof(IDomainEvent); + _domainEventTypes = assemblies + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p)); + + } + + public async Task InitializeCosmosDbAsync() + { + var database = await _client.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId }); + var collection = await _client.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri(DatabaseId), + new DocumentCollection { Id = CollectionId }); + if (database == null || collection == null) + { + throw new ArgumentException("Could not create Database or Collection with given CosmosDb Configuration Parameters!"); + } + } + + + public async Task CreateDomainEventAsync(IDomainEvent domainEvent) + { + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + await _client.CreateDocumentAsync(uri, domainEvent); + + } + + + public async Task> GetDomainEventsAsync(Identity identity) + { + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.DomainEvent.EntityId == identity) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = wrappedEvents.Select(e => JsonConvert.DeserializeObject(e.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == e.GetValue("DomainEventType").ToString()))).ToList(); + return new List(); + } + + + public async Task>> GetDomainEventsAsync(DateTimeOffset tickSince) + { + FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + var query = _client.CreateDocumentQuery(uri, queryOptions) + .Where(e => e.Created > tickSince); + return Result>.Ok(query.ToList()); + } + + public async Task CreateItemAsync(DomainEventWrapper domainEvent) + { + return await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), domainEvent); + } + + public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince) + { + FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + var query = _client.CreateDocumentQuery(uri, queryOptions) + .Where(e => e.DomainEventType == eventType); + return Result>.Ok(query.ToList()); + } + } + +} \ No newline at end of file diff --git a/Microwave.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Persistence.CosmosDb/CosmosDbEventRepository.cs new file mode 100644 index 00000000..d31e1c1b --- /dev/null +++ b/Microwave.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +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 Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; +using Microwave.EventStores.Ports; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDbEventRepository : IEventRepository + { + private readonly ICosmosDbClient _cosmosDbClient; + + + public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient) + { + _cosmosDbClient = cosmosDbClient; + } + + public async Task>> LoadEventsByEntity(Identity entityId, long @from = 0) + { + throw new NotImplementedException(); + //var uri = CreateUriForCosmosDb(entityId); + //var domainEvents = (await _client.ReadDocumentAsync>(uri)).Document; + //return new EventStoreResult>(domainEvents, domainEvents.Max(e => e.Version)); + } + + public async Task AppendAsync(IEnumerable domainEvents, long currentEntityVersion) + { + foreach (var domainEvent in domainEvents) + { + + await _cosmosDbClient.CreateDomainEventAsync(domainEvent); + } + + return Result.Ok(); + } + + public async Task>> LoadEvents(DateTimeOffset tickSince = default(DateTimeOffset)) + { + var result = await _cosmosDbClient.GetDomainEventsAsync(tickSince); + if (result.Value.Any()) + { + return Result>.Ok(result.Value); + } + else + { + return Result>.NotFound(null); + } + } + + public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince = default(DateTimeOffset)) + { + var result = _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); + return Result>.Ok(result.Result.Value); + } + + public async Task> GetLastEventOccuredOn(string domainEventType) + { + throw new NotImplementedException(); + //FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + //var uri = UriFactory.CreateDocumentCollectionUri(DatabaseName, CollectionId); + //var query = _client.CreateDocumentQuery(uri, queryOptions).ToList(); + //var latestEventTime = query.Max(e => e.Created); + + //return Result.Ok(latestEventTime); + } + + private Uri CreateUriForCosmosDb(Identity identity) + { + //return UriFactory.CreateDocumentUri(DatabaseName, CollectionId, identity.Id); + return null; + } + } +} diff --git a/Microwave.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Persistence.CosmosDb/ICosmosDb.cs new file mode 100644 index 00000000..764f5442 --- /dev/null +++ b/Microwave.Persistence.CosmosDb/ICosmosDb.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public interface ICosmosDb + { + DocumentClient GetCosmosDbClient(); + } +} \ No newline at end of file diff --git a/Microwave.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Persistence.CosmosDb/ICosmosDbClient.cs new file mode 100644 index 00000000..3dc3755f --- /dev/null +++ b/Microwave.Persistence.CosmosDb/ICosmosDbClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public interface ICosmosDbClient + { + Task CreateDomainEventAsync(IDomainEvent domainEvent); + Task>> GetDomainEventsAsync(DateTimeOffset tickSince); + Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); + Task CreateItemAsync(DomainEventWrapper domainEvent); + Task> GetDomainEventsAsync(Identity identity); + } +} \ No newline at end of file diff --git a/Microwave.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj b/Microwave.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj new file mode 100644 index 00000000..3eeae904 --- /dev/null +++ b/Microwave.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + Microwave.Persistence.CosmosDb + Microwave.Persistence.CosmosDb + + + + + + + + + + + diff --git a/Microwave.sln b/Microwave.sln index 8713c6a3..4dd1c353 100644 --- a/Microwave.sln +++ b/Microwave.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2026 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28822.285 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Domain", "Microwave.Domain\Microwave.Domain.csproj", "{9A1885D9-77BB-45D4-99E5-A9FC15F9B6A5}" EndProject @@ -19,39 +19,43 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave", "Microwave\Micr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UnitTests", "TestProjects\Microwave.UnitTests\Microwave.UnitTests.csproj", "{6DE4BD27-1E59-43F3-AF52-5925CEA1E9CA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Domain.UnitTests", "TestProjects\Microwave.Domain.UnitTests\Microwave.Domain.UnitTests.csproj", "{306D2634-E1A9-4C72-AEA5-AEB9FA26110F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Domain.UnitTests", "TestProjects\Microwave.Domain.UnitTests\Microwave.Domain.UnitTests.csproj", "{306D2634-E1A9-4C72-AEA5-AEB9FA26110F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.WebApi.UnitTests", "TestProjects\Microwave.WebApi.UnitTests\Microwave.WebApi.UnitTests.csproj", "{3622487E-A894-49FD-A8EF-5D11DAA01B62}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.WebApi.UnitTests", "TestProjects\Microwave.WebApi.UnitTests\Microwave.WebApi.UnitTests.csproj", "{3622487E-A894-49FD-A8EF-5D11DAA01B62}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.UnitTests.PublishedEventsDll", "TestProjects\Microwave.UnitTests.PublishedEventsDll\Microwave.UnitTests.PublishedEventsDll.csproj", "{5A17AEC8-F492-48F4-8C6F-4063489DA58F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UnitTests.PublishedEventsDll", "TestProjects\Microwave.UnitTests.PublishedEventsDll\Microwave.UnitTests.PublishedEventsDll.csproj", "{5A17AEC8-F492-48F4-8C6F-4063489DA58F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.MongoDb", "Microwave.Persistence.MongoDb\Microwave.Persistence.MongoDb.csproj", "{6394F395-66C1-483D-B4BC-8B19007F7785}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.MongoDb", "Microwave.Persistence.MongoDb\Microwave.Persistence.MongoDb.csproj", "{6394F395-66C1-483D-B4BC-8B19007F7785}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.UnitTests", "TestProjects\Microwave.Persistence.UnitTests\Microwave.Persistence.UnitTests.csproj", "{52EC4F8C-6145-4E15-8FE3-388431BEF5FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.UnitTests", "TestProjects\Microwave.Persistence.UnitTests\Microwave.Persistence.UnitTests.csproj", "{52EC4F8C-6145-4E15-8FE3-388431BEF5FA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Discovery", "Microwave.Discovery\Microwave.Discovery.csproj", "{80A57BBD-8D51-4638-9D74-E63785754CC7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Discovery", "Microwave.Discovery\Microwave.Discovery.csproj", "{80A57BBD-8D51-4638-9D74-E63785754CC7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestServices", "TestServices", "{4DA39CBA-8A2D-4C97-9B79-B118325D7D04}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.UI.UnitTests", "TestProjects\Microwave.UI.UnitTests\Microwave.UI.UnitTests.csproj", "{10F2D68D-5EE4-4DC4-81AB-DAA30A6BF951}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UI.UnitTests", "TestProjects\Microwave.UI.UnitTests\Microwave.UI.UnitTests.csproj", "{10F2D68D-5EE4-4DC4-81AB-DAA30A6BF951}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.UI", "Microwave.UI\Microwave.UI.csproj", "{446A53A9-B95A-4B3E-B067-6FBC126173E4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.UI", "Microwave.UI\Microwave.UI.csproj", "{446A53A9-B95A-4B3E-B067-6FBC126173E4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.MongoDb.UnitTestsSetup", "TestProjects\Microwave.Persistence.MongoDb.UnitTestsSetup\Microwave.Persistence.MongoDb.UnitTestsSetup.csproj", "{A7A69C69-F8D1-4866-A812-96FE7548F5D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.MongoDb.UnitTestsSetup", "TestProjects\Microwave.Persistence.MongoDb.UnitTestsSetup\Microwave.Persistence.MongoDb.UnitTestsSetup.csproj", "{A7A69C69-F8D1-4866-A812-96FE7548F5D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Persistence.UnitTestSetupPorts", "TestProjects\Microwave.Persistence.UnitTestSetupPorts\Microwave.Persistence.UnitTestSetupPorts.csproj", "{39C94AEB-1532-4826-BD46-5ADBFDC6BA6B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.UnitTestSetupPorts", "TestProjects\Microwave.Persistence.UnitTestSetupPorts\Microwave.Persistence.UnitTestSetupPorts.csproj", "{39C94AEB-1532-4826-BD46-5ADBFDC6BA6B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReadService1", "TestServices\ReadService1\ReadService1.csproj", "{15919721-12DD-4082-AE95-B3B26981433D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadService1", "TestServices\ReadService1\ReadService1.csproj", "{15919721-12DD-4082-AE95-B3B26981433D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerConfig", "TestServices\ServerConfig\ServerConfig.csproj", "{F759B847-0E47-4C6E-A26F-9FF4BAE397BA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerConfig", "TestServices\ServerConfig\ServerConfig.csproj", "{F759B847-0E47-4C6E-A26F-9FF4BAE397BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WriteService1", "TestServices\WriteService1\WriteService1.csproj", "{B8B78E4F-59D5-45AB-962E-2898A4368841}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WriteService1", "TestServices\WriteService1\WriteService1.csproj", "{B8B78E4F-59D5-45AB-962E-2898A4368841}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WriteService2", "TestServices\WriteService2\WriteService2.csproj", "{73988DBC-1385-4332-9733-DE74F6656D53}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WriteService2", "TestServices\WriteService2\WriteService2.csproj", "{73988DBC-1385-4332-9733-DE74F6656D53}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microwave.Discovery.UnitTests", "TestProjects\Microwave.Discovery.UnitTests\Microwave.Discovery.UnitTests.csproj", "{A12C678F-D971-4FAC-8E82-9F307770CD92}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Discovery.UnitTests", "TestProjects\Microwave.Discovery.UnitTests\Microwave.Discovery.UnitTests.csproj", "{A12C678F-D971-4FAC-8E82-9F307770CD92}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.CosmosDb.UnitTests", "TestProjects\Microwave.Persistence.CosmosDb.UnitTests\Microwave.Persistence.CosmosDb.UnitTests.csproj", "{E33DFA20-9B67-487D-BB1D-AAC87067F2EE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.CosmosDb", "Microwave.Eventstores.Persistence.CosmosDb\Microwave.Persistence.CosmosDb.csproj", "{27F80D87-E492-4574-8BEB-67036099BAA1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -151,28 +155,37 @@ Global {A12C678F-D971-4FAC-8E82-9F307770CD92}.Debug|Any CPU.Build.0 = Debug|Any CPU {A12C678F-D971-4FAC-8E82-9F307770CD92}.Release|Any CPU.ActiveCfg = Release|Any CPU {A12C678F-D971-4FAC-8E82-9F307770CD92}.Release|Any CPU.Build.0 = Release|Any CPU + {E33DFA20-9B67-487D-BB1D-AAC87067F2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E33DFA20-9B67-487D-BB1D-AAC87067F2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E33DFA20-9B67-487D-BB1D-AAC87067F2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E33DFA20-9B67-487D-BB1D-AAC87067F2EE}.Release|Any CPU.Build.0 = Release|Any CPU + {27F80D87-E492-4574-8BEB-67036099BAA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27F80D87-E492-4574-8BEB-67036099BAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27F80D87-E492-4574-8BEB-67036099BAA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27F80D87-E492-4574-8BEB-67036099BAA1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {10681780-EDC2-4AAC-8551-A22B38DC45CE} - EndGlobalSection GlobalSection(NestedProjects) = preSolution - {3622487E-A894-49FD-A8EF-5D11DAA01B62} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} - {5A17AEC8-F492-48F4-8C6F-4063489DA58F} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} - {6DE4BD27-1E59-43F3-AF52-5925CEA1E9CA} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} {C73734E8-928B-4C41-9054-F294D02219F1} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {B3AA5BEA-6586-4A39-9541-1812F456FB0B} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {6DE4BD27-1E59-43F3-AF52-5925CEA1E9CA} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} {306D2634-E1A9-4C72-AEA5-AEB9FA26110F} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} - {A7A69C69-F8D1-4866-A812-96FE7548F5D8} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {3622487E-A894-49FD-A8EF-5D11DAA01B62} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {5A17AEC8-F492-48F4-8C6F-4063489DA58F} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} {52EC4F8C-6145-4E15-8FE3-388431BEF5FA} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} - {39C94AEB-1532-4826-BD46-5ADBFDC6BA6B} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} - {B3AA5BEA-6586-4A39-9541-1812F456FB0B} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} {10F2D68D-5EE4-4DC4-81AB-DAA30A6BF951} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {A7A69C69-F8D1-4866-A812-96FE7548F5D8} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {39C94AEB-1532-4826-BD46-5ADBFDC6BA6B} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} {15919721-12DD-4082-AE95-B3B26981433D} = {4DA39CBA-8A2D-4C97-9B79-B118325D7D04} {F759B847-0E47-4C6E-A26F-9FF4BAE397BA} = {4DA39CBA-8A2D-4C97-9B79-B118325D7D04} {B8B78E4F-59D5-45AB-962E-2898A4368841} = {4DA39CBA-8A2D-4C97-9B79-B118325D7D04} {73988DBC-1385-4332-9733-DE74F6656D53} = {4DA39CBA-8A2D-4C97-9B79-B118325D7D04} {A12C678F-D971-4FAC-8E82-9F307770CD92} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + {E33DFA20-9B67-487D-BB1D-AAC87067F2EE} = {2DFC8EF1-71B5-4F61-9E1D-B06DAA1BC199} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {10681780-EDC2-4AAC-8551-A22B38DC45CE} EndGlobalSection EndGlobal diff --git a/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs similarity index 100% rename from Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs rename to TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs diff --git a/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj similarity index 63% rename from Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj rename to TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj index 34dc4ef5..d5587ffe 100644 --- a/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -13,8 +13,7 @@ - - + From cbe812ddc2ba334c46d387ee551a730e891af82a Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Wed, 24 Jul 2019 15:28:44 +0200 Subject: [PATCH 3/9] Added classes --- .../CosmosDb.cs | 24 +++++ .../CosmosDbClient.cs | 99 +++++++++++++++++++ .../CosmosDbEventRepository.cs | 82 +++++++++++++++ .../ICosmosDb.cs | 10 ++ .../ICosmosDbClient.cs | 20 ++++ .../CosmosDbEventRepositoryTests.cs | 2 +- 6 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs new file mode 100644 index 00000000..2452dd8e --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microwave.Domain; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDb : ICosmosDb + { + + private readonly IMicrowaveConfiguration _configuration; + + public CosmosDb(IMicrowaveConfiguration configuration) + { + _configuration = configuration; + } + + public DocumentClient GetCosmosDbClient() + { + return new DocumentClient(new Uri(_configuration.DatabaseConfiguration.ConnectionString), + _configuration.DatabaseConfiguration.PrimaryKey); + } + } +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs new file mode 100644 index 00000000..28d9fad8 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDbClient : ICosmosDbClient + { + private readonly DocumentClient _client; + private IEnumerable _domainEventTypes; + private const string DatabaseId = "Eventstore"; + private const string CollectionId = "DomainEvents"; + + public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) + { + _client = cosmosDb.GetCosmosDbClient(); + var type = typeof(IDomainEvent); + _domainEventTypes = assemblies + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p)); + + } + + public async Task InitializeCosmosDbAsync() + { + var database = await _client.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId }); + var collection = await _client.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri(DatabaseId), + new DocumentCollection { Id = CollectionId }); + if (database == null || collection == null) + { + throw new ArgumentException("Could not create Database or Collection with given CosmosDb Configuration Parameters!"); + } + } + + + public async Task CreateDomainEventAsync(IDomainEvent domainEvent) + { + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + await _client.CreateDocumentAsync(uri, domainEvent); + + } + + + public async Task> GetDomainEventsAsync(Identity identity) + { + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.DomainEvent.EntityId == identity) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = wrappedEvents.Select(e => JsonConvert.DeserializeObject(e.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == e.GetValue("DomainEventType").ToString()))).ToList(); + return new List(); + } + + + public async Task>> GetDomainEventsAsync(DateTimeOffset tickSince) + { + FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + var query = _client.CreateDocumentQuery(uri, queryOptions) + .Where(e => e.Created > tickSince); + return Result>.Ok(query.ToList()); + } + + public async Task CreateItemAsync(DomainEventWrapper domainEvent) + { + return await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), domainEvent); + } + + public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince) + { + FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); + var query = _client.CreateDocumentQuery(uri, queryOptions) + .Where(e => e.DomainEventType == eventType); + return Result>.Ok(query.ToList()); + } + } + +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs new file mode 100644 index 00000000..d31e1c1b --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +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 Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; +using Microwave.EventStores.Ports; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public class CosmosDbEventRepository : IEventRepository + { + private readonly ICosmosDbClient _cosmosDbClient; + + + public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient) + { + _cosmosDbClient = cosmosDbClient; + } + + public async Task>> LoadEventsByEntity(Identity entityId, long @from = 0) + { + throw new NotImplementedException(); + //var uri = CreateUriForCosmosDb(entityId); + //var domainEvents = (await _client.ReadDocumentAsync>(uri)).Document; + //return new EventStoreResult>(domainEvents, domainEvents.Max(e => e.Version)); + } + + public async Task AppendAsync(IEnumerable domainEvents, long currentEntityVersion) + { + foreach (var domainEvent in domainEvents) + { + + await _cosmosDbClient.CreateDomainEventAsync(domainEvent); + } + + return Result.Ok(); + } + + public async Task>> LoadEvents(DateTimeOffset tickSince = default(DateTimeOffset)) + { + var result = await _cosmosDbClient.GetDomainEventsAsync(tickSince); + if (result.Value.Any()) + { + return Result>.Ok(result.Value); + } + else + { + return Result>.NotFound(null); + } + } + + public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince = default(DateTimeOffset)) + { + var result = _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); + return Result>.Ok(result.Result.Value); + } + + public async Task> GetLastEventOccuredOn(string domainEventType) + { + throw new NotImplementedException(); + //FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; + //var uri = UriFactory.CreateDocumentCollectionUri(DatabaseName, CollectionId); + //var query = _client.CreateDocumentQuery(uri, queryOptions).ToList(); + //var latestEventTime = query.Max(e => e.Created); + + //return Result.Ok(latestEventTime); + } + + private Uri CreateUriForCosmosDb(Identity identity) + { + //return UriFactory.CreateDocumentUri(DatabaseName, CollectionId, identity.Id); + return null; + } + } +} diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs new file mode 100644 index 00000000..764f5442 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public interface ICosmosDb + { + DocumentClient GetCosmosDbClient(); + } +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs new file mode 100644 index 00000000..3dc3755f --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microwave.Domain.EventSourcing; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.EventStores; + +namespace Microwave.Eventstores.Persistence.CosmosDb +{ + public interface ICosmosDbClient + { + Task CreateDomainEventAsync(IDomainEvent domainEvent); + Task>> GetDomainEventsAsync(DateTimeOffset tickSince); + Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); + Task CreateItemAsync(DomainEventWrapper domainEvent); + Task> GetDomainEventsAsync(Identity identity); + } +} \ No newline at end of file diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs index 255c487a..c59bba59 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs @@ -6,7 +6,7 @@ using Microwave.Domain.EventSourcing; using Microwave.Domain.Identities; using Microwave.EventStores; -using Microwave.Eventstores.Persistence.CosmosDb; +using Microwave.Persistence.CosmosDb; namespace Microwave.Persistence.CosmosDb.UnitTests { From bfe91615e36d70069ab20bc62fcd2caad7ad99a3 Mon Sep 17 00:00:00 2001 From: Simon Heiss Date: Wed, 24 Jul 2019 23:24:18 +0200 Subject: [PATCH 4/9] fixed build and shit --- .../CosmosDb.cs | 20 ++++------ .../CosmosDbClient.cs | 3 +- .../CosmosDbEventRepository.cs | 6 +-- .../ICosmosDb.cs | 5 +-- .../ICosmosDbClient.cs | 2 +- .../Microwave.Persistence.CosmosDb.csproj | 7 ++-- .../CosmosDbEventRepositoryTests.cs | 40 ++++++++----------- .../IntegrationTests.cs | 37 +++++++++++++++++ ...wave.Persistence.CosmosDb.UnitTests.csproj | 18 ++++++--- 9 files changed, 82 insertions(+), 56 deletions(-) create mode 100644 TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs index 2452dd8e..704de5b9 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -1,24 +1,18 @@ using System; -using System.Threading.Tasks; +using System.Security; using Microsoft.Azure.Documents.Client; -using Microwave.Domain; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public class CosmosDb : ICosmosDb { - - private readonly IMicrowaveConfiguration _configuration; - - public CosmosDb(IMicrowaveConfiguration configuration) - { - _configuration = configuration; - } - public DocumentClient GetCosmosDbClient() { - return new DocumentClient(new Uri(_configuration.DatabaseConfiguration.ConnectionString), - _configuration.DatabaseConfiguration.PrimaryKey); + return new DocumentClient(CosmosDbLocation, PrimaryKey); } + + public SecureString PrimaryKey { get; set; } + + public Uri CosmosDbLocation { get; set; } } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs index 28d9fad8..c08d9627 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.Documents; @@ -14,7 +13,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public class CosmosDbClient : ICosmosDbClient { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs index d31e1c1b..a1143760 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -1,18 +1,14 @@ using System; -using System.Collections; 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 Microwave.Domain.EventSourcing; using Microwave.Domain.Identities; using Microwave.Domain.Results; using Microwave.EventStores; using Microwave.EventStores.Ports; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public class CosmosDbEventRepository : IEventRepository { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs index 764f5442..8380de81 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -1,7 +1,6 @@ -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Client; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public interface ICosmosDb { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs index 3dc3755f..cab29d78 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs @@ -7,7 +7,7 @@ using Microwave.Domain.Results; using Microwave.EventStores; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public interface ICosmosDbClient { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj index fb9d9915..338e07f5 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj +++ b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj @@ -1,9 +1,8 @@  - netcoreapp2.1 - Microwave.Persistence.CosmosDb - Microwave.Persistence.CosmosDb + netstandard2.0 + full @@ -11,7 +10,7 @@ - + diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs index c59bba59..6bc79a13 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Security; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microwave.Domain.EventSourcing; @@ -11,20 +12,19 @@ namespace Microwave.Persistence.CosmosDb.UnitTests { [TestClass] - public class CosmosDbEventRepositoryTests + public class CosmosDbEventRepositoryTests : IntegrationTests { [TestMethod] + [Ignore] public async Task DomainEventIsAppendedCorrectly() { - var databaseConfig = new DatabaseConfiguration(); - databaseConfig.ConnectionString = "https://spoppinga.documents.azure.com:443/"; - databaseConfig.PrimaryKey = - "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA=="; - var config = new MicrowaveConfiguration(); - config.DatabaseConfiguration = databaseConfig; - - var cosmosDatabse = new Eventstores.Persistence.CosmosDb.CosmosDb(config); - var cosmosDbClient = new CosmosDbClient(cosmosDatabse, new List { Assembly.GetAssembly(typeof(UserCreatedEvent)) }); + var cosmosDbClient = new CosmosDbClient( + Database, + new List + { + Assembly.GetAssembly(typeof(UserCreatedEvent)) + }); + await cosmosDbClient.InitializeCosmosDbAsync(); var eventRepository = new CosmosDbEventRepository(cosmosDbClient); @@ -39,25 +39,21 @@ await cosmosDbClient.CreateItemAsync(new DomainEventWrapper } [TestMethod] + [Ignore] public async Task DomainEventsAreGettedCorrectly() { - var databaseConfig = new DatabaseConfiguration(); - databaseConfig.ConnectionString = "https://spoppinga.documents.azure.com:443/"; - databaseConfig.PrimaryKey = - "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA=="; - var config = new MicrowaveConfiguration(); - config.DatabaseConfiguration = databaseConfig; + var cosmosDbClient = new CosmosDbClient( + Database, + new List + { + Assembly.GetAssembly(typeof(UserCreatedEvent)) + }); - var cosmosDatabse = new Eventstores.Persistence.CosmosDb.CosmosDb(config); - var cosmosDbClient = new CosmosDbClient(cosmosDatabse , new List{Assembly.GetAssembly(typeof(UserCreatedEvent))}); await cosmosDbClient.InitializeCosmosDbAsync(); var eventRepository = new CosmosDbEventRepository(cosmosDbClient); var result = await cosmosDbClient.GetDomainEventsAsync(Identity.Create(Guid.Parse("19cf121a-44cd-40bb-a3b5-ea9deb11d4f5"))); - } - - } public class UserCreatedEvent : IDomainEvent @@ -65,8 +61,6 @@ public class UserCreatedEvent : IDomainEvent public Identity EntityId { get; } public string Username { get; } - - public UserCreatedEvent(GuidIdentity entityId, string name) { EntityId = entityId; diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs new file mode 100644 index 00000000..32e69950 --- /dev/null +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Security; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microwave.Persistence.CosmosDb.UnitTests +{ + public class IntegrationTests + { + protected CosmosDb Database; + + public SecureString PrimaryKey + { + get + { + var secure = new SecureString(); + foreach (char c in "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA==") + { + secure.AppendChar(c); + } + + return secure; + } + } + + public Uri CosmosDbLocation => new Uri("https://spoppinga.documents.azure.com:443/"); + + [TestInitialize] + public void SetupMongoDb() + { + Database = new CosmosDb + { + PrimaryKey = PrimaryKey, + CosmosDbLocation = CosmosDbLocation + }; + } + } +} \ No newline at end of file diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj index d5587ffe..79ed01fb 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj @@ -1,19 +1,27 @@  - netcoreapp2.1 - + netcoreapp2.2 false + full - - - + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + From 6db1dacdafe85c1bd0e4e6a4c72eb2262ea7d0fc Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Thu, 25 Jul 2019 15:12:22 +0200 Subject: [PATCH 5/9] Added Persistence Layer and Stuff --- .../CosmosDb.cs | 12 +++---- .../CosmosDbClient.cs | 5 ++- .../CosmosDbEventRepository.cs | 19 +--------- .../CosmosDbPersistenceLayer.cs | 35 +++++++++++++++++++ .../ICosmosDb.cs | 5 ++- .../ICosmosDbClient.cs | 2 +- .../Microwave.Persistence.CosmosDb.csproj | 1 + .../CosmosDbEventRepositoryTests.cs | 16 ++++----- ...wave.Persistence.CosmosDb.UnitTests.csproj | 1 + 9 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs index 2452dd8e..1e37d0bb 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -1,24 +1,22 @@ using System; -using System.Threading.Tasks; using Microsoft.Azure.Documents.Client; -using Microwave.Domain; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public class CosmosDb : ICosmosDb { - private readonly IMicrowaveConfiguration _configuration; + private readonly MicrowaveCosmosDb _configuration; - public CosmosDb(IMicrowaveConfiguration configuration) + public CosmosDb(MicrowaveCosmosDb configuration) { _configuration = configuration; } public DocumentClient GetCosmosDbClient() { - return new DocumentClient(new Uri(_configuration.DatabaseConfiguration.ConnectionString), - _configuration.DatabaseConfiguration.PrimaryKey); + return new DocumentClient(new Uri(_configuration.DatabaseUrl), + _configuration.PrimaryKey); } } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs index 28d9fad8..51c74c88 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using Microsoft.Azure.Documents; @@ -14,7 +13,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public class CosmosDbClient : ICosmosDbClient { @@ -68,7 +67,7 @@ public async Task> GetDomainEventsAsync(Identity ident } var result = wrappedEvents.Select(e => JsonConvert.DeserializeObject(e.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == e.GetValue("DomainEventType").ToString()))).ToList(); - return new List(); + return result.Select(e => (IDomainEvent) e); } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs index d31e1c1b..b409fba2 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -1,18 +1,13 @@ using System; -using System.Collections; 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 Microwave.Domain.EventSourcing; using Microwave.Domain.Identities; using Microwave.Domain.Results; using Microwave.EventStores; -using Microwave.EventStores.Ports; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public class CosmosDbEventRepository : IEventRepository { @@ -65,18 +60,6 @@ public async Task AppendAsync(IEnumerable domainEvents, lo public async Task> GetLastEventOccuredOn(string domainEventType) { throw new NotImplementedException(); - //FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; - //var uri = UriFactory.CreateDocumentCollectionUri(DatabaseName, CollectionId); - //var query = _client.CreateDocumentQuery(uri, queryOptions).ToList(); - //var latestEventTime = query.Max(e => e.Created); - - //return Result.Ok(latestEventTime); - } - - private Uri CreateUriForCosmosDb(Identity identity) - { - //return UriFactory.CreateDocumentUri(DatabaseName, CollectionId, identity.Id); - return null; } } } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs new file mode 100644 index 00000000..243904c0 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microwave.EventStores; + +namespace Microwave.Persistence.CosmosDb +{ + public class CosmosDbPersistenceLayer : IPersistenceLayer + { + public MicrowaveCosmosDb MicrowaveCosmosDb { get; set; } = new MicrowaveCosmosDb(); + + public IServiceCollection AddPersistenceLayer(IServiceCollection services, IEnumerable assemblies) + { + services.AddTransient(); + //services.AddTransient(); + + //services.AddTransient(); + //services.AddTransient(); + //services.AddSingleton(MicrowaveCosmosDb); + //services.AddSingleton(new CosmosDbEventLocationCache()); + + services.AddTransient(); + //services.AddSingleton(); + //services.AddTransient(); + + return services; + } + } + + public class MicrowaveCosmosDb + { + public string DatabaseUrl { get; set; } + public string PrimaryKey { get; set; } + } +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs index 764f5442..8380de81 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -1,7 +1,6 @@ -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Client; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public interface ICosmosDb { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs index 3dc3755f..cab29d78 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs @@ -7,7 +7,7 @@ using Microwave.Domain.Results; using Microwave.EventStores; -namespace Microwave.Eventstores.Persistence.CosmosDb +namespace Microwave.Persistence.CosmosDb { public interface ICosmosDbClient { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj index fb9d9915..4140d714 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj +++ b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj @@ -12,6 +12,7 @@ + diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs index c59bba59..0865cfa7 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs @@ -16,14 +16,12 @@ public class CosmosDbEventRepositoryTests [TestMethod] public async Task DomainEventIsAppendedCorrectly() { - var databaseConfig = new DatabaseConfiguration(); - databaseConfig.ConnectionString = "https://spoppinga.documents.azure.com:443/"; + var databaseConfig = new MicrowaveCosmosDb(); + databaseConfig.DatabaseUrl = "https://spoppinga.documents.azure.com:443/"; databaseConfig.PrimaryKey = "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA=="; - var config = new MicrowaveConfiguration(); - config.DatabaseConfiguration = databaseConfig; - var cosmosDatabse = new Eventstores.Persistence.CosmosDb.CosmosDb(config); + var cosmosDatabse = new CosmosDb(databaseConfig); var cosmosDbClient = new CosmosDbClient(cosmosDatabse, new List { Assembly.GetAssembly(typeof(UserCreatedEvent)) }); await cosmosDbClient.InitializeCosmosDbAsync(); @@ -41,14 +39,12 @@ await cosmosDbClient.CreateItemAsync(new DomainEventWrapper [TestMethod] public async Task DomainEventsAreGettedCorrectly() { - var databaseConfig = new DatabaseConfiguration(); - databaseConfig.ConnectionString = "https://spoppinga.documents.azure.com:443/"; + var databaseConfig = new MicrowaveCosmosDb(); + databaseConfig.DatabaseUrl = "https://spoppinga.documents.azure.com:443/"; databaseConfig.PrimaryKey = "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA=="; - var config = new MicrowaveConfiguration(); - config.DatabaseConfiguration = databaseConfig; - var cosmosDatabse = new Eventstores.Persistence.CosmosDb.CosmosDb(config); + var cosmosDatabse = new CosmosDb(databaseConfig); var cosmosDbClient = new CosmosDbClient(cosmosDatabse , new List{Assembly.GetAssembly(typeof(UserCreatedEvent))}); await cosmosDbClient.InitializeCosmosDbAsync(); diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj index d5587ffe..0bbf2a0d 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/Microwave.Persistence.CosmosDb.UnitTests.csproj @@ -13,6 +13,7 @@ + From 5c903e3b51dc078bd7f5c0bd2c535fb80bfd0e11 Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Fri, 26 Jul 2019 15:31:50 +0200 Subject: [PATCH 6/9] Added SnapShotRepo and UnitTests are now Green everyTime + Concurrency --- .../CosmosDb.cs | 61 ++++++- .../CosmosDbClient.cs | 149 +++++++++++++----- .../CosmosDbEventRepository.cs | 14 +- .../CosmosDbPersistenceLayer.cs | 4 + .../CosmosDbSnapshotRepository.cs | 30 ++++ .../ICosmosDb.cs | 4 + .../ICosmosDbClient.cs | 12 +- .../Microwave.Persistence.CosmosDb.csproj | 1 + .../CosmosDbEventRepositoryTests.cs | 105 +++++++++++- 9 files changed, 327 insertions(+), 53 deletions(-) create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs index 704de5b9..d83b4283 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -1,18 +1,73 @@ using System; +using System.Collections.ObjectModel; using System.Security; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; namespace Microwave.Persistence.CosmosDb { public class CosmosDb : ICosmosDb { + public SecureString PrimaryKey { get; set; } + + public Uri CosmosDbLocation { get; set; } + public DocumentClient GetCosmosDbClient() { - return new DocumentClient(CosmosDbLocation, PrimaryKey); + var client = new DocumentClient(CosmosDbLocation, PrimaryKey); + return client; } - public SecureString PrimaryKey { get; set; } + public string DatabaseId => "Eventstore"; + public string EventsCollectionId => "DomainEvents"; + public string SnapshotsCollectionId => "Snapshots"; - public Uri CosmosDbLocation { get; set; } + public async Task InitializeCosmosDb() + { + var client = new DocumentClient(CosmosDbLocation, PrimaryKey); + + var domainEventsCollection = new DocumentCollection + { + Id = EventsCollectionId + }; + domainEventsCollection.UniqueKeyPolicy = new UniqueKeyPolicy + { + UniqueKeys = + new Collection + { + new UniqueKey {Paths = new Collection {"/Version", "/DomainEvent/EntityId/Id"}} + } + }; + + var snapShotCollection = new DocumentCollection + { + Id = SnapshotsCollectionId + }; + snapShotCollection.UniqueKeyPolicy = new UniqueKeyPolicy + { + UniqueKeys = + new Collection + { + new UniqueKey {Paths = new Collection {"/Version", "/Id/Id"}} + } + }; + + try + { + await client.CreateDatabaseIfNotExistsAsync(new Database {Id = DatabaseId}); + await client.CreateDocumentCollectionIfNotExistsAsync( + UriFactory.CreateDatabaseUri(DatabaseId), + domainEventsCollection); + await client.CreateDocumentCollectionIfNotExistsAsync( + UriFactory.CreateDatabaseUri(DatabaseId), + snapShotCollection); + } + catch (DocumentClientException e) + { + throw new ArgumentException( + "Could not create Database or Collection with given CosmosDb Configuration Parameters!"); + } + } } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs index 51c74c88..cc0cdf1e 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs @@ -10,6 +10,7 @@ using Microwave.Domain.Identities; using Microwave.Domain.Results; using Microwave.EventStores; +using Microwave.EventStores.Ports; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -17,13 +18,14 @@ namespace Microwave.Persistence.CosmosDb { public class CosmosDbClient : ICosmosDbClient { + private readonly ICosmosDb _cosmosDb; private readonly DocumentClient _client; private IEnumerable _domainEventTypes; - private const string DatabaseId = "Eventstore"; - private const string CollectionId = "DomainEvents"; + public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) { + _cosmosDb = cosmosDb; _client = cosmosDb.GetCosmosDbClient(); var type = typeof(IDomainEvent); _domainEventTypes = assemblies @@ -32,32 +34,80 @@ public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) } - public async Task InitializeCosmosDbAsync() - { - var database = await _client.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId }); - var collection = await _client.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri(DatabaseId), - new DocumentCollection { Id = CollectionId }); - if (database == null || collection == null) + public async Task> GetDomainEventsAsync(Identity identity) + { + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.DomainEvent.EntityId == identity) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) { - throw new ArgumentException("Could not create Database or Collection with given CosmosDb Configuration Parameters!"); + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), + Version = (long)wrappedEvent.GetValue("Version") + }); } + + return result; } + public async Task> LoadSnapshotAsync(Identity entityId) + { + var query = _client.CreateDocumentQuery>( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.SnapshotsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Id == entityId) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + var allSnapshots = new List>(); + foreach (var wrappedEvent in wrappedEvents) + { + var entity = JsonConvert.DeserializeObject(wrappedEvent.GetValue("Entity").ToString()); + var version = (long) wrappedEvent.GetValue("Version"); + Guid.TryParse(wrappedEvent.GetValue("id").ToString(), out var guid); + Identity identity = null; + if (guid != Guid.Empty) + { + identity = Identity.Create(guid); + } + else + { + identity = Identity.Create(wrappedEvent.GetValue("id").ToString()); + } + allSnapshots.Add(new SnapShotWrapper(entity, identity, version)); + } + var result = allSnapshots.Single(x => x.Version == allSnapshots.Max(s => s.Version)); + return new SnapShotResult(result.Entity, result.Version); + } - public async Task CreateDomainEventAsync(IDomainEvent domainEvent) + public async Task SaveSnapshotAsync(SnapShotWrapper snapShot) { - var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); - await _client.CreateDocumentAsync(uri, domainEvent); - + await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.SnapshotsCollectionId), snapShot); } - public async Task> GetDomainEventsAsync(Identity identity) - { + public async Task> GetDomainEventsAsync(DateTimeOffset tickSince) + { var query = _client.CreateDocumentQuery( - UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), new FeedOptions { MaxItemCount = -1 }) - .Where(e => e.DomainEvent.EntityId == identity) + .Where(e => e.Created >= tickSince) .AsDocumentQuery(); var wrappedEvents = new List(); @@ -65,33 +115,60 @@ public async Task> GetDomainEventsAsync(Identity ident { wrappedEvents.AddRange(await query.ExecuteNextAsync()); } + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) + { + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset) wrappedEvent.GetValue("Created"), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), + Version = (long) wrappedEvent.GetValue("Version") + }); + } - var result = wrappedEvents.Select(e => JsonConvert.DeserializeObject(e.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == e.GetValue("DomainEventType").ToString()))).ToList(); - return result.Select(e => (IDomainEvent) e); + return result; } - - public async Task>> GetDomainEventsAsync(DateTimeOffset tickSince) + public async Task CreateItemAsync(DomainEventWrapper domainEvent) { - FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; - var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); - var query = _client.CreateDocumentQuery(uri, queryOptions) - .Where(e => e.Created > tickSince); - return Result>.Ok(query.ToList()); + try + { + await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), domainEvent); + } + catch (DocumentClientException e) + { + var actualVersion = (await GetDomainEventsAsync(domainEvent.DomainEvent.EntityId)).Max(x => x.Version); + return Result.ConcurrencyResult(domainEvent.Version, actualVersion); + } + return Result.Ok(); } - public async Task CreateItemAsync(DomainEventWrapper domainEvent) + public async Task> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince) { - return await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), domainEvent); - } + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Created >= tickSince && e.DomainEventType == eventType) + .AsDocumentQuery(); - public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince) - { - FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 }; - var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); - var query = _client.CreateDocumentQuery(uri, queryOptions) - .Where(e => e.DomainEventType == eventType); - return Result>.Ok(query.ToList()); + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) + { + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), + Version = (long)wrappedEvent.GetValue("Version") + }); + } + + return result; } } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs index b409fba2..dc2f0232 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -6,6 +6,7 @@ using Microwave.Domain.Identities; using Microwave.Domain.Results; using Microwave.EventStores; +using Microwave.EventStores.Ports; namespace Microwave.Persistence.CosmosDb { @@ -32,7 +33,12 @@ public async Task AppendAsync(IEnumerable domainEvents, lo foreach (var domainEvent in domainEvents) { - await _cosmosDbClient.CreateDomainEventAsync(domainEvent); + await _cosmosDbClient.CreateItemAsync(new DomainEventWrapper + { + DomainEvent = domainEvent, + Created = DateTimeOffset.Now, + Version = currentEntityVersion + }); } return Result.Ok(); @@ -41,9 +47,9 @@ public async Task AppendAsync(IEnumerable domainEvents, lo public async Task>> LoadEvents(DateTimeOffset tickSince = default(DateTimeOffset)) { var result = await _cosmosDbClient.GetDomainEventsAsync(tickSince); - if (result.Value.Any()) + if (result.Any()) { - return Result>.Ok(result.Value); + return Result>.Ok(result); } else { @@ -54,7 +60,7 @@ public async Task AppendAsync(IEnumerable domainEvents, lo public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince = default(DateTimeOffset)) { var result = _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); - return Result>.Ok(result.Result.Value); + return Result>.Ok(result.Result); } public async Task> GetLastEventOccuredOn(string domainEventType) diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs index 243904c0..6c712eba 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs @@ -2,6 +2,7 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microwave.EventStores; +using Microwave.EventStores.Ports; namespace Microwave.Persistence.CosmosDb { @@ -12,6 +13,9 @@ public class CosmosDbPersistenceLayer : IPersistenceLayer public IServiceCollection AddPersistenceLayer(IServiceCollection services, IEnumerable assemblies) { services.AddTransient(); + var cosmosDb = new CosmosDb(); + cosmosDb.InitializeCosmosDb().Wait(); + //services.AddTransient(); //services.AddTransient(); diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs new file mode 100644 index 00000000..e63e2701 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microwave.Domain.Identities; +using Microwave.EventStores.Ports; + +namespace Microwave.Persistence.CosmosDb +{ + public class CosmosDbSnapshotRepository : ISnapShotRepository + { + private readonly ICosmosDbClient _cosmosDbClient; + + public CosmosDbSnapshotRepository(ICosmosDbClient cosmosDbClient) + { + _cosmosDbClient = cosmosDbClient; + } + + public async Task> LoadSnapShot(Identity entityId) where T : new() + { + var result = _cosmosDbClient.LoadSnapshotAsync(entityId); + return result.Result; + } + + public Task SaveSnapShot(SnapShotWrapper snapShot) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs index 8380de81..62e0caa6 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -4,6 +4,10 @@ namespace Microwave.Persistence.CosmosDb { public interface ICosmosDb { + string DatabaseId { get; } + + string EventsCollectionId { get; } + string SnapshotsCollectionId { get; } DocumentClient GetCosmosDbClient(); } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs index cab29d78..f7ebcd92 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs @@ -6,15 +6,17 @@ using Microwave.Domain.Identities; using Microwave.Domain.Results; using Microwave.EventStores; +using Microwave.EventStores.Ports; namespace Microwave.Persistence.CosmosDb { public interface ICosmosDbClient { - Task CreateDomainEventAsync(IDomainEvent domainEvent); - Task>> GetDomainEventsAsync(DateTimeOffset tickSince); - Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); - Task CreateItemAsync(DomainEventWrapper domainEvent); - Task> GetDomainEventsAsync(Identity identity); + Task> GetDomainEventsAsync(DateTimeOffset tickSince); + Task> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); + Task CreateItemAsync(DomainEventWrapper domainEvent); + Task> GetDomainEventsAsync(Identity identity); + Task> LoadSnapshotAsync(Identity entityId); + Task SaveSnapshotAsync(SnapShotWrapper snapShot); } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj index 338e07f5..470370e7 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj +++ b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj @@ -11,6 +11,7 @@ + diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs index 6bc79a13..3084c7b8 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Security; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microwave.Domain.EventSourcing; +using Microwave.Domain.Exceptions; using Microwave.Domain.Identities; +using Microwave.Domain.Results; using Microwave.EventStores; +using Microwave.EventStores.Ports; using Microwave.Persistence.CosmosDb; namespace Microwave.Persistence.CosmosDb.UnitTests @@ -15,7 +19,6 @@ namespace Microwave.Persistence.CosmosDb.UnitTests public class CosmosDbEventRepositoryTests : IntegrationTests { [TestMethod] - [Ignore] public async Task DomainEventIsAppendedCorrectly() { var cosmosDbClient = new CosmosDbClient( @@ -25,7 +28,7 @@ public async Task DomainEventIsAppendedCorrectly() Assembly.GetAssembly(typeof(UserCreatedEvent)) }); - await cosmosDbClient.InitializeCosmosDbAsync(); + await Database.InitializeCosmosDb(); var eventRepository = new CosmosDbEventRepository(cosmosDbClient); var domainEvent = new UserCreatedEvent(GuidIdentity.Create(Guid.NewGuid()), "Hans Wurst"); @@ -39,7 +42,36 @@ await cosmosDbClient.CreateItemAsync(new DomainEventWrapper } [TestMethod] - [Ignore] + public async Task DomainEventReturnsConcurrencyResult() + { + var cosmosDbClient = new CosmosDbClient( + Database, + new List + { + Assembly.GetAssembly(typeof(UserCreatedEvent)) + }); + + await Database.InitializeCosmosDb(); + var entityGuid = Guid.NewGuid(); + var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + var domainEvent = new UserCreatedEvent(GuidIdentity.Create(entityGuid), "Hans Wurst"); + await cosmosDbClient.CreateItemAsync(new DomainEventWrapper + { + Created = DateTimeOffset.Now, + Version = 0, + DomainEvent = domainEvent + }); + var result = await cosmosDbClient.CreateItemAsync(new DomainEventWrapper + { + Created = DateTimeOffset.Now, + Version = 0, + DomainEvent = domainEvent + }); + Assert.ThrowsException(() => result.Check()); + + } + + [TestMethod] public async Task DomainEventsAreGettedCorrectly() { var cosmosDbClient = new CosmosDbClient( @@ -49,13 +81,76 @@ public async Task DomainEventsAreGettedCorrectly() Assembly.GetAssembly(typeof(UserCreatedEvent)) }); - await cosmosDbClient.InitializeCosmosDbAsync(); + await Database.InitializeCosmosDb(); + var entityGuid = Guid.NewGuid(); + var domainEvent = new UserCreatedEvent(GuidIdentity.Create(entityGuid), "Hans Wurst"); + await cosmosDbClient.CreateItemAsync(new DomainEventWrapper + { + Created = DateTimeOffset.Now, + Version = 0, + DomainEvent = domainEvent + }); + var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + var result = await cosmosDbClient.GetDomainEventsAsync(Identity.Create(entityGuid)); + + Assert.AreEqual(result.Count(), 1); + } + + [TestMethod] + public async Task SnapShotIsCreatedSuccesfully() + { + var cosmosDbClient = new CosmosDbClient( + Database, + new List + { + Assembly.GetAssembly(typeof(UserCreatedEvent)) + }); + + await Database.InitializeCosmosDb(); + + await cosmosDbClient.SaveSnapshotAsync(new SnapShotWrapper(new TestUser + { + UserName = "TestUser", + Age = 28 + }, + new GuidIdentity(Guid.NewGuid().ToString()), 1)); + } + + [TestMethod] + public async Task SnapSHotAreGettedCorrectly() + { + var cosmosDbClient = new CosmosDbClient( + Database, + new List + { + Assembly.GetAssembly(typeof(UserCreatedEvent)) + }); + + await Database.InitializeCosmosDb(); var eventRepository = new CosmosDbEventRepository(cosmosDbClient); - var result = await cosmosDbClient.GetDomainEventsAsync(Identity.Create(Guid.Parse("19cf121a-44cd-40bb-a3b5-ea9deb11d4f5"))); + + var entityGuid = Guid.NewGuid(); + await cosmosDbClient.SaveSnapshotAsync(new SnapShotWrapper(new TestUser + { + UserName = "TestUser", + Age = 28 + }, + new GuidIdentity(entityGuid.ToString()), 1)); + + var result = await cosmosDbClient.LoadSnapshotAsync(Identity.Create(entityGuid)); + + Assert.AreEqual(result.Entity.Age, 28); + Assert.AreEqual(result.Entity.UserName, "TestUser"); } } + public class TestUser + { + public string UserName { get; set; } + public int Age { get; set; } + } + public class UserCreatedEvent : IDomainEvent { public Identity EntityId { get; } From a7d1e739245295f1538a56344b590bfe78b2610b Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Fri, 9 Aug 2019 13:45:55 +0200 Subject: [PATCH 7/9] CosmosDb some more stuff --- .../CosmosDb.cs | 8 +- .../CosmosDbClient.cs | 47 ++-------- .../CosmosDbEventRepository.cs | 52 +++++++++-- .../CosmosDbPersistenceLayer.cs | 14 ++- .../CosmosDbReadModelRepository.cs | 90 +++++++++++++++++++ .../CosmosDbSnapshotRepository.cs | 50 +++++++++-- .../CosmosDbStatusRepository.cs | 83 +++++++++++++++++ .../ICosmosDb.cs | 3 + .../ICosmosDbClient.cs | 4 +- .../Microwave.Persistence.CosmosDb.csproj | 1 + 10 files changed, 285 insertions(+), 67 deletions(-) create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbStatusRepository.cs diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs index d83b4283..68361942 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -22,6 +22,8 @@ public DocumentClient GetCosmosDbClient() public string DatabaseId => "Eventstore"; public string EventsCollectionId => "DomainEvents"; public string SnapshotsCollectionId => "Snapshots"; + public string ServiceMapCollectionId => "ServiceMap"; + public string StatusCollectionId => "Status"; public async Task InitializeCosmosDb() { @@ -59,6 +61,9 @@ public async Task InitializeCosmosDb() await client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DatabaseId), domainEventsCollection); + await client.CreateDocumentCollectionIfNotExistsAsync( + UriFactory.CreateDatabaseUri(DatabaseId), + new DocumentCollection{Id = StatusCollectionId}); await client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DatabaseId), snapShotCollection); @@ -66,7 +71,8 @@ await client.CreateDocumentCollectionIfNotExistsAsync( catch (DocumentClientException e) { throw new ArgumentException( - "Could not create Database or Collection with given CosmosDb Configuration Parameters!"); + $"Could not create Database or Collection with given CosmosDb Configuration Parameters! Exception : {e}" ); + } } } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs index cc0cdf1e..bfa165f0 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs @@ -34,12 +34,12 @@ public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) } - public async Task> GetDomainEventsAsync(Identity identity) + public async Task> GetDomainEventsAsync(Identity identity, long from) { var query = _client.CreateDocumentQuery( UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), new FeedOptions { MaxItemCount = -1 }) - .Where(e => e.DomainEvent.EntityId == identity) + .Where(e => e.DomainEvent.EntityId == identity && e.Version >= from) .AsDocumentQuery(); var wrappedEvents = new List(); @@ -53,48 +53,15 @@ public async Task> GetDomainEventsAsync(Identity { result.Add(new DomainEventWrapper { - Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), - DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), - Version = (long)wrappedEvent.GetValue("Version") + Created = (DateTimeOffset)wrappedEvent.GetValue(nameof(DomainEventWrapper.Created)), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue(nameof(DomainEventWrapper.DomainEvent)).ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue(nameof(DomainEventWrapper.DomainEvent)).ToString())), + Version = (long)wrappedEvent.GetValue(nameof(DomainEventWrapper.Version)) }); } return result; } - public async Task> LoadSnapshotAsync(Identity entityId) - { - var query = _client.CreateDocumentQuery>( - UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.SnapshotsCollectionId), - new FeedOptions { MaxItemCount = -1 }) - .Where(e => e.Id == entityId) - .AsDocumentQuery(); - - var wrappedEvents = new List(); - while (query.HasMoreResults) - { - wrappedEvents.AddRange(await query.ExecuteNextAsync()); - } - var allSnapshots = new List>(); - foreach (var wrappedEvent in wrappedEvents) - { - var entity = JsonConvert.DeserializeObject(wrappedEvent.GetValue("Entity").ToString()); - var version = (long) wrappedEvent.GetValue("Version"); - Guid.TryParse(wrappedEvent.GetValue("id").ToString(), out var guid); - Identity identity = null; - if (guid != Guid.Empty) - { - identity = Identity.Create(guid); - } - else - { - identity = Identity.Create(wrappedEvent.GetValue("id").ToString()); - } - allSnapshots.Add(new SnapShotWrapper(entity, identity, version)); - } - var result = allSnapshots.Single(x => x.Version == allSnapshots.Max(s => s.Version)); - return new SnapShotResult(result.Entity, result.Version); - } public async Task SaveSnapshotAsync(SnapShotWrapper snapShot) { @@ -135,9 +102,9 @@ public async Task CreateItemAsync(DomainEventWrapper domainEvent) { await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), domainEvent); } - catch (DocumentClientException e) + catch (DocumentClientException) { - var actualVersion = (await GetDomainEventsAsync(domainEvent.DomainEvent.EntityId)).Max(x => x.Version); + var actualVersion = (await GetDomainEventsAsync(domainEvent.DomainEvent.EntityId, domainEvent.Version)).Max(x => x.Version); return Result.ConcurrencyResult(domainEvent.Version, actualVersion); } return Result.Ok(); diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs index dc2f0232..827e659d 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -1,31 +1,44 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; using Microwave.Domain.EventSourcing; using Microwave.Domain.Identities; using Microwave.Domain.Results; using Microwave.EventStores; using Microwave.EventStores.Ports; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microwave.Persistence.CosmosDb { public class CosmosDbEventRepository : IEventRepository { private readonly ICosmosDbClient _cosmosDbClient; + private readonly ICosmosDb _cosmosDb; + private DocumentClient _client; + private IEnumerable _domainEventTypes; - public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient) + public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient, ICosmosDb cosmosDb, IEnumerable assemblies) { _cosmosDbClient = cosmosDbClient; + _cosmosDb = cosmosDb; + _client = cosmosDb.GetCosmosDbClient(); + var type = typeof(IDomainEvent); + _domainEventTypes = assemblies + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p)); + } public async Task>> LoadEventsByEntity(Identity entityId, long @from = 0) { - throw new NotImplementedException(); - //var uri = CreateUriForCosmosDb(entityId); - //var domainEvents = (await _client.ReadDocumentAsync>(uri)).Document; - //return new EventStoreResult>(domainEvents, domainEvents.Max(e => e.Version)); + var result = await _cosmosDbClient.GetDomainEventsAsync(entityId, from); + return Result>.Ok(result); } public async Task AppendAsync(IEnumerable domainEvents, long currentEntityVersion) @@ -59,13 +72,36 @@ await _cosmosDbClient.CreateItemAsync(new DomainEventWrapper public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince = default(DateTimeOffset)) { - var result = _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); - return Result>.Ok(result.Result); + var result = await _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); + return Result>.Ok(result); } public async Task> GetLastEventOccuredOn(string domainEventType) { - throw new NotImplementedException(); + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.DomainEventType == domainEventType) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) + { + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), + Version = (long)wrappedEvent.GetValue("Version") + }); + } + + return Result.Ok(result.Max(s => s.Created)); } } } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs index 6c712eba..2697d4a4 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microwave.Discovery; using Microwave.EventStores; using Microwave.EventStores.Ports; +using Microwave.Queries; namespace Microwave.Persistence.CosmosDb { @@ -16,16 +18,12 @@ public IServiceCollection AddPersistenceLayer(IServiceCollection services, IEnum var cosmosDb = new CosmosDb(); cosmosDb.InitializeCosmosDb().Wait(); - //services.AddTransient(); - - //services.AddTransient(); - //services.AddTransient(); - //services.AddSingleton(MicrowaveCosmosDb); - //services.AddSingleton(new CosmosDbEventLocationCache()); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(MicrowaveCosmosDb); services.AddTransient(); - //services.AddSingleton(); - //services.AddTransient(); + services.AddTransient(); return services; } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs new file mode 100644 index 00000000..e3922dcf --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microwave.Domain.Identities; +using Microwave.Domain.Results; +using Microwave.Persistence.MongoDb.Querries; +using Microwave.Queries; + +namespace Microwave.Persistence.CosmosDb +{ + public class CosmosDbReadModelRepository : IReadModelRepository + { + private readonly ICosmosDb _cosmosDb; + private DocumentClient _client; + private string GetReadModelCollectionName() => $"ReadModelDbos_{typeof(T).Name}"; + private string GetQuerryCollectionName() => $"QueryDbos_{typeof(T).Name}"; + + public CosmosDbReadModelRepository(ICosmosDb cosmosDb) + { + _cosmosDb = cosmosDb; + _client = _cosmosDb.GetCosmosDbClient(); + } + + public async Task> Load() where T : Query + { + var name = typeof(T).Name; + var result = Result.NotFound(StringIdentity.Create(name)); + var queryResult = _client.CreateDocumentQuery>( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, GetQuerryCollectionName()), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Type == name) + .AsEnumerable().FirstOrDefault(); + if (queryResult != null) + { + result = Result.Ok(queryResult.Payload); + } + + return result; + } + + public async Task> Load(Identity id) where T : ReadModel + { + var name = typeof(T).Name; + var queryResult = _client.CreateDocumentQuery>( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, GetReadModelCollectionName()), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Id == id.Id) + .AsEnumerable().FirstOrDefault(); + if (queryResult == null) + { + return ReadModelResult.NotFound(id); + } + + return ReadModelResult.Ok(queryResult.Payload, id, queryResult.Version); + } + + public async Task Save(T query) where T : Query + { + await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId,GetQuerryCollectionName()), query); + return Result.Ok(); + } + + public async Task Save(T readModel, Identity identity, long version) where T : ReadModel, new() + { + try + { + await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, GetReadModelCollectionName()), readModel); + } + catch (DocumentClientException) + { + var actualVersion = (await Load(identity)).Version; + return Result.ConcurrencyResult(version, actualVersion); + } + return Result.Ok(); + } + + public async Task>> LoadAll() where T : ReadModel + { + var name = typeof(T).Name; + var queryResult = _client.CreateDocumentQuery>( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, GetReadModelCollectionName()), + new FeedOptions { MaxItemCount = -1 }) + .AsEnumerable(); + return Result>.Ok(queryResult.Select(rm => rm.Payload).ToList()); + } + } +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs index e63e2701..83e11d3a 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbSnapshotRepository.cs @@ -1,30 +1,66 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; using Microwave.Domain.Identities; +using Microwave.EventStores; using Microwave.EventStores.Ports; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microwave.Persistence.CosmosDb { public class CosmosDbSnapshotRepository : ISnapShotRepository { - private readonly ICosmosDbClient _cosmosDbClient; + private readonly ICosmosDb _cosmosDb; + private DocumentClient _client; - public CosmosDbSnapshotRepository(ICosmosDbClient cosmosDbClient) + public CosmosDbSnapshotRepository(ICosmosDb cosmosDb) { - _cosmosDbClient = cosmosDbClient; + _cosmosDb = cosmosDb; + _client = _cosmosDb.GetCosmosDbClient(); } public async Task> LoadSnapShot(Identity entityId) where T : new() { - var result = _cosmosDbClient.LoadSnapshotAsync(entityId); - return result.Result; + var query = _client.CreateDocumentQuery>( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.SnapshotsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Id == entityId) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + var allSnapshots = new List>(); + foreach (var wrappedEvent in wrappedEvents) + { + var entity = JsonConvert.DeserializeObject(wrappedEvent.GetValue("Entity").ToString()); + var version = (long)wrappedEvent.GetValue(nameof(DomainEventWrapper.Version)); + Guid.TryParse(wrappedEvent.GetValue("id").ToString(), out var guid); + Identity identity = null; + if (guid != Guid.Empty) + { + identity = Identity.Create(guid); + } + else + { + identity = Identity.Create(wrappedEvent.GetValue("id").ToString()); + } + allSnapshots.Add(new SnapShotWrapper(entity, identity, version)); + } + var result = allSnapshots.Single(x => x.Version == allSnapshots.Max(s => s.Version)); + return new SnapShotResult(result.Entity, result.Version); } - public Task SaveSnapShot(SnapShotWrapper snapShot) + public async Task SaveSnapShot(SnapShotWrapper snapShot) { - throw new NotImplementedException(); + await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.SnapshotsCollectionId), snapShot); } } } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbStatusRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbStatusRepository.cs new file mode 100644 index 00000000..d409ab33 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbStatusRepository.cs @@ -0,0 +1,83 @@ +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 Microwave.Discovery; +using Microwave.Discovery.EventLocations; +using Microwave.Discovery.ServiceMaps; +using Microwave.Domain.EventSourcing; +using Microwave.EventStores; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microwave.Persistence.CosmosDb +{ + public class CosmosDbStatusRepository : IStatusRepository + { + private readonly ICosmosDb _cosmosDb; + private DocumentClient _client; + + public CosmosDbStatusRepository(ICosmosDb cosmosDb) + { + _cosmosDb = cosmosDb; + _client = cosmosDb.GetCosmosDbClient(); + } + + public async Task SaveEventLocation(EventLocation eventLocation) + { + var previousEventLocation = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId), + new FeedOptions {MaxItemCount = -1}).AsEnumerable().SingleOrDefault(); + + if (previousEventLocation == null) + { + await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId), eventLocation); + } + else + { + await _client.ReplaceDocumentAsync( + UriFactory.CreateDocumentUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId, + previousEventLocation.ResourceId), eventLocation); + } + } + + public async Task GetEventLocation() + { + var eventLocation = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId), + new FeedOptions { MaxItemCount = -1 }).AsEnumerable().FirstOrDefault(); + + return eventLocation; + } + + public async Task GetServiceMap() + { + var serviceMap = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId), + new FeedOptions { MaxItemCount = -1 }).AsEnumerable().FirstOrDefault(); + + return serviceMap; + } + + public async Task SaveServiceMap(ServiceMap map) + { + var previousEventLocation = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId), + new FeedOptions { MaxItemCount = -1 }).AsEnumerable().SingleOrDefault(); + + if (previousEventLocation == null) + { + await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId), map); + } + else + { + await _client.ReplaceDocumentAsync( + UriFactory.CreateDocumentUri(_cosmosDb.DatabaseId, _cosmosDb.StatusCollectionId, + previousEventLocation.ResourceId), map); + } + } + } +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs index 62e0caa6..97a6919f 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -8,6 +8,9 @@ public interface ICosmosDb string EventsCollectionId { get; } string SnapshotsCollectionId { get; } + string ServiceMapCollectionId { get; } + + string StatusCollectionId { get; } DocumentClient GetCosmosDbClient(); } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs index f7ebcd92..180feeed 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs @@ -15,8 +15,6 @@ public interface ICosmosDbClient Task> GetDomainEventsAsync(DateTimeOffset tickSince); Task> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); Task CreateItemAsync(DomainEventWrapper domainEvent); - Task> GetDomainEventsAsync(Identity identity); - Task> LoadSnapshotAsync(Identity entityId); - Task SaveSnapshotAsync(SnapShotWrapper snapShot); + Task> GetDomainEventsAsync(Identity identity, long from); } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj index 470370e7..8f3ee4da 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj +++ b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Persistence.CosmosDb.csproj @@ -11,6 +11,7 @@ + From e96cf714ecb356a6cb8f5fdce06c68f94e21f2c7 Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Mon, 7 Oct 2019 17:29:24 +0200 Subject: [PATCH 8/9] Added Stuff --- .../CosmosDbEventRepositoryTests.cs | 18 +++--- .../IntegrationTests.cs | 2 +- .../CosmosDbTestSetup.cs | 55 +++++++++++++++++++ ....Persistence.MongoDb.UnitTestsSetup.csproj | 1 + .../PersistenceTypeTestAttribute.cs | 3 +- 5 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/CosmosDbTestSetup.cs diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs index 3084c7b8..80e3b50f 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/CosmosDbEventRepositoryTests.cs @@ -29,8 +29,8 @@ public async Task DomainEventIsAppendedCorrectly() }); await Database.InitializeCosmosDb(); - - var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + + var eventRepository = new CosmosDbEventRepository(cosmosDbClient, Database, new List { Assembly.GetAssembly(typeof(UserCreatedEvent)) }); var domainEvent = new UserCreatedEvent(GuidIdentity.Create(Guid.NewGuid()), "Hans Wurst"); await cosmosDbClient.CreateItemAsync(new DomainEventWrapper { @@ -53,7 +53,7 @@ public async Task DomainEventReturnsConcurrencyResult() await Database.InitializeCosmosDb(); var entityGuid = Guid.NewGuid(); - var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + var eventRepository = new CosmosDbEventRepository(cosmosDbClient, Database, new List { Assembly.GetAssembly(typeof(UserCreatedEvent)) }); var domainEvent = new UserCreatedEvent(GuidIdentity.Create(entityGuid), "Hans Wurst"); await cosmosDbClient.CreateItemAsync(new DomainEventWrapper { @@ -90,8 +90,8 @@ await cosmosDbClient.CreateItemAsync(new DomainEventWrapper Version = 0, DomainEvent = domainEvent }); - var eventRepository = new CosmosDbEventRepository(cosmosDbClient); - var result = await cosmosDbClient.GetDomainEventsAsync(Identity.Create(entityGuid)); + var eventRepository = new CosmosDbEventRepository(cosmosDbClient, Database, new List{ Assembly.GetAssembly(typeof(UserCreatedEvent)) } ); + var result = await cosmosDbClient.GetDomainEventsAsync(Identity.Create(entityGuid), 0); Assert.AreEqual(result.Count(), 1); } @@ -128,7 +128,7 @@ public async Task SnapSHotAreGettedCorrectly() await Database.InitializeCosmosDb(); - var eventRepository = new CosmosDbEventRepository(cosmosDbClient); + var eventRepository = new CosmosDbEventRepository(cosmosDbClient, Database, new List { Assembly.GetAssembly(typeof(UserCreatedEvent)) }); var entityGuid = Guid.NewGuid(); await cosmosDbClient.SaveSnapshotAsync(new SnapShotWrapper(new TestUser @@ -138,10 +138,10 @@ await cosmosDbClient.SaveSnapshotAsync(new SnapShotWrapper(new TestUse }, new GuidIdentity(entityGuid.ToString()), 1)); - var result = await cosmosDbClient.LoadSnapshotAsync(Identity.Create(entityGuid)); + //var result = await eventRepository.LoadSnapshotAsync(Identity.Create(entityGuid)); - Assert.AreEqual(result.Entity.Age, 28); - Assert.AreEqual(result.Entity.UserName, "TestUser"); + //Assert.AreEqual(result.Entity.Age, 28); + //Assert.AreEqual(result.Entity.UserName, "TestUser"); } } diff --git a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs index 32e69950..6e51ff19 100644 --- a/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs +++ b/TestProjects/Microwave.Persistence.CosmosDb.UnitTests/IntegrationTests.cs @@ -25,7 +25,7 @@ public SecureString PrimaryKey public Uri CosmosDbLocation => new Uri("https://spoppinga.documents.azure.com:443/"); [TestInitialize] - public void SetupMongoDb() + public void SetupCosmosDb() { Database = new CosmosDb { diff --git a/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/CosmosDbTestSetup.cs b/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/CosmosDbTestSetup.cs new file mode 100644 index 00000000..c4294919 --- /dev/null +++ b/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/CosmosDbTestSetup.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Security; +using Microwave.Discovery; +using Microwave.Discovery.EventLocations; +using Microwave.EventStores; +using Microwave.EventStores.Ports; +using Microwave.Persistence.CosmosDb; +using Microwave.Persistence.MongoDb.Eventstores; +using Microwave.Persistence.MongoDb.Querries; +using Microwave.Persistence.UnitTestSetupPorts; +using Microwave.Queries; +using Microwave.Queries.Ports; + +namespace Microwave.Persistence.MongoDb.UnitTestsSetup +{ + public class CosmosDbTestSetup : IPersistenceLayerProvider + { + public CosmosDbTestSetup() + { + CosmosDb = new CosmosDb.CosmosDb(); + CosmosDb.PrimaryKey = PrimaryKey; + CosmosDb.CosmosDbLocation = CosmosDbLocation; + CosmosDb.InitializeCosmosDb(); + CosmosDbClient = new CosmosDbClient(CosmosDb, new List{ Assembly.GetCallingAssembly() }); + + } + + public CosmosDbClient CosmosDbClient { get; } + public CosmosDb.CosmosDb CosmosDb { get; } + public IVersionRepository VersionRepository { get; } + public IStatusRepository StatusRepository => new CosmosDbStatusRepository(CosmosDb); + public IReadModelRepository ReadModelRepository => new CosmosDbReadModelRepository(CosmosDb); + public ISnapShotRepository SnapShotRepository => new CosmosDbSnapshotRepository(CosmosDb); + public IEventRepository EventRepository => new CosmosDbEventRepository(CosmosDbClient, CosmosDb, new List { Assembly.GetCallingAssembly() }); + + public SecureString PrimaryKey + { + get + { + var secure = new SecureString(); + foreach (char c in "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA==") + { + secure.AppendChar(c); + } + + return secure; + } + } + + public Uri CosmosDbLocation => new Uri("https://spoppinga.documents.azure.com:443/"); + } + +} \ No newline at end of file diff --git a/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/Microwave.Persistence.MongoDb.UnitTestsSetup.csproj b/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/Microwave.Persistence.MongoDb.UnitTestsSetup.csproj index 03e0109a..46d0bdd6 100644 --- a/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/Microwave.Persistence.MongoDb.UnitTestsSetup.csproj +++ b/TestProjects/Microwave.Persistence.MongoDb.UnitTestsSetup/Microwave.Persistence.MongoDb.UnitTestsSetup.csproj @@ -15,6 +15,7 @@ + diff --git a/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs b/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs index a54ee8aa..4729fe17 100644 --- a/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs +++ b/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs @@ -11,7 +11,8 @@ public class PersistenceTypeTestAttribute : Attribute, ITestDataSource { public IEnumerable GetData(MethodInfo methodInfo) { - yield return new object[] { new MongoDbTestSetup() }; + // yield return new object[] { new MongoDbTestSetup() }; + yield return new object[] { new CosmosDbTestSetup() }; } public string GetDisplayName(MethodInfo methodInfo, object[] data) From 02e5fb57fd66c5fcde3e4137b465e5baf167f05a Mon Sep 17 00:00:00 2001 From: Simon Poppinga Date: Tue, 14 Jan 2020 13:28:49 +0100 Subject: [PATCH 9/9] Added Tests etc --- .../ServiceBaseAddressCollection.cs | 2 +- .../CosmosDb.cs | 16 ++ .../CosmosDbClient.cs | 142 ------------------ .../CosmosDbEventRepository.cs | 99 ++++++++++-- .../CosmosDbPersistenceLayer.cs | 2 +- .../CosmosDbReadModelRepository.cs | 21 +-- .../CosmosDbVersionCache.cs | 61 ++++++++ .../IAssemblyProvider.cs | 10 ++ .../ICosmosDb.cs | 1 + .../ICosmosDbClient.cs | 20 --- ...ve.Eventstores.Persistence.CosmosDb.csproj | 16 ++ .../VersionRepositoryMongoDb.cs | 52 +++++++ Microwave.sln | 6 + Microwave/MicrowaveConfiguration.cs | 1 + .../PersistenceTypeTestAttribute.cs | 2 + .../CosmosDb/AssemblyProvider.cs | 18 +++ .../CosmosDb/CosmosDbTestSetup.cs | 45 ++++++ ...icrowave.Persistence.UnitTestsSetup.csproj | 2 +- .../ServerConfig/ServiceConfiguration.cs | 1 + .../WriteService2/ApiKeyRequirement.cs | 2 +- TestServices/WriteService2/Startup.cs | 1 - 21 files changed, 332 insertions(+), 188 deletions(-) delete mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/CosmosDbVersionCache.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/IAssemblyProvider.cs delete mode 100644 Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj create mode 100644 Microwave.Eventstores.Persistence.CosmosDb/VersionRepositoryMongoDb.cs create mode 100644 TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/AssemblyProvider.cs create mode 100644 TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/CosmosDbTestSetup.cs diff --git a/Microwave.Discovery/ServiceBaseAddressCollection.cs b/Microwave.Discovery/ServiceBaseAddressCollection.cs index e2aa65bb..e4027627 100644 --- a/Microwave.Discovery/ServiceBaseAddressCollection.cs +++ b/Microwave.Discovery/ServiceBaseAddressCollection.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Microwave +namespace Microwave.Discovery { public class ServiceBaseAddressCollection : List { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs index 68361942..5e9d1d21 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDb.cs @@ -24,6 +24,7 @@ public DocumentClient GetCosmosDbClient() public string SnapshotsCollectionId => "Snapshots"; public string ServiceMapCollectionId => "ServiceMap"; public string StatusCollectionId => "Status"; + public string VersionCollectionId => "Versions"; public async Task InitializeCosmosDb() { @@ -54,6 +55,18 @@ public async Task InitializeCosmosDb() new UniqueKey {Paths = new Collection {"/Version", "/Id/Id"}} } }; + var versionCollection = new DocumentCollection + { + Id = VersionCollectionId + }; + //versionCollection.UniqueKeyPolicy = new UniqueKeyPolicy + //{ + // UniqueKeys = + // new Collection + // { + // new UniqueKey {Paths = new Collection {"/Version", "/DomainEventType"}} + // } + //}; try { @@ -67,6 +80,9 @@ await client.CreateDocumentCollectionIfNotExistsAsync( await client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DatabaseId), snapShotCollection); + await client.CreateDocumentCollectionIfNotExistsAsync( + UriFactory.CreateDatabaseUri(DatabaseId), + versionCollection); } catch (DocumentClientException e) { diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs deleted file mode 100644 index bfa165f0..00000000 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbClient.cs +++ /dev/null @@ -1,142 +0,0 @@ -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; -using Microsoft.Azure.Documents.Linq; -using Microwave.Domain.EventSourcing; -using Microwave.Domain.Identities; -using Microwave.Domain.Results; -using Microwave.EventStores; -using Microwave.EventStores.Ports; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microwave.Persistence.CosmosDb -{ - public class CosmosDbClient : ICosmosDbClient - { - private readonly ICosmosDb _cosmosDb; - private readonly DocumentClient _client; - private IEnumerable _domainEventTypes; - - - public CosmosDbClient(ICosmosDb cosmosDb, IEnumerable assemblies) - { - _cosmosDb = cosmosDb; - _client = cosmosDb.GetCosmosDbClient(); - var type = typeof(IDomainEvent); - _domainEventTypes = assemblies - .SelectMany(s => s.GetTypes()) - .Where(p => type.IsAssignableFrom(p)); - - } - - public async Task> GetDomainEventsAsync(Identity identity, long from) - { - var query = _client.CreateDocumentQuery( - UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), - new FeedOptions { MaxItemCount = -1 }) - .Where(e => e.DomainEvent.EntityId == identity && e.Version >= from) - .AsDocumentQuery(); - - var wrappedEvents = new List(); - while (query.HasMoreResults) - { - wrappedEvents.AddRange(await query.ExecuteNextAsync()); - } - - var result = new List(); - foreach (var wrappedEvent in wrappedEvents) - { - result.Add(new DomainEventWrapper - { - Created = (DateTimeOffset)wrappedEvent.GetValue(nameof(DomainEventWrapper.Created)), - DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue(nameof(DomainEventWrapper.DomainEvent)).ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue(nameof(DomainEventWrapper.DomainEvent)).ToString())), - Version = (long)wrappedEvent.GetValue(nameof(DomainEventWrapper.Version)) - }); - } - - return result; - } - - - public async Task SaveSnapshotAsync(SnapShotWrapper snapShot) - { - await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.SnapshotsCollectionId), snapShot); - } - - - public async Task> GetDomainEventsAsync(DateTimeOffset tickSince) - { - var query = _client.CreateDocumentQuery( - UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), - new FeedOptions { MaxItemCount = -1 }) - .Where(e => e.Created >= tickSince) - .AsDocumentQuery(); - - var wrappedEvents = new List(); - while (query.HasMoreResults) - { - wrappedEvents.AddRange(await query.ExecuteNextAsync()); - } - var result = new List(); - foreach (var wrappedEvent in wrappedEvents) - { - result.Add(new DomainEventWrapper - { - Created = (DateTimeOffset) wrappedEvent.GetValue("Created"), - DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), - Version = (long) wrappedEvent.GetValue("Version") - }); - } - - return result; - } - - public async Task CreateItemAsync(DomainEventWrapper domainEvent) - { - try - { - await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), domainEvent); - } - catch (DocumentClientException) - { - var actualVersion = (await GetDomainEventsAsync(domainEvent.DomainEvent.EntityId, domainEvent.Version)).Max(x => x.Version); - return Result.ConcurrencyResult(domainEvent.Version, actualVersion); - } - return Result.Ok(); - } - - public async Task> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince) - { - var query = _client.CreateDocumentQuery( - UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), - new FeedOptions { MaxItemCount = -1 }) - .Where(e => e.Created >= tickSince && e.DomainEventType == eventType) - .AsDocumentQuery(); - - var wrappedEvents = new List(); - while (query.HasMoreResults) - { - wrappedEvents.AddRange(await query.ExecuteNextAsync()); - } - - var result = new List(); - foreach (var wrappedEvent in wrappedEvents) - { - result.Add(new DomainEventWrapper - { - Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), - DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), - Version = (long)wrappedEvent.GetValue("Version") - }); - } - - return result; - } - } - -} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs index 827e659d..d0e10abd 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbEventRepository.cs @@ -1,8 +1,8 @@ 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; using Microsoft.Azure.Documents.Linq; using Microwave.Domain.EventSourcing; @@ -10,6 +10,7 @@ using Microwave.Domain.Results; using Microwave.EventStores; using Microwave.EventStores.Ports; +using Microwave.Persistence.MongoDb.Eventstores; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -17,19 +18,21 @@ namespace Microwave.Persistence.CosmosDb { public class CosmosDbEventRepository : IEventRepository { - private readonly ICosmosDbClient _cosmosDbClient; private readonly ICosmosDb _cosmosDb; + private readonly IAssemblyProvider _assemblyProvider; + private readonly IVersionCache _versionCache; private DocumentClient _client; private IEnumerable _domainEventTypes; - public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient, ICosmosDb cosmosDb, IEnumerable assemblies) + public CosmosDbEventRepository(ICosmosDb cosmosDb, IAssemblyProvider assemblyProvider, IVersionCache versionCache) { - _cosmosDbClient = cosmosDbClient; _cosmosDb = cosmosDb; + _assemblyProvider = assemblyProvider; + _versionCache = versionCache; _client = cosmosDb.GetCosmosDbClient(); var type = typeof(IDomainEvent); - _domainEventTypes = assemblies + _domainEventTypes = assemblyProvider.GetAssemblies() .SelectMany(s => s.GetTypes()) .Where(p => type.IsAssignableFrom(p)); @@ -37,7 +40,29 @@ public CosmosDbEventRepository(ICosmosDbClient cosmosDbClient, ICosmosDb cosmosD public async Task>> LoadEventsByEntity(Identity entityId, long @from = 0) { - var result = await _cosmosDbClient.GetDomainEventsAsync(entityId, from); + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.DomainEvent.EntityId == entityId && e.Version >= from) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) + { + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset)wrappedEvent.GetValue(nameof(DomainEventWrapper.Created)), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue(nameof(DomainEventWrapper.DomainEvent)).ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue(nameof(DomainEventWrapper.DomainEvent)).ToString())), + Version = (long)wrappedEvent.GetValue(nameof(DomainEventWrapper.Version)) + }); + } + return Result>.Ok(result); } @@ -46,20 +71,49 @@ public async Task AppendAsync(IEnumerable domainEvents, lo foreach (var domainEvent in domainEvents) { - await _cosmosDbClient.CreateItemAsync(new DomainEventWrapper + var wrappedEvent = new DomainEventWrapper { DomainEvent = domainEvent, Created = DateTimeOffset.Now, Version = currentEntityVersion - }); + }; + try + { + await _client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), wrappedEvent); + } + catch (DocumentClientException) + { + var actualVersion = await _versionCache.Get(domainEvent.EntityId); + return Result.ConcurrencyResult(currentEntityVersion, actualVersion); + } + } - return Result.Ok(); } public async Task>> LoadEvents(DateTimeOffset tickSince = default(DateTimeOffset)) { - var result = await _cosmosDbClient.GetDomainEventsAsync(tickSince); + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Created >= tickSince) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) + { + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), + Version = (long)wrappedEvent.GetValue("Version") + }); + } if (result.Any()) { return Result>.Ok(result); @@ -68,11 +122,34 @@ await _cosmosDbClient.CreateItemAsync(new DomainEventWrapper { return Result>.NotFound(null); } + } public async Task>> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince = default(DateTimeOffset)) { - var result = await _cosmosDbClient.LoadEventsByTypeAsync(eventType, tickSince); + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.Created >= tickSince && e.DomainEventType == eventType) + .AsDocumentQuery(); + + var wrappedEvents = new List(); + while (query.HasMoreResults) + { + wrappedEvents.AddRange(await query.ExecuteNextAsync()); + } + + var result = new List(); + foreach (var wrappedEvent in wrappedEvents) + { + result.Add(new DomainEventWrapper + { + Created = (DateTimeOffset)wrappedEvent.GetValue("Created"), + DomainEvent = (IDomainEvent)JsonConvert.DeserializeObject(wrappedEvent.GetValue("DomainEvent").ToString(), _domainEventTypes.Single(x => x.Name == wrappedEvent.GetValue("DomainEventType").ToString())), + Version = (long)wrappedEvent.GetValue("Version") + }); + } + return Result>.Ok(result); } diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs index 2697d4a4..c7a77089 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbPersistenceLayer.cs @@ -8,7 +8,7 @@ namespace Microwave.Persistence.CosmosDb { - public class CosmosDbPersistenceLayer : IPersistenceLayer + public class CosmosDbPersistenceLayer { public MicrowaveCosmosDb MicrowaveCosmosDb { get; set; } = new MicrowaveCosmosDb(); diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs index e3922dcf..3fa7a0ad 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbReadModelRepository.cs @@ -24,7 +24,7 @@ public CosmosDbReadModelRepository(ICosmosDb cosmosDb) _client = _cosmosDb.GetCosmosDbClient(); } - public async Task> Load() where T : Query + public async Task> LoadAsync() where T : Query { var name = typeof(T).Name; var result = Result.NotFound(StringIdentity.Create(name)); @@ -41,7 +41,7 @@ public async Task> Load() where T : Query return result; } - public async Task> Load(Identity id) where T : ReadModel + public async Task> LoadAsync(Identity id) where T : ReadModelBase { var name = typeof(T).Name; var queryResult = _client.CreateDocumentQuery>( @@ -51,19 +51,19 @@ public async Task> Load(Identity id) where T : ReadModel .AsEnumerable().FirstOrDefault(); if (queryResult == null) { - return ReadModelResult.NotFound(id); + return Result.NotFound(id); } - return ReadModelResult.Ok(queryResult.Payload, id, queryResult.Version); + return Result.Ok(queryResult.Payload); } - public async Task Save(T query) where T : Query + public async Task SaveQueryAsync(T query) where T : Query { - await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId,GetQuerryCollectionName()), query); + await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, GetQuerryCollectionName()), query); return Result.Ok(); } - public async Task Save(T readModel, Identity identity, long version) where T : ReadModel, new() + public async Task SaveReadModelAsync(T readModel) where T : ReadModelBase, new() { try { @@ -71,13 +71,14 @@ public async Task Save(T query) where T : Query } catch (DocumentClientException) { - var actualVersion = (await Load(identity)).Version; - return Result.ConcurrencyResult(version, actualVersion); + var actualVersion = (await LoadAsync(readModel.Identity)).Value.Version; + return Result.ConcurrencyResult(readModel.Version, actualVersion); } return Result.Ok(); } - public async Task>> LoadAll() where T : ReadModel + + public async Task>> LoadAllAsync() where T : ReadModelBase { var name = typeof(T).Name; var queryResult = _client.CreateDocumentQuery>( diff --git a/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbVersionCache.cs b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbVersionCache.cs new file mode 100644 index 00000000..b5e96370 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/CosmosDbVersionCache.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; +using Microwave.Domain.Identities; +using Microwave.EventStores; +using Microwave.Persistence.MongoDb.Eventstores; + +namespace Microwave.Persistence.CosmosDb +{ + public class CosmosDbVersionCache : IVersionCache + { + private readonly ICosmosDb _cosmosDb; + private DocumentClient _client; + private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + + public CosmosDbVersionCache(ICosmosDb cosmosDb) + { + _cosmosDb = cosmosDb; + _client = cosmosDb.GetCosmosDbClient(); + } + + public async Task Get(Identity entityId) + { + if (!_cache.TryGetValue(entityId, out var version)) + { + var actualVersion = await GetVersionFromDb(entityId); + _cache[entityId] = actualVersion; + return actualVersion; + } + + return version; + } + + private async Task GetVersionFromDb(Identity entityId) + { + var events = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.EventsCollectionId), + new FeedOptions {MaxItemCount = -1}) + .Where(e => e.DomainEvent.EntityId.Id == entityId.Id); + var actualVersion = events.Max(e => e.Version); + return actualVersion; + } + + public async Task GetForce(Identity entityId) + { + var actualVersion = await GetVersionFromDb(entityId); + _cache[entityId] = actualVersion; + return actualVersion; + } + + public void Update(Identity entityId, long actualVersion) + { + _cache[entityId] = actualVersion; + } + } +} diff --git a/Microwave.Eventstores.Persistence.CosmosDb/IAssemblyProvider.cs b/Microwave.Eventstores.Persistence.CosmosDb/IAssemblyProvider.cs new file mode 100644 index 00000000..afc9e3bc --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/IAssemblyProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Microwave.Persistence.CosmosDb +{ + public interface IAssemblyProvider + { + IEnumerable GetAssemblies(); + } +} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs index 97a6919f..22838af2 100644 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs +++ b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDb.cs @@ -11,6 +11,7 @@ public interface ICosmosDb string ServiceMapCollectionId { get; } string StatusCollectionId { get; } + string VersionCollectionId { get; } DocumentClient GetCosmosDbClient(); } } \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs b/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs deleted file mode 100644 index 180feeed..00000000 --- a/Microwave.Eventstores.Persistence.CosmosDb/ICosmosDbClient.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microwave.Domain.EventSourcing; -using Microwave.Domain.Identities; -using Microwave.Domain.Results; -using Microwave.EventStores; -using Microwave.EventStores.Ports; - -namespace Microwave.Persistence.CosmosDb -{ - public interface ICosmosDbClient - { - Task> GetDomainEventsAsync(DateTimeOffset tickSince); - Task> LoadEventsByTypeAsync(string eventType, DateTimeOffset tickSince); - Task CreateItemAsync(DomainEventWrapper domainEvent); - Task> GetDomainEventsAsync(Identity identity, long from); - } -} \ No newline at end of file diff --git a/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj new file mode 100644 index 00000000..2cf43180 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/Microwave.Eventstores.Persistence.CosmosDb.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + diff --git a/Microwave.Eventstores.Persistence.CosmosDb/VersionRepositoryMongoDb.cs b/Microwave.Eventstores.Persistence.CosmosDb/VersionRepositoryMongoDb.cs new file mode 100644 index 00000000..addec260 --- /dev/null +++ b/Microwave.Eventstores.Persistence.CosmosDb/VersionRepositoryMongoDb.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; +using Microwave.EventStores; +using Microwave.Persistence.MongoDb; +using Microwave.Persistence.MongoDb.Querries; +using Microwave.Queries.Ports; +using MongoDB.Driver; +using Newtonsoft.Json.Linq; + +namespace Microwave.Persistence.CosmosDb +{ + public class CosmosDbVersionRepository : IVersionRepository + { + private readonly ICosmosDb _cosmosDb; + private readonly string _lastProcessedVersions = "LastProcessedVersions"; + private DocumentClient _client; + + public CosmosDbVersionRepository(ICosmosDb cosmosDb) + { + _cosmosDb = cosmosDb; + _client = cosmosDb.GetCosmosDbClient(); + } + + public async Task GetVersionAsync(string domainEventType) + { + var query = _client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(_cosmosDb.DatabaseId, _cosmosDb.VersionCollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(e => e.EventType == domainEventType) + .AsDocumentQuery(); + var versions = new List(); + while (query.HasMoreResults) + { + versions.AddRange(await query.ExecuteNextAsync()); + } + + var lastProcessedVersion = versions.Where(version => version.EventType == domainEventType).FirstOrDefault(); + if (lastProcessedVersion == null) return DateTimeOffset.MinValue; + return lastProcessedVersion.LastVersion; + } + + public async Task SaveVersion(LastProcessedVersion version) + { + await _client.UpsertDocumentAsync(_cosmosDb.VersionCollectionId, version); + } + } +} \ No newline at end of file diff --git a/Microwave.sln b/Microwave.sln index 77eb0333..fd03bade 100644 --- a/Microwave.sln +++ b/Microwave.sln @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.InMem EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.UnitTestsSetup", "TestProjects\Microwave.Persistence.UnitTestsSetup\Microwave.Persistence.UnitTestsSetup.csproj", "{A1C79B11-FAAB-4D3F-822F-08C93C16D4BE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microwave.Persistence.CosmosDb", "Microwave.Eventstores.Persistence.CosmosDb\Microwave.Persistence.CosmosDb.csproj", "{0A4D3089-A5D1-4D3C-9F11-BD1D0E4DFC0C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -151,6 +153,10 @@ Global {A1C79B11-FAAB-4D3F-822F-08C93C16D4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1C79B11-FAAB-4D3F-822F-08C93C16D4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1C79B11-FAAB-4D3F-822F-08C93C16D4BE}.Release|Any CPU.Build.0 = Release|Any CPU + {0A4D3089-A5D1-4D3C-9F11-BD1D0E4DFC0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A4D3089-A5D1-4D3C-9F11-BD1D0E4DFC0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A4D3089-A5D1-4D3C-9F11-BD1D0E4DFC0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A4D3089-A5D1-4D3C-9F11-BD1D0E4DFC0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Microwave/MicrowaveConfiguration.cs b/Microwave/MicrowaveConfiguration.cs index bdd5c6d2..fdf7cdf7 100644 --- a/Microwave/MicrowaveConfiguration.cs +++ b/Microwave/MicrowaveConfiguration.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Microwave.Discovery; using Microwave.EventStores.SnapShots; using Microwave.Queries.Polling; using Microwave.WebApi; diff --git a/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs b/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs index 0796a759..ec3225e8 100644 --- a/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs +++ b/TestProjects/Microwave.Persistence.UnitTests/PersistenceTypeTestAttribute.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microwave.Persistence.UnitTestsSetup.CosmosDb; using Microwave.Persistence.UnitTestsSetup.InMemory; using Microwave.Persistence.UnitTestsSetup.MongoDb; @@ -14,6 +15,7 @@ public IEnumerable GetData(MethodInfo methodInfo) { yield return new object[] { new MongoDbTestSetup() }; yield return new object[] { new InMemroyTestSetup() }; + yield return new object[] { new CosmosDbTestSetup(), }; } public string GetDisplayName(MethodInfo methodInfo, object[] data) diff --git a/TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/AssemblyProvider.cs b/TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/AssemblyProvider.cs new file mode 100644 index 00000000..d7e2c34f --- /dev/null +++ b/TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/AssemblyProvider.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Reflection; +using Microwave.Persistence.CosmosDb; +using Microwave.Persistence.UnitTestsSetup.MongoDb; + +namespace Microwave.Persistence.UnitTestsSetup.CosmosDb +{ + public class AssemblyProvider : IAssemblyProvider + { + public IEnumerable GetAssemblies() + { + return new List + { + Assembly.GetAssembly(typeof(TestEvent1)) + }; + } + } +} \ No newline at end of file diff --git a/TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/CosmosDbTestSetup.cs b/TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/CosmosDbTestSetup.cs new file mode 100644 index 00000000..92adfd48 --- /dev/null +++ b/TestProjects/Microwave.Persistence.UnitTestsSetup/CosmosDb/CosmosDbTestSetup.cs @@ -0,0 +1,45 @@ +using System; +using System.Security; +using Microwave.Discovery; +using Microwave.EventStores.Ports; +using Microwave.Persistence.CosmosDb; +using Microwave.Queries; +using Microwave.Queries.Ports; + +namespace Microwave.Persistence.UnitTestsSetup.CosmosDb +{ + public class CosmosDbTestSetup : PersistenceLayerProvider + { + public CosmosDbTestSetup() + { + CosmosDb = new Persistence.CosmosDb.CosmosDb(); + CosmosDb.PrimaryKey = PrimaryKey; + CosmosDb.CosmosDbLocation = CosmosDbLocation; + CosmosDb.InitializeCosmosDb().Wait(); + } + + public SecureString PrimaryKey + { + get + { + var secure = new SecureString(); + foreach (char c in "mCPtXM99gxlUalpz6bkFiWib2QD2OvIB9oEYj8tlpCPz1I4jSkOzlhJGnxAAEH4uiqWiYZ7enElzAM0lopKlJA==") + { + secure.AppendChar(c); + } + + return secure; + } + } + + public Uri CosmosDbLocation => new Uri("https://spoppinga.documents.azure.com:443/"); + + public Persistence.CosmosDb.CosmosDb CosmosDb { get; } + public override IVersionRepository VersionRepository => new CosmosDbVersionRepository(CosmosDb); + public override IStatusRepository StatusRepository => new CosmosDbStatusRepository(CosmosDb); + public override IReadModelRepository ReadModelRepository => new CosmosDbReadModelRepository(CosmosDb); + public override ISnapShotRepository SnapShotRepository => new CosmosDbSnapshotRepository(CosmosDb); + public override IEventRepository EventRepository => new CosmosDbEventRepository(CosmosDb, new AssemblyProvider(), new CosmosDbVersionCache(CosmosDb)); + } + +} \ No newline at end of file diff --git a/TestProjects/Microwave.Persistence.UnitTestsSetup/Microwave.Persistence.UnitTestsSetup.csproj b/TestProjects/Microwave.Persistence.UnitTestsSetup/Microwave.Persistence.UnitTestsSetup.csproj index 12130721..1807552c 100644 --- a/TestProjects/Microwave.Persistence.UnitTestsSetup/Microwave.Persistence.UnitTestsSetup.csproj +++ b/TestProjects/Microwave.Persistence.UnitTestsSetup/Microwave.Persistence.UnitTestsSetup.csproj @@ -15,8 +15,8 @@ - + diff --git a/TestServices/ServerConfig/ServiceConfiguration.cs b/TestServices/ServerConfig/ServiceConfiguration.cs index a1968025..51111705 100644 --- a/TestServices/ServerConfig/ServiceConfiguration.cs +++ b/TestServices/ServerConfig/ServiceConfiguration.cs @@ -1,5 +1,6 @@ using System; using Microwave; +using Microwave.Discovery; namespace ServerConfig { diff --git a/TestServices/WriteService2/ApiKeyRequirement.cs b/TestServices/WriteService2/ApiKeyRequirement.cs index 24698d66..0b9008ae 100644 --- a/TestServices/WriteService2/ApiKeyRequirement.cs +++ b/TestServices/WriteService2/ApiKeyRequirement.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -namespace ReadService1 +namespace WriteService2 { public class ApiKeyRequirement : AuthorizationHandler, IAuthorizationRequirement { diff --git a/TestServices/WriteService2/Startup.cs b/TestServices/WriteService2/Startup.cs index c0391fae..47752f90 100644 --- a/TestServices/WriteService2/Startup.cs +++ b/TestServices/WriteService2/Startup.cs @@ -7,7 +7,6 @@ using Microwave; using Microwave.Persistence.MongoDb; using Microwave.UI; -using ReadService1; using ServerConfig; namespace WriteService2