diff --git a/.github/workflows/dotnet-library.yml b/.github/workflows/dotnet-library.yml index 6201ebd0..fefbcbce 100644 --- a/.github/workflows/dotnet-library.yml +++ b/.github/workflows/dotnet-library.yml @@ -1,7 +1,7 @@ name: .NET Library CI/CD env: - PRERELEASE_BRANCHES: eventsource # Comma separated list of prerelease branch names. 'alpha,rc, ...' + PRERELEASE_BRANCHES: aragorn # Comma separated list of prerelease branch names. 'alpha,rc, ...' CASCADES: "" # Comma separated list of cascading repos. 'dolittle/DotNet.SDK,...' NUGET_OUTPUT: Artifacts/NuGet COVERAGE_FOLDER: Coverage @@ -55,9 +55,18 @@ jobs: version: ${{ steps.context.outputs.current-version }} release-type: ${{ steps.context.outputs.release-type }} + - name: Create release notes + id: create-release-notes + if: ${{ steps.context.outputs.should-publish == 'true' }} + uses: dolittle/create-release-notes-action@v1 + with: + body: ${{ steps.context.outputs.pr-body }} + version: ${{ steps.increment-version.outputs.next-version }} + changelog-url: https://github.com/dolittle/DotNET.SDK/blob/master/CHANGELOG.md + output-format: msbuild - name: Create packages if: ${{ steps.context.outputs.should-publish == 'true' }} - run: dotnet pack --no-build --configuration Release -o ${{ env.NUGET_OUTPUT }} -p:PackageVersion=${{ steps.increment-version.outputs.next-version }} -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg + run: dotnet pack --configuration Release -o ${{ env.NUGET_OUTPUT }} -p:Version=${{ steps.increment-version.outputs.next-version }} -p:PackageVersion=${{ steps.increment-version.outputs.next-version }} -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -p:PackageReleaseNotes="${{ steps.create-release-notes.outputs.plaintext }}" - name: Prepend to Changelog if: ${{ steps.context.outputs.should-publish == 'true' && steps.context.outputs.release-type != 'prerelease' }} diff --git a/.gitignore b/.gitignore index aa0bde05..adf8f958 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ _site *.nuget.props .vs +.idea *.fsx_* diff --git a/DotNET.SDK.sln b/DotNET.SDK.sln index b5229bf2..2bb171fb 100644 --- a/DotNET.SDK.sln +++ b/DotNET.SDK.sln @@ -69,6 +69,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Projections", "Source\Proje EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Embeddings", "Source\Embeddings\Embeddings.csproj", "{7682BEC2-62D4-4DC7-B8E4-285BFED9807C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Extensions.Discovery", "Source\Extensions.Discovery\Extensions.Discovery.csproj", "{DDFA025E-F652-49A6-8A33-50CDA1960EDF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -454,6 +456,18 @@ Global {7682BEC2-62D4-4DC7-B8E4-285BFED9807C}.Release|x64.Build.0 = Release|Any CPU {7682BEC2-62D4-4DC7-B8E4-285BFED9807C}.Release|x86.ActiveCfg = Release|Any CPU {7682BEC2-62D4-4DC7-B8E4-285BFED9807C}.Release|x86.Build.0 = Release|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Debug|x64.ActiveCfg = Debug|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Debug|x64.Build.0 = Debug|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Debug|x86.ActiveCfg = Debug|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Debug|x86.Build.0 = Debug|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Release|Any CPU.Build.0 = Release|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Release|x64.ActiveCfg = Release|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Release|x64.Build.0 = Release|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Release|x86.ActiveCfg = Release|Any CPU + {DDFA025E-F652-49A6-8A33-50CDA1960EDF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {CCA92423-6099-4712-8599-46426D7895BC} = {4A67BEB1-4496-4C90-96C2-4D6E518C84C5} @@ -487,5 +501,6 @@ Global {4B3193F7-088F-42BC-B487-2184E317A56A} = {4A67BEB1-4496-4C90-96C2-4D6E518C84C5} {3215A1DA-A9E0-4939-AB25-DEEA2055FFED} = {F02DF920-7E63-4C4B-8494-8D98C34E27EF} {7682BEC2-62D4-4DC7-B8E4-285BFED9807C} = {F02DF920-7E63-4C4B-8494-8D98C34E27EF} + {DDFA025E-F652-49A6-8A33-50CDA1960EDF} = {F02DF920-7E63-4C4B-8494-8D98C34E27EF} EndGlobalSection EndGlobal diff --git a/Samples/Tutorials/Aggregates/Kitchen.cs b/Samples/Tutorials/Aggregates/Kitchen.cs index 96b6f196..a8b96575 100644 --- a/Samples/Tutorials/Aggregates/Kitchen.cs +++ b/Samples/Tutorials/Aggregates/Kitchen.cs @@ -11,7 +11,7 @@ namespace Kitchen [AggregateRoot("01ad9a9f-711f-47a8-8549-43320f782a1e")] public class Kitchen : AggregateRoot { - int _counter; + int _ingredients = 2; public Kitchen(EventSourceId eventSource) : base(eventSource) @@ -20,11 +20,12 @@ public Kitchen(EventSourceId eventSource) public void PrepareDish(string dish, string chef) { + if (_ingredients <= 0) throw new Exception("We have run out of ingredients, sorry!"); Apply(new DishPrepared(dish, chef)); - Console.WriteLine($"Kitchen Aggregate {EventSourceId} has applied {_counter} {typeof(DishPrepared)} events"); + Console.WriteLine($"Kitchen {EventSourceId} prepared a {dish}, there are {_ingredients} ingredients left."); } void On(DishPrepared @event) - => _counter++; + => _ingredients--; } } diff --git a/Samples/Tutorials/Aggregates/Program.cs b/Samples/Tutorials/Aggregates/Program.cs index 865c70b3..03ab9207 100644 --- a/Samples/Tutorials/Aggregates/Program.cs +++ b/Samples/Tutorials/Aggregates/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/csharp/ +using System.Threading.Tasks; using Dolittle.SDK; using Dolittle.SDK.Tenancy; @@ -9,7 +10,7 @@ namespace Kitchen { class Program { - public static void Main() + public static async Task Main() { var client = Client .ForMicroservice("f39b1f61-d360-4675-b859-53c05c87c0e6") @@ -17,9 +18,11 @@ public static void Main() eventTypes.Register()) .WithEventHandlers(builder => builder.RegisterEventHandler()) + .WithAggregateRoots(builder => + builder.Register()) .Build(); - client + await client .AggregateOf("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9", _ => _.ForTenant(TenantId.Development)) .Perform(kitchen => kitchen.PrepareDish("Bean Blaster Taco", "Mr. Taco")); diff --git a/Samples/Tutorials/Embeddings/Program.cs b/Samples/Tutorials/Embeddings/Program.cs index 56369d9d..ee000f1c 100644 --- a/Samples/Tutorials/Embeddings/Program.cs +++ b/Samples/Tutorials/Embeddings/Program.cs @@ -3,6 +3,7 @@ // Sample code for the tutorial at https://dolittle.io/tutorials/embeddings/ using System; +using System.Linq; using System.Threading.Tasks; using Dolittle.SDK; using Dolittle.SDK.Tenancy; @@ -41,15 +42,17 @@ await client.Embeddings .Update(updatedEmployee.Name, updatedEmployee); Console.WriteLine($"Updated {updatedEmployee.Name}."); - var mrTaco = client.Embeddings + var mrTaco = await client.Embeddings .ForTenant(TenantId.Development) - .Get("Mr. Taco"); - var allEmployees = client.Embeddings - .ForTenant(TenantId.Development) - .GetAll(); - var employeeKeys = client.Embeddings + .Get("Mr. Taco") + .ConfigureAwait(false); + Console.WriteLine($"Mr. Taco is now working at {mrTaco.State.Workplace}"); + + var allEmployeeNames = await client.Embeddings .ForTenant(TenantId.Development) - .GetKeys(); + .GetKeys() + .ConfigureAwait(false); + Console.WriteLine($"All current employees are {string.Join(",", allEmployeeNames)}"); await client.Embeddings .ForTenant(TenantId.Development) @@ -60,4 +63,4 @@ await client.Embeddings await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/Samples/Tutorials/GettingStarted/Program.cs b/Samples/Tutorials/GettingStarted/Program.cs index ea361709..52f39609 100644 --- a/Samples/Tutorials/GettingStarted/Program.cs +++ b/Samples/Tutorials/GettingStarted/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/csharp/ +using System.Threading.Tasks; using Dolittle.SDK; using Dolittle.SDK.Tenancy; @@ -9,7 +10,7 @@ namespace Kitchen { class Program { - public static void Main() + public static async Task Main() { var client = Client .ForMicroservice("f39b1f61-d360-4675-b859-53c05c87c0e6") @@ -21,15 +22,15 @@ public static void Main() var preparedTaco = new DishPrepared("Bean Blaster Taco", "Mr. Taco"); - client.EventStore + await client.EventStore .ForTenant(TenantId.Development) .Commit(eventsBuilder => eventsBuilder .CreateEvent(preparedTaco) - .FromEventSource("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9")); + .FromEventSource("Dolittle Tacos")); // Blocks until the EventHandlers are finished, i.e. forever client.Start().Wait(); } } -} +} \ No newline at end of file diff --git a/Samples/Tutorials/Projections/Program.cs b/Samples/Tutorials/Projections/Program.cs index 2379b69d..cf8f5bcc 100644 --- a/Samples/Tutorials/Projections/Program.cs +++ b/Samples/Tutorials/Projections/Program.cs @@ -17,8 +17,6 @@ public async static Task Main() .ForMicroservice("f39b1f61-d360-4675-b859-53c05c87c0e6") .WithEventTypes(eventTypes => eventTypes.Register()) - .WithEventHandlers(builder => - builder.RegisterEventHandler()) .WithProjections(builder => { builder.RegisterProjection(); @@ -38,19 +36,19 @@ public async static Task Main() await eventStore.Commit(_ => _.CreateEvent(new DishPrepared("Bean Blaster Taco", "Mr. Taco")) - .FromEventSource("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9")) + .FromEventSource("Dolittle Tacos")) .ConfigureAwait(false); await eventStore.Commit(_ => _.CreateEvent(new DishPrepared("Bean Blaster Taco", "Mrs. Tex Mex")) - .FromEventSource("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9")) + .FromEventSource("Dolittle Tacos")) .ConfigureAwait(false); await eventStore.Commit(_ => _.CreateEvent(new DishPrepared("Avocado Artillery Tortilla", "Mr. Taco")) - .FromEventSource("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9")) + .FromEventSource("Dolittle Tacos")) .ConfigureAwait(false); await eventStore.Commit(_ => _.CreateEvent(new DishPrepared("Chili Canon Wrap", "Mrs. Tex Mex")) - .FromEventSource("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9")) + .FromEventSource("Dolittle Tacos")) .ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); diff --git a/Samples/WithAllEventHandlers/Events/DishPrepared.cs b/Samples/WithAllEventHandlers/Events/DishPrepared.cs new file mode 100644 index 00000000..12183673 --- /dev/null +++ b/Samples/WithAllEventHandlers/Events/DishPrepared.cs @@ -0,0 +1,21 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/csharp/ + +using Dolittle.SDK.Events; + +namespace Events +{ + [EventType("1844473f-d714-4327-8b7f-5b3c2bdfc26a")] + public class DishPrepared + { + public DishPrepared(string dish, string chef) + { + Dish = dish; + Chef = chef; + } + + public string Dish { get; } + public string Chef { get; } + } +} diff --git a/Samples/WithAllEventHandlers/Events/Events.csproj b/Samples/WithAllEventHandlers/Events/Events.csproj new file mode 100644 index 00000000..00e59765 --- /dev/null +++ b/Samples/WithAllEventHandlers/Events/Events.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/Samples/Tutorials/Projections/DishHandler.cs b/Samples/WithAllEventHandlers/Handlers/DishHandler.cs similarity index 79% rename from Samples/Tutorials/Projections/DishHandler.cs rename to Samples/WithAllEventHandlers/Handlers/DishHandler.cs index 370d7cbe..6e1123ba 100644 --- a/Samples/Tutorials/Projections/DishHandler.cs +++ b/Samples/WithAllEventHandlers/Handlers/DishHandler.cs @@ -1,17 +1,17 @@ // Copyright (c) Dolittle. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -// Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/projections/ +// Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/csharp/ using System; using Dolittle.SDK.Events; using Dolittle.SDK.Events.Handling; -namespace Kitchen +namespace Handlers { [EventHandler("f2d366cf-c00a-4479-acc4-851e04b6fbba")] public class DishHandler { - public void Handle(DishPrepared @event, EventContext eventContext) + public void Handle(Events.DishPrepared @event, EventContext eventContext) { Console.WriteLine($"{@event.Chef} has prepared {@event.Dish}. Yummm!"); } diff --git a/Samples/WithAllEventHandlers/Handlers/Handlers.csproj b/Samples/WithAllEventHandlers/Handlers/Handlers.csproj new file mode 100644 index 00000000..94300409 --- /dev/null +++ b/Samples/WithAllEventHandlers/Handlers/Handlers.csproj @@ -0,0 +1,12 @@ + + + + net5.0 + + + + + + + + diff --git a/Samples/WithAllEventHandlers/Head/Head.csproj b/Samples/WithAllEventHandlers/Head/Head.csproj new file mode 100644 index 00000000..8608e1ff --- /dev/null +++ b/Samples/WithAllEventHandlers/Head/Head.csproj @@ -0,0 +1,14 @@ + + + + Exe + net5.0 + + + + + + + + + diff --git a/Samples/WithAllEventHandlers/Head/Program.cs b/Samples/WithAllEventHandlers/Head/Program.cs new file mode 100644 index 00000000..5c5a50ec --- /dev/null +++ b/Samples/WithAllEventHandlers/Head/Program.cs @@ -0,0 +1,35 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/csharp/ + +using Dolittle.SDK; +using Dolittle.SDK.Tenancy; +using Handlers; + +namespace Kitchen +{ + class Program + { + public static void Main() + { + var client = Client + .ForMicroservice("f39b1f61-d360-4675-b859-53c05c87c0e6") + .WithEventTypes(eventTypes => + eventTypes.Register()) + .WithEventHandlers() + .Build(); + + var preparedTaco = new Events.DishPrepared("Bean Blaster Taco", "Mr. Taco"); + client.EventStore + .ForTenant(TenantId.Development) + .Commit(eventsBuilder => + eventsBuilder + .CreateEvent(preparedTaco) + .FromEventSource("bfe6f6e4-ada2-4344-8a3b-65a3e1fe16e9")); + + // Blocks until the EventHandlers are finished, i.e. forever + client.Start().Wait(); + System.Console.WriteLine("Done waiting"); + } + } +} diff --git a/Source/Aggregates/AggregateOf.cs b/Source/Aggregates/AggregateOf.cs index 1235c08f..2466be88 100644 --- a/Source/Aggregates/AggregateOf.cs +++ b/Source/Aggregates/AggregateOf.cs @@ -1,10 +1,7 @@ // Copyright (c) Dolittle. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using System.Linq; -using System.Reflection; -using System.Threading; +using Dolittle.SDK.Aggregates.Internal; using Dolittle.SDK.Events; using Dolittle.SDK.Events.Store; using Microsoft.Extensions.Logging; @@ -20,6 +17,7 @@ public class AggregateOf : IAggregateOf { readonly IEventStore _eventStore; readonly IEventTypes _eventTypes; + readonly IAggregateRoots _aggregateRoots; readonly ILoggerFactory _loggerFactory; readonly ILogger _logger; @@ -28,10 +26,12 @@ public class AggregateOf : IAggregateOf /// /// The . /// The . + /// The . /// The . - public AggregateOf(IEventStore eventStore, IEventTypes eventTypes, ILoggerFactory loggerFactory) + public AggregateOf(IEventStore eventStore, IEventTypes eventTypes, IAggregateRoots aggregateRoots, ILoggerFactory loggerFactory) { _eventTypes = eventTypes; + _aggregateRoots = aggregateRoots; _eventStore = eventStore; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger>(); @@ -43,111 +43,11 @@ public IAggregateRootOperations Create() /// public IAggregateRootOperations Get(EventSourceId eventSourceId) - { - if (TryGetAggregateRoot(eventSourceId, out var aggregateRoot, out var exception)) - { - ReApplyEvents(aggregateRoot); - return new AggregateRootOperations( - _eventStore, - aggregateRoot, - _eventTypes, - _loggerFactory.CreateLogger>()); - } - - throw new CouldNotGetAggregateRoot(typeof(TAggregateRoot), eventSourceId, exception.Message); - } - - bool TryGetAggregateRoot(EventSourceId eventSourceId, out TAggregateRoot aggregateRoot, out Exception exception) - { - try - { - exception = default; - _logger.LogDebug( - "Getting aggregate root {AggregateRoot} with event source id {EventSource}", - typeof(TAggregateRoot), - eventSourceId); - aggregateRoot = CreateAggregateRoot(eventSourceId); - return true; - } - catch (Exception ex) - { - aggregateRoot = default; - exception = ex; - return false; - } - } - - void ReApplyEvents(TAggregateRoot aggregateRoot) - { - var eventSourceId = aggregateRoot.EventSourceId; - var aggregateRootId = aggregateRoot.GetAggregateRootId(); - _logger.LogDebug( - "Re-applying events for {AggregateRoot} with aggregate root id {AggregateRootId} with event source id {EventSourceId}", - typeof(TAggregateRoot), - aggregateRootId, - eventSourceId); - - var committedEvents = _eventStore.FetchForAggregate(aggregateRootId, eventSourceId, CancellationToken.None).GetAwaiter().GetResult(); - if (committedEvents.HasEvents) - { - _logger.LogTrace("Re-applying {NumberOfEvents} events", committedEvents.Count); - aggregateRoot.ReApply(committedEvents); - } - else - { - _logger.LogTrace("No events to re-apply"); - } - } - - TAggregateRoot CreateAggregateRoot(EventSourceId eventSourceId) - { - var aggregateRootType = typeof(TAggregateRoot); - ThrowIfInvalidConstructor(aggregateRootType); - var constructor = typeof(TAggregateRoot).GetConstructors().Single(); - - var aggregateRoot = GetInstanceFrom(eventSourceId, constructor); - ThrowIfCouldNotCreateAggregateRoot(aggregateRoot); - return aggregateRoot; - } - - TAggregateRoot GetInstanceFrom(EventSourceId id, ConstructorInfo constructor) - => constructor.Invoke( - new object[] - { - id - }) as TAggregateRoot; - - void ThrowIfInvalidConstructor(Type type) - { - ThrowIfNotOneConstructor(type); - ThrowIfConstructorIsInvalid(type, type.GetConstructors().Single()); - } - - void ThrowIfNotOneConstructor(Type type) - { - if (type.GetConstructors().Length != 1) - throw new InvalidAggregateRootConstructorSignature(type, "expected only a single constructor"); - } - - void ThrowIfConstructorIsInvalid(Type type, ConstructorInfo constructor) - { - var parameters = constructor.GetParameters(); - ThrowIfIncorrectParameter(type, parameters); - } - - void ThrowIfIncorrectParameter(Type type, ParameterInfo[] parameters) - { - if (parameters.Length != 1 || - (parameters[0].ParameterType != typeof(Guid) && - parameters[0].ParameterType != typeof(EventSourceId))) - { - throw new InvalidAggregateRootConstructorSignature(type, $"expected only one parameter and it must be of type {typeof(Guid)} or {typeof(EventSourceId)}"); - } - } - - void ThrowIfCouldNotCreateAggregateRoot(TAggregateRoot aggregateRoot) - { - if (aggregateRoot == default) throw new CouldNotCreateAggregateRootInstance(typeof(TAggregateRoot)); - } + => new AggregateRootOperations( + eventSourceId, + _eventStore, + _eventTypes, + _aggregateRoots, + _loggerFactory.CreateLogger>()); } } diff --git a/Source/Aggregates/AggregateRoot.cs b/Source/Aggregates/AggregateRoot.cs index 08dae032..795c80fc 100644 --- a/Source/Aggregates/AggregateRoot.cs +++ b/Source/Aggregates/AggregateRoot.cs @@ -1,7 +1,9 @@ // Copyright (c) Dolittle. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; +using System.Threading; using Dolittle.SDK.Artifacts; using Dolittle.SDK.Events; using Dolittle.SDK.Events.Store; @@ -43,7 +45,7 @@ public AggregateRoot(EventSourceId eventSourceId) /// /// Apply the event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -52,7 +54,7 @@ public void Apply(object @event) /// /// Apply the public event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -61,7 +63,7 @@ public void ApplyPublic(object @event) /// /// Apply the event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -71,7 +73,7 @@ public void Apply(object @event, EventTypeId eventTypeId) /// /// Apply the public event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -81,7 +83,7 @@ public void ApplyPublic(object @event, EventTypeId eventTypeId) /// /// Apply the event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -92,7 +94,7 @@ public void Apply(object @event, EventTypeId eventTypeId, Generation generation) /// /// Apply the public event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -103,7 +105,7 @@ public void ApplyPublic(object @event, EventTypeId eventTypeId, Generation gener /// /// Apply the event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. @@ -113,7 +115,7 @@ public void Apply(object @event, EventType eventType) /// /// Apply the public event to the so that it will be committed to the - /// when is invoked on the . + /// when is invoked on the . /// /// The state of the is changed by calling the appropriate On-methods for the applied events. /// The event to apply. diff --git a/Source/Aggregates/AggregateRootAlias.cs b/Source/Aggregates/AggregateRootAlias.cs new file mode 100644 index 00000000..56fc6695 --- /dev/null +++ b/Source/Aggregates/AggregateRootAlias.cs @@ -0,0 +1,19 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.SDK.Concepts; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Represents the alias of an event type. + /// + public class AggregateRootAlias : ConceptAs + { + /// + /// Implicitly convert from a to an . + /// + /// AggregateRootAlias as . + public static implicit operator AggregateRootAlias(string alias) => new AggregateRootAlias { Value = alias }; + } +} \ No newline at end of file diff --git a/Source/Aggregates/AggregateRootAttribute.cs b/Source/Aggregates/AggregateRootAttribute.cs index c9ffeaf9..5d046425 100644 --- a/Source/Aggregates/AggregateRootAttribute.cs +++ b/Source/Aggregates/AggregateRootAttribute.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using Dolittle.SDK.Artifacts; using Dolittle.SDK.Events; namespace Dolittle.SDK.Aggregates @@ -16,11 +17,15 @@ public class AggregateRootAttribute : Attribute /// Initializes a new instance of the class. /// /// The unique identifier. - public AggregateRootAttribute(string id) => Id = id; + /// The alias for the aggregate root. + public AggregateRootAttribute(string id, string alias = default) + { + Type = new AggregateRootType(id, Generation.First, alias); + } /// - /// Gets the . + /// Gets the . /// - public AggregateRootId Id { get; } + public AggregateRootType Type { get; } } } \ No newline at end of file diff --git a/Source/Aggregates/AggregateRootExtensions.cs b/Source/Aggregates/AggregateRootExtensions.cs index 05bdfab1..117e6328 100644 --- a/Source/Aggregates/AggregateRootExtensions.cs +++ b/Source/Aggregates/AggregateRootExtensions.cs @@ -46,7 +46,7 @@ public static AggregateRootId GetAggregateRootId(this AggregateRoot aggregateRoo var aggregateRootType = aggregateRoot.GetType(); var aggregateRootAttribute = aggregateRootType.GetCustomAttribute(); if (aggregateRootAttribute == null) throw new MissingAggregateRootAttribute(aggregateRootType); - return aggregateRootAttribute.Id; + return aggregateRootAttribute.Type.Id; } static Dictionary GetHandleMethodsFor(Type aggregateRootType) diff --git a/Source/Aggregates/AggregateRootIdCannotBeNull.cs b/Source/Aggregates/AggregateRootIdCannotBeNull.cs new file mode 100644 index 00000000..f4cf29d0 --- /dev/null +++ b/Source/Aggregates/AggregateRootIdCannotBeNull.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Dolittle.SDK.Events; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Exception that gets thrown when trying to construct an without an . + /// + public class AggregateRootIdCannotBeNull : Exception + { + /// + /// Initializes a new instance of the class. + /// + public AggregateRootIdCannotBeNull() + : base($"The {nameof(AggregateRootId)} of an {nameof(AggregateRootType)} cannot be null") + { + } + } +} \ No newline at end of file diff --git a/Source/Aggregates/AggregateRootOperationAlreadyPerformed.cs b/Source/Aggregates/AggregateRootOperationAlreadyPerformed.cs deleted file mode 100644 index c4060af5..00000000 --- a/Source/Aggregates/AggregateRootOperationAlreadyPerformed.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using Dolittle.SDK.Events; - -namespace Dolittle.SDK.Aggregates -{ - /// - /// Exception that gets thrown when is called twice - /// on the same instance of . - /// - public class AggregateRootOperationAlreadyPerformed : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// The of the aggregate root. - /// The . - /// The . - public AggregateRootOperationAlreadyPerformed(Type type, AggregateRootId aggregateRootId, EventSourceId eventSourceId) - : base($"Perform called twice on aggregate root {type} with id {aggregateRootId} and event source id {eventSourceId}") - { - } - } -} \ No newline at end of file diff --git a/Source/Aggregates/AggregateRootOperations.cs b/Source/Aggregates/AggregateRootOperations.cs index 4b8b4a38..2d254d30 100644 --- a/Source/Aggregates/AggregateRootOperations.cs +++ b/Source/Aggregates/AggregateRootOperations.cs @@ -3,7 +3,10 @@ using System; using System.Linq; +using System.Reflection; +using System.Threading; using System.Threading.Tasks; +using Dolittle.SDK.Aggregates.Internal; using Dolittle.SDK.Events; using Dolittle.SDK.Events.Builders; using Dolittle.SDK.Events.Store; @@ -19,71 +22,111 @@ namespace Dolittle.SDK.Aggregates public class AggregateRootOperations : IAggregateRootOperations where TAggregate : AggregateRoot { - readonly TAggregate _aggregateRoot; + readonly EventSourceId _eventSourceId; readonly IEventStore _eventStore; - readonly ILogger _logger; readonly IEventTypes _eventTypes; - bool _performed; + readonly IAggregateRoots _aggregateRoots; + readonly ILogger _logger; /// /// Initializes a new instance of the class. /// - /// - /// The used for committing the - /// when actions are performed on the aggregate. - /// - /// the operations are for. + /// The of the aggregate root instance. + /// The used for committing the when actions are performed on the aggregate. /// The . + /// The used for getting an aggregate root instance. /// The . - public AggregateRootOperations(IEventStore eventStore, TAggregate aggregateRoot, IEventTypes eventTypes, ILogger logger) + public AggregateRootOperations(EventSourceId eventSourceId, IEventStore eventStore, IEventTypes eventTypes, IAggregateRoots aggregateRoots, ILogger logger) { + _eventSourceId = eventSourceId; _eventTypes = eventTypes; - _aggregateRoot = aggregateRoot; _eventStore = eventStore; + _aggregateRoots = aggregateRoots; _logger = logger; } /// - public Task Perform(Action method) - => Perform(aggregate => + public Task Perform(Action method, CancellationToken cancellationToken) + => Perform( + aggregate => { method(aggregate); return Task.CompletedTask; - }); + }, + cancellationToken); /// - public async Task Perform(Func method) + public async Task Perform(Func method, CancellationToken cancellationToken) { - var aggregateRootId = _aggregateRoot.GetAggregateRootId(); + if (!TryGetAggregateRoot(_eventSourceId, out var aggregateRoot, out var exception)) + { + throw new CouldNotGetAggregateRoot(typeof(TAggregate), _eventSourceId, exception.Message); + } + + var aggregateRootId = aggregateRoot.GetAggregateRootId(); + await ReApplyEvents(aggregateRoot, aggregateRootId, cancellationToken).ConfigureAwait(false); + _logger.LogDebug( "Performing operation on {AggregateRoot} with aggregate root id {AggregateRootId} applying events to event source {EventSource}", - _aggregateRoot.GetType(), + aggregateRoot.GetType(), + aggregateRootId, + aggregateRoot.EventSourceId); + await method(aggregateRoot).ConfigureAwait(false); + + if (aggregateRoot.AppliedEvents.Any()) + { + await CommitAppliedEvents(aggregateRoot, aggregateRootId).ConfigureAwait(false); + } + } + + bool TryGetAggregateRoot(EventSourceId eventSourceId, out TAggregate aggregateRoot, out Exception exception) + { + aggregateRoot = default; + var getAggregateRoot = _aggregateRoots.TryGet(eventSourceId); + aggregateRoot = getAggregateRoot.Result; + exception = getAggregateRoot.Exception; + return getAggregateRoot.Success; + } + + async Task ReApplyEvents(TAggregate aggregateRoot, AggregateRootId aggregateRootId, CancellationToken cancellationToken) + { + var eventSourceId = aggregateRoot.EventSourceId; + _logger.LogDebug( + "Re-applying events for {AggregateRoot} with aggregate root id {AggregateRootId} with event source id {EventSourceId}", + typeof(TAggregate), aggregateRootId, - _aggregateRoot.EventSourceId); - if (_performed) throw new AggregateRootOperationAlreadyPerformed(typeof(TAggregate), aggregateRootId, _aggregateRoot.EventSourceId); - _performed = true; - await method(_aggregateRoot).ConfigureAwait(false); - if (_aggregateRoot.AppliedEvents.Any()) await CommitAppliedEvents(aggregateRootId).ConfigureAwait(false); + eventSourceId); + + var committedEvents = await _eventStore.FetchForAggregate(aggregateRootId, eventSourceId, cancellationToken).ConfigureAwait(false); + if (committedEvents.HasEvents) + { + _logger.LogTrace("Re-applying {NumberOfEvents} events", committedEvents.Count); + aggregateRoot.ReApply(committedEvents); + } + else + { + _logger.LogTrace("No events to re-apply"); + } } - Task CommitAppliedEvents(AggregateRootId aggregateRootId) + Task CommitAppliedEvents(TAggregate aggregateRoot, AggregateRootId aggregateRootId) { _logger.LogDebug( "{AggregateRoot} with aggregate root id {AggregateRootId} is committing {NumberOfEvents} events to event source {EventSource}", - _aggregateRoot.GetType(), + aggregateRoot.GetType(), aggregateRootId, - _aggregateRoot.AppliedEvents.Count(), - _aggregateRoot.EventSourceId); + aggregateRoot.AppliedEvents.Count(), + aggregateRoot.EventSourceId); return _eventStore .ForAggregate(aggregateRootId) - .WithEventSource(_aggregateRoot.EventSourceId) - .ExpectVersion(_aggregateRoot.Version.Value - (ulong)_aggregateRoot.AppliedEvents.Count()) - .Commit(CreateUncommittedEvents); + .WithEventSource(aggregateRoot.EventSourceId) + .ExpectVersion(aggregateRoot.Version.Value - (ulong)aggregateRoot.AppliedEvents.Count()) + .Commit(builder => CreateUncommittedEvents(builder, aggregateRoot)); } - void CreateUncommittedEvents(UncommittedAggregateEventsBuilder builder) + void CreateUncommittedEvents(UncommittedAggregateEventsBuilder builder, TAggregate aggregateRoot) { - foreach (var appliedEvent in _aggregateRoot.AppliedEvents) + foreach (var appliedEvent in aggregateRoot.AppliedEvents) { var uncommittedEvent = ToUncommittedEvent(appliedEvent); var eventBuilder = uncommittedEvent.IsPublic ? @@ -97,19 +140,30 @@ UncommittedAggregateEvent ToUncommittedEvent(AppliedEvent appliedEvent) { var @event = appliedEvent.Event; var eventType = appliedEvent.EventType; - if (appliedEvent.HasEventType) ThrowIfWrongEventType(@event, eventType); - else eventType = _eventTypes.GetFor(@event.GetType()); + if (appliedEvent.HasEventType) + { + ThrowIfWrongEventType(@event, eventType); + } + else + { + eventType = _eventTypes.GetFor(@event.GetType()); + } + return new UncommittedAggregateEvent(eventType, @event, appliedEvent.Public); } void ThrowIfWrongEventType(object @event, EventType eventType) { var typeOfEvent = @event.GetType(); - if (_eventTypes.HasFor(typeOfEvent)) + if (!_eventTypes.HasFor(typeOfEvent)) + { + return; + } + + var associatedEventType = _eventTypes.GetFor(typeOfEvent); + if (eventType != associatedEventType) { - var associatedEventType = _eventTypes.GetFor(typeOfEvent); - if (eventType != associatedEventType) - throw new ProvidedEventTypeDoesNotMatchEventTypeFromAttribute(eventType, associatedEventType, typeOfEvent); + throw new ProvidedEventTypeDoesNotMatchEventTypeFromAttribute(eventType, associatedEventType, typeOfEvent); } } } diff --git a/Source/Aggregates/AggregateRootType.cs b/Source/Aggregates/AggregateRootType.cs new file mode 100644 index 00000000..e7c7a50f --- /dev/null +++ b/Source/Aggregates/AggregateRootType.cs @@ -0,0 +1,79 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.SDK.Artifacts; +using Dolittle.SDK.Events; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Represents the type of an event. + /// + public class AggregateRootType : Artifact + { + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the . + public AggregateRootType(AggregateRootId id) + : this(id, alias: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the . + /// Alias of the . + public AggregateRootType(AggregateRootId id, AggregateRootAlias alias) + : base(id) + { + ThrowIfAggregateRootTypeIdIsNull(id); + Alias = alias; + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the . + /// Generation of the . + public AggregateRootType(AggregateRootId id, Generation generation) + : this(id, generation, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the . + /// Generation of the . + /// Alias of the . + public AggregateRootType(AggregateRootId id, Generation generation, AggregateRootAlias alias) + : base(id, generation) + { + ThrowIfAggregateRootTypeIdIsNull(id); + ThrowIfGenerationIsNull(generation); + Alias = alias; + } + + /// + /// Gets the alias for the Event Type. + /// + public AggregateRootAlias Alias { get; } + + /// + /// Gets a value indicating whether the Event Type has an alias or not. + /// + public bool HasAlias => Alias?.Value != default; + + static void ThrowIfAggregateRootTypeIdIsNull(AggregateRootId id) + { + if (id == null) throw new AggregateRootIdCannotBeNull(); + } + + static void ThrowIfGenerationIsNull(Generation generation) + { + if (generation == null) throw new AggregateRootTypeGenerationCannotBeNull(); + } + } +} diff --git a/Source/Aggregates/AggregateRootTypeAssociatedWithType.cs b/Source/Aggregates/AggregateRootTypeAssociatedWithType.cs new file mode 100644 index 00000000..2bb9a86b --- /dev/null +++ b/Source/Aggregates/AggregateRootTypeAssociatedWithType.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Exception that gets thrown when a does not have an association. + /// + public class AggregateRootTypeAssociatedWithType : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The that has a missing association. + public AggregateRootTypeAssociatedWithType(Type type) + : base($"{type} is not associated with an EventType") + { + } + } +} diff --git a/Source/Aggregates/AggregateRootTypeGenerationCannotBeNull.cs b/Source/Aggregates/AggregateRootTypeGenerationCannotBeNull.cs new file mode 100644 index 00000000..24876a13 --- /dev/null +++ b/Source/Aggregates/AggregateRootTypeGenerationCannotBeNull.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Dolittle.SDK.Artifacts; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Exception that gets thrown when trying to construct an with a that is null. + /// + public class AggregateRootTypeGenerationCannotBeNull : Exception + { + /// + /// Initializes a new instance of the class. + /// + public AggregateRootTypeGenerationCannotBeNull() + : base($"The {nameof(Generation)} of an {nameof(AggregateRootType)} cannot be null") + { + } + } +} \ No newline at end of file diff --git a/Source/Aggregates/AggregateRootTypes.cs b/Source/Aggregates/AggregateRootTypes.cs new file mode 100644 index 00000000..bdd71e41 --- /dev/null +++ b/Source/Aggregates/AggregateRootTypes.cs @@ -0,0 +1,41 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Dolittle.SDK.Artifacts; +using Dolittle.SDK.Events; +using Microsoft.Extensions.Logging; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Represents an implementation of . + /// + public class AggregateRootTypes : Artifacts, IAggregateRootTypes + { + /// + /// Initializes a new instance of the class. + /// + /// The . + public AggregateRootTypes(ILogger logger) + : base(logger) + { + } + + /// + protected override Exception CreateNoArtifactAssociatedWithType(Type type) + => new AggregateRootTypeAssociatedWithType(type); + + /// + protected override Exception CreateNoTypeAssociatedWithArtifact(AggregateRootType artifact) + => new NoTypeAssociatedWithAggregateRootType(artifact); + + /// + protected override Exception CreateCannotAssociateMultipleArtifactsWithType(Type type, AggregateRootType artifact, AggregateRootType existing) + => new CannotAssociateMultipleAggregateRootTypesWithType(type, artifact, existing); + + /// + protected override Exception CreateCannotAssociateMultipleTypesWithArtifact(AggregateRootType artifact, Type type, Type existing) + => new CannotAssociateMultipleTypesWithAggregateRootType(artifact, type, existing); + } +} diff --git a/Source/Aggregates/Aggregates.csproj b/Source/Aggregates/Aggregates.csproj index 698ecfda..3ac811c9 100644 --- a/Source/Aggregates/Aggregates.csproj +++ b/Source/Aggregates/Aggregates.csproj @@ -4,6 +4,7 @@ Dolittle.SDK.Aggregates + Dolittle.SDK.Aggregates @@ -12,6 +13,7 @@ + diff --git a/Source/Aggregates/Builders/AggregateRootsBuilder.cs b/Source/Aggregates/Builders/AggregateRootsBuilder.cs new file mode 100644 index 00000000..0238cf62 --- /dev/null +++ b/Source/Aggregates/Builders/AggregateRootsBuilder.cs @@ -0,0 +1,109 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Dolittle.SDK.Artifacts; +using Dolittle.SDK.Events; + +namespace Dolittle.SDK.Aggregates.Builders +{ + /// + /// Represents a system that registers to associations to an instance. + /// + public class AggregateRootsBuilder + { + readonly List<(Type, AggregateRootType)> _associations = new List<(Type, AggregateRootType)>(); + + /// + /// Associate a with the given by an attribute. + /// + /// The to associate with an . + /// The for continuation. + /// + /// The type must have a AggregateRoot attribute. + /// + public AggregateRootsBuilder Register() + where T : class + => Register(typeof(T)); + + /// + /// Associate the with the given by an attribute. + /// + /// The to associate with an . + /// The for continuation. + /// + /// The type must have a AggregateRoot attribute. + /// + public AggregateRootsBuilder Register(Type type) + { + ThrowIfTypeIsMissingAggregateRootAttribute(type); + TryGetAggregateRootTypeFromAttribute(type, out var eventType); + AddAssociation(type, eventType); + return this; + } + + /// + /// Registers all aggregate root classes from an . + /// + /// The to register the aggregate root classes from. + /// The for continuation. + public AggregateRootsBuilder RegisterAllFrom(Assembly assembly) + { + foreach (var type in assembly.ExportedTypes.Where(IsAggregateRoot)) + { + Register(type); + } + + return this; + } + + /// + /// Builds the aggregate roots by registering them with the Runtime. + /// + /// The . + /// The cancellation token. + /// A representing the asynchronous operation. + public Task BuildAndRegister(Internal.AggregateRootsClient aggregateRoots, CancellationToken cancellationToken) + => aggregateRoots.Register(_associations.Select(_ => _.Item2), cancellationToken); + + static bool IsAggregateRoot(Type type) + => type.GetCustomAttributes(typeof(AggregateRootAttribute), true).FirstOrDefault() is AggregateRootAttribute; + + static bool TryGetAggregateRootTypeFromAttribute(Type type, out AggregateRootType aggregateRootType) + { + if (Attribute.GetCustomAttribute(type, typeof(AggregateRootAttribute)) is AggregateRootAttribute attribute) + { + if (!attribute.Type.HasAlias) + { + aggregateRootType = new AggregateRootType(attribute.Type.Id, attribute.Type.Generation, type.Name); + return true; + } + + aggregateRootType = attribute.Type; + return true; + } + + aggregateRootType = default; + return false; + } + + void AddAssociation(Type type, AggregateRootType aggregateRootType) + { + _associations.Add((type, aggregateRootType)); + } + + void ThrowIfTypeIsMissingAggregateRootAttribute(Type type) + { + if (!TryGetAggregateRootTypeFromAttribute(type, out _)) + { + throw new TypeIsMissingAggregateRootAttribute(type); + } + } + } +} diff --git a/Source/Aggregates/Builders/TypeIsMissingAggregateRootAttribute.cs b/Source/Aggregates/Builders/TypeIsMissingAggregateRootAttribute.cs new file mode 100644 index 00000000..1ffc9c1f --- /dev/null +++ b/Source/Aggregates/Builders/TypeIsMissingAggregateRootAttribute.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Dolittle.SDK.Aggregates.Builders +{ + /// + /// Exception that gets thrown when an event type is missing an . + /// + public class TypeIsMissingAggregateRootAttribute : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The event type . + public TypeIsMissingAggregateRootAttribute(Type type) + : base($"{type} is missing the [AggregateRoot(...)] attribute") + { + } + } +} \ No newline at end of file diff --git a/Source/Aggregates/CannotAssociateMultipleAggregateRootTypesWithType.cs b/Source/Aggregates/CannotAssociateMultipleAggregateRootTypesWithType.cs new file mode 100644 index 00000000..32c032ef --- /dev/null +++ b/Source/Aggregates/CannotAssociateMultipleAggregateRootTypesWithType.cs @@ -0,0 +1,24 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Exception that gets thrown when attempting to associate multiple instance of with a single . + /// + public class CannotAssociateMultipleAggregateRootTypesWithType : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The that was attempted to associate with a . + /// The that was attempted to associate with. + /// The that the was already associated with. + public CannotAssociateMultipleAggregateRootTypesWithType(Type type, AggregateRootType aggregateRootType, AggregateRootType existing) + : base($"{type} cannot be associated with {aggregateRootType} because it is already associated with {existing}") + { + } + } +} diff --git a/Source/Aggregates/CannotAssociateMultipleTypesWithAggregateRootType.cs b/Source/Aggregates/CannotAssociateMultipleTypesWithAggregateRootType.cs new file mode 100644 index 00000000..58a61577 --- /dev/null +++ b/Source/Aggregates/CannotAssociateMultipleTypesWithAggregateRootType.cs @@ -0,0 +1,24 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Exception that gets thrown when attempting to associate multiple instance of with a single . + /// + public class CannotAssociateMultipleTypesWithAggregateRootType : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The that was attempted to associate with a . + /// The that was attempted to associate with. + /// The that the was already associated with. + public CannotAssociateMultipleTypesWithAggregateRootType(AggregateRootType aggregateRootType, Type type, Type existing) + : base($"{aggregateRootType} cannot be associated with {type} because it is already associated with {existing}") + { + } + } +} diff --git a/Source/Aggregates/IAggregateRootOperations.cs b/Source/Aggregates/IAggregateRootOperations.cs index 67011d1a..27d8772b 100644 --- a/Source/Aggregates/IAggregateRootOperations.cs +++ b/Source/Aggregates/IAggregateRootOperations.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Threading; using System.Threading.Tasks; namespace Dolittle.SDK.Aggregates @@ -18,14 +19,16 @@ public interface IAggregateRootOperations /// Perform an operation on an . /// /// Method to perform. + /// Token that can be used to cancel this operation. /// The representing the asynchronous operation. - Task Perform(Action method); + Task Perform(Action method, CancellationToken cancellationToken = default); /// /// Perform an asynchronous operation on an . /// /// Method to perform. + /// Token that can be used to cancel this operation. /// The representing the asynchronous operation. - Task Perform(Func method); + Task Perform(Func method, CancellationToken cancellationToken = default); } } diff --git a/Source/Aggregates/IAggregateRootTypes.cs b/Source/Aggregates/IAggregateRootTypes.cs new file mode 100644 index 00000000..90794862 --- /dev/null +++ b/Source/Aggregates/IAggregateRootTypes.cs @@ -0,0 +1,15 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.SDK.Artifacts; +using Dolittle.SDK.Events; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Defines a system that knows about . + /// + public interface IAggregateRootTypes : IArtifacts + { + } +} diff --git a/Source/Aggregates/Internal/AggregateRoots.cs b/Source/Aggregates/Internal/AggregateRoots.cs new file mode 100644 index 00000000..c54ecd6a --- /dev/null +++ b/Source/Aggregates/Internal/AggregateRoots.cs @@ -0,0 +1,119 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using System.Reflection; +using Dolittle.SDK.Async; +using Dolittle.SDK.Events; +using Microsoft.Extensions.Logging; + +namespace Dolittle.SDK.Aggregates.Internal +{ + /// + /// Represents an implementation of . + /// + public class AggregateRoots : IAggregateRoots + { + readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public AggregateRoots(ILogger logger) + { + _logger = logger; + } + + /// + public Try TryGet(EventSourceId eventSourceId) + where TAggregate : AggregateRoot + { + _logger.LogDebug( + "Getting aggregate root {AggregateRoot} with event source id {EventSource}", + typeof(TAggregate), + eventSourceId); + if (InvalidConstructor(typeof(TAggregate), out var constructor, out var exception)) + { + return exception; + } + + return CreateInstance(eventSourceId, constructor); + } + + static Try CreateInstance(EventSourceId eventSourceId, ConstructorInfo constructor) + where TAggregate : AggregateRoot + { + var aggregateRoot = constructor.Invoke(new object[] { eventSourceId }) as TAggregate; + if (CouldNotCreateAggregateRoot(aggregateRoot, out var exception)) + { + return exception; + } + + return aggregateRoot; + } + + static bool InvalidConstructor(Type type, out ConstructorInfo constructor, out Exception ex) + { + constructor = default; + if (NotOnlyOneConstructor(type, out ex)) + { + return true; + } + + constructor = type.GetConstructors().Single(); + return ConstructorIsInvalid(type, constructor, out ex); + } + + static bool NotOnlyOneConstructor(Type type, out Exception ex) + { + ex = default; + if (ThereIsOnlyOneConstructor(type)) + { + return false; + } + + ex = new InvalidAggregateRootConstructorSignature(type, "expected only a single constructor"); + return true; + } + + static bool ConstructorIsInvalid(Type type, ConstructorInfo constructor, out Exception ex) + { + ex = default; + return IncorrectParameter(type, constructor.GetParameters(), out ex); + } + + static bool IncorrectParameter(Type type, ParameterInfo[] parameters, out Exception ex) + { + ex = default; + if (ThereIsOnlyOneParameter(parameters) && ParameterTypeIsGuidOrEventSourceId(parameters)) + { + return false; + } + + ex = new InvalidAggregateRootConstructorSignature(type, $"expected only one parameter and it must be of type {typeof(Guid)} or {typeof(EventSourceId)}"); + return true; + } + + static bool CouldNotCreateAggregateRoot(TAggregate aggregateRoot, out Exception ex) + where TAggregate : AggregateRoot + { + ex = default; + if (aggregateRoot != default) + { + return false; + } + + ex = new CouldNotCreateAggregateRootInstance(typeof(TAggregate)); + return true; + } + + static bool ThereIsOnlyOneConstructor(Type type) => type.GetConstructors().Length == 1; + + static bool ThereIsOnlyOneParameter(ParameterInfo[] parameters) => parameters.Length == 1; + + static bool ParameterTypeIsGuidOrEventSourceId(ParameterInfo[] parameters) + => parameters[0].ParameterType == typeof(Guid) || parameters[0].ParameterType == typeof(EventSourceId); + } +} \ No newline at end of file diff --git a/Source/Aggregates/Internal/AggregateRootsClient.cs b/Source/Aggregates/Internal/AggregateRootsClient.cs new file mode 100644 index 00000000..27b39666 --- /dev/null +++ b/Source/Aggregates/Internal/AggregateRootsClient.cs @@ -0,0 +1,91 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Dolittle.Runtime.Aggregates.Contracts; +using Dolittle.SDK.Execution; +using Dolittle.SDK.Protobuf; +using Dolittle.SDK.Services; +using Dolittle.Services.Contracts; +using Microsoft.Extensions.Logging; +using ExecutionContext = Dolittle.SDK.Execution.ExecutionContext; + +namespace Dolittle.SDK.Aggregates.Internal +{ + /// + /// Represents a system that knows how to register aggregate roots with the Runtime. + /// + public class AggregateRootsClient + { + static readonly AggregateRootsRegisterAliasMethod _aliasMethod = new AggregateRootsRegisterAliasMethod(); + readonly IPerformMethodCalls _caller; + readonly ExecutionContext _executionContext; + readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The method caller to use to perform calls to the Runtime. + /// Tha base . + /// The to use. + public AggregateRootsClient(IPerformMethodCalls caller, ExecutionContext executionContext, ILogger logger) + { + _caller = caller; + _executionContext = executionContext; + _logger = logger; + } + + /// + /// Registers event types. + /// + /// The aggregate root types to register. + /// The . + /// A representing the asynchronous operation. + public Task Register(IEnumerable aggregateRootTypes, CancellationToken cancellationToken) + => Task.WhenAll(aggregateRootTypes.Select(CreateRequest).Select(_ => Register(_, cancellationToken))); + + AggregateRootAliasRegistrationRequest CreateRequest(AggregateRootType aggregateRootType) + => new AggregateRootAliasRegistrationRequest + { + Alias = aggregateRootType.HasAlias ? aggregateRootType.Alias : null, + AggregateRoot = aggregateRootType.ToProtobuf(), + CallContext = new CallRequestContext + { + ExecutionContext = _executionContext.ToProtobuf(), + HeadId = HeadId.NotSet.ToProtobuf() + } + }; + + async Task Register(AggregateRootAliasRegistrationRequest request, CancellationToken cancellationToken) + { + _logger.LogDebug( + "Registering Aggregate Root {AggregateRoot} with Alias {Alias}", + request.AggregateRoot.Id.ToGuid(), + request.Alias); + try + { + var response = await _caller.Call(_aliasMethod, request, cancellationToken).ConfigureAwait(false); + if (response.Failure != null) + { + _logger.LogWarning( + "An error occurred while registering Aggregate Root {AggregateRoot} with Alias {Alias} because {Reason}", + request.AggregateRoot.Id.ToGuid(), + request.Alias, + response.Failure.Reason); + } + } + catch (Exception ex) + { + _logger.LogWarning( + ex, + "An error occurred while registering Aggregate Root {AggregateRoot} with Alias {Alias}", + request.AggregateRoot.Id.ToGuid(), + request.Alias); + } + } + } +} \ No newline at end of file diff --git a/Source/Aggregates/Internal/AggregateRootsRegisterAliasMethod.cs b/Source/Aggregates/Internal/AggregateRootsRegisterAliasMethod.cs new file mode 100644 index 00000000..f70779e3 --- /dev/null +++ b/Source/Aggregates/Internal/AggregateRootsRegisterAliasMethod.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.Runtime.Aggregates.Contracts; +using Dolittle.SDK.Services; +using Grpc.Core; + +namespace Dolittle.SDK.Aggregates.Internal +{ + /// + /// Represents a wrapper for gRPC AggregateRoots.RegisterAlias. + /// + public class AggregateRootsRegisterAliasMethod : ICanCallAUnaryMethod + { + /// + public AsyncUnaryCall Call(AggregateRootAliasRegistrationRequest message, Channel channel, CallOptions callOptions) + { + var client = new Dolittle.Runtime.Aggregates.Contracts.AggregateRoots.AggregateRootsClient(channel); + return client.RegisterAliasAsync(message, callOptions); + } + } +} \ No newline at end of file diff --git a/Source/Aggregates/Internal/IAggregateRoots.cs b/Source/Aggregates/Internal/IAggregateRoots.cs new file mode 100644 index 00000000..5d7ade67 --- /dev/null +++ b/Source/Aggregates/Internal/IAggregateRoots.cs @@ -0,0 +1,23 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.SDK.Async; +using Dolittle.SDK.Events; + +namespace Dolittle.SDK.Aggregates.Internal +{ + /// + /// Defines a system that knows how to create aggregate root instances. + /// + public interface IAggregateRoots + { + /// + /// Tries to get an instance for the specified . + /// + /// The of the aggregate root instance to create. + /// type. + /// A with the . + Try TryGet(EventSourceId eventSourceId) + where TAggregate : AggregateRoot; + } +} \ No newline at end of file diff --git a/Source/Aggregates/NoTypeAssociatedWithAggregateRootType.cs b/Source/Aggregates/NoTypeAssociatedWithAggregateRootType.cs new file mode 100644 index 00000000..54dc1b1f --- /dev/null +++ b/Source/Aggregates/NoTypeAssociatedWithAggregateRootType.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Dolittle.SDK.Aggregates +{ + /// + /// Exception that gets thrown when a does not have an association. + /// + public class NoTypeAssociatedWithAggregateRootType : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The that has a missing association. + public NoTypeAssociatedWithAggregateRootType(AggregateRootType aggregateRootType) + : base($"{aggregateRootType} is not associated with a Type") + { + } + } +} diff --git a/Source/Artifacts/Artifacts.cs b/Source/Artifacts/Artifacts.cs index c1f228e4..8c6f270a 100644 --- a/Source/Artifacts/Artifacts.cs +++ b/Source/Artifacts/Artifacts.cs @@ -31,6 +31,9 @@ protected Artifacts(ILogger logger) _typeToArtifactMap = new Dictionary(); } + /// + public IEnumerable All => _artifactToTypeMap.Keys; + /// public void Associate(Type type, TArtifact artifact) { diff --git a/Source/Artifacts/IArtifacts.cs b/Source/Artifacts/IArtifacts.cs index e03576ce..7f4123f1 100644 --- a/Source/Artifacts/IArtifacts.cs +++ b/Source/Artifacts/IArtifacts.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; namespace Dolittle.SDK.Artifacts { @@ -14,6 +15,11 @@ public interface IArtifacts where TArtifact : Artifact where TId : ArtifactId { + /// + /// Gets all artifacts. + /// + IEnumerable All { get; } + /// /// Check if there is a type associated with an . /// diff --git a/Source/Events.Processing/EventProcessors.cs b/Source/Events.Processing/EventProcessors.cs index 73f308f3..058bf891 100644 --- a/Source/Events.Processing/EventProcessors.cs +++ b/Source/Events.Processing/EventProcessors.cs @@ -88,7 +88,7 @@ async Task RunProcessorForeverUntilCancelled with an . /// /// The that the is associated to. + /// Alias of the Event Type. /// The that gets associated to an . /// The for building . - public EventTypesBuilder Associate(EventTypeId eventTypeId) + public EventTypesBuilder Associate(EventTypeId eventTypeId, EventTypeAlias alias = default) where T : class - => Associate(typeof(T), eventTypeId); + => Associate(typeof(T), eventTypeId, alias); /// /// Associate a with an . /// /// The to associate with an . /// The that the is associated to. + /// Alias of the Event Type. /// The for building . - public EventTypesBuilder Associate(Type type, EventTypeId eventTypeId) - => Associate(type, new EventType(eventTypeId)); + public EventTypesBuilder Associate(Type type, EventTypeId eventTypeId, EventTypeAlias alias = default) + => Associate(type, new EventType(eventTypeId, alias)); /// /// Associate a with an . /// /// The that the is associated to. /// The of the . + /// Alias of the Event Type. /// The that gets associated to an . /// The for building . - public EventTypesBuilder Associate(EventTypeId eventTypeId, Generation generation) + public EventTypesBuilder Associate(EventTypeId eventTypeId, Generation generation, EventTypeAlias alias = default) where T : class - => Associate(typeof(T), eventTypeId, generation); + => Associate(typeof(T), eventTypeId, generation, alias); /// /// Associate a with an . @@ -74,9 +79,10 @@ public EventTypesBuilder Associate(EventTypeId eventTypeId, Generation genera /// The to associate with an . /// The that the is associated to. /// The of the . + /// Alias of the Event Type. /// The for building . - public EventTypesBuilder Associate(Type type, EventTypeId eventTypeId, Generation generation) - => Associate(type, new EventType(eventTypeId, generation)); + public EventTypesBuilder Associate(Type type, EventTypeId eventTypeId, Generation generation, EventTypeAlias alias = default) + => Associate(type, new EventType(eventTypeId, generation, alias)); /// /// Associate a with the given by an attribute. @@ -121,6 +127,15 @@ public EventTypesBuilder RegisterAllFrom(Assembly assembly) return this; } + /// + /// Builds the event types by registering them with the Runtime. + /// + /// The . + /// The cancellation token. + /// A representing the asynchronous operation. + public Task BuildAndRegister(Internal.EventTypesClient eventTypes, CancellationToken cancellationToken) + => eventTypes.Register(_associations.Select(_ => _.Item2), cancellationToken); + /// /// Adds all the to associations to the provided . /// @@ -133,24 +148,10 @@ public void AddAssociationsInto(IEventTypes eventTypes) } } - bool IsEventType(Type type) - => type.GetCustomAttributes(typeof(EventTypeAttribute), true).FirstOrDefault() as EventTypeAttribute != default; - - void AddAssociation(Type type, EventType eventType) - { - ThrowIfAttributeSpecifiesADifferentEventType(type, eventType); - _associations.Add((type, eventType)); - } - - void ThrowIfTypeIsMissingEventTypeAttribute(Type type) - { - if (!TryGetEventTypeFromAttribute(type, out _)) - { - throw new TypeIsMissingEventTypeAttribute(type); - } - } + static bool IsEventType(Type type) + => type.GetCustomAttributes(typeof(EventTypeAttribute), true).FirstOrDefault() is EventTypeAttribute; - void ThrowIfAttributeSpecifiesADifferentEventType(Type type, EventType providedType) + static void ThrowIfAttributeSpecifiesADifferentEventType(Type type, EventType providedType) { if (TryGetEventTypeFromAttribute(type, out var attributeType) && attributeType != providedType) { @@ -158,16 +159,36 @@ void ThrowIfAttributeSpecifiesADifferentEventType(Type type, EventType providedT } } - bool TryGetEventTypeFromAttribute(Type type, out EventType eventType) + static bool TryGetEventTypeFromAttribute(Type type, out EventType eventType) { if (Attribute.GetCustomAttribute(type, typeof(EventTypeAttribute)) is EventTypeAttribute attribute) { - eventType = attribute.EventType; + if (!attribute.HasAlias) + { + eventType = new EventType(attribute.Identifier, attribute.Generation, type.Name); + return true; + } + + eventType = new EventType(attribute.Identifier, attribute.Generation, attribute.Alias); return true; } eventType = default; return false; } + + void AddAssociation(Type type, EventType eventType) + { + ThrowIfAttributeSpecifiesADifferentEventType(type, eventType); + _associations.Add((type, eventType)); + } + + void ThrowIfTypeIsMissingEventTypeAttribute(Type type) + { + if (!TryGetEventTypeFromAttribute(type, out _)) + { + throw new TypeIsMissingEventTypeAttribute(type); + } + } } } diff --git a/Source/Events/EventType.cs b/Source/Events/EventType.cs index 39dd1a15..df74bd84 100644 --- a/Source/Events/EventType.cs +++ b/Source/Events/EventType.cs @@ -13,7 +13,7 @@ public class EventType : Artifact /// /// Initializes a new instance of the class. /// - /// The unique identifer of the . + /// The unique identifier of the . public EventType(EventTypeId id) : base(id) { @@ -23,7 +23,20 @@ public EventType(EventTypeId id) /// /// Initializes a new instance of the class. /// - /// The unique identifer of the . + /// The unique identifier of the . + /// Alias of the . + public EventType(EventTypeId id, EventTypeAlias alias) + : base(id) + { + ThrowIfEventTypeIdIsNull(id); + Alias = alias; + HasAlias = true; + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the . /// Generation of the . public EventType(EventTypeId id, Generation generation) : base(id, generation) @@ -32,12 +45,37 @@ public EventType(EventTypeId id, Generation generation) ThrowIfGenerationIsNull(generation); } - void ThrowIfEventTypeIdIsNull(EventTypeId id) + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the . + /// Generation of the . + /// Alias of the . + public EventType(EventTypeId id, Generation generation, EventTypeAlias alias) + : base(id, generation) + { + ThrowIfEventTypeIdIsNull(id); + ThrowIfGenerationIsNull(generation); + Alias = alias; + HasAlias = true; + } + + /// + /// Gets the alias for the Event Type. + /// + public EventTypeAlias Alias { get; } + + /// + /// Gets a value indicating whether the Event Type has an alias or not. + /// + public bool HasAlias { get; } + + static void ThrowIfEventTypeIdIsNull(EventTypeId id) { if (id == null) throw new EventTypeIdCannotBeNull(); } - void ThrowIfGenerationIsNull(Generation generation) + static void ThrowIfGenerationIsNull(Generation generation) { if (generation == null) throw new EventTypeGenerationCannotBeNull(); } diff --git a/Source/Events/EventTypeAlias.cs b/Source/Events/EventTypeAlias.cs new file mode 100644 index 00000000..9b73a417 --- /dev/null +++ b/Source/Events/EventTypeAlias.cs @@ -0,0 +1,19 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.SDK.Concepts; + +namespace Dolittle.SDK.Events +{ + /// + /// Represents the alias of an event type. + /// + public class EventTypeAlias : ConceptAs + { + /// + /// Implicitly convert from a to an . + /// + /// EventTypeAlias as . + public static implicit operator EventTypeAlias(string alias) => new EventTypeAlias { Value = alias }; + } +} \ No newline at end of file diff --git a/Source/Events/EventTypeAttribute.cs b/Source/Events/EventTypeAttribute.cs index 8f67c2fe..2f5af112 100644 --- a/Source/Events/EventTypeAttribute.cs +++ b/Source/Events/EventTypeAttribute.cs @@ -17,14 +17,36 @@ public class EventTypeAttribute : Attribute /// /// The unique identifier of the . /// The generation of the .. - public EventTypeAttribute(string eventTypeId, uint generation = 0) - => EventType = new EventType( - Guid.Parse(eventTypeId), - generation == 0 ? Generation.First : new Generation { Value = generation }); + /// The alias for the . + public EventTypeAttribute(string eventTypeId, uint generation = 0, string alias = default) + { + Identifier = Guid.Parse(eventTypeId); + Generation = generation == 0 ? Generation.First : new Generation { Value = generation }; + if (alias != default) + { + Alias = alias; + HasAlias = true; + } + } /// - /// Gets the . + /// Gets the unique identifier for this event type. /// - public EventType EventType { get; } + public EventTypeId Identifier { get; } + + /// + /// Gets the generation for this event type. + /// + public Generation Generation { get; } + + /// + /// Gets the . + /// + public EventTypeAlias Alias { get; } + + /// + /// Gets a value indicating whether this event type has an alias. + /// + public bool HasAlias { get; } } } diff --git a/Source/Events/Events.csproj b/Source/Events/Events.csproj index b10f7422..dbc19e68 100644 --- a/Source/Events/Events.csproj +++ b/Source/Events/Events.csproj @@ -4,10 +4,11 @@ Dolittle.SDK.Events + Dolittle.SDK.Events - + diff --git a/Source/Events/Internal/EventTypesClient.cs b/Source/Events/Internal/EventTypesClient.cs new file mode 100644 index 00000000..fea4e96d --- /dev/null +++ b/Source/Events/Internal/EventTypesClient.cs @@ -0,0 +1,99 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Dolittle.Runtime.Events.Contracts; +using Dolittle.SDK.Execution; +using Dolittle.SDK.Protobuf; +using Dolittle.SDK.Services; +using Dolittle.Services.Contracts; +using Microsoft.Extensions.Logging; +using ExecutionContext = Dolittle.SDK.Execution.ExecutionContext; + +namespace Dolittle.SDK.Events.Internal +{ + /// + /// Represents a system that knows how to register event types with the Runtime. + /// + public class EventTypesClient + { + static readonly EventTypesRegisterMethod _method = new EventTypesRegisterMethod(); + readonly IPerformMethodCalls _caller; + readonly ExecutionContext _executionContext; + readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The method caller to use to perform calls to the Runtime. + /// Tha base . + /// The to use. + public EventTypesClient(IPerformMethodCalls caller, ExecutionContext executionContext, ILogger logger) + { + _caller = caller; + _executionContext = executionContext; + _logger = logger; + } + + /// + /// Registers event types. + /// + /// The event types to register. + /// The . + /// A representing the asynchronous operation. + public Task Register(IEnumerable eventTypes, CancellationToken cancellationToken) + => Task.WhenAll(eventTypes.Select(CreateRequest).Select(_ => Register(_, cancellationToken))); + + EventTypeRegistrationRequest CreateRequest(EventType eventType) + { + var request = new EventTypeRegistrationRequest + { + EventType = eventType.ToProtobuf(), + CallContext = new CallRequestContext + { + ExecutionContext = _executionContext.ToProtobuf(), + HeadId = HeadId.NotSet.ToProtobuf() + } + }; + + if (eventType.HasAlias) + { + request.Alias = eventType.Alias; + } + + return request; + } + + async Task Register(EventTypeRegistrationRequest request, CancellationToken cancellationToken) + { + _logger.LogDebug( + "Registering Event Type {EventType} with Alias {Alias}", + request.EventType.Id.ToGuid(), + request.Alias); + try + { + var response = await _caller.Call(_method, request, cancellationToken).ConfigureAwait(false); + if (response.Failure != null) + { + _logger.LogWarning( + "An error occurred while registering Event Type {EventType} with Alias {Alias} because {Reason}", + request.EventType.Id.ToGuid(), + request.Alias, + response.Failure.Reason); + } + } + catch (Exception ex) + { + _logger.LogWarning( + ex, + "An error occurred while registering Event Type {EventType} with Alias {Alias}", + request.EventType.Id.ToGuid(), + request.Alias); + } + } + } +} \ No newline at end of file diff --git a/Source/Events/Internal/EventTypesRegisterMethod.cs b/Source/Events/Internal/EventTypesRegisterMethod.cs new file mode 100644 index 00000000..e60d98f2 --- /dev/null +++ b/Source/Events/Internal/EventTypesRegisterMethod.cs @@ -0,0 +1,22 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Dolittle.Runtime.Events.Contracts; +using Dolittle.SDK.Services; +using Grpc.Core; + +namespace Dolittle.SDK.Events.Internal +{ + /// + /// Represents a wrapper for gRPC Subscriptions.Subscribe. + /// + public class EventTypesRegisterMethod : ICanCallAUnaryMethod + { + /// + public AsyncUnaryCall Call(EventTypeRegistrationRequest message, Channel channel, CallOptions callOptions) + { + var client = new Dolittle.Runtime.Events.Contracts.EventTypes.EventTypesClient(channel); + return client.RegisterAsync(message, callOptions); + } + } +} \ No newline at end of file diff --git a/Source/Extensions.Discovery/ClientBuilderExtensions.cs b/Source/Extensions.Discovery/ClientBuilderExtensions.cs new file mode 100644 index 00000000..87a572b4 --- /dev/null +++ b/Source/Extensions.Discovery/ClientBuilderExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using BaselineTypeDiscovery; + +namespace Dolittle.SDK +{ + /// + /// Extensions for the to register artifacts by loading all assemblies in the current directory. + /// + public static class ClientBuilderExtensions + { + /// + /// Registers all event types by loading all assemblies in the current directory. + /// + /// The to use. + /// The client builder for continuation. + public static ClientBuilder WithAllEventTypes(this ClientBuilder builder) + => ForAllAllScannedAssemblies(builder, (clientBuilder, assembly) => clientBuilder.WithEventTypes(_ => _.RegisterAllFrom(assembly))); + + /// + /// Registers all aggregate roots by loading all assemblies in the current directory. + /// + /// The to use. + /// The client builder for continuation. + public static ClientBuilder WithAllAggregateRoots(this ClientBuilder builder) + => ForAllAllScannedAssemblies(builder, (clientBuilder, assembly) => clientBuilder.WithAggregateRoots(_ => _.RegisterAllFrom(assembly))); + + /// + /// Registers all event handlers by loading all assemblies in the current directory. + /// + /// The to use. + /// The client builder for continuation. + public static ClientBuilder WithAllEventHandlers(this ClientBuilder builder) + => ForAllAllScannedAssemblies(builder, (clientBuilder, assembly) => clientBuilder.WithEventHandlers(_ => _.RegisterAllFrom(assembly))); + + /// + /// Registers all projections by loading all assemblies in the current directory. + /// + /// The to use. + /// The client builder for continuation. + public static ClientBuilder WithAllProjections(this ClientBuilder builder) + => ForAllAllScannedAssemblies(builder, (clientBuilder, assembly) => clientBuilder.WithProjections(_ => _.RegisterAllFrom(assembly))); + + /// + /// Registers all embeddings by loading all assemblies in the current directory. + /// + /// The to use. + /// The client builder for continuation. + public static ClientBuilder WithAllEmbeddings(this ClientBuilder builder) + => ForAllAllScannedAssemblies(builder, (clientBuilder, assembly) => clientBuilder.WithEmbeddings(_ => _.RegisterAllFrom(assembly))); + + static ClientBuilder ForAllAllScannedAssemblies(ClientBuilder builder, Action perform) + { + foreach (var assembly in GetAllAssemblies()) + { + perform(builder, assembly); + } + + return builder; + } + + static IEnumerable GetAllAssemblies() + { + return AssemblyFinder.FindAssemblies( + failedFile => throw new CouldNotLoadAssemblyFromFile(failedFile), + _ => true, + false); + } + } +} \ No newline at end of file diff --git a/Source/Extensions.Discovery/CouldNotLoadAssemblyFromFile.cs b/Source/Extensions.Discovery/CouldNotLoadAssemblyFromFile.cs new file mode 100644 index 00000000..79c56783 --- /dev/null +++ b/Source/Extensions.Discovery/CouldNotLoadAssemblyFromFile.cs @@ -0,0 +1,23 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using BaselineTypeDiscovery; + +namespace Dolittle.SDK +{ + /// + /// Exception that gets thrown when the fails to load the assembly from a file. + /// + public class CouldNotLoadAssemblyFromFile : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The file that failed to load. + public CouldNotLoadAssemblyFromFile(string failedFile) + : base($"Could not load assembly from file {failedFile}") + { + } + } +} \ No newline at end of file diff --git a/Source/Extensions.Discovery/Extensions.Discovery.csproj b/Source/Extensions.Discovery/Extensions.Discovery.csproj new file mode 100644 index 00000000..ef8d82ab --- /dev/null +++ b/Source/Extensions.Discovery/Extensions.Discovery.csproj @@ -0,0 +1,17 @@ + + + + + + Dolittle.SDK.Extensions.Discovery + + + + + + + + + + + diff --git a/Source/Protobuf/ArtifactExtensions.cs b/Source/Protobuf/ArtifactExtensions.cs index 4e20fac5..0b65f5f2 100644 --- a/Source/Protobuf/ArtifactExtensions.cs +++ b/Source/Protobuf/ArtifactExtensions.cs @@ -58,7 +58,7 @@ public static bool TryTo(this PbArtifact source, out TArtifact a var generationType = typeof(TArtifact).GetProperty(nameof(Artifact.Generation)).PropertyType; try { - object artifactId = Activator.CreateInstance(idType); + var artifactId = Activator.CreateInstance(idType); var artifactIdValueProperty = idType.GetProperty(nameof(ArtifactId.Value)); artifactIdValueProperty.SetValue(artifactId, id); diff --git a/Source/SDK/Client.cs b/Source/SDK/Client.cs index aa23b7d8..891c9d70 100644 --- a/Source/SDK/Client.cs +++ b/Source/SDK/Client.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Dolittle.SDK.Aggregates; +using Dolittle.SDK.Aggregates.Internal; using Dolittle.SDK.DependencyInversion; using Dolittle.SDK.Embeddings; using Dolittle.SDK.Embeddings.Builder; @@ -40,6 +41,7 @@ public class Client : IDisposable readonly EventHorizons _eventHorizons; readonly IEventProcessors _eventProcessors; readonly IEventProcessingConverter _eventProcessingConverter; + readonly IAggregateRoots _aggregateRoots; readonly ILoggerFactory _loggerFactory; readonly CancellationToken _cancellation; IContainer _container; @@ -62,6 +64,7 @@ public class Client : IDisposable /// The . /// The . /// The . + /// The . /// The . /// The . public Client( @@ -79,6 +82,7 @@ public Client( EmbeddingsBuilder embeddingsBuilder, ProjectionStoreBuilder projectionStoreBuilder, IEmbeddings embeddings, + IAggregateRoots aggregateRoots, ILoggerFactory loggerFactory, CancellationToken cancellationToken) { @@ -96,6 +100,7 @@ public Client( _embeddingsBuilder = embeddingsBuilder; Projections = projectionStoreBuilder; Embeddings = embeddings; + _aggregateRoots = aggregateRoots; _loggerFactory = loggerFactory; _cancellation = cancellationToken; _container = new DefaultContainer(); @@ -188,7 +193,7 @@ public Task Start() /// The . public IAggregateRootOperations AggregateOf(Func buildEventStore) where TAggregateRoot : AggregateRoot - => new AggregateOf(buildEventStore(EventStore), EventTypes, _loggerFactory) + => new AggregateOf(buildEventStore(EventStore), EventTypes, _aggregateRoots, _loggerFactory) .Create(); /// @@ -200,7 +205,7 @@ public IAggregateRootOperations AggregateOf(Func /// The . public IAggregateRootOperations AggregateOf(EventSourceId eventSource, Func buildEventStore) where TAggregateRoot : AggregateRoot - => new AggregateOf(buildEventStore(EventStore), EventTypes, _loggerFactory) + => new AggregateOf(buildEventStore(EventStore), EventTypes, _aggregateRoots, _loggerFactory) .Get(eventSource); /// diff --git a/Source/SDK/ClientBuilder.cs b/Source/SDK/ClientBuilder.cs index e906d86d..a2615a87 100644 --- a/Source/SDK/ClientBuilder.cs +++ b/Source/SDK/ClientBuilder.cs @@ -5,6 +5,8 @@ using System.Globalization; using System.Threading; using System.Threading.Tasks; +using Dolittle.SDK.Aggregates.Builders; +using Dolittle.SDK.Aggregates.Internal; using Dolittle.SDK.Embeddings.Builder; using Dolittle.SDK.Embeddings.Store; using Dolittle.SDK.EventHorizon; @@ -38,6 +40,7 @@ namespace Dolittle.SDK public class ClientBuilder { readonly EventTypesBuilder _eventTypesBuilder; + readonly AggregateRootsBuilder _aggregateRootsBuilder; readonly EventFiltersBuilder _eventFiltersBuilder; readonly EventHandlersBuilder _eventHandlersBuilder; readonly ProjectionsBuilder _projectionsBuilder; @@ -78,6 +81,7 @@ public ClientBuilder(MicroserviceId microserviceId) _embeddingAssociations = new EmbeddingReadModelTypeAssociations(); _eventTypesBuilder = new EventTypesBuilder(); + _aggregateRootsBuilder = new AggregateRootsBuilder(); _eventFiltersBuilder = new EventFiltersBuilder(); _eventHandlersBuilder = new EventHandlersBuilder(); _projectionsBuilder = new ProjectionsBuilder(_projectionAssociations); @@ -169,6 +173,17 @@ public ClientBuilder WithEventTypes(Action callback) return this; } + /// + /// Sets the aggregate roots through the . + /// + /// The builder callback. + /// The client builder for continuation. + public ClientBuilder WithAggregateRoots(Action callback) + { + callback(_aggregateRootsBuilder); + return this; + } + /// /// Sets the filters through the . /// @@ -214,7 +229,7 @@ public ClientBuilder WithEmbeddings(Action callback) } /// - /// Sets the event handlers through the . + /// Sets the event horizons through the . /// /// The builder callback. /// The client builder for continuation. @@ -253,6 +268,7 @@ public ClientBuilder WithEventSerializerSettings(Action /// The . public Client Build() { + var methodCaller = new MethodCaller(_host, _port); var executionContext = new ExecutionContext( _microserviceId, TenantId.System, @@ -263,8 +279,9 @@ public Client Build() CultureInfo.InvariantCulture); var eventTypes = new EventTypes(_loggerFactory.CreateLogger()); _eventTypesBuilder.AddAssociationsInto(eventTypes); + _eventTypesBuilder.BuildAndRegister(new Events.Internal.EventTypesClient(methodCaller, executionContext, _loggerFactory.CreateLogger()), _cancellation); + _aggregateRootsBuilder.BuildAndRegister(new AggregateRootsClient(methodCaller, executionContext, _loggerFactory.CreateLogger()), _cancellation); - var methodCaller = new MethodCaller(_host, _port); var reverseCallClientsCreator = new ReverseCallClientCreator( _pingInterval, methodCaller, @@ -323,6 +340,9 @@ public Client Build() executionContext, _loggerFactory); + var aggregateRoots = new AggregateRoots( + _loggerFactory.CreateLogger()); + return new Client( eventTypes, eventStoreBuilder, @@ -338,11 +358,12 @@ public Client Build() _embeddingsBuilder, projectionStoreBuilder, embeddings, + aggregateRoots, _loggerFactory, _cancellation); } - async Task EventHorizonRetryPolicy(Subscription subscription, ILogger logger, Func> methodToPerform) + static async Task EventHorizonRetryPolicy(Subscription subscription, ILogger logger, Func> methodToPerform) { var retryCount = 0; diff --git a/Source/SDK/SDK.csproj b/Source/SDK/SDK.csproj index a3a87f52..e8df0ea5 100644 --- a/Source/SDK/SDK.csproj +++ b/Source/SDK/SDK.csproj @@ -12,7 +12,7 @@ - + diff --git a/Source/Services/CatchingAsyncStreamReader.cs b/Source/Services/CatchingAsyncStreamReader.cs new file mode 100644 index 00000000..7c3a4669 --- /dev/null +++ b/Source/Services/CatchingAsyncStreamReader.cs @@ -0,0 +1,62 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; + +namespace Dolittle.SDK.Services +{ + /// + /// Represents an implementation of that catches RPC exceptions and throws Dolittle SDK exceptions. + /// + /// The message type. + public class CatchingAsyncStreamReader : IAsyncStreamReader + { + readonly string _host; + readonly ushort _port; + readonly IAsyncStreamReader _original; + + /// + /// Initializes a new instance of the class. + /// + /// The host that the writer is attempting to connect to. + /// The port that the writer is attempting to connect to. + /// The original instance to catch from. + public CatchingAsyncStreamReader(string host, ushort port, IAsyncStreamReader original) + { + _host = host; + _port = port; + _original = original; + } + + /// + public T Current + { + get + { + try + { + return _original.Current; + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } + } + } + + /// + public async Task MoveNext(CancellationToken cancellationToken) + { + try + { + return await _original.MoveNext(cancellationToken).ConfigureAwait(false); + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } + } + } +} \ No newline at end of file diff --git a/Source/Services/CatchingClientStreamWriter.cs b/Source/Services/CatchingClientStreamWriter.cs new file mode 100644 index 00000000..0bf57e99 --- /dev/null +++ b/Source/Services/CatchingClientStreamWriter.cs @@ -0,0 +1,86 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; +using Grpc.Core; + +namespace Dolittle.SDK.Services +{ + /// + /// Represents an implementation of that catches RPC exceptions and throws Dolittle SDK exceptions. + /// + /// The message type. + public class CatchingClientStreamWriter : IClientStreamWriter + { + readonly string _host; + readonly ushort _port; + readonly IClientStreamWriter _original; + + /// + /// Initializes a new instance of the class. + /// + /// The host that the writer is attempting to connect to. + /// The port that the writer is attempting to connect to. + /// The original instance to catch from. + public CatchingClientStreamWriter(string host, ushort port, IClientStreamWriter original) + { + _host = host; + _port = port; + _original = original; + } + + /// + public WriteOptions WriteOptions + { + get + { + try + { + return _original.WriteOptions; + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } + } + + set + { + try + { + _original.WriteOptions = value; + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } + } + } + + /// + public async Task WriteAsync(T message) + { + try + { + await _original.WriteAsync(message).ConfigureAwait(false); + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } + } + + /// + public async Task CompleteAsync() + { + try + { + await _original.CompleteAsync().ConfigureAwait(false); + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } + } + } +} \ No newline at end of file diff --git a/Source/Services/CouldNotConnectToRuntime.cs b/Source/Services/CouldNotConnectToRuntime.cs new file mode 100644 index 00000000..9bfca688 --- /dev/null +++ b/Source/Services/CouldNotConnectToRuntime.cs @@ -0,0 +1,23 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Dolittle.SDK.Services +{ + /// + /// Exception that gets thrown when not the Client is not able to connect to a Runtime. + /// + public class CouldNotConnectToRuntime : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The host the Client attempted to connect to. + /// The port the Client attempted to connect to. + public CouldNotConnectToRuntime(string host, ushort port) + : base($"Could not connect to a Runtime on '{host}:{port}'. Please make sure a Runtime is running, and that the private port (usually 50053) is accessible on the specified port.") + { + } + } +} \ No newline at end of file diff --git a/Source/Services/MethodCaller.cs b/Source/Services/MethodCaller.cs index 06009111..6df677bc 100644 --- a/Source/Services/MethodCaller.cs +++ b/Source/Services/MethodCaller.cs @@ -1,6 +1,7 @@ // Copyright (c) Dolittle. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Threading; using System.Threading.Tasks; using Google.Protobuf; @@ -41,15 +42,36 @@ public AsyncDuplexStreamingCall Call( + new CatchingClientStreamWriter(_host, _port, originalStream.RequestStream), + new CatchingAsyncStreamReader(_host, _port, originalStream.ResponseStream), + originalStream.ResponseHeadersAsync, + originalStream.GetStatus, + originalStream.GetTrailers, + originalStream.Dispose); + } + catch (Exception) + { + throw new CouldNotConnectToRuntime(_host, _port); + } } /// - public Task Call(ICanCallAUnaryMethod method, TClientMessage request, CancellationToken token) + public async Task Call(ICanCallAUnaryMethod method, TClientMessage request, CancellationToken token) where TClientMessage : IMessage where TServerMessage : IMessage { - return method.Call(request, CreateChannel(), CreateCallOptions(token)).ResponseAsync; + try + { + return await method.Call(request, CreateChannel(), CreateCallOptions(token)).ResponseAsync.ConfigureAwait(false); + } + catch (RpcException exception) when (exception.StatusCode == StatusCode.Unavailable) + { + throw new CouldNotConnectToRuntime(_host, _port); + } } Channel CreateChannel() => new Channel(_host, _port, _channelCredentials, _channelOptions); diff --git a/Specifications/Aggregates/Aggregates.csproj b/Specifications/Aggregates/Aggregates.csproj index d9254af2..d0cac8fd 100755 --- a/Specifications/Aggregates/Aggregates.csproj +++ b/Specifications/Aggregates/Aggregates.csproj @@ -1,6 +1,6 @@  - + Dolittle.SDK.Aggregates.Specs diff --git a/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithIncorrectConstructorParameter.cs b/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithIncorrectConstructorParameter.cs deleted file mode 100644 index 7c48faef..00000000 --- a/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithIncorrectConstructorParameter.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.given -{ - [AggregateRoot("3ee85a6e-bf0c-4235-9a92-a9bb2a0ffd12")] - public class AggregateRootWithIncorrectConstructorParameter : AggregateRoot - { - public AggregateRootWithIncorrectConstructorParameter(string some_string) - : base("83c67dd2-56d1-4fa1-8e2d-5a121724c58b") - { - } - } -} \ No newline at end of file diff --git a/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithNoConstructorParameters.cs b/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithNoConstructorParameters.cs deleted file mode 100644 index a6284949..00000000 --- a/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithNoConstructorParameters.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.given -{ - [AggregateRoot("db7214f6-061e-4394-9dc9-cf5af05b72c1")] - public class AggregateRootWithNoConstructorParameters : AggregateRoot - { - public AggregateRootWithNoConstructorParameters() - : base("f47fd493-14f0-4b60-a0db-bee3d70cef6c") - { - } - } -} \ No newline at end of file diff --git a/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithTooManyConstructorParameters.cs b/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithTooManyConstructorParameters.cs deleted file mode 100644 index 8cb92d53..00000000 --- a/Specifications/Aggregates/for_AggregateOf/given/AggregateRootWithTooManyConstructorParameters.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Dolittle.SDK.Events; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.given -{ - [AggregateRoot("daaef579-b711-4339-bd09-e04f53cc01b5")] - public class AggregateRootWithTooManyConstructorParameters : AggregateRoot - { - public AggregateRootWithTooManyConstructorParameters(EventSourceId event_source, EventSourceId some_other) - : base(event_source) - { - } - } -} \ No newline at end of file diff --git a/Specifications/Aggregates/for_AggregateOf/given/all_dependencies.cs b/Specifications/Aggregates/for_AggregateOf/given/all_dependencies.cs deleted file mode 100644 index 488b53cc..00000000 --- a/Specifications/Aggregates/for_AggregateOf/given/all_dependencies.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Threading; -using System.Threading.Tasks; -using Dolittle.SDK.Events; -using Dolittle.SDK.Events.Store; -using Machine.Specifications; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.given -{ - public abstract class all_dependencies - { - protected static Mock event_types; - protected static ILoggerFactory logger_factory; - protected static Mock event_store; - - protected static void SetupCommittedEvents(AggregateRootId aggregate_root, EventSourceId event_source, params CommittedAggregateEvent[] events) - => event_store.Setup(_ => _ - .FetchForAggregate( - aggregate_root, - event_source, - Moq.It.IsAny())) - .Returns(Task.FromResult(new CommittedAggregateEvents(event_source, aggregate_root, events))); - - Establish context = () => - { - event_types = new Mock(); - logger_factory = NullLoggerFactory.Instance; - event_store = new Mock(); - }; - } -} diff --git a/Specifications/Aggregates/for_AggregateOf/when_getting/a_stateful_aggregate/with_no_prior_events.cs b/Specifications/Aggregates/for_AggregateOf/when_getting/a_stateful_aggregate/with_no_prior_events.cs deleted file mode 100644 index 1793237e..00000000 --- a/Specifications/Aggregates/for_AggregateOf/when_getting/a_stateful_aggregate/with_no_prior_events.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Dolittle.SDK.Events; -using Machine.Specifications; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.when_getting.a_stateful_aggregate -{ - public class with_no_prior_events : given.all_dependencies - { - static readonly EventSourceId event_source = "8b46cd1e-49c7-40fb-880b-c3a25ad099ea"; - static readonly AggregateRootId aggregate_root = new Aggregates.given.StatefulAggregateRoot(event_source).GetAggregateRootId(); - static IAggregateOf aggregate_of; - static IAggregateRootOperations result; - - Establish context = () => - { - SetupCommittedEvents(aggregate_root, event_source); - aggregate_of = new AggregateOf(event_store.Object, event_types.Object, logger_factory); - }; - - Because of = () => result = aggregate_of.Get(event_source); - - It should_get_aggregate = () => result.ShouldNotBeNull(); - } -} diff --git a/Specifications/Aggregates/for_AggregateOf/when_getting/a_stateless_aggregate/with_no_prior_events.cs b/Specifications/Aggregates/for_AggregateOf/when_getting/a_stateless_aggregate/with_no_prior_events.cs deleted file mode 100644 index ce3a7f0f..00000000 --- a/Specifications/Aggregates/for_AggregateOf/when_getting/a_stateless_aggregate/with_no_prior_events.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Dolittle.SDK.Events; -using Machine.Specifications; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.when_getting.a_stateless_aggregate -{ - public class with_no_prior_events : given.all_dependencies - { - static readonly EventSourceId event_source = "819e2d1a-b7a3-42dd-af9b-1fa7f89a1aac"; - static readonly AggregateRootId aggregate_root = new Aggregates.given.StatelessAggregateRoot(event_source).GetAggregateRootId(); - static IAggregateOf aggregate_of; - static IAggregateRootOperations result; - - Establish context = () => - { - SetupCommittedEvents(aggregate_root, event_source); - aggregate_of = new AggregateOf(event_store.Object, event_types.Object, logger_factory); - }; - - Because of = () => result = aggregate_of.Get(event_source); - - It should_get_aggregate = () => result.ShouldNotBeNull(); - } -} diff --git a/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_incorrect_constructor_parameter.cs b/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_incorrect_constructor_parameter.cs deleted file mode 100644 index da9a5726..00000000 --- a/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_incorrect_constructor_parameter.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using Dolittle.SDK.Events; -using Machine.Specifications; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.when_getting -{ - public class aggregate_root_with_incorrect_constructor_parameter : given.all_dependencies - { - static readonly EventSourceId event_source = "819e2d1a-b7a3-42dd-af9b-1fa7f89a1aac"; - static readonly AggregateRootId aggregate_root = new given.AggregateRootWithIncorrectConstructorParameter("").GetAggregateRootId(); - static IAggregateOf aggregate_of; - static Exception exception; - - Establish context = () => - aggregate_of = new AggregateOf(event_store.Object, event_types.Object, logger_factory); - - Because of = () => exception = Catch.Exception(() => aggregate_of.Get(event_source)); - - It should_fail_because_it_could_not_get_aggregate_root = () => exception.ShouldBeOfExactType(); - } -} diff --git a/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_no_constructor_parameters.cs b/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_no_constructor_parameters.cs deleted file mode 100644 index 8d2ffbd9..00000000 --- a/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_no_constructor_parameters.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using Dolittle.SDK.Events; -using Machine.Specifications; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.when_getting -{ - public class aggregate_root_with_no_constructor_parameters : given.all_dependencies - { - static readonly EventSourceId event_source = "819e2d1a-b7a3-42dd-af9b-1fa7f89a1aac"; - static readonly AggregateRootId aggregate_root = new given.AggregateRootWithNoConstructorParameters().GetAggregateRootId(); - static IAggregateOf aggregate_of; - static Exception exception; - - Establish context = () => - aggregate_of = new AggregateOf(event_store.Object, event_types.Object, logger_factory); - - Because of = () => exception = Catch.Exception(() => aggregate_of.Get(event_source)); - - It should_fail_because_it_could_not_get_aggregate_root = () => exception.ShouldBeOfExactType(); - } -} diff --git a/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_too_many_constructor_parameters.cs b/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_too_many_constructor_parameters.cs deleted file mode 100644 index ebf688e0..00000000 --- a/Specifications/Aggregates/for_AggregateOf/when_getting/aggregate_root_with_too_many_constructor_parameters.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Dolittle. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using Dolittle.SDK.Events; -using Machine.Specifications; - -namespace Dolittle.SDK.Aggregates.for_AggregateOf.when_getting -{ - public class aggregate_root_with_too_many_constructor_parameters : given.all_dependencies - { - static readonly EventSourceId event_source = "819e2d1a-b7a3-42dd-af9b-1fa7f89a1aac"; - static readonly AggregateRootId aggregate_root = new given.AggregateRootWithTooManyConstructorParameters("79235c32-d7a8-4338-ba8b-2109b19c7805", "5b08a724-4d29-4242-b3e9-2aa664563450").GetAggregateRootId(); - static IAggregateOf aggregate_of; - static Exception exception; - - Establish context = () => - aggregate_of = new AggregateOf(event_store.Object, event_types.Object, logger_factory); - - Because of = () => exception = Catch.Exception(() => aggregate_of.Get(event_source)); - - It should_fail_because_it_could_not_get_aggregate_root = () => exception.ShouldBeOfExactType(); - } -} diff --git a/default.props b/default.props index 524e0d37..c63e3d2c 100644 --- a/default.props +++ b/default.props @@ -3,6 +3,9 @@ netstandard2.1 + https://github.com/dolittle/DotNET.SDK + https://dolittle.io + https://github.com/dolittle/DotNET.SDK/blob/master/CHANGELOG.md diff --git a/versions.props b/versions.props index 3bb90618..8058e514 100644 --- a/versions.props +++ b/versions.props @@ -1,6 +1,7 @@ - 6.1.0 + 1.1.2 + 6.2.0 3.1.2 4.4.1 3.18.1