Browse Source

Refactorings (#893)

* Better immutability for domain objects.

* More tests

* Fix CI.
pull/895/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
d69649f3b9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeChangedTypes.cs
  2. 1
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs
  3. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs
  4. 11
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs
  5. 16
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs
  6. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  7. 64
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs
  8. 8
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObjectGrain.cs
  9. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/IAppGrain.cs
  10. 7
      backend/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs
  11. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsCacheGrain.cs
  12. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs
  13. 17
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs
  14. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs
  15. 12
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObjectGrain.cs
  16. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs
  17. 12
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObjectGrain.cs
  18. 3
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/IAssetGrain.cs
  19. 6
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs
  20. 10
      backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs
  21. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs
  22. 44
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs
  23. 8
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs
  24. 14
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs
  25. 26
      backend/src/Squidex.Domain.Apps.Entities/Backup/DefaultBackupHandlerFactory.cs
  26. 3
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupGrain.cs
  27. 14
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupHandlerFactory.cs
  28. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupReader.cs
  29. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupService.cs
  30. 3
      backend/src/Squidex.Domain.Apps.Entities/Backup/IRestoreGrain.cs
  31. 43
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  32. 9
      backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsCommandMiddleware.cs
  33. 37
      backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs
  34. 3
      backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/ICommentsGrain.cs
  35. 4
      backend/src/Squidex.Domain.Apps.Entities/Comments/WatchingGrain.cs
  36. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs
  37. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterGrain.cs
  38. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  39. 7
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObjectGrain.cs
  40. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/IContentGrain.cs
  41. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs
  42. 3
      backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/IRuleGrain.cs
  43. 29
      backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs
  44. 8
      backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObjectGrain.cs
  45. 10
      backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesCacheGrain.cs
  46. 2
      backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs
  47. 8
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs
  48. 26
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs
  49. 9
      backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs
  50. 3
      backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/ISchemaGrain.cs
  51. 9
      backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs
  52. 8
      backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObjectGrain.cs
  53. 8
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasCacheGrain.cs
  54. 2
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs
  55. 36
      backend/src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs
  56. 56
      backend/src/Squidex.Infrastructure/Commands/DefaultDomainObjectFactory.cs
  57. 97
      backend/src/Squidex.Infrastructure/Commands/DomainObject.cs
  58. 26
      backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  59. 6
      backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
  60. 18
      backend/src/Squidex.Infrastructure/Commands/IDomainObjectFactory.cs
  61. 3
      backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs
  62. 19
      backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs
  63. 31
      backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventConsumerFactory.cs
  64. 4
      backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventFormatter.cs
  65. 2
      backend/src/Squidex.Infrastructure/EventSourcing/EventCommit.cs
  66. 2
      backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs
  67. 28
      backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
  68. 4
      backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumer.cs
  69. 14
      backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumerFactory.cs
  70. 2
      backend/src/Squidex.Infrastructure/EventSourcing/IEventFormatter.cs
  71. 106
      backend/src/Squidex.Infrastructure/Orleans/ActivityPropagationFilter.cs
  72. 4
      backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs
  73. 9
      backend/src/Squidex.Infrastructure/Orleans/GrainBase.cs
  74. 50
      backend/src/Squidex.Infrastructure/Orleans/GrainOfString.cs
  75. 9
      backend/src/Squidex.Infrastructure/Orleans/Indexes/UniqueNameGrain.cs
  76. 34
      backend/src/Squidex.Infrastructure/Orleans/J.cs
  77. 46
      backend/src/Squidex.Infrastructure/Orleans/JsonSerializer.cs
  78. 82
      backend/src/Squidex.Infrastructure/Orleans/J{T}.cs
  79. 14
      backend/src/Squidex.Infrastructure/States/BatchContext.cs
  80. 2
      backend/src/Squidex.Infrastructure/States/DefaultEventStreamNames.cs
  81. 2
      backend/src/Squidex.Infrastructure/States/IEventStreamNames.cs
  82. 36
      backend/src/Squidex.Infrastructure/States/Persistence.cs
  83. 32
      backend/src/Squidex.Infrastructure/States/Store.cs
  84. 17
      backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs
  85. 4
      backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs
  86. 4
      backend/src/Squidex/Config/Domain/AppsServices.cs
  87. 7
      backend/src/Squidex/Config/Domain/AssetServices.cs
  88. 3
      backend/src/Squidex/Config/Domain/CommandsServices.cs
  89. 4
      backend/src/Squidex/Config/Domain/ContentsServices.cs
  90. 17
      backend/src/Squidex/Config/Domain/EventSourcingServices.cs
  91. 4
      backend/src/Squidex/Config/Domain/RuleServices.cs
  92. 6
      backend/src/Squidex/Config/Domain/SchemasServices.cs
  93. 18
      backend/src/Squidex/Config/Orleans/OrleansServices.cs
  94. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs
  95. 44
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs
  96. 7
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs
  97. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs
  98. 16
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
  99. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsCacheGrainTests.cs
  100. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs

1
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeChangedTypes.cs

@ -60,7 +60,6 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
return true; return true;
} }
} }
} }
} }

1
backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs

@ -7,7 +7,6 @@
using MongoDB.Driver; using MongoDB.Driver;
using NodaTime; using NodaTime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;

4
backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs

@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var app = await appGrain.GetStateAsync(); var app = await appGrain.GetStateAsync();
// If the app does not exist, the version is lower than zero. // If the app does not exist, the version is lower than zero.
if (app.Value.Version < 0) if (app.Version < 0)
{ {
return; return;
} }
@ -102,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
using (Telemetry.Activities.StartActivity(deleter.GetType().Name)) using (Telemetry.Activities.StartActivity(deleter.GetType().Name))
{ {
await deleter.DeleteAppAsync(app.Value, default); await deleter.DeleteAppAsync(app, default);
} }
} }
} }

11
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs

@ -8,7 +8,6 @@
using Orleans; using Orleans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
@ -38,11 +37,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
} }
} }
public async Task<JsonObject> GetAsync(DomainId appId, string? userId) public Task<JsonObject> GetAsync(DomainId appId, string? userId)
{ {
var result = await GetGrain(appId, userId).GetAsync(); return GetGrain(appId, userId).GetAsync();
return result.Value;
} }
public Task RemoveAsync(DomainId appId, string? userId, string path) public Task RemoveAsync(DomainId appId, string? userId, string path)
@ -52,12 +49,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
public Task SetAsync(DomainId appId, string? userId, string path, JsonValue value) public Task SetAsync(DomainId appId, string? userId, string path, JsonValue value)
{ {
return GetGrain(appId, userId).SetAsync(path, value.AsJ()); return GetGrain(appId, userId).SetAsync(path, value);
} }
public Task SetAsync(DomainId appId, string? userId, JsonObject settings) public Task SetAsync(DomainId appId, string? userId, JsonObject settings)
{ {
return GetGrain(appId, userId).SetAsync(settings.AsJ()); return GetGrain(appId, userId).SetAsync(settings);
} }
public Task ClearAsync(DomainId appId, string? userId) public Task ClearAsync(DomainId appId, string? userId)

