From 86707e75d8a5c9d08f1e8e242c0400f62965d93f Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 31 Aug 2016 22:36:55 +0200 Subject: [PATCH] Some progress --- PinkParrot.sln.DotSettings | 6 + .../Modules/Api/Schemas/SchemasController.cs | 26 ++ src/PinkParrot/Project_Readme.html | 187 ------------ src/PinkParrot/project.json | 24 +- .../PinkParrot.Core/Schema/ModelField.cs | 33 +- .../Schema/ModelFieldFactory.cs | 4 +- .../PinkParrot.Core/Schema/ModelSchema.cs | 26 +- .../PinkParrot.Core/Schema/NumberField.cs | 25 +- .../Schema/ModelFieldAdded.cs | 7 +- .../Schema/ModelFieldDeleted.cs | 5 +- .../Schema/ModelFieldDisabled.cs | 5 +- .../Schema/ModelFieldEnabled.cs | 5 +- .../Schema/ModelFieldHidden.cs | 5 +- .../Schema/ModelFieldShown.cs | 5 +- .../Schema/ModelFieldUpdated.cs | 5 +- .../Schema/ModelSchemaCreated.cs | 2 +- .../Schema/ModelSchemaDeleted.cs | 2 +- .../Schema/ModelSchemaUpdated.cs | 2 +- ...nsTest.cs => CollectionExtensionsTests.cs} | 0 .../DispatchingTests.cs | 284 ++++++++++++++++++ ...tensionsTest.cs => EnumExtensionsTests.cs} | 2 +- .../PropertiesBagTests.cs | 40 +++ .../CQRS/Commands/AggregateCommand.cs | 17 ++ .../CQRS/Commands/CommandContext.cs | 72 +++++ .../CQRS/Commands/CommandingExtensions.cs | 20 ++ .../CQRS/Commands/ICommand.cs | 14 + .../CQRS/Commands/ICommandBus.cs | 17 ++ .../CQRS/Commands/ICommandHandler.cs | 17 ++ .../CQRS/Commands/IDomainObjectFactory.cs | 17 ++ .../CQRS/Commands/IDomainObjectRepository.cs | 20 ++ .../CQRS/Commands/InMemoryCommandBus.cs | 59 ++++ .../CQRS/DomainObject.cs | 16 +- .../CQRS/EnvelopeFactory.cs | 1 + .../CQRS/{ => Events}/IEvent.cs | 2 +- .../CQRS/Events/IEventConsumer.cs | 15 + .../CQRS/IAggregate.cs | 1 + .../Dispatching/ActionContextDispatcher.cs | 45 +++ .../ActionContextDispatcherFactory.cs | 39 +++ .../Dispatching/ActionDispatcher.cs | 45 +++ .../Dispatching/ActionDispatcherFactory.cs | 39 +++ .../Dispatching/DispatchExtensions.cs | 81 +++++ .../Dispatching/FuncContextDispatcher.cs | 44 +++ .../FuncContextDispatcherFactory.cs | 39 +++ .../Dispatching/FuncDispatcher.cs | 44 +++ .../Dispatching/FuncDispatcherFactory.cs | 39 +++ .../Dispatching/Helper.cs | 39 +++ .../ICommandContext.cs | 15 + .../Json/JsonPropertiesBagConverter.cs | 76 +++++ .../PropertiesBag.cs | 7 +- .../PinkParrot.Infrastructure/project.json | 7 +- .../Schema/Commands/AddModelField.cs | 6 +- .../Schema/Commands/CreateModelSchema.cs | 6 +- .../Schema/Commands/DeleteModelField.cs | 8 +- .../Schema/Commands/DeleteModelSchema.cs | 5 +- .../Schema/Commands/DisableModelField.cs | 8 +- .../Schema/Commands/EnableModelField.cs | 8 +- .../Schema/Commands/HideModelField.cs | 8 +- .../Schema/Commands/ShowModelField.cs | 8 +- .../Schema/Commands/UpdateModelField.cs | 8 +- .../Schema/Commands/UpdateModelSchema.cs | 6 +- .../Schema/ModelSchemaCommandHandler.cs | 87 ++++++ .../Schema/ModelSchemaDomainObject.cs | 61 ++-- 62 files changed, 1402 insertions(+), 364 deletions(-) create mode 100644 src/PinkParrot/Modules/Api/Schemas/SchemasController.cs delete mode 100644 src/PinkParrot/Project_Readme.html rename src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/{CollectionExtensionsTest.cs => CollectionExtensionsTests.cs} (100%) create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/DispatchingTests.cs rename src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/{EnumExtensionsTest.cs => EnumExtensionsTests.cs} (94%) create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandingExtensions.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommand.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandBus.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandHandler.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectFactory.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs rename src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/{ => Events}/IEvent.cs (88%) create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcherFactory.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/DispatchExtensions.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcherFactory.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcherFactory.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/Helper.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/ICommandContext.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/JsonPropertiesBagConverter.cs create mode 100644 src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs diff --git a/PinkParrot.sln.DotSettings b/PinkParrot.sln.DotSettings index 6627add68..8f7549b08 100644 --- a/PinkParrot.sln.DotSettings +++ b/PinkParrot.sln.DotSettings @@ -33,6 +33,9 @@ False True + True + True + False SingleQuoted ========================================================================== $FILENAME$ @@ -83,4 +86,7 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True + True + True + True True \ No newline at end of file diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs new file mode 100644 index 000000000..750e5be11 --- /dev/null +++ b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace PinkParrot.Modules.Api.Schemas +{ + public class SchemasController : Controller + { + [HttpPost] + [Route("schemas/")] + public async Task Create() + { + } + + [HttpPut] + [Route("schemas/{name}/")] + public async Task Update() + { + } + + [HttpDelete] + [Route("schemas/{name}/")] + public async Task Delete() + { + } + } +} \ No newline at end of file diff --git a/src/PinkParrot/Project_Readme.html b/src/PinkParrot/Project_Readme.html deleted file mode 100644 index 1a0f5b51a..000000000 --- a/src/PinkParrot/Project_Readme.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - Welcome to ASP.NET Core - - - - - - - - - - diff --git a/src/PinkParrot/project.json b/src/PinkParrot/project.json index 361cfdacb..41a3470bb 100644 --- a/src/PinkParrot/project.json +++ b/src/PinkParrot/project.json @@ -1,14 +1,28 @@ { "dependencies": { + "Microsoft.AspNetCore.Diagnostics": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Razor.Tools": { + "version": "1.0.0-preview2-final", + "type": "build" + }, + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.StaticFiles": "1.0.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", + "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0", + "Microsoft.Extensions.Logging.Debug": "1.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.Diagnostics": "1.0.0", - - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", - "Microsoft.Extensions.Logging.Console": "1.0.0" + "PinkParrot.Core": "1.0.0-*", + "PinkParrot.Events": "1.0.0-*", + "PinkParrot.Infrastructure": "1.0.0-*", + "PinkParrot.Write": "1.0.0-*" }, "tools": { diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs index 816f6b296..ad4c39d26 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs @@ -6,9 +6,7 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; -using System.Globalization; using System.Threading.Tasks; using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.Tasks; @@ -19,7 +17,7 @@ namespace PinkParrot.Core.Schema { public abstract class ModelField { - private readonly Guid id; + private readonly long id; private string name; private string hint; private string displayName; @@ -27,7 +25,7 @@ namespace PinkParrot.Core.Schema private bool isDisabled; private bool isHidden; - public Guid Id + public long Id { get { return id; } } @@ -62,9 +60,9 @@ namespace PinkParrot.Core.Schema get { return isDisabled; } } - protected ModelField(Guid id, string name) + protected ModelField(long id, string name) { - Guard.NotEmpty(id, nameof(id)); + Guard.GreaterThan(id, 0, nameof(id)); Guard.ValidSlug(name, nameof(name)); this.id = id; @@ -72,13 +70,13 @@ namespace PinkParrot.Core.Schema this.name = name; } - public ModelField Configure(PropertiesBag settings, ICollection errors) + public ModelField Configure(dynamic settings, ICollection errors) { var clone = Clone(); if (settings.Contains("Name")) { - clone.name = settings["Name"].ToString(); + clone.name = settings.Name; if (!clone.name.IsSlug()) { @@ -86,26 +84,19 @@ namespace PinkParrot.Core.Schema } } - if (settings.Contains("Hint")) + if (settings.Contains("hint")) { - clone.hint = settings["Hint"].ToString()?.Trim() ?? string.Empty; + clone.hint = settings.Hint?.Trim(); } - if (settings.Contains("DisplayName")) + if (settings.Contains("displayName")) { - clone.displayName = settings["DisplayName"].ToString()?.Trim() ?? string.Empty; + clone.displayName = settings.DisplayName?.Trim(); } if (settings.Contains("IsRequired")) { - try - { - clone.isRequired = settings["IsRequired"].ToBoolean(CultureInfo.InvariantCulture); - } - catch (InvalidCastException) - { - errors.Add("IsRequired is not a valid boolean"); - } + clone.isRequired = settings.IsRequired; } clone.ConfigureCore(settings, errors); @@ -113,7 +104,7 @@ namespace PinkParrot.Core.Schema return clone; } - protected virtual void ConfigureCore(PropertiesBag settings, ICollection errors) + protected virtual void ConfigureCore(dynamic settings, ICollection errors) { } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs index e318249d9..e6cad2f5c 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs @@ -6,13 +6,11 @@ // All rights reserved. // ========================================================================== -using System; - namespace PinkParrot.Core.Schema { public class ModelFieldFactory { - public virtual ModelField CreateField(Guid id, string type, string fieldName) + public virtual ModelField CreateField(long id, string type, string fieldName) { return new NumberField(id, fieldName); } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs index cf46324ee..b45f13bb2 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs @@ -18,10 +18,10 @@ namespace PinkParrot.Core.Schema public sealed class ModelSchema { private readonly ModelSchemaMetadata metadata; - private readonly ImmutableDictionary fields; + private readonly ImmutableDictionary fields; private readonly Dictionary fieldsByName; - public ModelSchema(ModelSchemaMetadata metadata, ImmutableDictionary fields) + public ModelSchema(ModelSchemaMetadata metadata, ImmutableDictionary fields) { Guard.NotNull(fields, nameof(fields)); Guard.NotNull(metadata, nameof(metadata)); @@ -40,10 +40,10 @@ namespace PinkParrot.Core.Schema throw new DomainValidationException("Cannot create the schema.", $"'{name}' is not a valid slug."); } - return new ModelSchema(new ModelSchemaMetadata(name), ImmutableDictionary.Empty); + return new ModelSchema(new ModelSchemaMetadata(name), ImmutableDictionary.Empty); } - public IReadOnlyDictionary Fields + public IReadOnlyDictionary Fields { get { return fields; } } @@ -60,14 +60,14 @@ namespace PinkParrot.Core.Schema return new ModelSchema(newMetadata, fields); } - public ModelSchema AddField(Guid id, string type, string fieldName, ModelFieldFactory factory) + public ModelSchema AddField(long id, string type, string fieldName, ModelFieldFactory factory) { var field = factory.CreateField(id, type, fieldName); return SetField(field); } - public ModelSchema SetField(Guid fieldId, PropertiesBag settings) + public ModelSchema SetField(long fieldId, PropertiesBag settings) { Guard.NotNull(settings, nameof(settings)); @@ -86,22 +86,22 @@ namespace PinkParrot.Core.Schema }); } - public ModelSchema DisableField(Guid fieldId) + public ModelSchema DisableField(long fieldId) { return UpdateField(fieldId, field => field.Disable()); } - public ModelSchema EnableField(Guid fieldId) + public ModelSchema EnableField(long fieldId) { return UpdateField(fieldId, field => field.Enable()); } - public ModelSchema HideField(Guid fieldId) + public ModelSchema HideField(long fieldId) { return UpdateField(fieldId, field => field.Show()); } - public ModelSchema ShowField(Guid fieldId) + public ModelSchema ShowField(long fieldId) { return UpdateField(fieldId, field => field.Show()); } @@ -118,10 +118,8 @@ namespace PinkParrot.Core.Schema return new ModelSchema(metadata, fields.SetItem(field.Id, field)); } - public ModelSchema DeleteField(Guid fieldId) + public ModelSchema DeleteField(long fieldId) { - Guard.NotEmpty(fieldId, nameof(fieldId)); - if (!fields.ContainsKey(fieldId)) { throw new DomainValidationException($"A field with id {fieldId} does not exist."); @@ -130,7 +128,7 @@ namespace PinkParrot.Core.Schema return new ModelSchema(metadata, fields.Remove(fieldId)); } - private ModelSchema UpdateField(Guid fieldId, Func updater) + private ModelSchema UpdateField(long fieldId, Func updater) { ModelField field; diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs index 90d0ad8e1..60e0ff146 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs @@ -30,15 +30,15 @@ namespace PinkParrot.Core.Schema get { return minValue; } } - public NumberField(Guid id, string name) + public NumberField(long id, string name) : base(id, name) { } - protected override void ConfigureCore(PropertiesBag settings, ICollection errors) + protected override void ConfigureCore(dynamic settings, ICollection errors) { - maxValue = ParseNumber("MaxValue", settings, errors); - minValue = ParseNumber("MinValue", settings, errors); + maxValue = settings.MaxValue; + minValue = settings.MinValue; if (maxValue.HasValue && minValue.HasValue && minValue.Value > maxValue.Value) { @@ -46,23 +46,6 @@ namespace PinkParrot.Core.Schema } } - private static double? ParseNumber(string key, PropertiesBag settings, ICollection errors) - { - try - { - if (settings.Contains(key)) - { - return settings[key].ToNullableDouble(CultureInfo.InvariantCulture); - } - } - catch (InvalidCastException) - { - errors.Add($"'{key}' is not a valid number"); - } - - return null; - } - protected override Task ValidateCoreAsync(PropertyValue property, ICollection errors) { try diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs index ca22786d3..34b447473 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs @@ -6,17 +6,16 @@ // All rights reserved. // ========================================================================== -using System; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldAdded : IEvent { - public Guid FieldId { get; set; } + public long FieldId; public string FieldType; - public string FieldName { get; set; } + public string FieldName; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs index 04e5609c6..fa4f616fd 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs @@ -6,13 +6,12 @@ // All rights reserved. // ========================================================================== -using System; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldDeleted : IEvent { - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs index cbdaee9a2..9ced16879 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs @@ -6,13 +6,12 @@ // All rights reserved. // ========================================================================== -using System; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldDisabled : IEvent { - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs index 9a8ff9e24..8c3a777bc 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs @@ -6,13 +6,12 @@ // All rights reserved. // ========================================================================== -using System; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldEnabled : IEvent { - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs index fde8b7657..371572f54 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs @@ -6,13 +6,12 @@ // All rights reserved. // ========================================================================== -using System; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldHidden : IEvent { - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs index 212fe4114..af1714a43 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs @@ -6,13 +6,12 @@ // All rights reserved. // ========================================================================== -using System; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldShown : IEvent { - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs index e993c8068..a7071bcd0 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs @@ -6,15 +6,14 @@ // All rights reserved. // ========================================================================== -using System; using PinkParrot.Infrastructure; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { public class ModelFieldUpdated : IEvent { - public Guid FieldId { get; set; } + public long FieldId; public PropertiesBag Settings { get; set; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs index 9e9d8f8d7..1e60a854c 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs index 1671d9501..4e67bfbf9 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs index fcaaf5225..c4d345e89 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs @@ -7,7 +7,7 @@ // ========================================================================== using PinkParrot.Infrastructure; -using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Events.Schema { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTest.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs similarity index 100% rename from src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTest.cs rename to src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/DispatchingTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/DispatchingTests.cs new file mode 100644 index 000000000..ec89a3811 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/DispatchingTests.cs @@ -0,0 +1,284 @@ +// ========================================================================== +// DispatchingTests.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using PinkParrot.Infrastructure.Dispatching; +using PinkParrot.Infrastructure.Tasks; +using Xunit; + +namespace PinkParrot.Infrastructure +{ + public sealed class DispatchingTests + { + private interface IEvent { } + + private class EventA : IEvent { } + private class EventB : IEvent { } + private class Unknown : IEvent { } + + private class AsyncFuncConsumer + { + public int EventATriggered { get; private set; } + public int EventBTriggered { get; private set; } + + public Task DispatchEventAsync(IEvent @event) + { + return this.DispatchFuncAsync(@event, 9); + } + + public Task DispatchEventAsync(IEvent @event, int context) + { + return this.DispatchFuncAsync(@event, context, 13); + } + + public Task On(EventA @event) + { + return Task.FromResult(++EventATriggered); + } + + public Task On(EventB @event) + { + return Task.FromResult(++EventBTriggered); + } + + public Task On(EventA @event, int context) + { + return Task.FromResult(++EventATriggered + context); + } + + public Task On(EventB @event, int context) + { + return Task.FromResult(++EventBTriggered + context); + } + } + + private class AsyncConsumer + { + public int EventATriggered { get; private set; } + public int EventBTriggered { get; private set; } + + public Task DispatchEventAsync(IEvent @event) + { + return this.DispatchActionAsync(@event); + } + + public Task DispatchEventAsync(IEvent @event, int context) + { + return this.DispatchActionAsync(@event, context); + } + + public Task On(EventA @event) + { + EventATriggered++; + return TaskHelper.Done; + } + + public Task On(EventB @event) + { + EventBTriggered++; + return TaskHelper.Done; + } + + public Task On(EventA @event, int context) + { + EventATriggered = EventATriggered + context; + return TaskHelper.Done; + } + + public Task On(EventB @event, int context) + { + EventBTriggered = EventATriggered + context; + return TaskHelper.Done; + } + } + + private class SyncFuncConsumer + { + public int EventATriggered { get; private set; } + public int EventBTriggered { get; private set; } + + public int DispatchEvent(IEvent @event) + { + return this.DispatchFunc(@event, 9); + } + + public int DispatchEvent(IEvent @event, int context) + { + return this.DispatchFunc(@event, context, 13); + } + + public int On(EventA @event) + { + return ++EventATriggered; + } + + public int On(EventB @event) + { + return ++EventBTriggered; + } + + public int On(EventA @event, int context) + { + return ++EventATriggered + context; + } + + public int On(EventB @event, int context) + { + return ++EventBTriggered + context; + } + } + + private class SyncActionConsumer + { + public int EventATriggered { get; private set; } + public int EventBTriggered { get; private set; } + + public bool DispatchEvent(IEvent @event) + { + return this.DispatchAction(@event); + } + + public bool DispatchEvent(IEvent @event, int context) + { + return this.DispatchAction(@event, context); + } + + public void On(EventA @event) + { + EventATriggered++; + } + + public void On(EventB @event) + { + EventBTriggered++; + } + + public void On(EventA @event, int context) + { + EventATriggered = EventATriggered + context; + } + + public void On(EventB @event, int context) + { + EventBTriggered = EventATriggered + context; + } + } + + [Fact] + public void Should_invoke_correct_event() + { + var consumer = new SyncActionConsumer(); + + consumer.DispatchEvent(new EventA()); + consumer.DispatchEvent(new EventB()); + consumer.DispatchEvent(new EventB()); + consumer.DispatchEvent(new Unknown()); + + Assert.Equal(1, consumer.EventATriggered); + Assert.Equal(2, consumer.EventBTriggered); + } + + [Fact] + public void Should_invoke_correct_event_with_context() + { + var consumer = new SyncActionConsumer(); + + consumer.DispatchEvent(new EventA(), 2); + consumer.DispatchEvent(new EventB(), 2); + consumer.DispatchEvent(new EventB(), 2); + consumer.DispatchEvent(new Unknown(), 2); + + Assert.Equal(2, consumer.EventATriggered); + Assert.Equal(4, consumer.EventBTriggered); + } + + [Fact] + public async Task Should_invoke_correct_event_asynchronously() + { + var consumer = new AsyncConsumer(); + + await consumer.DispatchEventAsync(new EventA()); + await consumer.DispatchEventAsync(new EventB()); + await consumer.DispatchEventAsync(new EventB()); + await consumer.DispatchEventAsync(new Unknown()); + + Assert.Equal(1, consumer.EventATriggered); + Assert.Equal(2, consumer.EventBTriggered); + } + + [Fact] + public async Task Should_invoke_correct_event_with_context_asynchronously() + { + var consumer = new AsyncConsumer(); + + await consumer.DispatchEventAsync(new EventA(), 2); + await consumer.DispatchEventAsync(new EventB(), 2); + await consumer.DispatchEventAsync(new EventB(), 2); + await consumer.DispatchEventAsync(new Unknown(), 2); + + Assert.Equal(2, consumer.EventATriggered); + Assert.Equal(4, consumer.EventBTriggered); + } + + [Fact] + public void Should_invoke_correct_event_and_return() + { + var consumer = new SyncFuncConsumer(); + + Assert.Equal(1, consumer.DispatchEvent(new EventA())); + Assert.Equal(1, consumer.DispatchEvent(new EventB())); + Assert.Equal(2, consumer.DispatchEvent(new EventB())); + Assert.Equal(9, consumer.DispatchEvent(new Unknown())); + + Assert.Equal(1, consumer.EventATriggered); + Assert.Equal(2, consumer.EventBTriggered); + } + + [Fact] + public void Should_invoke_correct_event_with_context_and_return() + { + var consumer = new SyncFuncConsumer(); + + Assert.Equal(11, consumer.DispatchEvent(new EventA(), 10)); + Assert.Equal(11, consumer.DispatchEvent(new EventB(), 10)); + Assert.Equal(12, consumer.DispatchEvent(new EventB(), 10)); + Assert.Equal(13, consumer.DispatchEvent(new Unknown(), 10)); + + Assert.Equal(1, consumer.EventATriggered); + Assert.Equal(2, consumer.EventBTriggered); + } + + [Fact] + public async Task Should_invoke_correct_event_and_return_synchronously() + { + var consumer = new AsyncFuncConsumer(); + + Assert.Equal(1, await consumer.DispatchEventAsync(new EventA())); + Assert.Equal(1, await consumer.DispatchEventAsync(new EventB())); + Assert.Equal(2, await consumer.DispatchEventAsync(new EventB())); + Assert.Equal(9, await consumer.DispatchEventAsync(new Unknown())); + + Assert.Equal(1, consumer.EventATriggered); + Assert.Equal(2, consumer.EventBTriggered); + } + + [Fact] + public async Task Should_invoke_correct_event_with_context_and_return_synchronously() + { + var consumer = new AsyncFuncConsumer(); + + Assert.Equal(11, await consumer.DispatchEventAsync(new EventA(), 10)); + Assert.Equal(11, await consumer.DispatchEventAsync(new EventB(), 10)); + Assert.Equal(12, await consumer.DispatchEventAsync(new EventB(), 10)); + Assert.Equal(13, await consumer.DispatchEventAsync(new Unknown(), 10)); + + Assert.Equal(1, consumer.EventATriggered); + Assert.Equal(2, consumer.EventBTriggered); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTest.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTests.cs similarity index 94% rename from src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTest.cs rename to src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTests.cs index 17eb65aaa..735ef5b94 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTest.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTests.cs @@ -11,7 +11,7 @@ using Xunit; namespace PinkParrot.Infrastructure { - public sealed class EnumExtensionsTest + public sealed class EnumExtensionsTests { [Fact] public void Should_return_true_if_enum_is_valid() diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs index 3e437edab..31d214887 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs @@ -9,7 +9,9 @@ using System; using System.Globalization; using System.Linq; +using Newtonsoft.Json; using NodaTime; +using PinkParrot.Infrastructure.Json; using Xunit; // ReSharper disable PossibleInvalidOperationException @@ -28,6 +30,32 @@ namespace PinkParrot.Infrastructure dynamicBag = bag; } + [Fact] + public void Should_serialize_and_deserialize() + { + var time = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds()); + + bag.Set("Key1", time); + bag.Set("Key2", "MyString"); + bag.Set("Key3", 123L); + bag.Set("Key4", true); + bag.Set("Key5", Guid.NewGuid()); + + var serializerSettings = new JsonSerializerSettings(); + + serializerSettings.Converters.Add(new PropertiesBagConverter()); + + var content = JsonConvert.SerializeObject(bag, serializerSettings); + var response = JsonConvert.DeserializeObject(content, serializerSettings); + + foreach (var kvp in response.Properties.Take(4)) + { + Assert.Equal(kvp.Value.RawValue, bag[kvp.Key].RawValue); + } + + Assert.Equal(bag["Key5"].ToGuid(c), response["Key5"].ToGuid(c)); + } + [Fact] public void Should_return_false_when_renaming_unknown_property() { @@ -119,6 +147,18 @@ namespace PinkParrot.Infrastructure Assert.Throws(() => bag.Set("Key", (byte)1)); } + [Fact] + public void Should_return_false_when_making_contains_check() + { + Assert.False(dynamicBag.Contains("Key")); + } + + [Fact] + public void Should_provide_default_value_if_not_exists() + { + Assert.Equal(0, (int)dynamicBag.Key); + } + [Fact] public void Should_convert_string_to_numbers() { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs new file mode 100644 index 000000000..d9541efe1 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// AggregateCommand.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public class AggregateCommand : ICommand + { + public Guid AggregateId { get; set; } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs new file mode 100644 index 000000000..1689921b1 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs @@ -0,0 +1,72 @@ +// ========================================================================== +// CommandContext.cs +// Green Parrot Framework +// ========================================================================== +// Copyright (c) Sebastian Stehle +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public sealed class CommandContext + { + private readonly IDomainObjectFactory factory; + private readonly IDomainObjectRepository repository; + private readonly ICommand command; + private Exception exception; + private bool isSucceeded; + + public ICommand Command + { + get { return command; } + } + + public IDomainObjectFactory Factory + { + get { return factory; } + } + + public IDomainObjectRepository Repository + { + get { return repository; } + } + + public bool IsHandled + { + get { return isSucceeded || exception != null; } + } + + public bool IsSucceeded + { + get { return isSucceeded; } + } + + public Exception Exception + { + get { return exception; } + } + + public CommandContext(IDomainObjectFactory factory, IDomainObjectRepository repository, ICommand command) + { + Guard.NotNull(command, nameof(command)); + Guard.NotNull(factory, nameof(factory)); + Guard.NotNull(repository, nameof(repository)); + + this.command = command; + this.factory = factory; + this.repository = repository; + } + + public void MarkSucceeded() + { + isSucceeded = true; + } + + public void MarkFailed(Exception handlerException) + { + exception = handlerException; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandingExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandingExtensions.cs new file mode 100644 index 000000000..c6d4f5f02 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandingExtensions.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// CommandingExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public static class CommandingExtensions + { + public static T CreateNew(this IDomainObjectFactory factory, Guid id) where T : DomainObject + { + return (T)factory.CreateNew(typeof(T), id); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommand.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommand.cs new file mode 100644 index 000000000..465954f13 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommand.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// ICommand.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public interface ICommand + { + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandBus.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandBus.cs new file mode 100644 index 000000000..e486574fe --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandBus.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// ICommandBus.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public interface ICommandBus + { + Task PublishAsync(ICommand command); + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandHandler.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandHandler.cs new file mode 100644 index 000000000..81f1e0226 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/ICommandHandler.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// ICommandHandler.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public interface ICommandHandler + { + Task HandleAsync(CommandContext context); + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectFactory.cs new file mode 100644 index 000000000..36acf7810 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectFactory.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IDomainObjectFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public interface IDomainObjectFactory + { + IAggregate CreateNew(Type type, Guid id); + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs new file mode 100644 index 000000000..b2b719f93 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// IDomainObjectRepository.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public interface IDomainObjectRepository + { + Task GetByIdAsync(Guid id, int version = 0) where TDomainObject : class, IAggregate; + + Task SaveAsync(IAggregate domainObject, Guid commitId); + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs new file mode 100644 index 000000000..e01559228 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs @@ -0,0 +1,59 @@ +// ========================================================================== +// InMemoryCommandBus.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public sealed class InMemoryCommandBus : ICommandBus + { + private readonly IDomainObjectFactory factory; + private readonly IDomainObjectRepository repository; + private readonly IEnumerable handlers; + + public InMemoryCommandBus( + IDomainObjectRepository repository, + IDomainObjectFactory factory, + IEnumerable handlers) + { + Guard.NotNull(factory, nameof(factory)); + Guard.NotNull(handlers, nameof(handlers)); + Guard.NotNull(repository, nameof(repository)); + + this.factory = factory; + this.handlers = handlers; + this.repository = repository; + } + + public async Task PublishAsync(ICommand command) + { + Guard.NotNull(command, nameof(command)); + + var context = new CommandContext(factory, repository, command); + + foreach (var handler in handlers) + { + try + { + var isHandled = await handler.HandleAsync(context); + + if (isHandled) + { + context.MarkSucceeded(); + } + } + catch (Exception e) + { + context.MarkFailed(e); + } + } + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs index ab4a02804..d10c1ec32 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Infrastructure.CQRS { @@ -37,6 +38,8 @@ namespace PinkParrot.Infrastructure.CQRS this.version = version; } + protected abstract void ApplyEvent(IEvent @event); + protected void RaiseEvent(Envelope envelope, bool disableApply = false) where TEvent : class, IEvent { Guard.NotNull(envelope, nameof(envelope)); @@ -61,20 +64,9 @@ namespace PinkParrot.Infrastructure.CQRS } } - protected void Apply(object @event) - { - } - - private void ApplyEvent(dynamic @event) - { - Apply(@event); - version++; - } - void IAggregate.ApplyEvent(IEvent @event) { - Apply(@event as dynamic); - version++; + ApplyEvent(@event); version++; } void IAggregate.ClearUncommittedEvents() diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs index fcfa1d203..dbf64ce45 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs @@ -8,6 +8,7 @@ using System; using NodaTime; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Infrastructure.CQRS { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IEvent.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEvent.cs similarity index 88% rename from src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IEvent.cs rename to src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEvent.cs index 6ee3da7ea..7d9370c77 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IEvent.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEvent.cs @@ -5,7 +5,7 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== -namespace PinkParrot.Infrastructure.CQRS +namespace PinkParrot.Infrastructure.CQRS.Events { public interface IEvent { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs new file mode 100644 index 000000000..f7a243d13 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IEventConsumer.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +namespace PinkParrot.Infrastructure.CQRS.Events +{ + public interface IEventConsumer + { + void On(Envelope @event); + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs index f640513c4..8d71829ee 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Infrastructure.CQRS { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs new file mode 100644 index 000000000..a01cc87be --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// ActionDispatcher.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + public sealed class ActionContextDispatcher + { + private static readonly Dictionary> Handlers; + + static ActionContextDispatcher() + { + Handlers = + typeof(TTarget) + .GetMethods() + .Where(Helper.HasRightName) + .Where(Helper.HasRightParameters) + .Select(ActionContextDispatcherFactory.CreateActionHandler) + .ToDictionary(h => h.Item1, h => h.Item2); + } + + public static bool Dispatch(TTarget target, TIn input, TContext context) + { + Action handler; + + if (!Handlers.TryGetValue(input.GetType(), out handler)) + { + return false; + } + + handler(target, input, context); + + return true; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs new file mode 100644 index 000000000..e9d7aa153 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// ActionHandlerFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + internal class ActionContextDispatcherFactory + { + public static Tuple> CreateActionHandler(MethodInfo methodInfo) + { + var inputType = methodInfo.GetParameters()[0].ParameterType; + + var factoryMethod = + typeof(ActionContextDispatcherFactory) + .GetMethod("Factory", BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(typeof(TTarget), inputType, typeof(TContext)); + + var handler = factoryMethod.Invoke(null, new object[] { methodInfo }); + + return new Tuple>(inputType, (Action)handler); + } + + private static Action Factory(MethodInfo methodInfo) + { + var type = typeof(Action); + + var handler = (Action)methodInfo.CreateDelegate(type); + + return (target, input, context) => handler(target, (TIn)input, context); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs new file mode 100644 index 000000000..d0b1ede37 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// ActionDispatcher.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + public sealed class ActionDispatcher + { + private static readonly Dictionary> Handlers; + + static ActionDispatcher() + { + Handlers = + typeof(TTarget) + .GetMethods() + .Where(Helper.HasRightName) + .Where(Helper.HasRightParameters) + .Select(ActionDispatcherFactory.CreateActionHandler) + .ToDictionary(h => h.Item1, h => h.Item2); + } + + public static bool Dispatch(TTarget target, TIn item) + { + Action handler; + + if (!Handlers.TryGetValue(item.GetType(), out handler)) + { + return false; + } + + handler(target, item); + + return true; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcherFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcherFactory.cs new file mode 100644 index 000000000..1222e4419 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcherFactory.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// ActionHandlerFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + internal class ActionDispatcherFactory + { + public static Tuple> CreateActionHandler(MethodInfo methodInfo) + { + var inputType = methodInfo.GetParameters()[0].ParameterType; + + var factoryMethod = + typeof(ActionDispatcherFactory) + .GetMethod("Factory", BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(typeof(T), inputType); + + var handler = factoryMethod.Invoke(null, new object[] { methodInfo }); + + return new Tuple>(inputType, (Action)handler); + } + + private static Action Factory(MethodInfo methodInfo) + { + var type = typeof(Action); + + var handler = (Action)methodInfo.CreateDelegate(type); + + return (target, input) => handler(target, (TIn)input); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/DispatchExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/DispatchExtensions.cs new file mode 100644 index 000000000..d97348b84 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/DispatchExtensions.cs @@ -0,0 +1,81 @@ +// ========================================================================== +// DispatchExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.Dispatching +{ + public static class DispatchExtensions + { + public static bool DispatchAction(this TTarget target, TIn input) + { + return ActionDispatcher.Dispatch(target, input); + } + + public static bool DispatchAction(this TTarget target, TIn input, TContext context) + { + return ActionContextDispatcher.Dispatch(target, input, context); + } + + public static async Task DispatchActionAsync(this TTarget target, TIn input) + { + var task = FuncDispatcher.Dispatch(target, input); + + if (task == null) + { + return false; + } + + await task; + + return true; + } + + public static async Task DispatchActionAsync(this TTarget target, TIn input, TContext context) + { + var task = FuncContextDispatcher.Dispatch(target, input, context); + + if (task == null) + { + return false; + } + + await task; + + return true; + } + + public static TOut DispatchFunc(this TTarget target, TIn input, TOut fallback) + { + var result = FuncDispatcher.Dispatch(target, input); + + return Equals(result, default(TOut)) ? fallback : result; + } + + public static TOut DispatchFunc(this TTarget target, TIn input, TContext context, TOut fallback) + { + var result = FuncContextDispatcher.Dispatch(target, input, context); + + return Equals(result, default(TOut)) ? fallback : result; + } + + public static Task DispatchFuncAsync(this TTarget target, TIn input, TOut fallback) + { + var result = FuncDispatcher>.Dispatch(target, input); + + return result ?? Task.FromResult(fallback); + } + + public static Task DispatchFuncAsync(this TTarget target, TIn input, TContext context, TOut fallback) + { + var result = FuncContextDispatcher>.Dispatch(target, input, context); + + return result ?? Task.FromResult(fallback); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs new file mode 100644 index 000000000..e166d71da --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// FuncDispatcher.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + public sealed class FuncContextDispatcher + { + private static readonly Dictionary> Handlers; + + static FuncContextDispatcher() + { + Handlers = + typeof(TTarget) + .GetMethods() + .Where(Helper.HasRightName) + .Where(Helper.HasRightParameters) + .Where(Helper.HasRightReturnType) + .Select(FuncContextDispatcherFactory.CreateFuncHandler) + .ToDictionary(h => h.Item1, h => h.Item2); + } + + public static TOut Dispatch(TTarget target, TIn item, TContext context) + { + Func handler; + + if (Handlers.TryGetValue(item.GetType(), out handler)) + { + return handler(target, item, context); + } + + return default(TOut); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcherFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcherFactory.cs new file mode 100644 index 000000000..0849d245e --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcherFactory.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// HandlerFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + internal static class FuncContextDispatcherFactory + { + public static Tuple> CreateFuncHandler(MethodInfo methodInfo) + { + var inputType = methodInfo.GetParameters()[0].ParameterType; + + var factoryMethod = + typeof(FuncContextDispatcherFactory) + .GetMethod("Factory", BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(typeof(TTarget), inputType, typeof(TContext), methodInfo.ReturnType); + + var handler = factoryMethod.Invoke(null, new object[] { methodInfo }); + + return new Tuple>(inputType, (Func)handler); + } + + private static Func Factory(MethodInfo methodInfo) + { + var type = typeof(Func); + + var handler = (Func)methodInfo.CreateDelegate(type); + + return (target, input, context) => handler(target, (TIn)input, context); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs new file mode 100644 index 000000000..488197286 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// FuncDispatcher.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + public sealed class FuncDispatcher + { + private static readonly Dictionary> Handlers; + + static FuncDispatcher() + { + Handlers = + typeof(TTarget) + .GetMethods() + .Where(Helper.HasRightName) + .Where(Helper.HasRightParameters) + .Where(Helper.HasRightReturnType) + .Select(FuncDispatcherFactory.CreateFuncHandler) + .ToDictionary(h => h.Item1, h => h.Item2); + } + + public static TOut Dispatch(TTarget target, TIn item) + { + Func handler; + + if (Handlers.TryGetValue(item.GetType(), out handler)) + { + return handler(target, item); + } + + return default(TOut); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcherFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcherFactory.cs new file mode 100644 index 000000000..797816c4c --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcherFactory.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// HandlerFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + internal static class FuncDispatcherFactory + { + public static Tuple> CreateFuncHandler(MethodInfo methodInfo) + { + var inputType = methodInfo.GetParameters()[0].ParameterType; + + var factoryMethod = + typeof(FuncDispatcherFactory) + .GetMethod("Factory", BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(typeof(TTarget), inputType, methodInfo.ReturnType); + + var handler = factoryMethod.Invoke(null, new object[] { methodInfo }); + + return new Tuple>(inputType, (Func)handler); + } + + private static Func Factory(MethodInfo methodInfo) + { + var type = typeof(Func); + + var handler = (Func)methodInfo.CreateDelegate(type); + + return (target, input) => handler(target, (TIn)input); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/Helper.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/Helper.cs new file mode 100644 index 000000000..cc97c9a06 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/Helper.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Helper.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Reflection; + +namespace PinkParrot.Infrastructure.Dispatching +{ + internal static class Helper + { + public static bool HasRightName(MethodInfo method) + { + return method.Name == "On"; + } + + public static bool HasRightReturnType(MethodInfo method) + { + return method.ReturnType == typeof(TOut); + } + + public static bool HasRightParameters(MethodInfo method) + { + var parameters = method.GetParameters(); + + return parameters.Length == 1 && typeof(TIn).IsAssignableFrom(parameters[0].ParameterType); + } + + public static bool HasRightParameters(MethodInfo method) + { + var parameters = method.GetParameters(); + + return parameters.Length == 2 && typeof(TIn).IsAssignableFrom(parameters[0].ParameterType) && parameters[1].ParameterType == typeof(TContext); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/ICommandContext.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/ICommandContext.cs new file mode 100644 index 000000000..c1aad3861 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/ICommandContext.cs @@ -0,0 +1,15 @@ +using System; + +namespace PinkParrot.Infrastructure.CQRS.Commands +{ + public interface ICommandContext + { + ICommand Command { get; } + Exception Exception { get; } + IDomainObjectFactory Factory { get; } + bool IsFailed { get; } + bool IsHandled { get; } + bool IsSucceeded { get; } + IDomainObjectRepository Repository { get; } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/JsonPropertiesBagConverter.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/JsonPropertiesBagConverter.cs new file mode 100644 index 000000000..af3b5a364 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/JsonPropertiesBagConverter.cs @@ -0,0 +1,76 @@ +// ========================================================================== +// JsonPropertiesBagConverter.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using Newtonsoft.Json; +using NodaTime; +using NodaTime.Extensions; + +namespace PinkParrot.Infrastructure.Json +{ + public sealed class PropertiesBagConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(PropertiesBag); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var properties = new PropertiesBag(); + + while (reader.Read()) + { + if (reader.TokenType != JsonToken.PropertyName) + { + break; + } + + var key = reader.Value.ToString(); + + reader.Read(); + + var value = reader.Value; + + if (value is DateTime) + { + properties.Set(key, ((DateTime)value).ToInstant()); + } + else + { + properties.Set(key, value); + } + } + + return properties; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var properties = (PropertiesBag)value; + + writer.WriteStartObject(); + + foreach (var kvp in properties.Properties) + { + writer.WritePropertyName(kvp.Key); + + if (kvp.Value.RawValue is Instant) + { + writer.WriteValue(kvp.Value.ToString()); + } + else + { + writer.WriteValue(kvp.Value.RawValue); + } + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs index 612803f97..72d0acd2a 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs @@ -14,6 +14,7 @@ namespace PinkParrot.Infrastructure { public class PropertiesBag : DynamicObject { + private static readonly PropertyValue FallbackValue = new PropertyValue(null); private readonly Dictionary internalDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); public int Count @@ -37,7 +38,7 @@ namespace PinkParrot.Infrastructure { Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); - return internalDictionary[propertyName]; + return internalDictionary.GetOrDefault(propertyName) ?? FallbackValue; } } @@ -48,7 +49,9 @@ namespace PinkParrot.Infrastructure public override bool TryGetMember(GetMemberBinder binder, out object result) { - return internalDictionary.TryGetValueAsObject(binder.Name, out result); + result = this[binder.Name]; + + return true; } public override bool TrySetMember(SetMemberBinder binder, object value) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json index 51889eb58..e07b8b190 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json @@ -1,8 +1,11 @@ -{ +{ "version": "1.0.0-*", "dependencies": { "NodaTime": "2.0.0-alpha20160729", - "NETStandard.Library": "1.6.0" + "NETStandard.Library": "1.6.0", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Linq": "4.1.0", + "Newtonsoft.Json": "9.0.1" }, "frameworks": { "netcoreapp1.0": { diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs index 1a334abd6..dd9b1937c 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class AddModelField + public class AddModelField : AggregateCommand { - public Guid AggregateId; - public string FieldType; public string FieldName; diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs index cf7109f6d..6be8f93b7 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class CreateModelSchema + public class CreateModelSchema : AggregateCommand { - public Guid AggregateId; - public string Name; } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs index 434cd385f..fb650e360 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DeleteModelField + public class DeleteModelField : AggregateCommand { - public Guid AggregateId; - - public Guid FieldId; + public long FieldId; } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs index dd737f813..5a35849bb 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs @@ -6,12 +6,11 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DeleteModelSchema + public class DeleteModelSchema : AggregateCommand { - public Guid AggregateId; } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs index 3a115a3b9..d07d1a0fe 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DisableModelField + public class DisableModelField : AggregateCommand { - public Guid AggregateId; - - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs index 5d99b1b82..fd28e8e32 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class EnableModelField + public class EnableModelField : AggregateCommand { - public Guid AggregateId; - - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs index dc4c9ffe8..2b6640ce0 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class HideModelField + public class HideModelField : AggregateCommand { - public Guid AggregateId; - - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs index f65f8dd9e..9cef4a2cd 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using System; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class ShowModelField + public class ShowModelField : AggregateCommand { - public Guid AggregateId; - - public Guid FieldId; + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs index 68c604dfe..387c34335 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs @@ -6,16 +6,14 @@ // All rights reserved. // ========================================================================== -using System; using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class UpdateModelField + public class UpdateModelField : AggregateCommand { - public Guid AggregateId; - - public Guid FieldId; + public long FieldId; public PropertiesBag Settings; } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs index 75b636e49..3aada1f07 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs @@ -6,15 +6,13 @@ // All rights reserved. // ========================================================================== -using System; using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class UpdateModelSchema + public class UpdateModelSchema : AggregateCommand { - public Guid AggregateId; - public string NewName; public PropertiesBag Settings { get; set; } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs new file mode 100644 index 000000000..dd29aa781 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs @@ -0,0 +1,87 @@ +// ========================================================================== +// ModelSchemaCommandHandler.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Infrastructure.Dispatching; +using PinkParrot.Write.Schema.Commands; + +namespace PinkParrot.Write.Schema +{ + public class ModelSchemaCommandHandler : ICommandHandler + { + public Task HandleAsync(CommandContext context) + { + return this.DispatchActionAsync(context.Command, context); + } + + protected Task On(AddModelField command, CommandContext context) + { + return Update(command, context, schema => schema.AddField(command)); + } + + protected Task On(DeleteModelField command, CommandContext context) + { + return Update(command, context, schema => schema.DeleteField(command)); + } + + protected Task On(DeleteModelSchema command, CommandContext context) + { + return Update(command, context, schema => schema.Delete(command)); + } + + protected Task On(DisableModelField command, CommandContext context) + { + return Update(command, context, schema => schema.DisableField(command)); + } + + protected Task On(EnableModelField command, CommandContext context) + { + return Update(command, context, schema => schema.EnableField(command)); + } + + protected Task On(HideModelField command, CommandContext context) + { + return Update(command, context, schema => schema.HideField(command)); + } + + protected Task On(ShowModelField command, CommandContext context) + { + return Update(command, context, schema => schema.ShowField(command)); + } + + protected Task On(UpdateModelField command, CommandContext context) + { + return Update(command, context, schema => schema.UpdateField(command)); + } + + protected Task On(UpdateModelSchema command, CommandContext context) + { + return Update(command, context, schema => schema.Update(command)); + } + + protected Task On(CreateModelSchema command, CommandContext context) + { + var schema = context.Factory.CreateNew(command.AggregateId); + + schema.Create(command); + + return context.Repository.SaveAsync(schema, Guid.NewGuid()); + } + + private async Task Update(AggregateCommand command, CommandContext context, Action updater) + { + var schema = await context.Repository.GetByIdAsync(command.AggregateId); + + updater(schema); + + await context.Repository.SaveAsync(schema, Guid.NewGuid()); + } + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs index d7a3171e6..2b7b1e882 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs @@ -10,6 +10,8 @@ using System; using PinkParrot.Core.Schema; using PinkParrot.Events.Schema; using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; +using PinkParrot.Infrastructure.Dispatching; using PinkParrot.Write.Schema.Commands; namespace PinkParrot.Write.Schema @@ -18,14 +20,9 @@ namespace PinkParrot.Write.Schema { private readonly ModelFieldFactory fieldFactory; private bool isDeleted; + private long totalFields; private ModelSchema schema; - public ModelSchemaDomainObject(Guid id, int version, ModelFieldFactory fieldFactory) - : base(id, version) - { - this.fieldFactory = fieldFactory; - } - public ModelSchema Schema { get { return schema; } @@ -36,14 +33,22 @@ namespace PinkParrot.Write.Schema get { return isDeleted; } } - protected void Apply(ModelSchemaCreated @event) + public ModelSchemaDomainObject(Guid id, int version, ModelFieldFactory fieldFactory) + : base(id, version) { - schema = ModelSchema.Create(@event.Name); + this.fieldFactory = fieldFactory; } protected void Apply(ModelFieldAdded @event) { schema = schema.AddField(@event.FieldId, @event.FieldType, @event.FieldName, fieldFactory); + + totalFields++; + } + + protected void Apply(ModelSchemaCreated @event) + { + schema = ModelSchema.Create(@event.Name); } protected void Apply(ModelFieldUpdated @event) @@ -86,23 +91,24 @@ namespace PinkParrot.Write.Schema isDeleted = false; } - public void Create(CreateModelSchema command) + public void AddField(AddModelField command) { - VerifyNotCreated(); + VerifyCreatedAndNotDeleted(); - schema = ModelSchema.Create(command.Name); + var id = ++totalFields; + + schema = schema.AddField(id, command.FieldType, command.FieldName, fieldFactory); - RaiseEvent(new ModelSchemaCreated {Name = command.Name}, true); + RaiseEvent(new ModelFieldAdded { FieldId = id, FieldType = command.FieldType, FieldName = command.FieldName }, true); } - public void AddField(Guid id, AddModelField command) + public void Create(CreateModelSchema command) { - VerifyCreatedAndNotDeleted(); + VerifyNotCreated(); - schema = schema.AddField(id, command.FieldType, command.FieldName, fieldFactory); + schema = ModelSchema.Create(command.Name); - RaiseEvent( - new ModelFieldAdded {FieldId = id, FieldType = command.FieldType, FieldName = command.FieldName}, true); + RaiseEvent(new ModelSchemaCreated { Name = command.Name }, true); } public void Update(UpdateModelSchema command) @@ -111,7 +117,7 @@ namespace PinkParrot.Write.Schema schema = schema.Update(schema.Metadata.Configure(command.NewName, command.Settings)); - RaiseEvent(new ModelSchemaUpdated {NewName = command.NewName, Settings = command.Settings}, true); + RaiseEvent(new ModelSchemaUpdated { NewName = command.NewName, Settings = command.Settings }, true); } public void UpdateField(UpdateModelField command) @@ -120,7 +126,7 @@ namespace PinkParrot.Write.Schema schema = schema.SetField(command.FieldId, command.Settings); - RaiseEvent(new ModelFieldUpdated {FieldId = command.FieldId, Settings = command.Settings}, true); + RaiseEvent(new ModelFieldUpdated { FieldId = command.FieldId, Settings = command.Settings }, true); } public void HideField(HideModelField command) @@ -129,16 +135,16 @@ namespace PinkParrot.Write.Schema schema = schema.HideField(command.FieldId); - RaiseEvent(new ModelFieldHidden {FieldId = command.FieldId}, true); + RaiseEvent(new ModelFieldHidden { FieldId = command.FieldId }, true); } - public void ShowField(HideModelField command) + public void ShowField(ShowModelField command) { VerifyCreatedAndNotDeleted(); schema = schema.ShowField(command.FieldId); - RaiseEvent(new ModelFieldShown {FieldId = command.FieldId}, true); + RaiseEvent(new ModelFieldShown { FieldId = command.FieldId }, true); } public void DisableField(DisableModelField command) @@ -147,7 +153,7 @@ namespace PinkParrot.Write.Schema schema = schema.DisableField(command.FieldId); - RaiseEvent(new ModelFieldDisabled {FieldId = command.FieldId}, true); + RaiseEvent(new ModelFieldDisabled { FieldId = command.FieldId }, true); } public void EnableField(EnableModelField command) @@ -156,7 +162,7 @@ namespace PinkParrot.Write.Schema schema = schema.EnableField(command.FieldId); - RaiseEvent(new ModelFieldEnabled {FieldId = command.FieldId}, true); + RaiseEvent(new ModelFieldEnabled { FieldId = command.FieldId }, true); } public void Delete(DeleteModelSchema command) @@ -174,7 +180,7 @@ namespace PinkParrot.Write.Schema schema = schema.DeleteField(command.FieldId); - RaiseEvent(new ModelFieldDeleted {FieldId = command.FieldId}, true); + RaiseEvent(new ModelFieldDeleted { FieldId = command.FieldId }, true); } private void VerifyNotCreated() @@ -192,5 +198,10 @@ namespace PinkParrot.Write.Schema throw new InvalidOperationException("Schema has already been deleted or not created yet."); } } + + protected override void ApplyEvent(IEvent @event) + { + this.DispatchAction(@event); + } } } \ No newline at end of file