From 5e8009f4584be70b26e32f38ae9119a94baa8317 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 12 Feb 2018 19:03:17 +0100 Subject: [PATCH] App and schema grain. --- .../AppProvider.cs | 46 +-- .../Apps/AppCommandMiddleware.cs | 200 ----------- .../Apps/AppDomainObject.cs | 228 ------------- .../Apps/AppGrain.cs | 320 ++++++++++++++++++ .../Apps/Guards/GuardAppClients.cs | 2 +- .../Apps/Guards/GuardAppContributors.cs | 2 +- .../Apps/Guards/GuardAppLanguages.cs | 2 +- .../Apps/IAppEntity.cs | 6 +- .../Apps/IAppGrain.cs | 18 + .../Rules/IRuleGrain.cs | 3 + .../Rules/RuleGrain.cs | 16 +- .../Schemas/ISchemaGrain.cs | 3 + .../Schemas/SchemaGrain.cs | 36 +- src/Squidex/Config/Domain/WriteServices.cs | 9 +- .../EnrichWithSchemaIdCommandMiddleware.cs | 2 +- tools/Migrate_01/Migrations/AddPatterns.cs | 21 +- tools/Migrate_01/Rebuilder.cs | 17 +- 17 files changed, 417 insertions(+), 514 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs delete mode 100644 src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/IAppGrain.cs diff --git a/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/src/Squidex.Domain.Apps.Entities/AppProvider.cs index b2e028a12..a81cf8e22 100644 --- a/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Orleans; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Rules; @@ -16,52 +17,51 @@ using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities { public sealed class AppProvider : IAppProvider { + private readonly IGrainFactory grainFactory; private readonly IAppRepository appRepository; private readonly IRuleRepository ruleRepository; private readonly ISchemaRepository schemaRepository; - private readonly IStateFactory stateFactory; public AppProvider( + IGrainFactory grainFactory, IAppRepository appRepository, ISchemaRepository schemaRepository, - IStateFactory stateFactory, IRuleRepository ruleRepository) { + Guard.NotNull(grainFactory, nameof(grainFactory)); Guard.NotNull(appRepository, nameof(appRepository)); Guard.NotNull(schemaRepository, nameof(schemaRepository)); - Guard.NotNull(stateFactory, nameof(stateFactory)); Guard.NotNull(ruleRepository, nameof(ruleRepository)); + this.grainFactory = grainFactory; this.appRepository = appRepository; this.schemaRepository = schemaRepository; - this.stateFactory = stateFactory; this.ruleRepository = ruleRepository; } public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id) { - var app = await stateFactory.GetSingleAsync(appId); + var app = await grainFactory.GetGrain(appId).GetStateAsync(); - if (!IsFound(app)) + if (!IsFound(app.Value)) { return (null, null); } - var schema = await stateFactory.GetSingleAsync(id); + var schema = await grainFactory.GetGrain(id).GetStateAsync(); - if (!IsFound(schema) || schema.Snapshot.IsDeleted) + if (!IsFound(schema.Value) || schema.Value.IsDeleted) { return (null, null); } - return (app.Snapshot, schema.Snapshot); + return (app.Value, schema.Value); } public async Task GetAppAsync(string appName) @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities return null; } - return (await stateFactory.GetSingleAsync(appId)).Snapshot; + return (await grainFactory.GetGrain(appId).GetStateAsync()).Value; } public async Task GetSchemaAsync(Guid appId, string name) @@ -85,19 +85,19 @@ namespace Squidex.Domain.Apps.Entities return null; } - return (await stateFactory.GetSingleAsync(schemaId)).Snapshot; + return (await grainFactory.GetGrain(schemaId).GetStateAsync()).Value; } public async Task GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false) { - var schema = await stateFactory.GetSingleAsync(id); + var schema = await grainFactory.GetGrain(id).GetStateAsync(); - if (!IsFound(schema) || (schema.Snapshot.IsDeleted && !allowDeleted) || schema.Snapshot.AppId.Id != appId) + if (!IsFound(schema.Value) || (schema.Value.IsDeleted && !allowDeleted) || schema.Value.AppId.Id != appId) { return null; } - return schema.Snapshot; + return schema.Value; } public async Task> GetSchemasAsync(Guid appId) @@ -106,9 +106,9 @@ namespace Squidex.Domain.Apps.Entities var schemas = await Task.WhenAll( - ids.Select(id => stateFactory.GetSingleAsync(id))); + ids.Select(id => grainFactory.GetGrain(id).GetStateAsync())); - return schemas.Where(IsFound).Select(s => (ISchemaEntity)s.Snapshot).ToList(); + return schemas.Where(s => IsFound(s.Value)).Select(s => (ISchemaEntity)s.Value).ToList(); } public async Task> GetRulesAsync(Guid appId) @@ -117,9 +117,9 @@ namespace Squidex.Domain.Apps.Entities var rules = await Task.WhenAll( - ids.Select(id => stateFactory.GetSingleAsync(id))); + ids.Select(id => grainFactory.GetGrain(id).GetStateAsync())); - return rules.Where(IsFound).Select(r => (IRuleEntity)r.Snapshot).ToList(); + return rules.Where(r => IsFound(r.Value)).Select(r => (IRuleEntity)r.Value).ToList(); } public async Task> GetUserApps(string userId) @@ -128,9 +128,9 @@ namespace Squidex.Domain.Apps.Entities var apps = await Task.WhenAll( - ids.Select(id => stateFactory.GetSingleAsync(id))); + ids.Select(id => grainFactory.GetGrain(id).GetStateAsync())); - return apps.Where(IsFound).Select(a => (IAppEntity)a.Snapshot).ToList(); + return apps.Where(a => IsFound(a.Value)).Select(a => (IAppEntity)a.Value).ToList(); } private Task GetAppIdAsync(string name) @@ -143,9 +143,9 @@ namespace Squidex.Domain.Apps.Entities return await schemaRepository.FindSchemaIdAsync(appId, name); } - private static bool IsFound(IDomainObject app) + private static bool IsFound(IEntityWithVersion entity) { - return app.Version > EtagVersion.Empty; + return entity.Version > EtagVersion.Empty; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs deleted file mode 100644 index 829c1388c..000000000 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ /dev/null @@ -1,200 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Domain.Apps.Entities.Apps.Guards; -using Squidex.Domain.Apps.Entities.Apps.Services; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; -using Squidex.Shared.Users; - -namespace Squidex.Domain.Apps.Entities.Apps -{ - public class AppCommandMiddleware : ICommandMiddleware - { - private readonly IAggregateHandler handler; - private readonly IAppProvider appProvider; - private readonly IAppPlansProvider appPlansProvider; - private readonly IAppPlanBillingManager appPlansBillingManager; - private readonly IUserResolver userResolver; - - public AppCommandMiddleware( - IAggregateHandler handler, - IAppProvider appProvider, - IAppPlansProvider appPlansProvider, - IAppPlanBillingManager appPlansBillingManager, - IUserResolver userResolver) - { - Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); - Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); - - this.handler = handler; - this.userResolver = userResolver; - this.appProvider = appProvider; - this.appPlansProvider = appPlansProvider; - this.appPlansBillingManager = appPlansBillingManager; - } - - protected async Task On(CreateApp command, CommandContext context) - { - var app = await handler.CreateSyncedAsync(context, async a => - { - await GuardApp.CanCreate(command, appProvider); - - a.Create(command); - - context.Complete(EntityCreatedResult.Create(command.AppId, a.Version)); - }); - } - - protected Task On(AssignContributor command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, async a => - { - await GuardAppContributors.CanAssign(a.Snapshot.Contributors, command, userResolver, appPlansProvider.GetPlan(a.Snapshot.Plan?.PlanId)); - - a.AssignContributor(command); - }); - } - - protected Task On(RemoveContributor command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppContributors.CanRemove(a.Snapshot.Contributors, command); - - a.RemoveContributor(command); - }); - } - - protected Task On(AttachClient command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppClients.CanAttach(a.Snapshot.Clients, command); - - a.AttachClient(command); - }); - } - - protected Task On(UpdateClient command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppClients.CanUpdate(a.Snapshot.Clients, command); - - a.UpdateClient(command); - }); - } - - protected Task On(RevokeClient command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppClients.CanRevoke(a.Snapshot.Clients, command); - - a.RevokeClient(command); - }); - } - - protected Task On(AddLanguage command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppLanguages.CanAdd(a.Snapshot.LanguagesConfig, command); - - a.AddLanguage(command); - }); - } - - protected Task On(RemoveLanguage command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppLanguages.CanRemove(a.Snapshot.LanguagesConfig, command); - - a.RemoveLanguage(command); - }); - } - - protected Task On(UpdateLanguage command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppLanguages.CanUpdate(a.Snapshot.LanguagesConfig, command); - - a.UpdateLanguage(command); - }); - } - - protected Task On(AddPattern command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppPattern.CanAdd(a.Snapshot.Patterns, command); - - a.AddPattern(command); - }); - } - - protected Task On(DeletePattern command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAppPattern.CanDelete(a.Snapshot.Patterns, command); - - a.DeletePattern(command); - }); - } - - protected async Task On(UpdatePattern command, CommandContext context) - { - await handler.UpdateSyncedAsync(context, a => - { - GuardAppPattern.CanUpdate(a.Snapshot.Patterns, command); - - a.UpdatePattern(command); - }); - } - - protected Task On(ChangePlan command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, async a => - { - GuardApp.CanChangePlan(command, a.Snapshot.Plan, appPlansProvider); - - if (command.FromCallback) - { - a.ChangePlan(command); - } - else - { - var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Snapshot.Id, a.Snapshot.Name, command.PlanId); - - if (result is PlanChangedResult) - { - a.ChangePlan(command); - } - - context.Complete(result); - } - }); - } - - public async Task HandleAsync(CommandContext context, Func next) - { - await this.DispatchActionAsync(context.Command, context); - await next(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs deleted file mode 100644 index 2dedb3a7a..000000000 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ /dev/null @@ -1,228 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Domain.Apps.Entities.Apps.State; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Entities.Apps -{ - public sealed class AppDomainObject : SquidexDomainObjectBase - { - private readonly InitialPatterns initialPatterns; - - public AppDomainObject(InitialPatterns initialPatterns) - { - Guard.NotNull(initialPatterns, nameof(initialPatterns)); - - this.initialPatterns = initialPatterns; - } - - public AppDomainObject Create(CreateApp command) - { - ThrowIfCreated(); - - var appId = new NamedId(command.AppId, command.Name); - - var events = new List - { - CreateInitalEvent(command.Name), - CreateInitialOwner(command.Actor), - CreateInitialLanguage() - }; - - foreach (var pattern in initialPatterns) - { - events.Add(CreateInitialPattern(pattern.Key, pattern.Value)); - } - - foreach (var @event in events) - { - @event.Actor = command.Actor; - @event.AppId = appId; - - RaiseEvent(@event); - } - - return this; - } - - public AppDomainObject UpdateLanguage(UpdateLanguage command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); - - return this; - } - - public AppDomainObject UpdateClient(UpdateClient command) - { - ThrowIfNotCreated(); - - if (!string.IsNullOrWhiteSpace(command.Name)) - { - RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); - } - - if (command.Permission.HasValue) - { - RaiseEvent(SimpleMapper.Map(command, new AppClientUpdated { Permission = command.Permission.Value })); - } - - return this; - } - - public AppDomainObject AssignContributor(AssignContributor command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); - - return this; - } - - public AppDomainObject RemoveContributor(RemoveContributor command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); - - return this; - } - - public AppDomainObject AttachClient(AttachClient command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppClientAttached())); - - return this; - } - - public AppDomainObject RevokeClient(RevokeClient command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); - - return this; - } - - public AppDomainObject AddLanguage(AddLanguage command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded())); - - return this; - } - - public AppDomainObject RemoveLanguage(RemoveLanguage command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved())); - - return this; - } - - public AppDomainObject ChangePlan(ChangePlan command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged())); - - return this; - } - - public AppDomainObject AddPattern(AddPattern command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppPatternAdded())); - - return this; - } - - public AppDomainObject DeletePattern(DeletePattern command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppPatternDeleted())); - - return this; - } - - public AppDomainObject UpdatePattern(UpdatePattern command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppPatternUpdated())); - - return this; - } - - private void RaiseEvent(AppEvent @event) - { - if (@event.AppId == null) - { - @event.AppId = new NamedId(Snapshot.Id, Snapshot.Name); - } - - RaiseEvent(Envelope.Create(@event)); - } - - private static AppCreated CreateInitalEvent(string name) - { - return new AppCreated { Name = name }; - } - - private static AppPatternAdded CreateInitialPattern(Guid id, AppPattern pattern) - { - return new AppPatternAdded { PatternId = id, Name = pattern.Name, Pattern = pattern.Pattern, Message = pattern.Message }; - } - - private static AppLanguageAdded CreateInitialLanguage() - { - return new AppLanguageAdded { Language = Language.EN }; - } - - private static AppContributorAssigned CreateInitialOwner(RefToken actor) - { - return new AppContributorAssigned { ContributorId = actor.Identifier, Permission = AppContributorPermission.Owner }; - } - - private void ThrowIfNotCreated() - { - if (string.IsNullOrWhiteSpace(Snapshot.Name)) - { - throw new DomainException("App has not been created."); - } - } - - private void ThrowIfCreated() - { - if (!string.IsNullOrWhiteSpace(Snapshot.Name)) - { - throw new DomainException("App has already been created."); - } - } - - public override void ApplyEvent(Envelope @event) - { - ApplySnapshot(Snapshot.Apply(@event)); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs new file mode 100644 index 000000000..ae1ad9337 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs @@ -0,0 +1,320 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Guards; +using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Domain.Apps.Entities.Apps.State; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Apps; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Orleans; +using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.States; +using Squidex.Shared.Users; + +namespace Squidex.Domain.Apps.Entities.Apps +{ + public sealed class AppGrain : DomainObjectGrain, IAppGrain + { + private readonly InitialPatterns initialPatterns; + private readonly IAppProvider appProvider; + private readonly IAppPlansProvider appPlansProvider; + private readonly IAppPlanBillingManager appPlansBillingManager; + private readonly IUserResolver userResolver; + + public AppGrain( + IStore store, + IAppProvider appProvider, + IAppPlansProvider appPlansProvider, + IAppPlanBillingManager appPlansBillingManager, + IUserResolver userResolver, + InitialPatterns initialPatterns) + : base(store) + { + Guard.NotNull(initialPatterns, nameof(initialPatterns)); + Guard.NotNull(appProvider, nameof(appProvider)); + Guard.NotNull(userResolver, nameof(userResolver)); + Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); + Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); + + this.userResolver = userResolver; + this.appProvider = appProvider; + this.appPlansProvider = appPlansProvider; + this.appPlansBillingManager = appPlansBillingManager; + this.initialPatterns = initialPatterns; + } + + public override Task ExecuteAsync(IAggregateCommand command) + { + switch (command) + { + case CreateApp createApp: + return CreateAsync(createApp, async c => + { + await GuardApp.CanCreate(c, appProvider); + + Create(c); + }); + + case AssignContributor assigneContributor: + return UpdateAsync(assigneContributor, async c => + { + await GuardAppContributors.CanAssign(Snapshot.Contributors, c, userResolver, appPlansProvider.GetPlan(Snapshot.Plan?.PlanId)); + + AssignContributor(c); + }); + + case RemoveContributor removeContributor: + return UpdateAsync(removeContributor, c => + { + GuardAppContributors.CanRemove(Snapshot.Contributors, c); + + RemoveContributor(c); + }); + + case AttachClient attachClient: + return UpdateAsync(attachClient, c => + { + GuardAppClients.CanAttach(Snapshot.Clients, c); + + AttachClient(c); + }); + + case UpdateClient updateClient: + return UpdateAsync(updateClient, c => + { + GuardAppClients.CanUpdate(Snapshot.Clients, c); + + UpdateClient(c); + }); + + case RevokeClient revokeClient: + return UpdateAsync(revokeClient, c => + { + GuardAppClients.CanRevoke(Snapshot.Clients, c); + + RevokeClient(c); + }); + + case AddLanguage addLanguage: + return UpdateAsync(addLanguage, c => + { + GuardAppLanguages.CanAdd(Snapshot.LanguagesConfig, c); + + AddLanguage(c); + }); + + case RemoveLanguage removeLanguage: + return UpdateAsync(removeLanguage, c => + { + GuardAppLanguages.CanRemove(Snapshot.LanguagesConfig, c); + + RemoveLanguage(c); + }); + + case UpdateLanguage updateLanguage: + return UpdateAsync(updateLanguage, c => + { + GuardAppLanguages.CanUpdate(Snapshot.LanguagesConfig, c); + + UpdateLanguage(c); + }); + + case AddPattern addPattern: + return UpdateAsync(addPattern, c => + { + GuardAppPattern.CanAdd(Snapshot.Patterns, c); + + AddPattern(c); + }); + + case DeletePattern deletePattern: + return UpdateAsync(deletePattern, c => + { + GuardAppPattern.CanDelete(Snapshot.Patterns, c); + + DeletePattern(c); + }); + + case UpdatePattern updatePattern: + return UpdateAsync(updatePattern, c => + { + GuardAppPattern.CanUpdate(Snapshot.Patterns, c); + + UpdatePattern(c); + }); + + case ChangePlan changePlan: + return UpdateReturnAsync(changePlan, async c => + { + GuardApp.CanChangePlan(c, Snapshot.Plan, appPlansProvider); + + if (c.FromCallback) + { + ChangePlan(c); + + return null; + } + else + { + var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, c.PlanId); + + if (result is PlanChangedResult) + { + ChangePlan(c); + } + + return result; + } + }); + + default: + throw new NotSupportedException(); + } + } + + public void Create(CreateApp command) + { + var appId = new NamedId(command.AppId, command.Name); + + var events = new List + { + CreateInitalEvent(command.Name), + CreateInitialOwner(command.Actor), + CreateInitialLanguage() + }; + + foreach (var pattern in initialPatterns) + { + events.Add(CreateInitialPattern(pattern.Key, pattern.Value)); + } + + foreach (var @event in events) + { + @event.Actor = command.Actor; + @event.AppId = appId; + + RaiseEvent(@event); + } + } + + public void UpdateClient(UpdateClient command) + { + if (!string.IsNullOrWhiteSpace(command.Name)) + { + RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); + } + + if (command.Permission.HasValue) + { + RaiseEvent(SimpleMapper.Map(command, new AppClientUpdated { Permission = command.Permission.Value })); + } + } + + public void UpdateLanguage(UpdateLanguage command) + { + RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); + } + + public void AssignContributor(AssignContributor command) + { + RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); + } + + public void RemoveContributor(RemoveContributor command) + { + RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); + } + + public void AttachClient(AttachClient command) + { + RaiseEvent(SimpleMapper.Map(command, new AppClientAttached())); + } + + public void RevokeClient(RevokeClient command) + { + RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); + } + + public void AddLanguage(AddLanguage command) + { + RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded())); + } + + public void RemoveLanguage(RemoveLanguage command) + { + RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved())); + } + + public void ChangePlan(ChangePlan command) + { + RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged())); + } + + public void AddPattern(AddPattern command) + { + RaiseEvent(SimpleMapper.Map(command, new AppPatternAdded())); + } + + public void DeletePattern(DeletePattern command) + { + RaiseEvent(SimpleMapper.Map(command, new AppPatternDeleted())); + } + + public void UpdatePattern(UpdatePattern command) + { + RaiseEvent(SimpleMapper.Map(command, new AppPatternUpdated())); + } + + private void RaiseEvent(AppEvent @event) + { + if (@event.AppId == null) + { + @event.AppId = new NamedId(Snapshot.Id, Snapshot.Name); + } + + RaiseEvent(Envelope.Create(@event)); + } + + private static AppCreated CreateInitalEvent(string name) + { + return new AppCreated { Name = name }; + } + + private static AppPatternAdded CreateInitialPattern(Guid id, AppPattern pattern) + { + return new AppPatternAdded { PatternId = id, Name = pattern.Name, Pattern = pattern.Pattern, Message = pattern.Message }; + } + + private static AppLanguageAdded CreateInitialLanguage() + { + return new AppLanguageAdded { Language = Language.EN }; + } + + private static AppContributorAssigned CreateInitialOwner(RefToken actor) + { + return new AppContributorAssigned { ContributorId = actor.Identifier, Permission = AppContributorPermission.Owner }; + } + + public override void ApplyEvent(Envelope @event) + { + ApplySnapshot(Snapshot.Apply(@event)); + } + + public Task> GetStateAsync() + { + return Task.FromResult(new J(Snapshot)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs index b46cf9240..3689ba571 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!clients.TryGetValue(id, out var client)) { - throw new DomainObjectNotFoundException(id, "Clients", typeof(AppDomainObject)); + throw new DomainObjectNotFoundException(id, "Clients", typeof(AppGrain)); } return client; diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs index 08860f0e9..277a89c4c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!contributors.ContainsKey(command.ContributorId)) { - throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(AppDomainObject)); + throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(AppGrain)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs index a860def06..edb3b288c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!languages.TryGetConfig(language, out var languageConfig)) { - throw new DomainObjectNotFoundException(language, "Languages", typeof(AppDomainObject)); + throw new DomainObjectNotFoundException(language, "Languages", typeof(AppGrain)); } return languageConfig; diff --git a/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs index facea56c0..88b6b7b17 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs @@ -9,7 +9,11 @@ using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Domain.Apps.Entities.Apps { - public interface IAppEntity : IEntity, IEntityWithVersion + public interface IAppEntity : + IEntity, + IEntityWithCreatedBy, + IEntityWithLastModifiedBy, + IEntityWithVersion { string Name { get; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/IAppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppGrain.cs new file mode 100644 index 000000000..d98dd68b8 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/IAppGrain.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Orleans; + +namespace Squidex.Domain.Apps.Entities.Apps +{ + public interface IAppGrain : IDomainObjectGrain + { + Task> GetStateAsync(); + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs b/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs index 7bab17d69..4ba5432c2 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs @@ -5,11 +5,14 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Threading.Tasks; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Entities.Rules { public interface IRuleGrain : IDomainObjectGrain { + Task> GetStateAsync(); } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs index 668296c4e..d61214ace 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs @@ -15,6 +15,7 @@ using Squidex.Domain.Apps.Events.Rules; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; @@ -34,6 +35,8 @@ namespace Squidex.Domain.Apps.Entities.Rules public override Task ExecuteAsync(IAggregateCommand command) { + VerifyNotDeleted(); + switch (command) { case CreateRule createRule: @@ -83,29 +86,21 @@ namespace Squidex.Domain.Apps.Entities.Rules public void Update(UpdateRule command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); } public void Enable(EnableRule command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); } public void Disable(DisableRule command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); } public void Delete(DeleteRule command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); } @@ -131,5 +126,10 @@ namespace Squidex.Domain.Apps.Entities.Rules { ApplySnapshot(Snapshot.Apply(@event)); } + + public Task> GetStateAsync() + { + return Task.FromResult(new J(Snapshot)); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs index 1f3b46dbf..ab41ae13e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs @@ -5,11 +5,14 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Threading.Tasks; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Entities.Schemas { public interface ISchemaGrain : IDomainObjectGrain { + Task> GetStateAsync(); } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 332129c30..219c42c42 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; @@ -41,6 +42,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas public override Task ExecuteAsync(IAggregateCommand command) { + VerifyNotDeleted(); + switch (command) { case CreateSchema createSchema: @@ -191,99 +194,71 @@ namespace Squidex.Domain.Apps.Entities.Schemas public void Add(AddField command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(Snapshot.TotalFields + 1, command.Name) })); } public void UpdateField(UpdateField command) { - VerifyNotDeleted(); - RaiseEvent(command, SimpleMapper.Map(command, new FieldUpdated())); } public void LockField(LockField command) { - VerifyNotDeleted(); - RaiseEvent(command, new FieldLocked()); } public void HideField(HideField command) { - VerifyNotDeleted(); - RaiseEvent(command, new FieldHidden()); } public void ShowField(ShowField command) { - VerifyNotDeleted(); - RaiseEvent(command, new FieldShown()); } public void DisableField(DisableField command) { - VerifyNotDeleted(); - RaiseEvent(command, new FieldDisabled()); } public void EnableField(EnableField command) { - VerifyNotDeleted(); - RaiseEvent(command, new FieldEnabled()); } public void DeleteField(DeleteField command) { - VerifyNotDeleted(); - RaiseEvent(command, new FieldDeleted()); } public void Reorder(ReorderFields command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered())); } public void Publish(PublishSchema command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new SchemaPublished())); } public void Unpublish(UnpublishSchema command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new SchemaUnpublished())); } public void ConfigureScripts(ConfigureScripts command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured())); } public void Delete(DeleteSchema command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new SchemaDeleted())); } public void Update(UpdateSchema command) { - VerifyNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated())); } @@ -326,5 +301,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas { ApplySnapshot(Snapshot.Apply(@event, registry)); } + + public Task> GetStateAsync() + { + return Task.FromResult(new J(Snapshot)); + } } } \ No newline at end of file diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/WriteServices.cs index b1d1f0423..633695575 100644 --- a/src/Squidex/Config/Domain/WriteServices.cs +++ b/src/Squidex/Config/Domain/WriteServices.cs @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents; @@ -52,15 +53,15 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); - services.AddSingletonAs() .As(); services.AddSingletonAs() .As(); + services.AddSingletonAs>() + .As(); + services.AddSingletonAs>() .As(); @@ -91,7 +92,7 @@ namespace Squidex.Config.Domain services.AddTransientAs() .AsSelf(); - services.AddTransientAs() + services.AddTransientAs() .AsSelf(); services.AddTransientAs() diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index 495131855..09b27c89d 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -77,7 +77,7 @@ namespace Squidex.Pipeline.CommandMiddlewares if (schema == null) { - throw new DomainObjectNotFoundException(schemaName, typeof(SchemaDomainObject)); + throw new DomainObjectNotFoundException(schemaName, typeof(ISchemaEntity)); } schemaCommand.SchemaId = new NamedId(schema.Id, schema.Name); diff --git a/tools/Migrate_01/Migrations/AddPatterns.cs b/tools/Migrate_01/Migrations/AddPatterns.cs index 68aa6e9dd..ca06601ec 100644 --- a/tools/Migrate_01/Migrations/AddPatterns.cs +++ b/tools/Migrate_01/Migrations/AddPatterns.cs @@ -7,6 +7,7 @@ using System; using System.Threading.Tasks; +using Orleans; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Repositories; @@ -18,14 +19,14 @@ namespace Migrate_01.Migrations public sealed class AddPatterns : IMigration { private readonly InitialPatterns initialPatterns; - private readonly IStateFactory stateFactory; + private readonly IGrainFactory grainFactory; private readonly IAppRepository appRepository; - public AddPatterns(InitialPatterns initialPatterns, IAppRepository appRepository, IStateFactory stateFactory) + public AddPatterns(InitialPatterns initialPatterns, IAppRepository appRepository, IGrainFactory grainFactory) { this.initialPatterns = initialPatterns; this.appRepository = appRepository; - this.stateFactory = stateFactory; + this.grainFactory = grainFactory; } public async Task UpdateAsync() @@ -34,27 +35,29 @@ namespace Migrate_01.Migrations foreach (var id in ids) { - var app = await stateFactory.GetSingleAsync(id); + var app = grainFactory.GetGrain(id); - if (app.Snapshot.Patterns.Count == 0) + var state = await app.GetStateAsync(); + + if (state.Value.Patterns.Count == 0) { foreach (var pattern in initialPatterns.Values) { var command = new AddPattern { - Actor = app.Snapshot.CreatedBy, - AppId = app.Snapshot.Id, + Actor = state.Value.CreatedBy, + AppId = state.Value.Id, Name = pattern.Name, PatternId = Guid.NewGuid(), Pattern = pattern.Pattern, Message = pattern.Message }; - app.AddPattern(command); + await app.ExecuteAsync(command); } - await app.WriteAsync(); + await app.WriteSnapshotAsync(); } } } diff --git a/tools/Migrate_01/Rebuilder.cs b/tools/Migrate_01/Rebuilder.cs index a4a350b9b..d93f3528f 100644 --- a/tools/Migrate_01/Rebuilder.cs +++ b/tools/Migrate_01/Rebuilder.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Orleans; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.State; @@ -39,7 +40,7 @@ namespace Migrate_01 private readonly ISnapshotStore snapshotContentStore; private readonly ISnapshotStore snapshotRuleStore; private readonly ISnapshotStore snapshotSchemaStore; - private readonly IStateFactory stateFactory; + private readonly IGrainFactory grainFactory; public Rebuilder( FieldRegistry fieldRegistry, @@ -50,7 +51,7 @@ namespace Migrate_01 ISnapshotStore snapshotAssetStore, ISnapshotStore snapshotRuleStore, ISnapshotStore snapshotSchemaStore, - IStateFactory stateFactory) + IGrainFactory grainFactory) { this.fieldRegistry = fieldRegistry; this.eventDataFormatter = eventDataFormatter; @@ -60,7 +61,7 @@ namespace Migrate_01 this.snapshotContentStore = snapshotContentStore; this.snapshotRuleStore = snapshotRuleStore; this.snapshotSchemaStore = snapshotSchemaStore; - this.stateFactory = stateFactory; + this.grainFactory = grainFactory; } public async Task RebuildAssetsAsync() @@ -79,9 +80,7 @@ namespace Migrate_01 { if (@event.Payload is AssetEvent assetEvent && handledIds.Add(assetEvent.AssetId)) { - var asset = await stateFactory.CreateAsync(assetEvent.AssetId); - - asset.ApplySnapshot(asset.Snapshot.Apply(@event)); + var asset = grainFactory.GetGrain(assetEvent.AssetId); await asset.WriteSnapshotAsync(); } @@ -107,19 +106,19 @@ namespace Migrate_01 { if (@event.Payload is SchemaEvent schemaEvent && handledIds.Add(schemaEvent.SchemaId.Id)) { - var schema = await stateFactory.GetSingleAsync(schemaEvent.SchemaId.Id); + var schema = grainFactory.GetGrain(schemaEvent.SchemaId.Id); await schema.WriteSnapshotAsync(); } else if (@event.Payload is RuleEvent ruleEvent && handledIds.Add(ruleEvent.RuleId)) { - var rule = await stateFactory.GetSingleAsync(ruleEvent.RuleId); + var rule = grainFactory.GetGrain(ruleEvent.RuleId); await rule.WriteSnapshotAsync(); } else if (@event.Payload is AppEvent appEvent && handledIds.Add(appEvent.AppId.Id)) { - var app = await stateFactory.GetSingleAsync(appEvent.AppId.Id); + var app = grainFactory.GetGrain(appEvent.AppId.Id); await app.WriteSnapshotAsync(); }