16
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -12,7 +13,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public sealed class AppUISettingsGrain : GrainOfString, IAppUISettingsGrain public sealed class AppUISettingsGrain : GrainBase, IAppUISettingsGrain
{ {
private readonly IGrainState<State> state; private readonly IGrainState<State> state;
@ -22,14 +23,15 @@ namespace Squidex.Domain.Apps.Entities.Apps
public JsonObject Settings { get; set; } = new JsonObject(); public JsonObject Settings { get; set; } = new JsonObject();
} }
public AppUISettingsGrain(IGrainState<State> state) public AppUISettingsGrain(IGrainIdentity identity, IGrainState<State> state)
: base(identity)
{ {
this.state = state; this.state = state;
} }
public Task<J<JsonObject>> GetAsync() public Task<JsonObject> GetAsync()
{ {
return Task.FromResult(state.Value.Settings.AsJ()); return Task.FromResult(state.Value.Settings);
} }
public Task ClearAsync() public Task ClearAsync()
@ -39,14 +41,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
return state.ClearAsync(); return state.ClearAsync();
} }
public Task SetAsync(J<JsonObject> settings) public Task SetAsync(JsonObject settings)
{ {
state.Value.Settings = settings; state.Value.Settings = settings;
return state.WriteAsync(); return state.WriteAsync();
} }
public Task SetAsync(string path, J<JsonValue> value) public Task SetAsync(string path, JsonValue value)
{ {
var container = GetContainer(path, true, out var key); var container = GetContainer(path, true, out var key);
@ -56,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return Task.CompletedTask; return Task.CompletedTask;
} }
container[key] = value.Value; container[key] = value;
return state.WriteAsync(); return state.WriteAsync();
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -37,9 +37,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
IAppUISettings appUISettings) IAppUISettings appUISettings)
{ {
this.appsIndex = appsIndex; this.appsIndex = appsIndex;
this.rebuilder = rebuilder;
this.appImageStore = appImageStore; this.appImageStore = appImageStore;
this.appUISettings = appUISettings; this.appUISettings = appUISettings;
this.rebuilder = rebuilder;
} }
public async Task BackupEventAsync(Envelope<IEvent> @event, BackupContext context, public async Task BackupEventAsync(Envelope<IEvent> @event, BackupContext context,

64
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -25,22 +26,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
public sealed partial class AppDomainObject : DomainObject<AppDomainObject.State> public sealed partial class AppDomainObject : DomainObject<AppDomainObject.State>
{ {
private readonly InitialSettings initialSettings; private readonly IServiceProvider serviceProvider;
private readonly IAppPlansProvider appPlansProvider;
private readonly IAppPlanBillingManager appPlansBillingManager;
private readonly IUserResolver userResolver;
public AppDomainObject(IPersistenceFactory<State> persistence, ILogger<AppDomainObject> log, public AppDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<AppDomainObject> log,
InitialSettings initialSettings, IServiceProvider serviceProvider)
IAppPlansProvider appPlansProvider, : base(id, persistence, log)
IAppPlanBillingManager appPlansBillingManager,
IUserResolver userResolver)
: base(persistence, log)
{ {
this.userResolver = userResolver; this.serviceProvider = serviceProvider;
this.appPlansProvider = appPlansProvider;
this.appPlansBillingManager = appPlansBillingManager;
this.initialSettings = initialSettings;
} }
protected override bool IsDeleted(State snapshot) protected override bool IsDeleted(State snapshot)
@ -125,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case AssignContributor assignContributor: case AssignContributor assignContributor:
return UpdateReturnAsync(assignContributor, async c => return UpdateReturnAsync(assignContributor, async c =>
{ {
await GuardAppContributors.CanAssign(c, Snapshot, userResolver, GetPlan()); await GuardAppContributors.CanAssign(c, Snapshot, UserResolver(), GetPlan());
AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId)); AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId));
@ -265,7 +257,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case ChangePlan changePlan: case ChangePlan changePlan:
return UpdateReturnAsync(changePlan, async c => return UpdateReturnAsync(changePlan, async c =>
{ {
GuardApp.CanChangePlan(c, Snapshot, appPlansProvider); GuardApp.CanChangePlan(c, Snapshot, AppPlansProvider());
if (c.FromCallback) if (c.FromCallback)
{ {
@ -275,9 +267,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
} }
else else
{ {
var result = var result = await AppPlanBillingManager().ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), c.PlanId, c.Referer);
await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier,
Snapshot.NamedId(), c.PlanId, c.Referer);
switch (result) switch (result)
{ {
@ -293,7 +283,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case DeleteApp delete: case DeleteApp delete:
return UpdateAsync(delete, async c => return UpdateAsync(delete, async c =>
{ {
await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null, null); await AppPlanBillingManager().ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null, null);
DeleteApp(c); DeleteApp(c);
}); });
@ -304,11 +294,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
} }
} }
private IAppLimitsPlan GetPlan()
{
return appPlansProvider.GetPlanForApp(Snapshot).Plan;
}
private void Create(CreateApp command) private void Create(CreateApp command)
{ {
var appId = NamedId.Of(command.AppId, command.Name); var appId = NamedId.Of(command.AppId, command.Name);
@ -335,7 +320,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
private void ChangePlan(ChangePlan command) private void ChangePlan(ChangePlan command)
{ {
if (string.Equals(appPlansProvider.GetFreePlan()?.Id, command.PlanId, StringComparison.Ordinal)) if (string.Equals(GetFreePlan()?.Id, command.PlanId, StringComparison.Ordinal))
{ {
Raise(command, new AppPlanReset()); Raise(command, new AppPlanReset());
} }
@ -466,7 +451,32 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
private AppSettingsUpdated CreateInitialSettings() private AppSettingsUpdated CreateInitialSettings()
{ {
return new AppSettingsUpdated { Settings = initialSettings.Settings }; return new AppSettingsUpdated { Settings = serviceProvider.GetRequiredService<InitialSettings>().Settings };
}
private IAppPlansProvider AppPlansProvider()
{
return serviceProvider.GetRequiredService<IAppPlansProvider>();
}
private IAppPlanBillingManager AppPlanBillingManager()
{
return serviceProvider.GetRequiredService<IAppPlanBillingManager>();
}
private IUserResolver UserResolver()
{
return serviceProvider.GetRequiredService<IUserResolver>();
}
private IAppLimitsPlan GetFreePlan()
{
return AppPlansProvider().GetFreePlan();
}
private IAppLimitsPlan GetPlan()
{
return AppPlansProvider().GetPlanForApp(Snapshot).Plan;
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObjectGrain.cs

@ -5,19 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
public sealed class AppDomainObjectGrain : DomainObjectGrain<AppDomainObject, AppDomainObject.State>, IAppGrain public sealed class AppDomainObjectGrain : DomainObjectGrain<AppDomainObject, AppDomainObject.State>, IAppGrain
{ {
public AppDomainObjectGrain(IServiceProvider serviceProvider) public AppDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(serviceProvider) : base(identity, factory)
{ {
} }
public async Task<J<IAppEntity>> GetStateAsync() public async Task<IAppEntity> GetStateAsync()
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();

3
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/IAppGrain.cs

@ -6,12 +6,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
public interface IAppGrain : IDomainObjectGrain public interface IAppGrain : IDomainObjectGrain
{ {
Task<J<IAppEntity>> GetStateAsync(); Task<IAppEntity> GetStateAsync();
} }
} }

7
backend/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs

@ -7,17 +7,16 @@
using Orleans; using Orleans;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public interface IAppUISettingsGrain : IGrainWithStringKey public interface IAppUISettingsGrain : IGrainWithStringKey
{ {
Task<J<JsonObject>> GetAsync(); Task<JsonObject> GetAsync();
Task SetAsync(string path, J<JsonValue> value); Task SetAsync(string path, JsonValue value);
Task SetAsync(J<JsonObject> settings); Task SetAsync(JsonObject settings);
Task RemoveAsync(string path); Task RemoveAsync(string path);

4
backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsCacheGrain.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using Orleans.Concurrency; using Orleans.Concurrency;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans.Indexes; using Squidex.Infrastructure.Orleans.Indexes;
@ -18,7 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
private readonly IAppRepository appRepository; private readonly IAppRepository appRepository;
private readonly Dictionary<string, DomainId> appIds = new Dictionary<string, DomainId>(); private readonly Dictionary<string, DomainId> appIds = new Dictionary<string, DomainId>();
public AppsCacheGrain(IAppRepository appRepository) public AppsCacheGrain(IGrainIdentity identity, IAppRepository appRepository)
: base(identity)
{ {
this.appRepository = appRepository; this.appRepository = appRepository;
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs

@ -227,7 +227,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
private async Task<IAppEntity?> GetAppCoreAsync(DomainId id, bool allowArchived = false) private async Task<IAppEntity?> GetAppCoreAsync(DomainId id, bool allowArchived = false)
{ {
var app = (await grainFactory.GetGrain<IAppGrain>(id.ToString()).GetStateAsync()).Value; var app = await grainFactory.GetGrain<IAppGrain>(id.ToString()).GetStateAsync();
if (app.Version <= EtagVersion.Empty || (app.IsDeleted && !allowArchived)) if (app.Version <= EtagVersion.Empty || (app.IsDeleted && !allowArchived))
{ {

17
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using NodaTime; using NodaTime;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Notifications; using Squidex.Domain.Apps.Entities.Notifications;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -15,10 +16,10 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.Plans namespace Squidex.Domain.Apps.Entities.Apps.Plans
{ {
public sealed class UsageNotifierGrain : GrainOfString, IUsageNotifierGrain public sealed class UsageNotifierGrain : GrainBase, IUsageNotifierGrain
{ {
private static readonly TimeSpan TimeBetweenNotifications = TimeSpan.FromDays(3); private static readonly TimeSpan TimeBetweenNotifications = TimeSpan.FromDays(3);
private readonly IGrainState<State> state; private readonly IGrainState<State> grainState;
private readonly INotificationSender notificationSender; private readonly INotificationSender notificationSender;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly IClock clock; private readonly IClock clock;
@ -29,9 +30,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
public Dictionary<DomainId, DateTime> NotificationsSent { get; } = new Dictionary<DomainId, DateTime>(); public Dictionary<DomainId, DateTime> NotificationsSent { get; } = new Dictionary<DomainId, DateTime>();
} }
public UsageNotifierGrain(IGrainState<State> state, INotificationSender notificationSender, IUserResolver userResolver, IClock clock) public UsageNotifierGrain(IGrainIdentity identity,
IGrainState<State> grainState, INotificationSender notificationSender, IUserResolver userResolver, IClock clock)
: base(identity)
{ {
this.state = state; this.grainState = grainState;
this.notificationSender = notificationSender; this.notificationSender = notificationSender;
this.userResolver = userResolver; this.userResolver = userResolver;
this.clock = clock; this.clock = clock;
@ -70,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
private bool HasBeenSentBefore(DomainId appId, DateTime now) private bool HasBeenSentBefore(DomainId appId, DateTime now)
{ {
if (state.Value.NotificationsSent.TryGetValue(appId, out var lastSent)) if (grainState.Value.NotificationsSent.TryGetValue(appId, out var lastSent))
{ {
var elapsed = now - lastSent; var elapsed = now - lastSent;
@ -82,9 +85,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
private Task TrackNotifiedAsync(DomainId appId, DateTime now) private Task TrackNotifiedAsync(DomainId appId, DateTime now)
{ {
state.Value.NotificationsSent[appId] = now; grainState.Value.NotificationsSent[appId] = now;
return state.WriteAsync(); return grainState.WriteAsync();
} }
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs

@ -24,9 +24,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public AssetDomainObject(IPersistenceFactory<State> factory, ILogger<AssetDomainObject> log, public AssetDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<AssetDomainObject> log,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
: base(factory, log) : base(id, persistence, log)
{ {
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;

12
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObjectGrain.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -15,20 +16,21 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5);
public AssetDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit) public AssetDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory,
: base(serviceProvider) IActivationLimit limit)
: base(identity, factory)
{ {
limit?.SetLimit(5000, Lifetime); limit?.SetLimit(5000, Lifetime);
} }
protected override Task OnActivateAsync(string key) public override Task OnActivateAsync()
{ {
TryDelayDeactivation(Lifetime); TryDelayDeactivation(Lifetime);
return base.OnActivateAsync(key); return base.OnActivateAsync();
} }
public async Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Any) public async Task<IAssetEntity> GetStateAsync(long version = EtagVersion.Any)
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();

4
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs

@ -24,9 +24,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public AssetFolderDomainObject(IPersistenceFactory<State> factory, ILogger<AssetFolderDomainObject> log, public AssetFolderDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<AssetFolderDomainObject> log,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
: base(factory, log) : base(id, persistence, log)
{ {
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
} }

12
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObjectGrain.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -14,20 +15,21 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5);
public AssetFolderDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit) public AssetFolderDomainObjectGrain(IGrainIdentity grainIdentity, IDomainObjectFactory factory,
: base(serviceProvider) IActivationLimit limit)
: base(grainIdentity, factory)
{ {
limit?.SetLimit(5000, Lifetime); limit?.SetLimit(5000, Lifetime);
} }
protected override Task OnActivateAsync(string key) public override Task OnActivateAsync()
{ {
TryDelayDeactivation(Lifetime); TryDelayDeactivation(Lifetime);
return base.OnActivateAsync(key); return base.OnActivateAsync();
} }
public async Task<J<IAssetFolderEntity>> GetStateAsync() public async Task<IAssetFolderEntity> GetStateAsync()
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();

3
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/IAssetGrain.cs

@ -7,12 +7,11 @@
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
public interface IAssetGrain : IDomainObjectGrain public interface IAssetGrain : IDomainObjectGrain
{ {
Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Any); Task<IAssetEntity> GetStateAsync(long version = EtagVersion.Any);
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs

@ -29,14 +29,12 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
var assetGrain = grainFactory.GetGrain<IAssetGrain>(key.ToString()); var assetGrain = grainFactory.GetGrain<IAssetGrain>(key.ToString());
var assetState = await assetGrain.GetStateAsync(version); var assetState = await assetGrain.GetStateAsync(version);
var asset = assetState.Value; if (assetState == null || assetState.Version <= EtagVersion.Empty || (version > EtagVersion.Any && assetState.Version != version))
if (asset == null || asset.Version <= EtagVersion.Empty || (version > EtagVersion.Any && asset.Version != version))
{ {
return null; return null;
} }
return asset; return assetState;
} }
} }
} }

10
backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs

@ -17,17 +17,17 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
private static readonly MemoryStream DummyStream = new MemoryStream(Encoding.UTF8.GetBytes("dummy")); private static readonly MemoryStream DummyStream = new MemoryStream(Encoding.UTF8.GetBytes("dummy"));
private readonly IAssetFileStore assetFileStore; private readonly IAssetFileStore assetFileStore;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
public RebuildFiles( public RebuildFiles(
IAssetFileStore assetFileStore, IAssetFileStore assetFileStore,
IEventStore eventStore, IEventFormatter eventFormatter,
IEventDataFormatter eventDataFormatter) IEventStore eventStore)
{ {
this.assetFileStore = assetFileStore; this.assetFileStore = assetFileStore;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventFormatter = eventFormatter;
} }
public async Task RepairAsync( public async Task RepairAsync(
@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
await foreach (var storedEvent in eventStore.QueryAllAsync("^asset\\-", ct: ct)) await foreach (var storedEvent in eventStore.QueryAllAsync("^asset\\-", ct: ct))
{ {
var @event = eventDataFormatter.ParseIfKnown(storedEvent); var @event = eventFormatter.ParseIfKnown(storedEvent);
if (@event != null) if (@event != null)
{ {

4
backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public static async Task<string?> GetBlurHashAsync(this AssetRef asset, BlurOptions options, public static async Task<string?> GetBlurHashAsync(this AssetRef asset, BlurOptions options,
IAssetFileStore assetFileStore, IAssetFileStore assetFileStore,
IAssetThumbnailGenerator thumbnailGenerator, IAssetThumbnailGenerator assetThumbnails,
CancellationToken ct = default) CancellationToken ct = default)
{ {
using (var stream = DefaultPools.MemoryStream.GetStream()) using (var stream = DefaultPools.MemoryStream.GetStream())
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
stream.Position = 0; stream.Position = 0;
return await thumbnailGenerator.ComputeBlurHashAsync(stream, asset.MimeType, options, ct); return await assetThumbnails.ComputeBlurHashAsync(stream, asset.MimeType, options, ct);
} }
} }
} }

44
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs

@ -6,10 +6,10 @@
// ========================================================================== // ==========================================================================
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NodaTime; using NodaTime;
using Orleans.Concurrency; using Orleans.Concurrency;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Backup.State; using Squidex.Domain.Apps.Entities.Backup.State;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -22,46 +22,47 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
[Reentrant] [Reentrant]
public sealed class BackupGrain : GrainOfString, IBackupGrain public sealed class BackupGrain : GrainBase, IBackupGrain
{ {
private const int MaxBackups = 10; private const int MaxBackups = 10;
private static readonly Duration UpdateDuration = Duration.FromSeconds(1); private static readonly Duration UpdateDuration = Duration.FromSeconds(1);
private readonly IGrainState<BackupState> state;
private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IBackupArchiveStore backupArchiveStore; private readonly IBackupArchiveStore backupArchiveStore;
private readonly IBackupHandlerFactory backupHandlers;
private readonly IClock clock; private readonly IClock clock;
private readonly IServiceProvider serviceProvider; private readonly IEventFormatter eventFormatter;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly ILogger<BackupGrain> log; private readonly IGrainState<BackupState> state;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly ILogger<BackupGrain> log;
private CancellationTokenSource? currentJobToken; private CancellationTokenSource? currentJobToken;
private BackupJob? currentJob; private BackupJob? currentJob;
public BackupGrain( public BackupGrain(IGrainIdentity identity,
IBackupArchiveLocation backupArchiveLocation, IBackupArchiveLocation backupArchiveLocation,
IBackupArchiveStore backupArchiveStore, IBackupArchiveStore backupArchiveStore,
IBackupHandlerFactory backupHandlers,
IClock clock, IClock clock,
IEventDataFormatter eventDataFormatter, IEventFormatter eventFormatter,
IEventStore eventStore, IEventStore eventStore,
IGrainState<BackupState> state, IGrainState<BackupState> state,
IServiceProvider serviceProvider,
IUserResolver userResolver, IUserResolver userResolver,
ILogger<BackupGrain> log) ILogger<BackupGrain> log)
: base(identity)
{ {
this.backupArchiveLocation = backupArchiveLocation; this.backupArchiveLocation = backupArchiveLocation;
this.backupArchiveStore = backupArchiveStore; this.backupArchiveStore = backupArchiveStore;
this.backupHandlers = backupHandlers;
this.clock = clock; this.clock = clock;
this.eventDataFormatter = eventDataFormatter; this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.serviceProvider = serviceProvider;
this.state = state; this.state = state;
this.userResolver = userResolver; this.userResolver = userResolver;
this.log = log; this.log = log;
} }
protected override Task OnActivateAsync(string key) public override Task OnActivateAsync()
{ {
RecoverAfterRestartAsync().Forget(); RecoverAfterRestartAsync().Forget();
@ -127,14 +128,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ProcessAsync(BackupJob job, RefToken actor, private async Task ProcessAsync(BackupJob job, RefToken actor,
CancellationToken ct) CancellationToken ct)
{ {
var handlers = CreateHandlers(); var handlers = backupHandlers.CreateMany();
var lastTimestamp = job.Started; var lastTimestamp = job.Started;
try try
{ {
var appId = DomainId.Create(Key);
await using (var stream = backupArchiveLocation.OpenStream(job.Id)) await using (var stream = backupArchiveLocation.OpenStream(job.Id))
{ {
using (var writer = await backupArchiveLocation.OpenWriterAsync(stream)) using (var writer = await backupArchiveLocation.OpenWriterAsync(stream))
@ -143,11 +142,11 @@ namespace Squidex.Domain.Apps.Entities.Backup
var userMapping = new UserMapping(actor); var userMapping = new UserMapping(actor);
var context = new BackupContext(appId, userMapping, writer); var context = new BackupContext(Key, userMapping, writer);
await foreach (var storedEvent in eventStore.QueryAllAsync(GetFilter(), ct: ct)) await foreach (var storedEvent in eventStore.QueryAllAsync(GetFilter(), ct: ct))
{ {
var @event = eventDataFormatter.Parse(storedEvent); var @event = eventFormatter.Parse(storedEvent);
if (@event.Payload is SquidexEvent squidexEvent && squidexEvent.Actor != null) if (@event.Payload is SquidexEvent squidexEvent && squidexEvent.Actor != null)
{ {
@ -217,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private string GetFilter() private string GetFilter()
{ {
return $"^[^\\-]*-{Regex.Escape(Key)}"; return $"^[^\\-]*-{Regex.Escape(Key.ToString())}";
} }
private async Task<Instant> WritePeriodically(Instant lastTimestamp) private async Task<Instant> WritePeriodically(Instant lastTimestamp)
@ -278,14 +277,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
await state.WriteAsync(); await state.WriteAsync();
} }
private IEnumerable<IBackupHandler> CreateHandlers() public Task<List<IBackupJob>> GetStateAsync()
{
return serviceProvider.GetRequiredService<IEnumerable<IBackupHandler>>();
}
public Task<J<List<IBackupJob>>> GetStateAsync()
{ {
return J.AsTask(state.Value.Jobs.OfType<IBackupJob>().ToList()); return Task.FromResult(state.Value.Jobs.OfType<IBackupJob>().ToList());
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -97,11 +97,11 @@ namespace Squidex.Domain.Apps.Entities.Backup
return attachmentEntry; return attachmentEntry;
} }
public async IAsyncEnumerable<(string Stream, Envelope<IEvent> Event)> ReadEventsAsync(IStreamNameResolver streamNameResolver, IEventDataFormatter formatter, public async IAsyncEnumerable<(string Stream, Envelope<IEvent> Event)> ReadEventsAsync(IEventStreamNames eventStreams, IEventFormatter eventFormatter,
[EnumeratorCancellation] CancellationToken ct = default) [EnumeratorCancellation] CancellationToken ct = default)
{ {
Guard.NotNull(formatter); Guard.NotNull(eventFormatter);
Guard.NotNull(streamNameResolver); Guard.NotNull(eventStreams);
while (!ct.IsCancellationRequested) while (!ct.IsCancellationRequested)
{ {
@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
var storedEvent = serializer.Deserialize<CompatibleStoredEvent>(stream).ToStoredEvent(); var storedEvent = serializer.Deserialize<CompatibleStoredEvent>(stream).ToStoredEvent();
var eventStream = storedEvent.StreamName; var eventStream = storedEvent.StreamName;
var eventEnvelope = formatter.Parse(storedEvent); var eventEnvelope = eventFormatter.Parse(storedEvent);
yield return (eventStream, eventEnvelope); yield return (eventStream, eventEnvelope);
} }

14
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs

@ -43,20 +43,16 @@ namespace Squidex.Domain.Apps.Entities.Backup
return BackupGrain(appId).DeleteAsync(backupId); return BackupGrain(appId).DeleteAsync(backupId);
} }
public async Task<IRestoreJob?> GetRestoreAsync( public Task<IRestoreJob> GetRestoreAsync(
CancellationToken ct = default) CancellationToken ct = default)
{ {
var state = await RestoreGrain().GetStateAsync(); return RestoreGrain().GetStateAsync();
return state.Value;
} }
public async Task<List<IBackupJob>> GetBackupsAsync(DomainId appId, public Task<List<IBackupJob>> GetBackupsAsync(DomainId appId,
CancellationToken ct = default) CancellationToken ct = default)
{ {
var state = await BackupGrain(appId).GetStateAsync(); return BackupGrain(appId).GetStateAsync();
return state.Value;
} }
public async Task<IBackupJob?> GetBackupAsync(DomainId appId, DomainId backupId, public async Task<IBackupJob?> GetBackupAsync(DomainId appId, DomainId backupId,
@ -64,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
var state = await BackupGrain(appId).GetStateAsync(); var state = await BackupGrain(appId).GetStateAsync();
return state.Value.Find(x => x.Id == backupId); return state.Find(x => x.Id == backupId);
} }
private IRestoreGrain RestoreGrain() private IRestoreGrain RestoreGrain()

26
backend/src/Squidex.Domain.Apps.Entities/Backup/DefaultBackupHandlerFactory.cs

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class DefaultBackupHandlerFactory : IBackupHandlerFactory
{
private readonly IServiceProvider serviceProvider;
public DefaultBackupHandlerFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public IEnumerable<IBackupHandler> CreateMany()
{
return serviceProvider.GetRequiredService<IEnumerable<IBackupHandler>>();
}
}
}

3
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupGrain.cs

@ -7,7 +7,6 @@
using Orleans; using Orleans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
@ -19,6 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
Task ClearAsync(); Task ClearAsync();
Task<J<List<IBackupJob>>> GetStateAsync(); Task<List<IBackupJob>> GetStateAsync();
} }
} }

14
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupHandlerFactory.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Backup
{
public interface IBackupHandlerFactory
{
IEnumerable<IBackupHandler> CreateMany();
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupReader.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Task<bool> HasFileAsync(string name, Task<bool> HasFileAsync(string name,
CancellationToken ct = default); CancellationToken ct = default);
IAsyncEnumerable<(string Stream, Envelope<IEvent> Event)> ReadEventsAsync(IStreamNameResolver streamNameResolver, IEventDataFormatter formatter, IAsyncEnumerable<(string Stream, Envelope<IEvent> Event)> ReadEventsAsync(IEventStreamNames eventStreams, IEventFormatter eventFormatter,
CancellationToken ct = default); CancellationToken ct = default);
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupService.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Task StartRestoreAsync(RefToken actor, Uri url, string? newAppName); Task StartRestoreAsync(RefToken actor, Uri url, string? newAppName);
Task<IRestoreJob?> GetRestoreAsync( Task<IRestoreJob> GetRestoreAsync(
CancellationToken ct = default); CancellationToken ct = default);
Task<List<IBackupJob>> GetBackupsAsync(DomainId appId, Task<List<IBackupJob>> GetBackupsAsync(DomainId appId,

3
backend/src/Squidex.Domain.Apps.Entities/Backup/IRestoreGrain.cs

@ -7,7 +7,6 @@
using Orleans; using Orleans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
@ -15,6 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
Task RestoreAsync(Uri url, RefToken actor, string? newAppName = null); Task RestoreAsync(Uri url, RefToken actor, string? newAppName = null);
Task<J<IRestoreJob>> GetStateAsync(); Task<IRestoreJob> GetStateAsync();
} }
} }

43
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -6,9 +6,9 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NodaTime; using NodaTime;
using Orleans.Core;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Backup.State; using Squidex.Domain.Apps.Entities.Backup.State;
@ -25,18 +25,18 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
public sealed class RestoreGrain : GrainOfString, IRestoreGrain public sealed class RestoreGrain : GrainBase, IRestoreGrain
{ {
private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IBackupHandlerFactory backupHandlers;
private readonly IClock clock; private readonly IClock clock;
private readonly ICommandBus commandBus; private readonly ICommandBus commandBus;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventStreamNames eventStreams;
private readonly IGrainState<BackupRestoreState> state;
private readonly ILogger<RestoreGrain> log; private readonly ILogger<RestoreGrain> log;
private readonly IServiceProvider serviceProvider;
private readonly IStreamNameResolver streamNameResolver;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly IGrainState<BackupRestoreState> state;
private RestoreContext runningContext; private RestoreContext runningContext;
private StreamMapper runningStreamMapper; private StreamMapper runningStreamMapper;
@ -47,29 +47,31 @@ namespace Squidex.Domain.Apps.Entities.Backup
public RestoreGrain( public RestoreGrain(
IBackupArchiveLocation backupArchiveLocation, IBackupArchiveLocation backupArchiveLocation,
IBackupHandlerFactory backupHandlers,
IClock clock, IClock clock,
ICommandBus commandBus, ICommandBus commandBus,
IEventDataFormatter eventDataFormatter, IEventFormatter eventFormatter,
IEventStore eventStore, IEventStore eventStore,
IEventStreamNames eventStreams,
IGrainIdentity identity,
IGrainState<BackupRestoreState> state, IGrainState<BackupRestoreState> state,
IServiceProvider serviceProvider,
IStreamNameResolver streamNameResolver,
IUserResolver userResolver, IUserResolver userResolver,
ILogger<RestoreGrain> log) ILogger<RestoreGrain> log)
: base(identity)
{ {
this.backupArchiveLocation = backupArchiveLocation; this.backupArchiveLocation = backupArchiveLocation;
this.backupHandlers = backupHandlers;
this.clock = clock; this.clock = clock;
this.commandBus = commandBus; this.commandBus = commandBus;
this.eventDataFormatter = eventDataFormatter; this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.serviceProvider = serviceProvider; this.eventStreams = eventStreams;
this.state = state; this.state = state;
this.streamNameResolver = streamNameResolver;
this.userResolver = userResolver; this.userResolver = userResolver;
this.log = log; this.log = log;
} }
protected override Task OnActivateAsync(string key) public override Task OnActivateAsync()
{ {
RecoverAfterRestartAsync().Forget(); RecoverAfterRestartAsync().Forget();
@ -127,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ProcessAsync() private async Task ProcessAsync()
{ {
var handlers = CreateHandlers(); var handlers = backupHandlers.CreateMany();
var ct = default(CancellationToken); var ct = default(CancellationToken);
@ -300,7 +302,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
var offset = runningStreamMapper.GetStreamOffset(stream); var offset = runningStreamMapper.GetStreamOffset(stream);
commits.Add(EventCommit.Create(stream, offset, @event, eventDataFormatter)); commits.Add(EventCommit.Create(stream, offset, @event, eventFormatter));
} }
await eventStore.AppendUnsafeAsync(commits); await eventStore.AppendUnsafeAsync(commits);
@ -328,7 +330,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
batchBlock.BidirectionalLinkTo(writeBlock); batchBlock.BidirectionalLinkTo(writeBlock);
await foreach (var job in reader.ReadEventsAsync(streamNameResolver, eventDataFormatter)) await foreach (var job in reader.ReadEventsAsync(eventStreams, eventFormatter))
{ {
var newStream = await HandleEventAsync(reader, handlers, job.Stream, job.Event); var newStream = await HandleEventAsync(reader, handlers, job.Stream, job.Event);
@ -425,14 +427,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
} }
} }
private IEnumerable<IBackupHandler> CreateHandlers() public Task<IRestoreJob> GetStateAsync()
{
return serviceProvider.GetRequiredService<IEnumerable<IBackupHandler>>();
}
public Task<J<IRestoreJob>> GetStateAsync()
{ {
return Task.FromResult<J<IRestoreJob>>(CurrentJob); return Task.FromResult<IRestoreJob>(CurrentJob);
} }
} }
} }

9
backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsCommandMiddleware.cs

@ -9,7 +9,6 @@ using System.Text.RegularExpressions;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
@ -36,17 +35,15 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
await MentionUsersAsync(createComment); await MentionUsersAsync(createComment);
} }
await ExecuteCommandAsync(context, commentsCommand); await ExecuteCommandAsync(commentsCommand);
} }
await next(context); await next(context);
} }
private async Task ExecuteCommandAsync(CommandContext context, CommentsCommand commentsCommand) private Task ExecuteCommandAsync(CommentsCommand commentsCommand)
{ {
var result = await GetGrain(commentsCommand).ExecuteAsync(commentsCommand.AsJ()); return GetGrain(commentsCommand).ExecuteAsync(commentsCommand);
context.Complete(result.Value);
} }
private ICommentsGrain GetGrain(CommentsCommand commentsCommand) private ICommentsGrain GetGrain(CommentsCommand commentsCommand)

37
backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.Comments.DomainObject.Guards; using Squidex.Domain.Apps.Entities.Comments.DomainObject.Guards;
using Squidex.Domain.Apps.Events.Comments; using Squidex.Domain.Apps.Events.Comments;
@ -18,12 +19,12 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
{ {
public sealed class CommentsGrain : GrainOfString, ICommentsGrain public sealed class CommentsGrain : GrainBase, ICommentsGrain
{ {
private readonly List<Envelope<CommentsEvent>> uncommittedEvents = new List<Envelope<CommentsEvent>>(); private readonly List<Envelope<CommentsEvent>> uncommittedEvents = new List<Envelope<CommentsEvent>>();
private readonly List<Envelope<CommentsEvent>> events = new List<Envelope<CommentsEvent>>(); private readonly List<Envelope<CommentsEvent>> events = new List<Envelope<CommentsEvent>>();
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private long version = EtagVersion.Empty; private long version = EtagVersion.Empty;
private string streamName; private string streamName;
@ -32,21 +33,24 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
get => version; get => version;
} }
public CommentsGrain(IEventStore eventStore, IEventDataFormatter eventDataFormatter) public CommentsGrain(IGrainIdentity identity,
IEventFormatter eventFormatter,
IEventStore eventStore)
: base(identity)
{ {
this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
} }
protected override async Task OnActivateAsync(string key) public override async Task OnActivateAsync()
{ {
streamName = $"comments-{key}"; streamName = $"comments-{Key}";
var storedEvents = await eventStore.QueryReverseAsync(streamName, 100); var storedEvents = await eventStore.QueryReverseAsync(streamName, 100);
foreach (var @event in storedEvents) foreach (var @event in storedEvents)
{ {
var parsedEvent = eventDataFormatter.Parse(@event); var parsedEvent = eventFormatter.Parse(@event);
version = @event.EventStreamNumber; version = @event.EventStreamNumber;
@ -54,14 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
} }
} }
public async Task<J<CommandResult>> ExecuteAsync(J<CommentsCommand> command) public Task<CommandResult> ExecuteAsync(CommentsCommand command)
{
var result = await ExecuteAsync(command.Value);
return result.AsJ();
}
private Task<CommandResult> ExecuteAsync(CommentsCommand command)
{ {
switch (command) switch (command)
{ {
@ -76,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
case UpdateComment updateComment: case UpdateComment updateComment:
return Upsert(updateComment, c => return Upsert(updateComment, c =>
{ {
GuardComments.CanUpdate(c, Key, events); GuardComments.CanUpdate(c, Key.ToString(), events);
Update(c); Update(c);
}); });
@ -84,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
case DeleteComment deleteComment: case DeleteComment deleteComment:
return Upsert(deleteComment, c => return Upsert(deleteComment, c =>
{ {
GuardComments.CanDelete(c, Key, events); GuardComments.CanDelete(c, Key.ToString(), events);
Delete(c); Delete(c);
}); });
@ -102,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version) if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version)
{ {
throw new DomainObjectVersionException(Key, Version, command.ExpectedVersion); throw new DomainObjectVersionException(Key.ToString(), Version, command.ExpectedVersion);
} }
var previousVersion = version; var previousVersion = version;
@ -115,14 +112,14 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
{ {
var commitId = Guid.NewGuid(); var commitId = Guid.NewGuid();
var eventData = uncommittedEvents.Select(x => eventDataFormatter.ToEventData(x, commitId)).ToList(); var eventData = uncommittedEvents.Select(x => eventFormatter.ToEventData(x, commitId)).ToList();
await eventStore.AppendAsync(commitId, streamName, previousVersion, eventData); await eventStore.AppendAsync(commitId, streamName, previousVersion, eventData);
} }
events.AddRange(uncommittedEvents); events.AddRange(uncommittedEvents);
return CommandResult.Empty(DomainId.Create(Key), Version, previousVersion); return CommandResult.Empty(Key, Version, previousVersion);
} }
catch catch
{ {

3
backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/ICommentsGrain.cs

@ -9,13 +9,12 @@ using Orleans;
using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
{ {
public interface ICommentsGrain : IGrainWithStringKey public interface ICommentsGrain : IGrainWithStringKey
{ {
Task<J<CommandResult>> ExecuteAsync(J<CommentsCommand> command); Task<CommandResult> ExecuteAsync(CommentsCommand command);
Task<CommentsResult> GetCommentsAsync(long sinceVersion = EtagVersion.Any); Task<CommentsResult> GetCommentsAsync(long sinceVersion = EtagVersion.Any);
} }

4
backend/src/Squidex.Domain.Apps.Entities/Comments/WatchingGrain.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using NodaTime; using NodaTime;
using Orleans.Core;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -17,7 +18,8 @@ namespace Squidex.Domain.Apps.Entities.Comments
private readonly Dictionary<string, Dictionary<string, Instant>> users = new Dictionary<string, Dictionary<string, Instant>>(); private readonly Dictionary<string, Dictionary<string, Instant>> users = new Dictionary<string, Dictionary<string, Instant>>();
private readonly IClock clock; private readonly IClock clock;
public WatchingGrain(IClock clock) public WatchingGrain(IGrainIdentity grainIdentity, IClock clock)
: base(grainIdentity)
{ {
this.clock = clock; this.clock = clock;
} }

1
backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs

@ -41,7 +41,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
public BackupContents(Rebuilder rebuilder, IUrlGenerator urlGenerator) public BackupContents(Rebuilder rebuilder, IUrlGenerator urlGenerator)
{ {
this.rebuilder = rebuilder; this.rebuilder = rebuilder;
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
} }

6
backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterGrain.cs

@ -5,12 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Contents.Counter namespace Squidex.Domain.Apps.Entities.Contents.Counter
{ {
public sealed class CounterGrain : GrainOfString, ICounterGrain public sealed class CounterGrain : GrainBase, ICounterGrain
{ {
private readonly IGrainState<State> state; private readonly IGrainState<State> state;
@ -20,7 +21,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter
public Dictionary<string, long> Counters { get; set; } = new Dictionary<string, long>(); public Dictionary<string, long> Counters { get; set; } = new Dictionary<string, long>();
} }
public CounterGrain(IGrainState<State> state) public CounterGrain(IGrainIdentity identity, IGrainState<State> state)
: base(identity)
{ {
this.state = state; this.state = state;
} }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs

@ -28,9 +28,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public ContentDomainObject(IPersistenceFactory<State> persistence, ILogger<ContentDomainObject> log, public ContentDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<ContentDomainObject> log,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
: base(persistence, log) : base(id, persistence, log)
{ {
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;

7
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObjectGrain.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -14,13 +15,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{ {
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5);
public ContentDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit) public ContentDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory, IActivationLimit limit)
: base(serviceProvider) : base(identity, factory)
{ {
limit?.SetLimit(5000, Lifetime); limit?.SetLimit(5000, Lifetime);
} }
public async Task<J<IContentEntity>> GetStateAsync(long version = -2) public async Task<IContentEntity> GetStateAsync(long version = -2)
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();

3
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/IContentGrain.cs

@ -7,12 +7,11 @@
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{ {
public interface IContentGrain : IDomainObjectGrain public interface IContentGrain : IDomainObjectGrain
{ {
Task<J<IContentEntity>> GetStateAsync(long version = EtagVersion.Any); Task<IContentEntity> GetStateAsync(long version = EtagVersion.Any);
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs

@ -29,14 +29,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var contentGrain = grainFactory.GetGrain<IContentGrain>(key); var contentGrain = grainFactory.GetGrain<IContentGrain>(key);
var contentState = await contentGrain.GetStateAsync(version); var contentState = await contentGrain.GetStateAsync(version);
var content = contentState.Value; if (contentState == null || contentState.Version <= EtagVersion.Empty || (version > EtagVersion.Any && contentState.Version != version))
if (content == null || content.Version <= EtagVersion.Empty || (version > EtagVersion.Any && content.Version != version))
{ {
return null; return null;
} }
return content; return contentState;
} }
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/IRuleGrain.cs

@ -6,12 +6,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules.DomainObject namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{ {
public interface IRuleGrain : IDomainObjectGrain public interface IRuleGrain : IDomainObjectGrain
{ {
Task<J<IRuleEntity>> GetStateAsync(); Task<IRuleEntity> GetStateAsync();
} }
} }

29
backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards; using Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards;
@ -22,15 +23,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{ {
public sealed partial class RuleDomainObject : DomainObject<RuleDomainObject.State> public sealed partial class RuleDomainObject : DomainObject<RuleDomainObject.State>
{ {
private readonly IAppProvider appProvider; private readonly IServiceProvider serviceProvider;
private readonly IRuleEnqueuer ruleEnqueuer;
public RuleDomainObject(IPersistenceFactory<State> factory, ILogger<RuleDomainObject> log, public RuleDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<RuleDomainObject> log,
IAppProvider appProvider, IRuleEnqueuer ruleEnqueuer) IServiceProvider serviceProvider)
: base(factory, log) : base(id, persistence, log)
{ {
this.appProvider = appProvider; this.serviceProvider = serviceProvider;
this.ruleEnqueuer = ruleEnqueuer;
} }
protected override bool IsDeleted(State snapshot) protected override bool IsDeleted(State snapshot)
@ -57,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
case CreateRule createRule: case CreateRule createRule:
return CreateReturnAsync(createRule, async c => return CreateReturnAsync(createRule, async c =>
{ {
await GuardRule.CanCreate(c, appProvider); await GuardRule.CanCreate(c, AppProvider());
Create(c); Create(c);
@ -67,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
case UpdateRule updateRule: case UpdateRule updateRule:
return UpdateReturnAsync(updateRule, async c => return UpdateReturnAsync(updateRule, async c =>
{ {
await GuardRule.CanUpdate(c, Snapshot, appProvider); await GuardRule.CanUpdate(c, Snapshot, AppProvider());
Update(c); Update(c);
@ -117,7 +116,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
SimpleMapper.Map(command, @event); SimpleMapper.Map(command, @event);
SimpleMapper.Map(Snapshot, @event); SimpleMapper.Map(Snapshot, @event);
await ruleEnqueuer.EnqueueAsync(Snapshot.RuleDef, Snapshot.Id, Envelope.Create(@event)); await RuleEnqueuer().EnqueueAsync(Snapshot.RuleDef, Snapshot.Id, Envelope.Create(@event));
}
private IRuleEnqueuer RuleEnqueuer()
{
return serviceProvider.GetRequiredService<IRuleEnqueuer>();
} }
private void Create(CreateRule command) private void Create(CreateRule command)
@ -149,5 +153,10 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{ {
RaiseEvent(Envelope.Create(SimpleMapper.Map(command, @event))); RaiseEvent(Envelope.Create(SimpleMapper.Map(command, @event)));
} }
private IAppProvider AppProvider()
{
return serviceProvider.GetRequiredService<IAppProvider>();
}
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObjectGrain.cs

@ -5,19 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules.DomainObject namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{ {
public sealed class RuleDomainObjectGrain : DomainObjectGrain<RuleDomainObject, RuleDomainObject.State>, IRuleGrain public sealed class RuleDomainObjectGrain : DomainObjectGrain<RuleDomainObject, RuleDomainObject.State>, IRuleGrain
{ {
public RuleDomainObjectGrain(IServiceProvider serviceProvider) public RuleDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(serviceProvider) : base(identity, factory)
{ {
} }
public async Task<J<IRuleEntity>> GetStateAsync() public async Task<IRuleEntity> GetStateAsync()
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();

10
backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesCacheGrain.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using Orleans.Concurrency; using Orleans.Concurrency;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -13,14 +14,13 @@ using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules.Indexes namespace Squidex.Domain.Apps.Entities.Rules.Indexes
{ {
[Reentrant] [Reentrant]
public sealed class RulesCacheGrain : GrainOfString, IRulesCacheGrain public sealed class RulesCacheGrain : GrainBase, IRulesCacheGrain
{ {
private readonly IRuleRepository ruleRepository; private readonly IRuleRepository ruleRepository;
private List<DomainId>? ruleIds; private List<DomainId>? ruleIds;
private DomainId AppId => DomainId.Create(Key); public RulesCacheGrain(IGrainIdentity grainIdentity, IRuleRepository ruleRepository)
: base(grainIdentity)
public RulesCacheGrain(IRuleRepository ruleRepository)
{ {
this.ruleRepository = ruleRepository; this.ruleRepository = ruleRepository;
} }
@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes
if (ids == null) if (ids == null)
{ {
ids = await ruleRepository.QueryIdsAsync(AppId); ids = await ruleRepository.QueryIdsAsync(Key);
ruleIds = ids; ruleIds = ids;
} }

2
backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs

@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes
private async Task<IRuleEntity?> GetRuleCoreAsync(DomainId id) private async Task<IRuleEntity?> GetRuleCoreAsync(DomainId id)
{ {
var rule = (await grainFactory.GetGrain<IRuleGrain>(id.ToString()).GetStateAsync()).Value; var rule = await grainFactory.GetGrain<IRuleGrain>(id.ToString()).GetStateAsync();
if (rule.Version <= EtagVersion.Empty || rule.IsDeleted) if (rule.Version <= EtagVersion.Empty || rule.IsDeleted)
{ {

8
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs

@ -19,18 +19,18 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
public sealed class DefaultRuleRunnerService : IRuleRunnerService public sealed class DefaultRuleRunnerService : IRuleRunnerService
{ {
private const int MaxSimulatedEvents = 100; private const int MaxSimulatedEvents = 100;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IGrainFactory grainFactory; private readonly IGrainFactory grainFactory;
private readonly IRuleService ruleService; private readonly IRuleService ruleService;
public DefaultRuleRunnerService(IGrainFactory grainFactory, public DefaultRuleRunnerService(IGrainFactory grainFactory,
IEventFormatter eventFormatter,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IRuleService ruleService) IRuleService ruleService)
{ {
this.grainFactory = grainFactory; this.grainFactory = grainFactory;
this.eventDataFormatter = eventDataFormatter; this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.ruleService = ruleService; this.ruleService = ruleService;
} }
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
await foreach (var storedEvent in eventStore.QueryAllReverseAsync($"^([a-zA-Z0-9]+)\\-{appId.Id}", fromNow, MaxSimulatedEvents, ct)) await foreach (var storedEvent in eventStore.QueryAllReverseAsync($"^([a-zA-Z0-9]+)\\-{appId.Id}", fromNow, MaxSimulatedEvents, ct))
{ {
var @event = eventDataFormatter.ParseIfKnown(storedEvent); var @event = eventFormatter.ParseIfKnown(storedEvent);
if (@event?.Payload is AppEvent appEvent) if (@event?.Payload is AppEvent appEvent)
{ {

26
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs

@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Orleans; using Orleans;
using Orleans.Core;
using Orleans.Runtime; using Orleans.Runtime;
using Squidex.Caching; using Squidex.Caching;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
@ -22,14 +23,14 @@ using TaskExtensions = Squidex.Infrastructure.Tasks.TaskExtensions;
namespace Squidex.Domain.Apps.Entities.Rules.Runner namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
public sealed class RuleRunnerGrain : GrainOfString, IRuleRunnerGrain, IRemindable public sealed class RuleRunnerGrain : GrainBase, IRuleRunnerGrain, IRemindable
{ {
private const int MaxErrors = 10; private const int MaxErrors = 10;
private readonly IGrainState<State> state;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly ILocalCache localCache; private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IGrainState<State> state;
private readonly ILocalCache localCache;
private readonly IRuleEventRepository ruleEventRepository; private readonly IRuleEventRepository ruleEventRepository;
private readonly IRuleService ruleService; private readonly IRuleService ruleService;
private readonly ILogger<RuleRunnerGrain> log; private readonly ILogger<RuleRunnerGrain> log;
@ -47,27 +48,28 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
public bool RunFromSnapshots { get; set; } public bool RunFromSnapshots { get; set; }
} }
public RuleRunnerGrain( public RuleRunnerGrain(IGrainIdentity identity,
IGrainState<State> state,
IAppProvider appProvider, IAppProvider appProvider,
ILocalCache localCache, IEventFormatter eventFormatter,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IGrainState<State> state,
ILocalCache localCache,
IRuleEventRepository ruleEventRepository, IRuleEventRepository ruleEventRepository,
IRuleService ruleService, IRuleService ruleService,
ILogger<RuleRunnerGrain> log) ILogger<RuleRunnerGrain> log)
: base(identity)
{ {
this.state = state; this.state = state;
this.appProvider = appProvider; this.appProvider = appProvider;
this.localCache = localCache; this.localCache = localCache;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventFormatter = eventFormatter;
this.ruleEventRepository = ruleEventRepository; this.ruleEventRepository = ruleEventRepository;
this.ruleService = ruleService; this.ruleService = ruleService;
this.log = log; this.log = log;
} }
protected override Task OnActivateAsync(string key) public override Task OnActivateAsync()
{ {
return EnsureIsRunningAsync(true); return EnsureIsRunningAsync(true);
} }
@ -154,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
currentReminder = await RegisterOrUpdateReminder("KeepAlive", TimeSpan.Zero, TimeSpan.FromMinutes(2)); currentReminder = await RegisterOrUpdateReminder("KeepAlive", TimeSpan.Zero, TimeSpan.FromMinutes(2));
var rule = await appProvider.GetRuleAsync(DomainId.Create(Key), currentState.RuleId!.Value, ct); var rule = await appProvider.GetRuleAsync(Key, currentState.RuleId!.Value, ct);
if (rule == null) if (rule == null)
{ {
@ -247,7 +249,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
try try
{ {
var @event = eventDataFormatter.ParseIfKnown(storedEvent); var @event = eventFormatter.ParseIfKnown(storedEvent);
if (@event != null) if (@event != null)
{ {

9
backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs

@ -7,6 +7,7 @@
using Orleans; using Orleans;
using Orleans.Concurrency; using Orleans.Concurrency;
using Orleans.Core;
using Orleans.Runtime; using Orleans.Runtime;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -18,7 +19,7 @@ using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{ {
[Reentrant] [Reentrant]
public sealed class UsageTrackerGrain : GrainOfString, IRemindable, IUsageTrackerGrain public sealed class UsageTrackerGrain : GrainBase, IRemindable, IUsageTrackerGrain
{ {
private readonly IGrainState<State> state; private readonly IGrainState<State> state;
private readonly IApiUsageTracker usageTracker; private readonly IApiUsageTracker usageTracker;
@ -61,14 +62,14 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
public Dictionary<DomainId, Target> Targets { get; set; } = new Dictionary<DomainId, Target>(); public Dictionary<DomainId, Target> Targets { get; set; } = new Dictionary<DomainId, Target>();
} }
public UsageTrackerGrain(IGrainState<State> state, IApiUsageTracker usageTracker) public UsageTrackerGrain(IGrainIdentity identity, IGrainState<State> state, IApiUsageTracker usageTracker)
: base(identity)
{ {
this.state = state; this.state = state;
this.usageTracker = usageTracker; this.usageTracker = usageTracker;
} }
protected override Task OnActivateAsync(string key) public override Task OnActivateAsync()
{ {
DelayDeactivation(TimeSpan.FromDays(1)); DelayDeactivation(TimeSpan.FromDays(1));

3
backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/ISchemaGrain.cs

@ -6,12 +6,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{ {
public interface ISchemaGrain : IDomainObjectGrain public interface ISchemaGrain : IDomainObjectGrain
{ {
Task<J<ISchemaEntity>> GetStateAsync(); Task<ISchemaEntity> GetStateAsync();
} }
} }

9
backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs

@ -15,7 +15,6 @@ using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -25,8 +24,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{ {
public sealed partial class SchemaDomainObject : DomainObject<SchemaDomainObject.State> public sealed partial class SchemaDomainObject : DomainObject<SchemaDomainObject.State>
{ {
public SchemaDomainObject(IPersistenceFactory<State> persistence, ILogger<SchemaDomainObject> log) public SchemaDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<SchemaDomainObject> log)
: base(persistence, log) : base(id, persistence, log)
{ {
} }
@ -407,9 +406,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
return NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name); return NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name);
} }
public Task<J<ISchemaEntity>> GetStateAsync() public Task<ISchemaEntity> GetStateAsync()
{ {
return J.AsTask<ISchemaEntity>(Snapshot); return Task.FromResult<ISchemaEntity>(Snapshot);
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObjectGrain.cs

@ -5,19 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{ {
public sealed class SchemaDomainObjectGrain : DomainObjectGrain<SchemaDomainObject, SchemaDomainObject.State>, ISchemaGrain public sealed class SchemaDomainObjectGrain : DomainObjectGrain<SchemaDomainObject, SchemaDomainObject.State>, ISchemaGrain
{ {
public SchemaDomainObjectGrain(IServiceProvider serviceProvider) public SchemaDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(serviceProvider) : base(identity, factory)
{ {
} }
public async Task<J<ISchemaEntity>> GetStateAsync() public async Task<ISchemaEntity> GetStateAsync()
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();

8
backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasCacheGrain.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using Orleans.Concurrency; using Orleans.Concurrency;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans.Indexes; using Squidex.Infrastructure.Orleans.Indexes;
@ -18,9 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
private readonly ISchemaRepository schemaRepository; private readonly ISchemaRepository schemaRepository;
private Dictionary<string, DomainId>? schemaIds; private Dictionary<string, DomainId>? schemaIds;
private DomainId AppId => DomainId.Create(Key); public SchemasCacheGrain(IGrainIdentity identity, ISchemaRepository schemaRepository)
: base(identity)
public SchemasCacheGrain(ISchemaRepository schemaRepository)
{ {
this.schemaRepository = schemaRepository; this.schemaRepository = schemaRepository;
} }
@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
if (ids == null) if (ids == null)
{ {
ids = await schemaRepository.QueryIdsAsync(AppId); ids = await schemaRepository.QueryIdsAsync(Key);
schemaIds = ids; schemaIds = ids;
} }

2
backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs

@ -205,7 +205,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
private async Task<ISchemaEntity?> GetSchemaCoreAsync(DomainId id, bool allowDeleted = false) private async Task<ISchemaEntity?> GetSchemaCoreAsync(DomainId id, bool allowDeleted = false)
{ {
var schema = (await grainFactory.GetGrain<ISchemaGrain>(id.ToString()).GetStateAsync()).Value; var schema = await grainFactory.GetGrain<ISchemaGrain>(id.ToString()).GetStateAsync();
if (schema.Version <= EtagVersion.Empty || (schema.IsDeleted && !allowDeleted)) if (schema.Version <= EtagVersion.Empty || (schema.IsDeleted && !allowDeleted))
{ {

36
backend/src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
@ -12,35 +13,42 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Tags namespace Squidex.Domain.Apps.Entities.Tags
{ {
public sealed class TagGrain : GrainOfString, ITagGrain public sealed class TagGrain : GrainBase, ITagGrain
{ {
private readonly IGrainState<State> state; private readonly IGrainState<State> grainState;
[CollectionName("Index_Tags")] [CollectionName("Index_Tags")]
public sealed class State : TagsExport public sealed class State : TagsExport
{ {
} }
private Dictionary<string, Tag> Tags => state.Value.Tags ??= new Dictionary<string, Tag>(); private Dictionary<string, Tag> Tags
{
get => grainState.Value.Tags ??= new Dictionary<string, Tag>();
}
private Dictionary<string, string> Alias => state.Value.Alias ??= new Dictionary<string, string>(); private Dictionary<string, string> Alias
{
get => grainState.Value.Alias ??= new Dictionary<string, string>();
}
public TagGrain(IGrainState<State> state) public TagGrain(IGrainIdentity grainIdentity, IGrainState<State> grainState)
: base(grainIdentity)
{ {
this.state = state; this.grainState = grainState;
} }
public Task ClearAsync() public Task ClearAsync()
{ {
return state.ClearAsync(); return grainState.ClearAsync();
} }
public Task RebuildAsync(TagsExport export) public Task RebuildAsync(TagsExport export)
{ {
state.Value.Tags = export.Tags; grainState.Value.Tags = export.Tags;
state.Value.Alias = export.Alias; grainState.Value.Alias = export.Alias;
return state.WriteAsync(); return grainState.WriteAsync();
} }
public Task RenameTagAsync(string name, string newName) public Task RenameTagAsync(string name, string newName)
@ -73,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Tags
Alias[name] = newName; Alias[name] = newName;
return state.WriteAsync(); return grainState.WriteAsync();
} }
public async Task<Dictionary<string, string>> NormalizeTagsAsync(HashSet<string>? names, HashSet<string>? ids) public async Task<Dictionary<string, string>> NormalizeTagsAsync(HashSet<string>? names, HashSet<string>? ids)
@ -112,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Tags
} }
} }
await state.WriteAsync(); await grainState.WriteAsync();
return result; return result;
} }
@ -157,12 +165,12 @@ namespace Squidex.Domain.Apps.Entities.Tags
{ {
var tags = Tags.Values.ToDictionary(x => x.Name, x => x.Count); var tags = Tags.Values.ToDictionary(x => x.Name, x => x.Count);
return Task.FromResult(new TagsSet(tags, state.Version)); return Task.FromResult(new TagsSet(tags, grainState.Version));
} }
public Task<TagsExport> GetExportableTagsAsync() public Task<TagsExport> GetExportableTagsAsync()
{ {
return Task.FromResult(state.Value.Clone()); return Task.FromResult(grainState.Value.Clone());
} }
private string GetId(string name, HashSet<string>? ids) private string GetId(string name, HashSet<string>? ids)

56
backend/src/Squidex.Infrastructure/Commands/DefaultDomainObjectFactory.cs

@ -0,0 +1,56 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure.States;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.Commands
{
public sealed class DefaultDomainObjectFactory : IDomainObjectFactory
{
private readonly IServiceProvider serviceProvider;
private static class DefaultFactory<T>
{
private static readonly ObjectFactory ObjectFactory =
ActivatorUtilities.CreateFactory(typeof(T), new[] { typeof(DomainId) });
public static T Create(IServiceProvider serviceProvider, DomainId id)
{
return (T)ObjectFactory(serviceProvider, new object[] { id });
}
}
private static class PersistenceFactory<T, TState>
{
private static readonly ObjectFactory ObjectFactory =
ActivatorUtilities.CreateFactory(typeof(T), new[] { typeof(DomainId), typeof(IPersistenceFactory<TState>) });
public static T Create(IServiceProvider serviceProvider, DomainId id, IPersistenceFactory<TState> persistenceFactory)
{
return (T)ObjectFactory(serviceProvider, new object[] { id, persistenceFactory });
}
}
public DefaultDomainObjectFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public T Create<T>(DomainId id)
{
return DefaultFactory<T>.Create(serviceProvider, id);
}
public T Create<T, TState>(DomainId id, IPersistenceFactory<TState> factory)
{
return PersistenceFactory<T, TState>.Create(serviceProvider, id, factory);
}
}
}

97
backend/src/Squidex.Infrastructure/Commands/DomainObject.cs

@ -15,11 +15,11 @@ namespace Squidex.Infrastructure.Commands
{ {
private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>(); private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>();
private readonly SnapshotList<T> snapshots = new SnapshotList<T>(); private readonly SnapshotList<T> snapshots = new SnapshotList<T>();
private readonly IPersistenceFactory<T> factory;
private readonly ILogger log; private readonly ILogger log;
private IPersistence<T>? persistence; private readonly IPersistenceFactory<T> factory;
private readonly IPersistence<T> persistence;
private readonly DomainId uniqueId;
private bool isLoaded; private bool isLoaded;
private DomainId uniqueId;
public DomainId UniqueId public DomainId UniqueId
{ {
@ -42,14 +42,29 @@ namespace Squidex.Infrastructure.Commands
set => snapshots.Capacity = value; set => snapshots.Capacity = value;
} }
protected DomainObject(IPersistenceFactory<T> factory, protected DomainObject(DomainId uniqueId, IPersistenceFactory<T> factory,
ILogger log) ILogger log)
{ {
Guard.NotNull(factory); Guard.NotNull(factory);
Guard.NotNull(log); Guard.NotNull(log);
this.uniqueId = uniqueId;
this.factory = factory; this.factory = factory;
persistence = factory.WithSnapshotsAndEventSourcing(GetType(), UniqueId,
new HandleSnapshot<T>((snapshot, version) =>
{
snapshot.Version = version;
snapshots.Add(snapshot, version, true);
}),
@event =>
{
// Some migrations needs the current state.
@event = @event.Migrate(Snapshot);
return ApplyEvent(@event, true, Snapshot, Version, true).Success;
});
this.log = log; this.log = log;
} }
@ -68,6 +83,7 @@ namespace Squidex.Infrastructure.Commands
var allEvents = factory.WithEventSourcing(GetType(), UniqueId, @event => var allEvents = factory.WithEventSourcing(GetType(), UniqueId, @event =>
{ {
// Some migrations needs the current state.
@event = @event.Migrate(snapshot); @event = @event.Migrate(snapshot);
var (newSnapshot, isChanged) = ApplyEvent(@event, true, snapshot, snapshot.Version, false); var (newSnapshot, isChanged) = ApplyEvent(@event, true, snapshot, snapshot.Version, false);
@ -90,45 +106,24 @@ namespace Squidex.Infrastructure.Commands
return result ?? new T { Version = EtagVersion.Empty }; return result ?? new T { Version = EtagVersion.Empty };
} }
public virtual void Setup(DomainId uniqueId) public virtual async Task EnsureLoadedAsync()
{
this.uniqueId = uniqueId;
persistence = factory.WithSnapshotsAndEventSourcing(GetType(), UniqueId,
new HandleSnapshot<T>((snapshot, version) =>
{
snapshot.Version = version;
snapshots.Add(snapshot, version, true);
}),
@event =>
{
@event = @event.Migrate(Snapshot);
return ApplyEvent(@event, true, Snapshot, Version, true).Success;
});
}
public virtual async Task EnsureLoadedAsync(bool silent = false)
{ {
if (isLoaded) if (isLoaded)
{ {
return; return;
} }
if (silent) await persistence.ReadAsync();
{
await ReadAsync(); if (persistence.IsSnapshotStale)
}
else
{ {
var watch = ValueStopwatch.StartNew();
try try
{ {
await ReadAsync(); await persistence.WriteSnapshotAsync(Snapshot);
} }
finally catch (Exception ex)
{ {
log.LogInformation("Activated domain object of type {type} with ID {id} in {time}.", GetType(), UniqueId, watch.Stop()); log.LogError(ex, "Failed to repair snapshot for domain object of type {type} with ID {id}.", GetType(), UniqueId);
} }
} }
@ -179,15 +174,12 @@ namespace Squidex.Infrastructure.Commands
var deletedId = DomainId.Combine(UniqueId, DomainId.Create("deleted")); var deletedId = DomainId.Combine(UniqueId, DomainId.Create("deleted"));
var deletedStream = factory.WithEventSourcing(GetType(), deletedId, null); var deletedStream = factory.WithEventSourcing(GetType(), deletedId, null);
// Write to the deleted stream first so we never loose this information.
await deletedStream.WriteEventsAsync(events); await deletedStream.WriteEventsAsync(events);
if (persistence != null) // Cleanup the secondary stream first.
{
await persistence.DeleteAsync(); await persistence.DeleteAsync();
Setup(uniqueId);
}
snapshots.Clear(); snapshots.Clear();
} }
@ -225,7 +217,7 @@ namespace Squidex.Infrastructure.Commands
} }
catch (InconsistentStateException) catch (InconsistentStateException)
{ {
await EnsureLoadedAsync(true); await EnsureLoadedAsync();
if (wasDeleted) if (wasDeleted)
{ {
@ -334,49 +326,28 @@ namespace Squidex.Infrastructure.Commands
return (newSnapshot, isChanged); return (newSnapshot, isChanged);
} }
private async Task ReadAsync() private async Task WriteAsync(Envelope<IEvent>[] newEvents)
{
if (persistence != null)
{
await persistence.ReadAsync();
if (persistence.IsSnapshotStale)
{ {
try if (newEvents.Length == 0)
{ {
await persistence.WriteSnapshotAsync(Snapshot); return;
}
catch (Exception ex)
{
log.LogError(ex, "Failed to repair snapshot for domain object of type {type} with ID {id}.", GetType(), UniqueId);
}
}
}
} }
private async Task WriteAsync(Envelope<IEvent>[] newEvents)
{
if (newEvents.Length > 0 && persistence != null)
{
await persistence.WriteEventsAsync(newEvents); await persistence.WriteEventsAsync(newEvents);
await persistence.WriteSnapshotAsync(Snapshot); await persistence.WriteSnapshotAsync(Snapshot);
} }
}
public async Task RebuildStateAsync() public async Task RebuildStateAsync()
{ {
await EnsureLoadedAsync(true); await EnsureLoadedAsync();
if (Version <= EtagVersion.Empty) if (Version <= EtagVersion.Empty)
{ {
throw new DomainObjectNotFoundException(UniqueId.ToString()); throw new DomainObjectNotFoundException(UniqueId.ToString());
} }
if (persistence != null)
{
await persistence.WriteSnapshotAsync(Snapshot); await persistence.WriteSnapshotAsync(Snapshot);
} }
}
protected virtual T Apply(T snapshot, Envelope<IEvent> @event) protected virtual T Apply(T snapshot, Envelope<IEvent> @event)
{ {

26
backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -5,12 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.DependencyInjection; using Orleans.Core;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
public abstract class DomainObjectGrain<T, TState> : GrainOfString where T : DomainObject<TState> where TState : class, IDomainState<TState>, new() public abstract class DomainObjectGrain<T, TState> : GrainBase where T : DomainObject<TState> where TState : class, IDomainState<TState>, new()
{ {
private readonly T domainObject; private readonly T domainObject;
@ -24,27 +24,15 @@ namespace Squidex.Infrastructure.Commands
get => domainObject; get => domainObject;
} }
protected DomainObjectGrain(IServiceProvider serviceProvider) protected DomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(identity)
{ {
Guard.NotNull(serviceProvider); domainObject = factory.Create<T>(DomainId.Create(identity.PrimaryKeyString));
domainObject = serviceProvider.GetRequiredService<T>();
} }
protected override Task OnActivateAsync(string key) public Task<CommandResult> ExecuteAsync(IAggregateCommand command)
{ {
domainObject.Setup(DomainId.Create(key)); return domainObject.ExecuteAsync(command);
return base.OnActivateAsync(key);
}
public async Task<J<CommandResult>> ExecuteAsync(J<CommandRequest> request)
{
request.Value.ApplyContext();
var result = await domainObject.ExecuteAsync(request.Value.Command);
return result;
} }
} }
} }

6
backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs

@ -42,13 +42,11 @@ namespace Squidex.Infrastructure.Commands
return Task.FromResult(result.Payload is None ? result : result.Payload); return Task.FromResult(result.Payload is None ? result : result.Payload);
} }
private async Task<CommandResult> ExecuteCommandAsync(TCommand typedCommand) private Task<CommandResult> ExecuteCommandAsync(TCommand typedCommand)
{ {
var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId.ToString()); var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId.ToString());
var result = await grain.ExecuteAsync(CommandRequest.Create(typedCommand)); return grain.ExecuteAsync(typedCommand);
return result.Value;
} }
} }
} }

18
backend/src/Squidex.Infrastructure/Commands/IDomainObjectFactory.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.States;
namespace Squidex.Infrastructure.Commands
{
public interface IDomainObjectFactory
{
T Create<T>(DomainId id);
T Create<T, TState>(DomainId id, IPersistenceFactory<TState> factory);
}
}

3
backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs

@ -6,12 +6,11 @@
// ========================================================================== // ==========================================================================
using Orleans; using Orleans;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
public interface IDomainObjectGrain : IGrainWithStringKey public interface IDomainObjectGrain : IGrainWithStringKey
{ {
Task<J<CommandResult>> ExecuteAsync(J<CommandRequest> request); Task<CommandResult> ExecuteAsync(IAggregateCommand command);
} }
} }

19
backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs

@ -13,28 +13,18 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
public class Rebuilder public class Rebuilder
{ {
private readonly IDomainObjectFactory domainObjectFactory;
private readonly ILocalCache localCache; private readonly ILocalCache localCache;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ILogger<Rebuilder> log; private readonly ILogger<Rebuilder> log;
private static class Factory<T, TState> where T : DomainObject<TState> where TState : class, IDomainState<TState>, new()
{
private static readonly ObjectFactory ObjectFactory = ActivatorUtilities.CreateFactory(typeof(T), new[] { typeof(IPersistenceFactory<TState>) });
public static T Create(IServiceProvider serviceProvider, IPersistenceFactory<TState> persistenceFactory)
{
return (T)ObjectFactory(serviceProvider, new object[] { persistenceFactory });
}
}
public Rebuilder( public Rebuilder(
IDomainObjectFactory domainObjectFactory,
ILocalCache localCache, ILocalCache localCache,
IEventStore eventStore, IEventStore eventStore,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
@ -42,6 +32,7 @@ namespace Squidex.Infrastructure.Commands
{ {
this.eventStore = eventStore; this.eventStore = eventStore;
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
this.domainObjectFactory = domainObjectFactory;
this.localCache = localCache; this.localCache = localCache;
this.log = log; this.log = log;
} }
@ -107,9 +98,7 @@ namespace Squidex.Infrastructure.Commands
{ {
try try
{ {
var domainObject = Factory<T, TState>.Create(serviceProvider, context); var domainObject = domainObjectFactory.Create<T, TState>(id, context);
domainObject.Setup(id);
await domainObject.RebuildStateAsync(); await domainObject.RebuildStateAsync();
} }

31
backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventConsumerFactory.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing
{
public sealed class DefaultEventConsumerFactory : IEventConsumerFactory
{
private readonly Dictionary<string, IEventConsumer> eventConsumers;
public DefaultEventConsumerFactory(IEnumerable<IEventConsumer> eventConsumers)
{
this.eventConsumers = eventConsumers.ToDictionary(x => x.Name);
}
public IEventConsumer Create(string name)
{
Guard.NotNullOrEmpty(name);
if (!eventConsumers.TryGetValue(name, out var eventConsumer))
{
throw new ArgumentException($"Cannot find event consuemr with name '{name}'", nameof(name));
}
return eventConsumer;
}
}
}

4
backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs → backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventFormatter.cs

@ -11,12 +11,12 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public sealed class DefaultEventDataFormatter : IEventDataFormatter public sealed class DefaultEventFormatter : IEventFormatter
{ {
private readonly IJsonSerializer serializer; private readonly IJsonSerializer serializer;
private readonly TypeNameRegistry typeNameRegistry; private readonly TypeNameRegistry typeNameRegistry;
public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer) public DefaultEventFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer)
{ {
this.typeNameRegistry = typeNameRegistry; this.typeNameRegistry = typeNameRegistry;

2
backend/src/Squidex.Infrastructure/EventSourcing/EventCommit.cs

@ -16,7 +16,7 @@ namespace Squidex.Infrastructure.EventSourcing
return new EventCommit(id, streamName, offset, new List<EventData> { @event }); return new EventCommit(id, streamName, offset, new List<EventData> { @event });
} }
public static EventCommit Create(string streamName, long offset, Envelope<IEvent> envelope, IEventDataFormatter eventDataFormatter) public static EventCommit Create(string streamName, long offset, Envelope<IEvent> envelope, IEventFormatter eventDataFormatter)
{ {
var id = Guid.NewGuid(); var id = Guid.NewGuid();

2
backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs

@ -33,7 +33,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
public BatchSubscriber( public BatchSubscriber(
EventConsumerGrain grain, EventConsumerGrain grain,
IEventDataFormatter eventDataFormatter, IEventFormatter eventDataFormatter,
IEventConsumer eventConsumer, IEventConsumer eventConsumer,
Func<IEventSubscriber, IEventSubscription> factory) Func<IEventSubscriber, IEventSubscription> factory)
{ {

28
backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs

@ -7,21 +7,21 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Orleans.Core;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing.Grains namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
public class EventConsumerGrain : GrainOfString, IEventConsumerGrain public class EventConsumerGrain : GrainBase, IEventConsumerGrain
{ {
private readonly EventConsumerFactory eventConsumerFactory;
private readonly IGrainState<EventConsumerState> state; private readonly IGrainState<EventConsumerState> state;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventConsumer eventConsumer;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly ILogger<EventConsumerGrain> log; private readonly ILogger<EventConsumerGrain> log;
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
private IEventSubscription? currentSubscription; private IEventSubscription? currentSubscription;
private IEventConsumer? eventConsumer;
private EventConsumerState State private EventConsumerState State
{ {
@ -30,27 +30,21 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
} }
public EventConsumerGrain( public EventConsumerGrain(
EventConsumerFactory eventConsumerFactory, IGrainIdentity identity,
IGrainState<EventConsumerState> state, IGrainState<EventConsumerState> state,
IEventConsumerFactory eventConsumerFactory,
IEventFormatter eventFormatter,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
ILogger<EventConsumerGrain> log) ILogger<EventConsumerGrain> log)
: base(identity)
{ {
this.eventConsumer = eventConsumerFactory.Create(identity.PrimaryKeyString);
this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.eventConsumerFactory = eventConsumerFactory;
this.state = state; this.state = state;
this.log = log; this.log = log;
} }
protected override Task OnActivateAsync(string key)
{
eventConsumer = eventConsumerFactory(key);
return Task.CompletedTask;
}
public override Task OnDeactivateAsync() public override Task OnDeactivateAsync()
{ {
CompleteAsync().Forget(); CompleteAsync().Forget();
@ -276,7 +270,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
private BatchSubscriber CreateSubscription() private BatchSubscriber CreateSubscription()
{ {
return new BatchSubscriber(this, eventDataFormatter, eventConsumer!, CreateRetrySubscription); return new BatchSubscriber(this, eventFormatter, eventConsumer!, CreateRetrySubscription);
} }
protected virtual IEventSubscription CreateRetrySubscription(IEventSubscriber subscriber) protected virtual IEventSubscription CreateRetrySubscription(IEventSubscriber subscriber)

4
backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumer.cs

@ -5,12 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
#pragma warning disable MA0048 // File name must match type name
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public delegate IEventConsumer EventConsumerFactory(string name);
public interface IEventConsumer public interface IEventConsumer
{ {
int BatchDelay => 500; int BatchDelay => 500;

14
backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumerFactory.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing
{
public interface IEventConsumerFactory
{
IEventConsumer Create(string name);
}
}

2
backend/src/Squidex.Infrastructure/EventSourcing/IEventDataFormatter.cs → backend/src/Squidex.Infrastructure/EventSourcing/IEventFormatter.cs

@ -7,7 +7,7 @@
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public interface IEventDataFormatter public interface IEventFormatter
{ {
Envelope<IEvent> Parse(StoredEvent storedEvent); Envelope<IEvent> Parse(StoredEvent storedEvent);

106
backend/src/Squidex.Infrastructure/Orleans/ActivityPropagationGrainCallFilter.cs → backend/src/Squidex.Infrastructure/Orleans/ActivityPropagationFilter.cs

@ -9,18 +9,57 @@ using System.Diagnostics;
using Orleans; using Orleans;
using Orleans.Runtime; using Orleans.Runtime;
#pragma warning disable MA0048 // File name must match type name
namespace Squidex.Infrastructure.Orleans namespace Squidex.Infrastructure.Orleans
{ {
public abstract class ActivityPropagationGrainCallFilter public sealed class ActivityPropagationFilter : IOutgoingGrainCallFilter, IIncomingGrainCallFilter
{
private const string ActivityNameIn = "Orleans.Runtime.GrainCall.In";
private const string ActivityNameOut = "Orleans.Runtime.GrainCall.Out";
private const string TraceParentHeaderName = "traceparent";
private const string TraceStateHeaderName = "tracestate";
public Task Invoke(IOutgoingGrainCallContext context)
{
if (Activity.Current != null)
{
return ProcessCurrentActivity(context);
}
return ProcessNewActivity(context, ActivityNameOut, ActivityKind.Client, default);
}
public Task Invoke(IIncomingGrainCallContext context)
{ {
public const string ActivityNameIn = "Orleans.Runtime.GrainCall.In"; var traceParent = RequestContext.Get(TraceParentHeaderName) as string;
public const string ActivityNameOut = "Orleans.Runtime.GrainCall.Out"; var traceState = RequestContext.Get(TraceStateHeaderName) as string;
protected const string TraceParentHeaderName = "traceparent"; var parentContext = default(ActivityContext);
protected const string TraceStateHeaderName = "tracestate";
protected static async Task ProcessNewActivity(IGrainCallContext context, string activityName, ActivityKind activityKind, ActivityContext activityContext) if (traceParent is not null)
{
parentContext = ActivityContext.Parse(traceParent, traceState);
}
return ProcessNewActivity(context, ActivityNameIn, ActivityKind.Server, parentContext);
}
private static Task ProcessCurrentActivity(IOutgoingGrainCallContext context)
{
var currentActivity = Activity.Current;
if (currentActivity != null && currentActivity.IdFormat == ActivityIdFormat.W3C)
{
RequestContext.Set(TraceParentHeaderName, currentActivity.Id);
if (currentActivity.TraceStateString is not null)
{
RequestContext.Set(TraceStateHeaderName, currentActivity.TraceStateString);
}
}
return context.Invoke();
}
private static async Task ProcessNewActivity(IGrainCallContext context, string activityName, ActivityKind activityKind, ActivityContext activityContext)
{ {
ActivityTagsCollection? tags = null; ActivityTagsCollection? tags = null;
@ -37,7 +76,7 @@ namespace Squidex.Infrastructure.Orleans
using (var activity = Telemetry.Activities.StartActivity(activityName, activityKind, activityContext, tags)) using (var activity = Telemetry.Activities.StartActivity(activityName, activityKind, activityContext, tags))
{ {
if (activity is not null) if (activity != null)
{ {
RequestContext.Set(TraceParentHeaderName, activity.Id); RequestContext.Set(TraceParentHeaderName, activity.Id);
} }
@ -46,7 +85,7 @@ namespace Squidex.Infrastructure.Orleans
{ {
await context.Invoke(); await context.Invoke();
if (activity is not null && activity.IsAllDataRequested) if (activity != null && activity.IsAllDataRequested)
{ {
activity.SetTag("status", "Ok"); activity.SetTag("status", "Ok");
} }
@ -68,51 +107,4 @@ namespace Squidex.Infrastructure.Orleans
} }
} }
} }
public sealed class ActivityPropagationOutgoingGrainCallFilter : ActivityPropagationGrainCallFilter, IOutgoingGrainCallFilter
{
public Task Invoke(IOutgoingGrainCallContext context)
{
if (Activity.Current != null)
{
return ProcessCurrentActivity(context);
}
return ProcessNewActivity(context, ActivityNameOut, ActivityKind.Client, default);
}
private static Task ProcessCurrentActivity(IOutgoingGrainCallContext context)
{
var currentActivity = Activity.Current;
if (currentActivity is not null && currentActivity.IdFormat == ActivityIdFormat.W3C)
{
RequestContext.Set(TraceParentHeaderName, currentActivity.Id);
if (currentActivity.TraceStateString is not null)
{
RequestContext.Set(TraceStateHeaderName, currentActivity.TraceStateString);
}
}
return context.Invoke();
}
}
public sealed class ActivityPropagationIncomingGrainCallFilter : ActivityPropagationGrainCallFilter, IIncomingGrainCallFilter
{
public Task Invoke(IIncomingGrainCallContext context)
{
var traceParent = RequestContext.Get(TraceParentHeaderName) as string;
var traceState = RequestContext.Get(TraceStateHeaderName) as string;
var parentContext = default(ActivityContext);
if (traceParent is not null)
{
parentContext = ActivityContext.Parse(traceParent, traceState);
}
return ProcessNewActivity(context, ActivityNameIn, ActivityKind.Server, parentContext);
}
}
} }

4
backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs

@ -21,7 +21,7 @@ namespace Squidex.Infrastructure.Orleans
return context.Invoke(); return context.Invoke();
} }
public Task Invoke(IIncomingGrainCallContext context) public async Task Invoke(IIncomingGrainCallContext context)
{ {
if (RequestContext.Get("Culture") is string culture) if (RequestContext.Get("Culture") is string culture)
{ {
@ -33,7 +33,7 @@ namespace Squidex.Infrastructure.Orleans
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(cultureUI); CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(cultureUI);
} }
return context.Invoke(); await context.Invoke();
} }
} }
} }

9
backend/src/Squidex.Infrastructure/Orleans/GrainBase.cs

@ -14,13 +14,14 @@ namespace Squidex.Infrastructure.Orleans
{ {
public abstract class GrainBase : Grain public abstract class GrainBase : Grain
{ {
protected GrainBase() public DomainId Key { get; private set; }
{
}
protected GrainBase(IGrainIdentity? identity, IGrainRuntime? runtime) protected GrainBase(IGrainIdentity identity, IGrainRuntime? runtime = null)
: base(identity, runtime) : base(identity, runtime)
{ {
Guard.NotNull(identity);
Key = DomainId.Create(identity.PrimaryKeyString);
} }
public void ReportIAmAlive() public void ReportIAmAlive()

50
backend/src/Squidex.Infrastructure/Orleans/GrainOfString.cs

@ -1,50 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Orleans;
using Orleans.Core;
using Orleans.Runtime;
namespace Squidex.Infrastructure.Orleans
{
public abstract class GrainOfString : GrainBase
{
public string Key { get; private set; }
protected GrainOfString()
{
}
protected GrainOfString(IGrainIdentity identity, IGrainRuntime runtime)
: base(identity, runtime)
{
}
public sealed override Task OnActivateAsync()
{
return ActivateAsync(this.GetPrimaryKeyString());
}
public async Task ActivateAsync(string key)
{
Key = key;
await OnLoadAsync(key);
await OnActivateAsync(key);
}
protected virtual Task OnLoadAsync(string key)
{
return Task.CompletedTask;
}
protected virtual Task OnActivateAsync(string key)
{
return Task.CompletedTask;
}
}
}

9
backend/src/Squidex.Infrastructure/Orleans/Indexes/UniqueNameGrain.cs

@ -5,12 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Orleans.Core;
namespace Squidex.Infrastructure.Orleans.Indexes namespace Squidex.Infrastructure.Orleans.Indexes
{ {
public class UniqueNameGrain<T> : GrainOfString, IUniqueNameGrain<T> public class UniqueNameGrain<T> : GrainBase, IUniqueNameGrain<T>
{ {
private readonly Dictionary<string, (string Name, T Id)> reservations = new Dictionary<string, (string Name, T Id)>(); private readonly Dictionary<string, (string Name, T Id)> reservations = new Dictionary<string, (string Name, T Id)>();
public UniqueNameGrain(IGrainIdentity identity)
: base(identity)
{
}
public virtual Task<string?> ReserveAsync(T id, string name) public virtual Task<string?> ReserveAsync(T id, string name)
{ {
string? token = null; string? token = null;

34
backend/src/Squidex.Infrastructure/Orleans/J.cs

@ -1,34 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Json;
#pragma warning disable SA1401 // Fields must be private
#pragma warning disable CA2211 // Non-constant fields should not be visible
namespace Squidex.Infrastructure.Orleans
{
public static class J
{
public static IJsonSerializer DefaultSerializer;
public static J<T> AsJ<T>(this T value)
{
return new J<T>(value);
}
public static J<T> Of<T>(T value)
{
return value;
}
public static Task<J<T>> AsTask<T>(T value)
{
return Task.FromResult<J<T>>(value);
}
}
}

46
backend/src/Squidex.Infrastructure/Orleans/JsonSerializer.cs

@ -0,0 +1,46 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Orleans.Serialization;
using Squidex.Infrastructure.Json;
namespace Squidex.Infrastructure.Orleans
{
public sealed class JsonSerializer : IExternalSerializer
{
private readonly IJsonSerializer jsonSerializer;
public JsonSerializer(IJsonSerializer jsonSerializer)
{
this.jsonSerializer = jsonSerializer;
}
public bool IsSupportedType(Type itemType)
{
return itemType.Namespace?.StartsWith("Squidex", StringComparison.OrdinalIgnoreCase) == true;
}
public object DeepCopy(object source, ICopyContext context)
{
return source;
}
public object Deserialize(Type expectedType, IDeserializationContext context)
{
var stream = new StreamReaderWrapper(context.StreamReader);
return jsonSerializer.Deserialize<object>(stream, expectedType);
}
public void Serialize(object item, ISerializationContext context, Type expectedType)
{
var stream = new StreamWriterWrapper(context.StreamWriter);
jsonSerializer.Serialize(item, stream);
}
}
}

82
backend/src/Squidex.Infrastructure/Orleans/J{T}.cs

@ -1,82 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Orleans.CodeGeneration;
using Orleans.Concurrency;
using Orleans.Serialization;
using Squidex.Infrastructure.Json;
#pragma warning disable IDE0060 // Remove unused parameter
namespace Squidex.Infrastructure.Orleans
{
[Immutable]
public readonly struct J<T>
{
public T Value { get; }
public J(T value)
{
Value = value;
}
public static implicit operator T(J<T> value)
{
return value.Value;
}
public static implicit operator J<T>(T d)
{
return new J<T>(d);
}
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
public static Task<J<T>> AsTask(T value)
{
return Task.FromResult<J<T>>(value);
}
[CopierMethod]
public static object? Copy(object? input, ICopyContext? context)
{
return input;
}
[SerializerMethod]
public static void Serialize(object? input, ISerializationContext context, Type? expected)
{
var stream = new StreamWriterWrapper(context.StreamWriter);
GetSerializer(context).Serialize(input, stream);
}
[DeserializerMethod]
public static object? Deserialize(Type expected, IDeserializationContext context)
{
var stream = new StreamReaderWrapper(context.StreamReader);
return GetSerializer(context).Deserialize<object>(stream, expected);
}
private static IJsonSerializer GetSerializer(ISerializerContext context)
{
try
{
return context?.ServiceProvider?.GetRequiredService<IJsonSerializer>() ?? J.DefaultSerializer;
}
catch
{
return J.DefaultSerializer;
}
}
}
}

14
backend/src/Squidex.Infrastructure/States/BatchContext.cs

@ -16,24 +16,24 @@ namespace Squidex.Infrastructure.States
private static readonly List<Envelope<IEvent>> EmptyStream = new List<Envelope<IEvent>>(); private static readonly List<Envelope<IEvent>> EmptyStream = new List<Envelope<IEvent>>();
private readonly Type owner; private readonly Type owner;
private readonly ISnapshotStore<T> snapshotStore; private readonly ISnapshotStore<T> snapshotStore;
private readonly IEventFormatter eventDataFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventStreamNames eventStreams;
private readonly IStreamNameResolver streamNameResolver;
private readonly Dictionary<DomainId, (long, List<Envelope<IEvent>>)> @events = new Dictionary<DomainId, (long, List<Envelope<IEvent>>)>(); private readonly Dictionary<DomainId, (long, List<Envelope<IEvent>>)> @events = new Dictionary<DomainId, (long, List<Envelope<IEvent>>)>();
private Dictionary<DomainId, SnapshotWriteJob<T>>? snapshots; private Dictionary<DomainId, SnapshotWriteJob<T>>? snapshots;
internal BatchContext( internal BatchContext(
Type owner, Type owner,
ISnapshotStore<T> snapshotStore, IEventFormatter eventDataFormatter,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventStreamNames streamNameResolver,
IStreamNameResolver streamNameResolver) ISnapshotStore<T> snapshotStore)
{ {
this.owner = owner; this.owner = owner;
this.snapshotStore = snapshotStore; this.snapshotStore = snapshotStore;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.streamNameResolver = streamNameResolver; this.eventStreams = streamNameResolver;
} }
internal void Add(DomainId key, T snapshot, long version) internal void Add(DomainId key, T snapshot, long version)
@ -48,7 +48,7 @@ namespace Squidex.Infrastructure.States
public async Task LoadAsync(IEnumerable<DomainId> ids) public async Task LoadAsync(IEnumerable<DomainId> ids)
{ {
var streamNames = ids.ToDictionary(x => x, x => streamNameResolver.GetStreamName(owner, x.ToString())); var streamNames = ids.ToDictionary(x => x, x => eventStreams.GetStreamName(owner, x.ToString()));
if (streamNames.Count == 0) if (streamNames.Count == 0)
{ {

2
backend/src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs → backend/src/Squidex.Infrastructure/States/DefaultEventStreamNames.cs

@ -9,7 +9,7 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public sealed class DefaultStreamNameResolver : IStreamNameResolver public sealed class DefaultEventStreamNames : IEventStreamNames
{ {
private static readonly string[] Suffixes = { "Grain", "DomainObject", "State" }; private static readonly string[] Suffixes = { "Grain", "DomainObject", "State" };

2
backend/src/Squidex.Infrastructure/States/IStreamNameResolver.cs → backend/src/Squidex.Infrastructure/States/IEventStreamNames.cs

@ -7,7 +7,7 @@
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public interface IStreamNameResolver public interface IEventStreamNames
{ {
string GetStreamName(Type aggregateType, string id); string GetStreamName(Type aggregateType, string id);
} }

36
backend/src/Squidex.Infrastructure/States/Persistence.cs

@ -14,13 +14,13 @@ namespace Squidex.Infrastructure.States
internal sealed class Persistence<T> : IPersistence<T> internal sealed class Persistence<T> : IPersistence<T>
{ {
private readonly DomainId ownerKey; private readonly DomainId ownerKey;
private readonly ISnapshotStore<T> snapshotStore;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly PersistenceMode persistenceMode;
private readonly HandleSnapshot<T>? applyState;
private readonly HandleEvent? applyEvent; private readonly HandleEvent? applyEvent;
private readonly HandleSnapshot<T>? applyState;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly ISnapshotStore<T> snapshotStore;
private readonly Lazy<string> streamName; private readonly Lazy<string> streamName;
private readonly PersistenceMode persistenceMode;
private long versionSnapshot = EtagVersion.Empty; private long versionSnapshot = EtagVersion.Empty;
private long versionEvents = EtagVersion.Empty; private long versionEvents = EtagVersion.Empty;
private long version = EtagVersion.Empty; private long version = EtagVersion.Empty;
@ -46,23 +46,23 @@ namespace Squidex.Infrastructure.States
} }
public Persistence(DomainId ownerKey, Type ownerType, public Persistence(DomainId ownerKey, Type ownerType,
ISnapshotStore<T> snapshotStore,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IStreamNameResolver streamNameResolver,
PersistenceMode persistenceMode, PersistenceMode persistenceMode,
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventStreamNames eventStreams,
ISnapshotStore<T> snapshotStore,
HandleSnapshot<T>? applyState, HandleSnapshot<T>? applyState,
HandleEvent? applyEvent) HandleEvent? applyEvent)
{ {
this.ownerKey = ownerKey;
this.applyState = applyState;
this.applyEvent = applyEvent; this.applyEvent = applyEvent;
this.applyState = applyState;
this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.ownerKey = ownerKey;
this.persistenceMode = persistenceMode; this.persistenceMode = persistenceMode;
this.snapshotStore = snapshotStore; this.snapshotStore = snapshotStore;
streamName = new Lazy<string>(() => streamNameResolver.GetStreamName(ownerType, ownerKey.ToString()!)); streamName = new Lazy<string>(() => eventStreams.GetStreamName(ownerType, ownerKey.ToString()!));
} }
public async Task DeleteAsync( public async Task DeleteAsync(
@ -74,6 +74,8 @@ namespace Squidex.Infrastructure.States
{ {
await snapshotStore.RemoveAsync(ownerKey, ct); await snapshotStore.RemoveAsync(ownerKey, ct);
} }
versionSnapshot = EtagVersion.Empty;
} }
if (UseEventSourcing) if (UseEventSourcing)
@ -82,7 +84,11 @@ namespace Squidex.Infrastructure.States
{ {
await eventStore.DeleteStreamAsync(streamName.Value, ct); await eventStore.DeleteStreamAsync(streamName.Value, ct);
} }
versionEvents = EtagVersion.Empty;
} }
UpdateVersion();
} }
public async Task ReadAsync(long expectedVersion = EtagVersion.Any, public async Task ReadAsync(long expectedVersion = EtagVersion.Any,
@ -153,7 +159,7 @@ namespace Squidex.Infrastructure.States
if (!isStopped) if (!isStopped)
{ {
var parsedEvent = eventDataFormatter.ParseIfKnown(@event); var parsedEvent = eventFormatter.ParseIfKnown(@event);
if (applyEvent != null && parsedEvent != null) if (applyEvent != null && parsedEvent != null)
{ {
@ -210,7 +216,7 @@ namespace Squidex.Infrastructure.States
} }
var eventCommitId = Guid.NewGuid(); var eventCommitId = Guid.NewGuid();
var eventData = events.Select(x => eventDataFormatter.ToEventData(x, eventCommitId, true)).ToArray(); var eventData = events.Select(x => eventFormatter.ToEventData(x, eventCommitId, true)).ToArray();
try try
{ {

32
backend/src/Squidex.Infrastructure/States/Store.cs

@ -11,21 +11,21 @@ namespace Squidex.Infrastructure.States
{ {
public sealed class Store<T> : IStore<T> public sealed class Store<T> : IStore<T>
{ {
private readonly IStreamNameResolver streamNameResolver; private readonly IEventFormatter eventFormatter;
private readonly ISnapshotStore<T> snapshotStore;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventStreamNames eventStreamNames;
private readonly ISnapshotStore<T> snapshotStore;
public Store( public Store(
ISnapshotStore<T> snapshotStore, IEventFormatter eventFormatter,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventStreamNames eventStreamNames,
IStreamNameResolver streamNameResolver) ISnapshotStore<T> snapshotStore)
{ {
this.snapshotStore = snapshotStore; this.eventFormatter = eventFormatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventStreamNames = eventStreamNames;
this.streamNameResolver = streamNameResolver; this.snapshotStore = snapshotStore;
} }
public Task ClearSnapshotsAsync() public Task ClearSnapshotsAsync()
@ -36,10 +36,10 @@ namespace Squidex.Infrastructure.States
public IBatchContext<T> WithBatchContext(Type owner) public IBatchContext<T> WithBatchContext(Type owner)
{ {
return new BatchContext<T>(owner, return new BatchContext<T>(owner,
snapshotStore, eventFormatter,
eventStore, eventStore,
eventDataFormatter, eventStreamNames,
streamNameResolver); snapshotStore);
} }
public IPersistence<T> WithEventSourcing(Type owner, DomainId key, HandleEvent? applyEvent) public IPersistence<T> WithEventSourcing(Type owner, DomainId key, HandleEvent? applyEvent)
@ -62,11 +62,11 @@ namespace Squidex.Infrastructure.States
Guard.NotNull(key); Guard.NotNull(key);
return new Persistence<T>(key, owner, return new Persistence<T>(key, owner,
snapshotStore,
eventStore,
eventDataFormatter,
streamNameResolver,
mode, mode,
eventFormatter,
eventStore,
eventStreamNames,
snapshotStore,
applySnapshot, applySnapshot,
applyEvent); applyEvent);
} }

17
backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs

@ -8,35 +8,34 @@
using System.Diagnostics; using System.Diagnostics;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Log; using Squidex.Log;
namespace Squidex.Web.Pipeline namespace Squidex.Web.Pipeline
{ {
public sealed class ActionContextLogAppender : ILogAppender public sealed class ActionContextLogAppender : ILogAppender
{ {
private readonly IServiceProvider services; private readonly IHttpContextAccessor httpContextAccessor;
private readonly IActionContextAccessor actionContextAccessor;
public ActionContextLogAppender(IServiceProvider services) public ActionContextLogAppender(IHttpContextAccessor httpContextAccessor, IActionContextAccessor actionContextAccessor)
{ {
this.services = services; this.httpContextAccessor = httpContextAccessor;
this.actionContextAccessor = actionContextAccessor;
} }
public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception) public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{ {
var httpContextAccessor = services.GetService<IHttpContextAccessor>(); var httpContext = httpContextAccessor.HttpContext;
if (string.IsNullOrEmpty(httpContextAccessor?.HttpContext?.Request?.Method)) if (string.IsNullOrEmpty(httpContext?.Request?.Method))
{ {
return; return;
} }
var actionContext = services.GetRequiredService<IActionContextAccessor>()?.ActionContext; var actionContext = actionContextAccessor.ActionContext;
try try
{ {
var httpContext = httpContextAccessor.HttpContext;
if (string.IsNullOrEmpty(httpContext?.Request?.Method)) if (string.IsNullOrEmpty(httpContext?.Request?.Method))
{ {
return; return;

4
backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs

@ -21,11 +21,9 @@ namespace Squidex.Areas.IdentityServer.Config
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private CompletionTimer timer; private CompletionTimer timer;
public TokenStoreInitializer(IOptions<OpenIddictMongoDbOptions> options, public TokenStoreInitializer(IOptions<OpenIddictMongoDbOptions> options, IServiceProvider serviceProvider)
IServiceProvider serviceProvider)
{ {
this.options = options.Value; this.options = options.Value;
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
} }

4
backend/src/Squidex/Config/Domain/AppsServices.cs

@ -10,7 +10,6 @@ using Squidex.Areas.Api.Controllers.UI;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
@ -28,9 +27,6 @@ namespace Squidex.Config.Domain
.As<IEventConsumer>(); .As<IEventConsumer>();
} }
services.AddTransientAs<AppDomainObject>()
.AsSelf();
services.AddSingletonAs<RolePermissionsProvider>() services.AddSingletonAs<RolePermissionsProvider>()
.AsSelf(); .AsSelf();

7
backend/src/Squidex/Config/Domain/AssetServices.cs

@ -10,7 +10,6 @@ using MongoDB.Driver.GridFS;
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Entities.Assets.Queries; using Squidex.Domain.Apps.Entities.Assets.Queries;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
@ -40,12 +39,6 @@ namespace Squidex.Config.Domain
.As<IEventConsumer>(); .As<IEventConsumer>();
} }
services.AddTransientAs<AssetDomainObject>()
.AsSelf();
services.AddTransientAs<AssetFolderDomainObject>()
.AsSelf();
services.AddSingletonAs<AssetQueryParser>() services.AddSingletonAs<AssetQueryParser>()
.AsSelf(); .AsSelf();

3
backend/src/Squidex/Config/Domain/CommandsServices.cs

@ -117,6 +117,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<UsageTrackerCommandMiddleware>() services.AddSingletonAs<UsageTrackerCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<DefaultDomainObjectFactory>()
.As<IDomainObjectFactory>();
} }
} }
} }

4
backend/src/Squidex/Config/Domain/ContentsServices.cs

@ -10,7 +10,6 @@ using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Counter; using Squidex.Domain.Apps.Entities.Contents.Counter;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Domain.Apps.Entities.Contents.Queries; using Squidex.Domain.Apps.Entities.Contents.Queries;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text;
@ -38,9 +37,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentQueryParser>() services.AddSingletonAs<ContentQueryParser>()
.AsSelf(); .AsSelf();
services.AddTransientAs<ContentDomainObject>()
.AsSelf();
services.AddTransientAs<CounterDeleter>() services.AddTransientAs<CounterDeleter>()
.As<IDeleter>(); .As<IDeleter>();

17
backend/src/Squidex/Config/Domain/EventSourcingServices.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using EventStore.Client; using EventStore.Client;
using MongoDB.Driver;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
@ -56,18 +55,14 @@ namespace Squidex.Config.Domain
services.AddTransientAs<Rebuilder>() services.AddTransientAs<Rebuilder>()
.AsSelf(); .AsSelf();
services.AddSingletonAs<DefaultStreamNameResolver>() services.AddSingletonAs<DefaultEventStreamNames>()
.As<IStreamNameResolver>(); .As<IEventStreamNames>();
services.AddSingletonAs<DefaultEventDataFormatter>() services.AddSingletonAs<DefaultEventFormatter>()
.As<IEventDataFormatter>(); .As<IEventFormatter>();
services.AddSingletonAs(c => services.AddSingletonAs<DefaultEventConsumerFactory>()
{ .As<IEventConsumerFactory>();
var allEventConsumers = c.GetServices<IEventConsumer>();
return new EventConsumerFactory(n => allEventConsumers.First(x => x.Name == n));
});
} }
} }
} }

4
backend/src/Squidex/Config/Domain/RuleServices.cs

@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Comments; using Squidex.Domain.Apps.Entities.Comments;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.DomainObject;
using Squidex.Domain.Apps.Entities.Rules.Queries; using Squidex.Domain.Apps.Entities.Rules.Queries;
using Squidex.Domain.Apps.Entities.Rules.Runner; using Squidex.Domain.Apps.Entities.Rules.Runner;
using Squidex.Domain.Apps.Entities.Rules.UsageTracking; using Squidex.Domain.Apps.Entities.Rules.UsageTracking;
@ -32,9 +31,6 @@ namespace Squidex.Config.Domain
services.Configure<RuleOptions>(config, services.Configure<RuleOptions>(config,
"rules"); "rules");
services.AddTransientAs<RuleDomainObject>()
.AsSelf();
services.AddSingletonAs<EventEnricher>() services.AddSingletonAs<EventEnricher>()
.As<IEventEnricher>(); .As<IEventEnricher>();

6
backend/src/Squidex/Config/Domain/SchemasServices.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.DomainObject;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
namespace Squidex.Config.Domain namespace Squidex.Config.Domain
@ -16,9 +15,6 @@ namespace Squidex.Config.Domain
{ {
public static void AddSquidexSchemas(this IServiceCollection services) public static void AddSquidexSchemas(this IServiceCollection services)
{ {
services.AddTransientAs<SchemaDomainObject>()
.AsSelf();
services.AddTransientAs<SchemasSearchSource>() services.AddTransientAs<SchemasSearchSource>()
.As<ISearchSource>(); .As<ISearchSource>();

18
backend/src/Squidex/Config/Orleans/OrleansServices.cs

@ -13,11 +13,11 @@ using Orleans.Configuration;
using Orleans.Hosting; using Orleans.Hosting;
using Orleans.Providers.MongoDB.Configuration; using Orleans.Providers.MongoDB.Configuration;
using Orleans.Providers.MongoDB.Utils; using Orleans.Providers.MongoDB.Utils;
using Orleans.Runtime;
using OrleansDashboard; using OrleansDashboard;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Hosting.Configuration; using Squidex.Hosting.Configuration;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Web; using Squidex.Web;
@ -42,10 +42,7 @@ namespace Squidex.Config.Orleans
services.AddScopedAs<ActivationLimit>() services.AddScopedAs<ActivationLimit>()
.As<IActivationLimit>(); .As<IActivationLimit>();
services.AddInitializer<IJsonSerializer>("Serializer (Orleans)", serializer => services.AddScoped(x => x.GetRequiredService<IGrainActivationContext>().GrainIdentity);
{
J.DefaultSerializer = serializer;
}, -1);
}); });
builder.ConfigureApplicationParts(parts => builder.ConfigureApplicationParts(parts =>
@ -54,6 +51,11 @@ namespace Squidex.Config.Orleans
parts.AddApplicationPart(SquidexInfrastructure.Assembly); parts.AddApplicationPart(SquidexInfrastructure.Assembly);
}); });
builder.Configure<SerializationProviderOptions>(options =>
{
options.SerializationProviders.Add(typeof(JsonSerializer));
});
builder.Configure<SchedulingOptions>(options => builder.Configure<SchedulingOptions>(options =>
{ {
options.TurnWarningLengthThreshold = TimeSpan.FromSeconds(5); options.TurnWarningLengthThreshold = TimeSpan.FromSeconds(5);
@ -75,10 +77,12 @@ namespace Squidex.Config.Orleans
options.HostSelf = false; options.HostSelf = false;
}); });
builder.AddOutgoingGrainCallFilter<ActivityPropagationOutgoingGrainCallFilter>(); builder.AddOutgoingGrainCallFilter<ActivityPropagationFilter>();
builder.AddOutgoingGrainCallFilter<CultureFilter>();
builder.AddIncomingGrainCallFilter<ExceptionWrapperFilter>(); builder.AddIncomingGrainCallFilter<ExceptionWrapperFilter>();
builder.AddIncomingGrainCallFilter<ActivityPropagationIncomingGrainCallFilter>(); builder.AddIncomingGrainCallFilter<ActivityPropagationFilter>();
builder.AddIncomingGrainCallFilter<ActivationLimiterFilter>(); builder.AddIncomingGrainCallFilter<ActivationLimiterFilter>();
builder.AddIncomingGrainCallFilter<CultureFilter>();
builder.AddIncomingGrainCallFilter<LocalCacheFilter>(); builder.AddIncomingGrainCallFilter<LocalCacheFilter>();
builder.AddIncomingGrainCallFilter<LoggingFilter>(); builder.AddIncomingGrainCallFilter<LoggingFilter>();
builder.AddIncomingGrainCallFilter<StateFilter>(); builder.AddIncomingGrainCallFilter<StateFilter>();

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs

@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Xunit; using Xunit;
@ -117,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var grain = A.Fake<IAppGrain>(); var grain = A.Fake<IAppGrain>();
A.CallTo(() => grain.GetStateAsync()) A.CallTo(() => grain.GetStateAsync())
.Returns(app.AsJ()); .Returns(app);
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(app.Id.ToString(), null)) A.CallTo(() => grainFactory.GetGrain<IAppGrain>(app.Id.ToString(), null))
.Returns(grain); .Returns(grain);

44
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using FakeItEasy; using FakeItEasy;
using Orleans.Core;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Xunit; using Xunit;
@ -14,50 +15,51 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
public sealed class AppUISettingsGrainTests public sealed class AppUISettingsGrainTests
{ {
private readonly IGrainState<AppUISettingsGrain.State> grainState = A.Fake<IGrainState<AppUISettingsGrain.State>>(); private readonly IGrainIdentity identity = A.Fake<IGrainIdentity>();
private readonly IGrainState<AppUISettingsGrain.State> state = A.Fake<IGrainState<AppUISettingsGrain.State>>();
private readonly AppUISettingsGrain sut; private readonly AppUISettingsGrain sut;
public AppUISettingsGrainTests() public AppUISettingsGrainTests()
{ {
sut = new AppUISettingsGrain(grainState); sut = new AppUISettingsGrain(identity, state);
} }
[Fact] [Fact]
public async Task Should_set_setting() public async Task Should_set_setting()
{ {
await sut.SetAsync(new JsonObject().Add("key", 15).AsJ()); await sut.SetAsync(new JsonObject().Add("key", 15));
var actual = await sut.GetAsync(); var actual = await sut.GetAsync();
var expected = var expected =
new JsonObject().Add("key", 15); new JsonObject().Add("key", 15);
Assert.Equal(expected.ToString(), actual.Value.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_set_root_value() public async Task Should_set_root_value()
{ {
await sut.SetAsync("key", JsonValue.Create(123).AsJ()); await sut.SetAsync("key", JsonValue.Create(123));
var actual = await sut.GetAsync(); var actual = await sut.GetAsync();
var expected = var expected =
new JsonObject().Add("key", 123); new JsonObject().Add("key", 123);
Assert.Equal(expected.ToString(), actual.Value.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_remove_root_value() public async Task Should_remove_root_value()
{ {
await sut.SetAsync("key", JsonValue.Create(123).AsJ()); await sut.SetAsync("key", JsonValue.Create(123));
await sut.RemoveAsync("key"); await sut.RemoveAsync("key");
@ -65,16 +67,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
var expected = new JsonObject(); var expected = new JsonObject();
Assert.Equal(expected.ToString(), actual.Value.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
[Fact] [Fact]
public async Task Should_set_nested_value() public async Task Should_set_nested_value()
{ {
await sut.SetAsync("root.nested", JsonValue.Create(123).AsJ()); await sut.SetAsync("root.nested", JsonValue.Create(123));
var actual = await sut.GetAsync(); var actual = await sut.GetAsync();
@ -82,16 +84,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
new JsonObject().Add("root", new JsonObject().Add("root",
new JsonObject().Add("nested", 123)); new JsonObject().Add("nested", 123));
Assert.Equal(expected.ToString(), actual.Value.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_remove_nested_value() public async Task Should_remove_nested_value()
{ {
await sut.SetAsync("root.nested", JsonValue.Create(123).AsJ()); await sut.SetAsync("root.nested", JsonValue.Create(123));
await sut.RemoveAsync("root.nested"); await sut.RemoveAsync("root.nested");
@ -101,18 +103,18 @@ namespace Squidex.Domain.Apps.Entities.Apps
new JsonObject().Add("root", new JsonObject().Add("root",
new JsonObject()); new JsonObject());
Assert.Equal(expected.ToString(), actual.Value.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
[Fact] [Fact]
public async Task Should_throw_exception_if_nested_not_an_object() public async Task Should_throw_exception_if_nested_not_an_object()
{ {
await sut.SetAsync("root.nested", JsonValue.Create(123).AsJ()); await sut.SetAsync("root.nested", JsonValue.Create(123));
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.SetAsync("root.nested.value", JsonValue.Create(123).AsJ())); await Assert.ThrowsAsync<InvalidOperationException>(() => sut.SetAsync("root.nested.value", JsonValue.Create(123)));
} }
[Fact] [Fact]
@ -120,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
await sut.RemoveAsync("root.nested"); await sut.RemoveAsync("root.nested");
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -129,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
await sut.RemoveAsync("root"); await sut.RemoveAsync("root");
A.CallTo(() => grainState.WriteAsync()) A.CallTo(() => state.WriteAsync())
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
} }

7
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs

@ -10,7 +10,6 @@ using Orleans;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
using Xunit; using Xunit;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var settings = new JsonObject(); var settings = new JsonObject();
A.CallTo(() => grain.GetAsync()) A.CallTo(() => grain.GetAsync())
.Returns(settings.AsJ()); .Returns(settings);
var result = await sut.GetAsync(DomainId.NewGuid(), "user"); var result = await sut.GetAsync(DomainId.NewGuid(), "user");
@ -49,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
await sut.SetAsync(DomainId.NewGuid(), "user", "the.path", value); await sut.SetAsync(DomainId.NewGuid(), "user", "the.path", value);
A.CallTo(() => grain.SetAsync("the.path", A<J<JsonValue>>.That.Matches(x => x.Value == value))) A.CallTo(() => grain.SetAsync("the.path", value))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -60,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
await sut.SetAsync(DomainId.NewGuid(), "user", value); await sut.SetAsync(DomainId.NewGuid(), "user", value);
A.CallTo(() => grain.SetAsync(A<J<JsonObject>>.That.Matches(x => x.Value == value))) A.CallTo(() => grain.SetAsync(value))
.MustHaveHappened(); .MustHaveHappened();
} }

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs

@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Xunit; using Xunit;
@ -90,7 +89,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
var grain = A.Fake<IAppGrain>(); var grain = A.Fake<IAppGrain>();
A.CallTo(() => grain.ExecuteAsync(A<J<CommandRequest>>._)) A.CallTo(() => grain.ExecuteAsync(A<IAggregateCommand>._))
.Returns(new CommandResult(command.AggregateId, 1, 0, result)); .Returns(new CommandResult(command.AggregateId, 1, 0, result));
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(command.AggregateId.ToString(), null)) A.CallTo(() => grainFactory.GetGrain<IAppGrain>(command.AggregateId.ToString(), null))

16
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -67,15 +68,18 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
} }
}; };
var serviceProvider =
new ServiceCollection()
.AddSingleton(initialSettings)
.AddSingleton(appPlansProvider)
.AddSingleton(appPlansBillingManager)
.AddSingleton(userResolver)
.BuildServiceProvider();
var log = A.Fake<ILogger<AppDomainObject>>(); var log = A.Fake<ILogger<AppDomainObject>>();
sut = new AppDomainObject(PersistenceFactory, log,
initialSettings,
appPlansProvider,
appPlansBillingManager,
userResolver);
#pragma warning disable MA0056 // Do not call overridable members in constructor #pragma warning disable MA0056 // Do not call overridable members in constructor
sut.Setup(Id); sut = new AppDomainObject(Id, PersistenceFactory, log, serviceProvider);
#pragma warning restore MA0056 // Do not call overridable members in constructor #pragma warning restore MA0056 // Do not call overridable members in constructor
} }

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsCacheGrainTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using FakeItEasy; using FakeItEasy;
using Orleans.Core;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -15,14 +16,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{ {
public class AppsCacheGrainTests public class AppsCacheGrainTests
{ {
private readonly IGrainIdentity identity = A.Fake<IGrainIdentity>();
private readonly IAppRepository appRepository = A.Fake<IAppRepository>(); private readonly IAppRepository appRepository = A.Fake<IAppRepository>();
private readonly DomainId appId = DomainId.NewGuid(); private readonly DomainId appId = DomainId.NewGuid();
private readonly AppsCacheGrain sut; private readonly AppsCacheGrain sut;
public AppsCacheGrainTests() public AppsCacheGrainTests()
{ {
sut = new AppsCacheGrain(appRepository); A.CallTo(() => identity.PrimaryKeyString)
sut.ActivateAsync(appId.ToString()).Wait(); .Returns(appId.ToString());
sut = new AppsCacheGrain(identity, appRepository);
} }
[Fact] [Fact]

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs

@ -358,7 +358,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
var appGrain = A.Fake<IAppGrain>(); var appGrain = A.Fake<IAppGrain>();
A.CallTo(() => appGrain.GetStateAsync()) A.CallTo(() => appGrain.GetStateAsync())
.Returns(J.Of(app)); .Returns(app);
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId.Id.ToString(), null)) A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId.Id.ToString(), null))
.Returns(appGrain); .Returns(appGrain);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save