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;
}
}
}
}

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

@ -7,7 +7,6 @@
using MongoDB.Driver;
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
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();
// If the app does not exist, the version is lower than zero.
if (app.Value.Version < 0)
if (app.Version < 0)
{
return;
}
@ -102,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
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 Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
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 result.Value;
return GetGrain(appId, userId).GetAsync();
}
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)
{
return GetGrain(appId, userId).SetAsync(path, value.AsJ());
return GetGrain(appId, userId).SetAsync(path, value);
}
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)

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
@ -12,7 +13,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class AppUISettingsGrain : GrainOfString, IAppUISettingsGrain
public sealed class AppUISettingsGrain : GrainBase, IAppUISettingsGrain
{
private readonly IGrainState<State> state;
@ -22,14 +23,15 @@ namespace Squidex.Domain.Apps.Entities.Apps
public JsonObject Settings { get; set; } = new JsonObject();
}
public AppUISettingsGrain(IGrainState<State> state)
public AppUISettingsGrain(IGrainIdentity identity, IGrainState<State> state)
: base(identity)
{
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()
@ -39,14 +41,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
return state.ClearAsync();
}
public Task SetAsync(J<JsonObject> settings)
public Task SetAsync(JsonObject settings)
{
state.Value.Settings = settings;
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);
@ -56,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return Task.CompletedTask;
}
container[key] = value.Value;
container[key] = value;
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)
{
this.appsIndex = appsIndex;
this.rebuilder = rebuilder;
this.appImageStore = appImageStore;
this.appUISettings = appUISettings;
this.rebuilder = rebuilder;
}
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.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Core.Apps;
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>
{
private readonly InitialSettings initialSettings;
private readonly IAppPlansProvider appPlansProvider;
private readonly IAppPlanBillingManager appPlansBillingManager;
private readonly IUserResolver userResolver;
private readonly IServiceProvider serviceProvider;
public AppDomainObject(IPersistenceFactory<State> persistence, ILogger<AppDomainObject> log,
InitialSettings initialSettings,
IAppPlansProvider appPlansProvider,
IAppPlanBillingManager appPlansBillingManager,
IUserResolver userResolver)
: base(persistence, log)
public AppDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<AppDomainObject> log,
IServiceProvider serviceProvider)
: base(id, persistence, log)
{
this.userResolver = userResolver;
this.appPlansProvider = appPlansProvider;
this.appPlansBillingManager = appPlansBillingManager;
this.initialSettings = initialSettings;
this.serviceProvider = serviceProvider;
}
protected override bool IsDeleted(State snapshot)
@ -125,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case AssignContributor assignContributor:
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));
@ -265,7 +257,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case ChangePlan changePlan:
return UpdateReturnAsync(changePlan, async c =>
{
GuardApp.CanChangePlan(c, Snapshot, appPlansProvider);
GuardApp.CanChangePlan(c, Snapshot, AppPlansProvider());
if (c.FromCallback)
{
@ -275,9 +267,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
}
else
{
var result =
await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier,
Snapshot.NamedId(), c.PlanId, c.Referer);
var result = await AppPlanBillingManager().ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), c.PlanId, c.Referer);
switch (result)
{
@ -293,7 +283,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
case DeleteApp delete:
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);
});
@ -304,11 +294,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
}
}
private IAppLimitsPlan GetPlan()
{
return appPlansProvider.GetPlanForApp(Snapshot).Plan;
}
private void Create(CreateApp command)
{
var appId = NamedId.Of(command.AppId, command.Name);
@ -335,7 +320,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
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());
}
@ -466,7 +451,32 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{
public sealed class AppDomainObjectGrain : DomainObjectGrain<AppDomainObject, AppDomainObject.State>, IAppGrain
{
public AppDomainObjectGrain(IServiceProvider serviceProvider)
: base(serviceProvider)
public AppDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(identity, factory)
{
}
public async Task<J<IAppEntity>> GetStateAsync()
public async Task<IAppEntity> GetStateAsync()
{
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.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{
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 Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps
{
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);

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

@ -6,6 +6,7 @@
// ==========================================================================
using Orleans.Concurrency;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans.Indexes;
@ -18,7 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
private readonly IAppRepository appRepository;
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;
}

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)
{
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))
{

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

@ -6,6 +6,7 @@
// ==========================================================================
using NodaTime;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Notifications;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
@ -15,10 +16,10 @@ using Squidex.Shared.Users;
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 readonly IGrainState<State> state;
private readonly IGrainState<State> grainState;
private readonly INotificationSender notificationSender;
private readonly IUserResolver userResolver;
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 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.userResolver = userResolver;
this.clock = clock;
@ -70,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
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;
@ -82,9 +85,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
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;
public AssetDomainObject(IPersistenceFactory<State> factory, ILogger<AssetDomainObject> log,
public AssetDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<AssetDomainObject> log,
IServiceProvider serviceProvider)
: base(factory, log)
: base(id, persistence, log)
{
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -15,20 +16,21 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5);
public AssetDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit)
: base(serviceProvider)
public AssetDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory,
IActivationLimit limit)
: base(identity, factory)
{
limit?.SetLimit(5000, Lifetime);
}
protected override Task OnActivateAsync(string key)
public override Task OnActivateAsync()
{
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();

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;
public AssetFolderDomainObject(IPersistenceFactory<State> factory, ILogger<AssetFolderDomainObject> log,
public AssetFolderDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<AssetFolderDomainObject> log,
IServiceProvider serviceProvider)
: base(factory, log)
: base(id, persistence, log)
{
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -14,20 +15,21 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5);
public AssetFolderDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit)
: base(serviceProvider)
public AssetFolderDomainObjectGrain(IGrainIdentity grainIdentity, IDomainObjectFactory factory,
IActivationLimit limit)
: base(grainIdentity, factory)
{
limit?.SetLimit(5000, Lifetime);
}
protected override Task OnActivateAsync(string key)
public override Task OnActivateAsync()
{
TryDelayDeactivation(Lifetime);
return base.OnActivateAsync(key);
return base.OnActivateAsync();
}
public async Task<J<IAssetFolderEntity>> GetStateAsync()
public async Task<IAssetFolderEntity> GetStateAsync()
{
await DomainObject.EnsureLoadedAsync();

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

@ -7,12 +7,11 @@
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{
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 assetState = await assetGrain.GetStateAsync(version);
var asset = assetState.Value;
if (asset == null || asset.Version <= EtagVersion.Empty || (version > EtagVersion.Any && asset.Version != version))
if (assetState == null || assetState.Version <= EtagVersion.Empty || (version > EtagVersion.Any && assetState.Version != version))
{
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 readonly IAssetFileStore assetFileStore;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
public RebuildFiles(
IAssetFileStore assetFileStore,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter)
IEventFormatter eventFormatter,
IEventStore eventStore)
{
this.assetFileStore = assetFileStore;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.eventFormatter = eventFormatter;
}
public async Task RepairAsync(
@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
await foreach (var storedEvent in eventStore.QueryAllAsync("^asset\\-", ct: ct))
{
var @event = eventDataFormatter.ParseIfKnown(storedEvent);
var @event = eventFormatter.ParseIfKnown(storedEvent);
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,
IAssetFileStore assetFileStore,
IAssetThumbnailGenerator thumbnailGenerator,
IAssetThumbnailGenerator assetThumbnails,
CancellationToken ct = default)
{
using (var stream = DefaultPools.MemoryStream.GetStream())
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NodaTime;
using Orleans.Concurrency;
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Backup.State;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
@ -22,46 +22,47 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup
{
[Reentrant]
public sealed class BackupGrain : GrainOfString, IBackupGrain
public sealed class BackupGrain : GrainBase, IBackupGrain
{
private const int MaxBackups = 10;
private static readonly Duration UpdateDuration = Duration.FromSeconds(1);
private readonly IGrainState<BackupState> state;
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IBackupArchiveStore backupArchiveStore;
private readonly IBackupHandlerFactory backupHandlers;
private readonly IClock clock;
private readonly IServiceProvider serviceProvider;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly ILogger<BackupGrain> log;
private readonly IGrainState<BackupState> state;
private readonly IUserResolver userResolver;
private readonly ILogger<BackupGrain> log;
private CancellationTokenSource? currentJobToken;
private BackupJob? currentJob;
public BackupGrain(
public BackupGrain(IGrainIdentity identity,
IBackupArchiveLocation backupArchiveLocation,
IBackupArchiveStore backupArchiveStore,
IBackupHandlerFactory backupHandlers,
IClock clock,
IEventDataFormatter eventDataFormatter,
IEventFormatter eventFormatter,
IEventStore eventStore,
IGrainState<BackupState> state,
IServiceProvider serviceProvider,
IUserResolver userResolver,
ILogger<BackupGrain> log)
: base(identity)
{
this.backupArchiveLocation = backupArchiveLocation;
this.backupArchiveStore = backupArchiveStore;
this.backupHandlers = backupHandlers;
this.clock = clock;
this.eventDataFormatter = eventDataFormatter;
this.eventFormatter = eventFormatter;
this.eventStore = eventStore;
this.serviceProvider = serviceProvider;
this.state = state;
this.userResolver = userResolver;
this.log = log;
}
protected override Task OnActivateAsync(string key)
public override Task OnActivateAsync()
{
RecoverAfterRestartAsync().Forget();
@ -127,14 +128,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ProcessAsync(BackupJob job, RefToken actor,
CancellationToken ct)
{
var handlers = CreateHandlers();
var handlers = backupHandlers.CreateMany();
var lastTimestamp = job.Started;
try
{
var appId = DomainId.Create(Key);
await using (var stream = backupArchiveLocation.OpenStream(job.Id))
{
using (var writer = await backupArchiveLocation.OpenWriterAsync(stream))
@ -143,11 +142,11 @@ namespace Squidex.Domain.Apps.Entities.Backup
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))
{
var @event = eventDataFormatter.Parse(storedEvent);
var @event = eventFormatter.Parse(storedEvent);
if (@event.Payload is SquidexEvent squidexEvent && squidexEvent.Actor != null)
{
@ -217,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private string GetFilter()
{
return $"^[^\\-]*-{Regex.Escape(Key)}";
return $"^[^\\-]*-{Regex.Escape(Key.ToString())}";
}
private async Task<Instant> WritePeriodically(Instant lastTimestamp)
@ -278,14 +277,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
await state.WriteAsync();
}
private IEnumerable<IBackupHandler> CreateHandlers()
{
return serviceProvider.GetRequiredService<IEnumerable<IBackupHandler>>();
}
public Task<J<List<IBackupJob>>> GetStateAsync()
public Task<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;
}
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)
{
Guard.NotNull(formatter);
Guard.NotNull(streamNameResolver);
Guard.NotNull(eventFormatter);
Guard.NotNull(eventStreams);
while (!ct.IsCancellationRequested)
{
@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
var storedEvent = serializer.Deserialize<CompatibleStoredEvent>(stream).ToStoredEvent();
var eventStream = storedEvent.StreamName;
var eventEnvelope = formatter.Parse(storedEvent);
var eventEnvelope = eventFormatter.Parse(storedEvent);
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);
}
public async Task<IRestoreJob?> GetRestoreAsync(
public Task<IRestoreJob> GetRestoreAsync(
CancellationToken ct = default)
{
var state = await RestoreGrain().GetStateAsync();
return state.Value;
return RestoreGrain().GetStateAsync();
}
public async Task<List<IBackupJob>> GetBackupsAsync(DomainId appId,
public Task<List<IBackupJob>> GetBackupsAsync(DomainId appId,
CancellationToken ct = default)
{
var state = await BackupGrain(appId).GetStateAsync();
return state.Value;
return BackupGrain(appId).GetStateAsync();
}
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();
return state.Value.Find(x => x.Id == backupId);
return state.Find(x => x.Id == backupId);
}
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 Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Backup
{
@ -19,6 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
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,
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);
}
}

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<IRestoreJob?> GetRestoreAsync(
Task<IRestoreJob> GetRestoreAsync(
CancellationToken ct = default);
Task<List<IBackupJob>> GetBackupsAsync(DomainId appId,

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

@ -7,7 +7,6 @@
using Orleans;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
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<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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NodaTime;
using Orleans.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Backup.State;
@ -25,18 +25,18 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class RestoreGrain : GrainOfString, IRestoreGrain
public sealed class RestoreGrain : GrainBase, IRestoreGrain
{
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IBackupHandlerFactory backupHandlers;
private readonly IClock clock;
private readonly ICommandBus commandBus;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStreamNames eventStreams;
private readonly IGrainState<BackupRestoreState> state;
private readonly ILogger<RestoreGrain> log;
private readonly IServiceProvider serviceProvider;
private readonly IStreamNameResolver streamNameResolver;
private readonly IUserResolver userResolver;
private readonly IGrainState<BackupRestoreState> state;
private RestoreContext runningContext;
private StreamMapper runningStreamMapper;
@ -47,29 +47,31 @@ namespace Squidex.Domain.Apps.Entities.Backup
public RestoreGrain(
IBackupArchiveLocation backupArchiveLocation,
IBackupHandlerFactory backupHandlers,
IClock clock,
ICommandBus commandBus,
IEventDataFormatter eventDataFormatter,
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventStreamNames eventStreams,
IGrainIdentity identity,
IGrainState<BackupRestoreState> state,
IServiceProvider serviceProvider,
IStreamNameResolver streamNameResolver,
IUserResolver userResolver,
ILogger<RestoreGrain> log)
: base(identity)
{
this.backupArchiveLocation = backupArchiveLocation;
this.backupHandlers = backupHandlers;
this.clock = clock;
this.commandBus = commandBus;
this.eventDataFormatter = eventDataFormatter;
this.eventFormatter = eventFormatter;
this.eventStore = eventStore;
this.serviceProvider = serviceProvider;
this.eventStreams = eventStreams;
this.state = state;
this.streamNameResolver = streamNameResolver;
this.userResolver = userResolver;
this.log = log;
}
protected override Task OnActivateAsync(string key)
public override Task OnActivateAsync()
{
RecoverAfterRestartAsync().Forget();
@ -127,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ProcessAsync()
{
var handlers = CreateHandlers();
var handlers = backupHandlers.CreateMany();
var ct = default(CancellationToken);
@ -300,7 +302,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
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);
@ -328,7 +330,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
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);
@ -425,14 +427,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
}
private IEnumerable<IBackupHandler> CreateHandlers()
{
return serviceProvider.GetRequiredService<IEnumerable<IBackupHandler>>();
}
public Task<J<IRestoreJob>> GetStateAsync()
public Task<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 Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
@ -36,17 +35,15 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
await MentionUsersAsync(createComment);
}
await ExecuteCommandAsync(context, commentsCommand);
await ExecuteCommandAsync(commentsCommand);
}
await next(context);
}
private async Task ExecuteCommandAsync(CommandContext context, CommentsCommand commentsCommand)
private Task ExecuteCommandAsync(CommentsCommand commentsCommand)
{
var result = await GetGrain(commentsCommand).ExecuteAsync(commentsCommand.AsJ());
context.Complete(result.Value);
return GetGrain(commentsCommand).ExecuteAsync(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.
// ==========================================================================
using Orleans.Core;
using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.Comments.DomainObject.Guards;
using Squidex.Domain.Apps.Events.Comments;
@ -18,12 +19,12 @@ using Squidex.Infrastructure.Reflection;
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>> events = new List<Envelope<CommentsEvent>>();
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private long version = EtagVersion.Empty;
private string streamName;
@ -32,21 +33,24 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
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.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);
foreach (var @event in storedEvents)
{
var parsedEvent = eventDataFormatter.Parse(@event);
var parsedEvent = eventFormatter.Parse(@event);
version = @event.EventStreamNumber;
@ -54,14 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
}
}
public async Task<J<CommandResult>> ExecuteAsync(J<CommentsCommand> command)
{
var result = await ExecuteAsync(command.Value);
return result.AsJ();
}
private Task<CommandResult> ExecuteAsync(CommentsCommand command)
public Task<CommandResult> ExecuteAsync(CommentsCommand command)
{
switch (command)
{
@ -76,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
case UpdateComment updateComment:
return Upsert(updateComment, c =>
{
GuardComments.CanUpdate(c, Key, events);
GuardComments.CanUpdate(c, Key.ToString(), events);
Update(c);
});
@ -84,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
case DeleteComment deleteComment:
return Upsert(deleteComment, c =>
{
GuardComments.CanDelete(c, Key, events);
GuardComments.CanDelete(c, Key.ToString(), events);
Delete(c);
});
@ -102,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
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;
@ -115,14 +112,14 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
{
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);
}
events.AddRange(uncommittedEvents);
return CommandResult.Empty(DomainId.Create(Key), Version, previousVersion);
return CommandResult.Empty(Key, Version, previousVersion);
}
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.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
{
public interface ICommentsGrain : IGrainWithStringKey
{
Task<J<CommandResult>> ExecuteAsync(J<CommentsCommand> command);
Task<CommandResult> ExecuteAsync(CommentsCommand command);
Task<CommentsResult> GetCommentsAsync(long sinceVersion = EtagVersion.Any);
}

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

@ -6,6 +6,7 @@
// ==========================================================================
using NodaTime;
using Orleans.Core;
using Squidex.Infrastructure;
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 IClock clock;
public WatchingGrain(IClock clock)
public WatchingGrain(IGrainIdentity grainIdentity, IClock clock)
: base(grainIdentity)
{
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)
{
this.rebuilder = rebuilder;
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Contents.Counter
{
public sealed class CounterGrain : GrainOfString, ICounterGrain
public sealed class CounterGrain : GrainBase, ICounterGrain
{
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 CounterGrain(IGrainState<State> state)
public CounterGrain(IGrainIdentity identity, IGrainState<State> state)
: base(identity)
{
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;
public ContentDomainObject(IPersistenceFactory<State> persistence, ILogger<ContentDomainObject> log,
public ContentDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<ContentDomainObject> log,
IServiceProvider serviceProvider)
: base(persistence, log)
: base(id, persistence, log)
{
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -14,13 +15,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5);
public ContentDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit)
: base(serviceProvider)
public ContentDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory, IActivationLimit limit)
: base(identity, factory)
{
limit?.SetLimit(5000, Lifetime);
}
public async Task<J<IContentEntity>> GetStateAsync(long version = -2)
public async Task<IContentEntity> GetStateAsync(long version = -2)
{
await DomainObject.EnsureLoadedAsync();

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

@ -7,12 +7,11 @@
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{
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 contentState = await contentGrain.GetStateAsync(version);
var content = contentState.Value;
if (content == null || content.Version <= EtagVersion.Empty || (version > EtagVersion.Any && content.Version != version))
if (contentState == null || contentState.Version <= EtagVersion.Empty || (version > EtagVersion.Any && contentState.Version != version))
{
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.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{
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.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Entities.Rules.Commands;
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>
{
private readonly IAppProvider appProvider;
private readonly IRuleEnqueuer ruleEnqueuer;
private readonly IServiceProvider serviceProvider;
public RuleDomainObject(IPersistenceFactory<State> factory, ILogger<RuleDomainObject> log,
IAppProvider appProvider, IRuleEnqueuer ruleEnqueuer)
: base(factory, log)
public RuleDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<RuleDomainObject> log,
IServiceProvider serviceProvider)
: base(id, persistence, log)
{
this.appProvider = appProvider;
this.ruleEnqueuer = ruleEnqueuer;
this.serviceProvider = serviceProvider;
}
protected override bool IsDeleted(State snapshot)
@ -57,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
case CreateRule createRule:
return CreateReturnAsync(createRule, async c =>
{
await GuardRule.CanCreate(c, appProvider);
await GuardRule.CanCreate(c, AppProvider());
Create(c);
@ -67,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
case UpdateRule updateRule:
return UpdateReturnAsync(updateRule, async c =>
{
await GuardRule.CanUpdate(c, Snapshot, appProvider);
await GuardRule.CanUpdate(c, Snapshot, AppProvider());
Update(c);
@ -117,7 +116,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
SimpleMapper.Map(command, @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)
@ -149,5 +153,10 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{
public sealed class RuleDomainObjectGrain : DomainObjectGrain<RuleDomainObject, RuleDomainObject.State>, IRuleGrain
{
public RuleDomainObjectGrain(IServiceProvider serviceProvider)
: base(serviceProvider)
public RuleDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(identity, factory)
{
}
public async Task<J<IRuleEntity>> GetStateAsync()
public async Task<IRuleEntity> GetStateAsync()
{
await DomainObject.EnsureLoadedAsync();

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

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

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
{
private const int MaxSimulatedEvents = 100;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly IGrainFactory grainFactory;
private readonly IRuleService ruleService;
public DefaultRuleRunnerService(IGrainFactory grainFactory,
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IRuleService ruleService)
{
this.grainFactory = grainFactory;
this.eventDataFormatter = eventDataFormatter;
this.eventFormatter = eventFormatter;
this.eventStore = eventStore;
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))
{
var @event = eventDataFormatter.ParseIfKnown(storedEvent);
var @event = eventFormatter.ParseIfKnown(storedEvent);
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 Orleans;
using Orleans.Core;
using Orleans.Runtime;
using Squidex.Caching;
using Squidex.Domain.Apps.Core.HandleRules;
@ -22,14 +23,14 @@ using TaskExtensions = Squidex.Infrastructure.Tasks.TaskExtensions;
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 readonly IGrainState<State> state;
private readonly IAppProvider appProvider;
private readonly ILocalCache localCache;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IGrainState<State> state;
private readonly ILocalCache localCache;
private readonly IRuleEventRepository ruleEventRepository;
private readonly IRuleService ruleService;
private readonly ILogger<RuleRunnerGrain> log;
@ -47,27 +48,28 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
public bool RunFromSnapshots { get; set; }
}
public RuleRunnerGrain(
IGrainState<State> state,
public RuleRunnerGrain(IGrainIdentity identity,
IAppProvider appProvider,
ILocalCache localCache,
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IGrainState<State> state,
ILocalCache localCache,
IRuleEventRepository ruleEventRepository,
IRuleService ruleService,
ILogger<RuleRunnerGrain> log)
: base(identity)
{
this.state = state;
this.appProvider = appProvider;
this.localCache = localCache;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.eventFormatter = eventFormatter;
this.ruleEventRepository = ruleEventRepository;
this.ruleService = ruleService;
this.log = log;
}
protected override Task OnActivateAsync(string key)
public override Task OnActivateAsync()
{
return EnsureIsRunningAsync(true);
}
@ -154,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{
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)
{
@ -247,7 +249,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{
try
{
var @event = eventDataFormatter.ParseIfKnown(storedEvent);
var @event = eventFormatter.ParseIfKnown(storedEvent);
if (@event != null)
{

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

@ -7,6 +7,7 @@
using Orleans;
using Orleans.Concurrency;
using Orleans.Core;
using Orleans.Runtime;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
@ -18,7 +19,7 @@ using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
[Reentrant]
public sealed class UsageTrackerGrain : GrainOfString, IRemindable, IUsageTrackerGrain
public sealed class UsageTrackerGrain : GrainBase, IRemindable, IUsageTrackerGrain
{
private readonly IGrainState<State> state;
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 UsageTrackerGrain(IGrainState<State> state, IApiUsageTracker usageTracker)
public UsageTrackerGrain(IGrainIdentity identity, IGrainState<State> state, IApiUsageTracker usageTracker)
: base(identity)
{
this.state = state;
this.usageTracker = usageTracker;
}
protected override Task OnActivateAsync(string key)
public override Task OnActivateAsync()
{
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.Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{
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.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
@ -25,8 +24,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{
public sealed partial class SchemaDomainObject : DomainObject<SchemaDomainObject.State>
{
public SchemaDomainObject(IPersistenceFactory<State> persistence, ILogger<SchemaDomainObject> log)
: base(persistence, log)
public SchemaDomainObject(DomainId id, IPersistenceFactory<State> persistence, ILogger<SchemaDomainObject> log)
: base(id, persistence, log)
{
}
@ -407,9 +406,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
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.
// ==========================================================================
using Orleans.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{
public sealed class SchemaDomainObjectGrain : DomainObjectGrain<SchemaDomainObject, SchemaDomainObject.State>, ISchemaGrain
{
public SchemaDomainObjectGrain(IServiceProvider serviceProvider)
: base(serviceProvider)
public SchemaDomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(identity, factory)
{
}
public async Task<J<ISchemaEntity>> GetStateAsync()
public async Task<ISchemaEntity> GetStateAsync()
{
await DomainObject.EnsureLoadedAsync();

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

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

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Orleans.Core;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
@ -12,35 +13,42 @@ using Squidex.Infrastructure.States;
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")]
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()
{
return state.ClearAsync();
return grainState.ClearAsync();
}
public Task RebuildAsync(TagsExport export)
{
state.Value.Tags = export.Tags;
state.Value.Alias = export.Alias;
grainState.Value.Tags = export.Tags;
grainState.Value.Alias = export.Alias;
return state.WriteAsync();
return grainState.WriteAsync();
}
public Task RenameTagAsync(string name, string newName)
@ -73,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Tags
Alias[name] = newName;
return state.WriteAsync();
return grainState.WriteAsync();
}
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;
}
@ -157,12 +165,12 @@ namespace Squidex.Domain.Apps.Entities.Tags
{
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()
{
return Task.FromResult(state.Value.Clone());
return Task.FromResult(grainState.Value.Clone());
}
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 SnapshotList<T> snapshots = new SnapshotList<T>();
private readonly IPersistenceFactory<T> factory;
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 DomainId uniqueId;
public DomainId UniqueId
{
@ -42,14 +42,29 @@ namespace Squidex.Infrastructure.Commands
set => snapshots.Capacity = value;
}
protected DomainObject(IPersistenceFactory<T> factory,
protected DomainObject(DomainId uniqueId, IPersistenceFactory<T> factory,
ILogger log)
{
Guard.NotNull(factory);
Guard.NotNull(log);
this.uniqueId = uniqueId;
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;
}
@ -68,6 +83,7 @@ namespace Squidex.Infrastructure.Commands
var allEvents = factory.WithEventSourcing(GetType(), UniqueId, @event =>
{
// Some migrations needs the current state.
@event = @event.Migrate(snapshot);
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 };
}
public virtual void Setup(DomainId uniqueId)
{
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)
public virtual async Task EnsureLoadedAsync()
{
if (isLoaded)
{
return;
}
if (silent)
{
await ReadAsync();
}
else
await persistence.ReadAsync();
if (persistence.IsSnapshotStale)
{
var watch = ValueStopwatch.StartNew();
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 deletedStream = factory.WithEventSourcing(GetType(), deletedId, null);
// Write to the deleted stream first so we never loose this information.
await deletedStream.WriteEventsAsync(events);
if (persistence != null)
{
// Cleanup the secondary stream first.
await persistence.DeleteAsync();
Setup(uniqueId);
}
snapshots.Clear();
}
@ -225,7 +217,7 @@ namespace Squidex.Infrastructure.Commands
}
catch (InconsistentStateException)
{
await EnsureLoadedAsync(true);
await EnsureLoadedAsync();
if (wasDeleted)
{
@ -334,49 +326,28 @@ namespace Squidex.Infrastructure.Commands
return (newSnapshot, isChanged);
}
private async Task ReadAsync()
{
if (persistence != null)
{
await persistence.ReadAsync();
if (persistence.IsSnapshotStale)
private async Task WriteAsync(Envelope<IEvent>[] newEvents)
{
try
if (newEvents.Length == 0)
{
await persistence.WriteSnapshotAsync(Snapshot);
}
catch (Exception ex)
{
log.LogError(ex, "Failed to repair snapshot for domain object of type {type} with ID {id}.", GetType(), UniqueId);
}
}
}
return;
}
private async Task WriteAsync(Envelope<IEvent>[] newEvents)
{
if (newEvents.Length > 0 && persistence != null)
{
await persistence.WriteEventsAsync(newEvents);
await persistence.WriteSnapshotAsync(Snapshot);
}
}
public async Task RebuildStateAsync()
{
await EnsureLoadedAsync(true);
await EnsureLoadedAsync();
if (Version <= EtagVersion.Empty)
{
throw new DomainObjectNotFoundException(UniqueId.ToString());
}
if (persistence != null)
{
await persistence.WriteSnapshotAsync(Snapshot);
}
}
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.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Orleans.Core;
using Squidex.Infrastructure.Orleans;
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;
@ -24,27 +24,15 @@ namespace Squidex.Infrastructure.Commands
get => domainObject;
}
protected DomainObjectGrain(IServiceProvider serviceProvider)
protected DomainObjectGrain(IGrainIdentity identity, IDomainObjectFactory factory)
: base(identity)
{
Guard.NotNull(serviceProvider);
domainObject = serviceProvider.GetRequiredService<T>();
domainObject = factory.Create<T>(DomainId.Create(identity.PrimaryKeyString));
}
protected override Task OnActivateAsync(string key)
public Task<CommandResult> ExecuteAsync(IAggregateCommand command)
{
domainObject.Setup(DomainId.Create(key));
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;
return domainObject.ExecuteAsync(command);
}
}
}

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);
}
private async Task<CommandResult> ExecuteCommandAsync(TCommand typedCommand)
private Task<CommandResult> ExecuteCommandAsync(TCommand typedCommand)
{
var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId.ToString());
var result = await grain.ExecuteAsync(CommandRequest.Create(typedCommand));
return result.Value;
return grain.ExecuteAsync(typedCommand);
}
}
}

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 Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands
{
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.Tasks;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.Commands
{
public class Rebuilder
{
private readonly IDomainObjectFactory domainObjectFactory;
private readonly ILocalCache localCache;
private readonly IEventStore eventStore;
private readonly IServiceProvider serviceProvider;
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(
IDomainObjectFactory domainObjectFactory,
ILocalCache localCache,
IEventStore eventStore,
IServiceProvider serviceProvider,
@ -42,6 +32,7 @@ namespace Squidex.Infrastructure.Commands
{
this.eventStore = eventStore;
this.serviceProvider = serviceProvider;
this.domainObjectFactory = domainObjectFactory;
this.localCache = localCache;
this.log = log;
}
@ -107,9 +98,7 @@ namespace Squidex.Infrastructure.Commands
{
try
{
var domainObject = Factory<T, TState>.Create(serviceProvider, context);
domainObject.Setup(id);
var domainObject = domainObjectFactory.Create<T, TState>(id, context);
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
{
public sealed class DefaultEventDataFormatter : IEventDataFormatter
public sealed class DefaultEventFormatter : IEventFormatter
{
private readonly IJsonSerializer serializer;
private readonly TypeNameRegistry typeNameRegistry;
public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer)
public DefaultEventFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer)
{
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 });
}
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();

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

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

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

@ -7,21 +7,21 @@
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using Orleans.Core;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks;
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 IEventDataFormatter eventDataFormatter;
private readonly IEventConsumer eventConsumer;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly ILogger<EventConsumerGrain> log;
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
private IEventSubscription? currentSubscription;
private IEventConsumer? eventConsumer;
private EventConsumerState State
{
@ -30,27 +30,21 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
}
public EventConsumerGrain(
EventConsumerFactory eventConsumerFactory,
IGrainIdentity identity,
IGrainState<EventConsumerState> state,
IEventConsumerFactory eventConsumerFactory,
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
ILogger<EventConsumerGrain> log)
: base(identity)
{
this.eventConsumer = eventConsumerFactory.Create(identity.PrimaryKeyString);
this.eventFormatter = eventFormatter;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.eventConsumerFactory = eventConsumerFactory;
this.state = state;
this.log = log;
}
protected override Task OnActivateAsync(string key)
{
eventConsumer = eventConsumerFactory(key);
return Task.CompletedTask;
}
public override Task OnDeactivateAsync()
{
CompleteAsync().Forget();
@ -276,7 +270,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
private BatchSubscriber CreateSubscription()
{
return new BatchSubscriber(this, eventDataFormatter, eventConsumer!, CreateRetrySubscription);
return new BatchSubscriber(this, eventFormatter, eventConsumer!, CreateRetrySubscription);
}
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.
// ==========================================================================
#pragma warning disable MA0048 // File name must match type name
namespace Squidex.Infrastructure.EventSourcing
{
public delegate IEventConsumer EventConsumerFactory(string name);
public interface IEventConsumer
{
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
{
public interface IEventDataFormatter
public interface IEventFormatter
{
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.Runtime;
#pragma warning disable MA0048 // File name must match type name
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";
public const string ActivityNameOut = "Orleans.Runtime.GrainCall.Out";
protected const string TraceParentHeaderName = "traceparent";
protected const string TraceStateHeaderName = "tracestate";
var traceParent = RequestContext.Get(TraceParentHeaderName) as string;
var traceState = RequestContext.Get(TraceStateHeaderName) as string;
var parentContext = default(ActivityContext);
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;
@ -37,7 +76,7 @@ namespace Squidex.Infrastructure.Orleans
using (var activity = Telemetry.Activities.StartActivity(activityName, activityKind, activityContext, tags))
{
if (activity is not null)
if (activity != null)
{
RequestContext.Set(TraceParentHeaderName, activity.Id);
}
@ -46,7 +85,7 @@ namespace Squidex.Infrastructure.Orleans
{
await context.Invoke();
if (activity is not null && activity.IsAllDataRequested)
if (activity != null && activity.IsAllDataRequested)
{
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();
}
public Task Invoke(IIncomingGrainCallContext context)
public async Task Invoke(IIncomingGrainCallContext context)
{
if (RequestContext.Get("Culture") is string culture)
{
@ -33,7 +33,7 @@ namespace Squidex.Infrastructure.Orleans
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
{
protected GrainBase()
{
}
public DomainId Key { get; private set; }
protected GrainBase(IGrainIdentity? identity, IGrainRuntime? runtime)
protected GrainBase(IGrainIdentity identity, IGrainRuntime? runtime = null)
: base(identity, runtime)
{
Guard.NotNull(identity);
Key = DomainId.Create(identity.PrimaryKeyString);
}
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.
// ==========================================================================
using Orleans.Core;
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)>();
public UniqueNameGrain(IGrainIdentity identity)
: base(identity)
{
}
public virtual Task<string?> ReserveAsync(T id, string name)
{
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 readonly Type owner;
private readonly ISnapshotStore<T> snapshotStore;
private readonly IEventFormatter eventDataFormatter;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IStreamNameResolver streamNameResolver;
private readonly IEventStreamNames eventStreams;
private readonly Dictionary<DomainId, (long, List<Envelope<IEvent>>)> @events = new Dictionary<DomainId, (long, List<Envelope<IEvent>>)>();
private Dictionary<DomainId, SnapshotWriteJob<T>>? snapshots;
internal BatchContext(
Type owner,
ISnapshotStore<T> snapshotStore,
IEventFormatter eventDataFormatter,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IStreamNameResolver streamNameResolver)
IEventStreamNames streamNameResolver,
ISnapshotStore<T> snapshotStore)
{
this.owner = owner;
this.snapshotStore = snapshotStore;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.streamNameResolver = streamNameResolver;
this.eventStreams = streamNameResolver;
}
internal void Add(DomainId key, T snapshot, long version)
@ -48,7 +48,7 @@ namespace Squidex.Infrastructure.States
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)
{

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
{
public sealed class DefaultStreamNameResolver : IStreamNameResolver
public sealed class DefaultEventStreamNames : IEventStreamNames
{
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
{
public interface IStreamNameResolver
public interface IEventStreamNames
{
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>
{
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 HandleSnapshot<T>? applyState;
private readonly IEventFormatter eventFormatter;
private readonly IEventStore eventStore;
private readonly ISnapshotStore<T> snapshotStore;
private readonly Lazy<string> streamName;
private readonly PersistenceMode persistenceMode;
private long versionSnapshot = EtagVersion.Empty;
private long versionEvents = EtagVersion.Empty;
private long version = EtagVersion.Empty;
@ -46,23 +46,23 @@ namespace Squidex.Infrastructure.States
}
public Persistence(DomainId ownerKey, Type ownerType,
ISnapshotStore<T> snapshotStore,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IStreamNameResolver streamNameResolver,
PersistenceMode persistenceMode,
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventStreamNames eventStreams,
ISnapshotStore<T> snapshotStore,
HandleSnapshot<T>? applyState,
HandleEvent? applyEvent)
{
this.ownerKey = ownerKey;
this.applyState = applyState;
this.applyEvent = applyEvent;
this.applyState = applyState;
this.eventFormatter = eventFormatter;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.ownerKey = ownerKey;
this.persistenceMode = persistenceMode;
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(
@ -74,6 +74,8 @@ namespace Squidex.Infrastructure.States
{
await snapshotStore.RemoveAsync(ownerKey, ct);
}
versionSnapshot = EtagVersion.Empty;
}
if (UseEventSourcing)
@ -82,7 +84,11 @@ namespace Squidex.Infrastructure.States
{
await eventStore.DeleteStreamAsync(streamName.Value, ct);
}
versionEvents = EtagVersion.Empty;
}
UpdateVersion();
}
public async Task ReadAsync(long expectedVersion = EtagVersion.Any,
@ -153,7 +159,7 @@ namespace Squidex.Infrastructure.States
if (!isStopped)
{
var parsedEvent = eventDataFormatter.ParseIfKnown(@event);
var parsedEvent = eventFormatter.ParseIfKnown(@event);
if (applyEvent != null && parsedEvent != null)
{
@ -210,7 +216,7 @@ namespace Squidex.Infrastructure.States
}
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
{

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

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

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

@ -8,35 +8,34 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Log;
namespace Squidex.Web.Pipeline
{
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)
{
var httpContextAccessor = services.GetService<IHttpContextAccessor>();
var httpContext = httpContextAccessor.HttpContext;
if (string.IsNullOrEmpty(httpContextAccessor?.HttpContext?.Request?.Method))
if (string.IsNullOrEmpty(httpContext?.Request?.Method))
{
return;
}
var actionContext = services.GetRequiredService<IActionContextAccessor>()?.ActionContext;
var actionContext = actionContextAccessor.ActionContext;
try
{
var httpContext = httpContextAccessor.HttpContext;
if (string.IsNullOrEmpty(httpContext?.Request?.Method))
{
return;

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

@ -21,11 +21,9 @@ namespace Squidex.Areas.IdentityServer.Config
private readonly IServiceProvider serviceProvider;
private CompletionTimer timer;
public TokenStoreInitializer(IOptions<OpenIddictMongoDbOptions> options,
IServiceProvider serviceProvider)
public TokenStoreInitializer(IOptions<OpenIddictMongoDbOptions> options, IServiceProvider serviceProvider)
{
this.options = options.Value;
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.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Search;
using Squidex.Infrastructure.Collections;
@ -28,9 +27,6 @@ namespace Squidex.Config.Domain
.As<IEventConsumer>();
}
services.AddTransientAs<AppDomainObject>()
.AsSelf();
services.AddSingletonAs<RolePermissionsProvider>()
.AsSelf();

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

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

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

@ -117,6 +117,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<UsageTrackerCommandMiddleware>()
.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.Contents;
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.Steps;
using Squidex.Domain.Apps.Entities.Contents.Text;
@ -38,9 +37,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentQueryParser>()
.AsSelf();
services.AddTransientAs<ContentDomainObject>()
.AsSelf();
services.AddTransientAs<CounterDeleter>()
.As<IDeleter>();

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

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

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

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

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

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

@ -13,11 +13,11 @@ using Orleans.Configuration;
using Orleans.Hosting;
using Orleans.Providers.MongoDB.Configuration;
using Orleans.Providers.MongoDB.Utils;
using Orleans.Runtime;
using OrleansDashboard;
using Squidex.Domain.Apps.Entities;
using Squidex.Hosting.Configuration;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Orleans;
using Squidex.Web;
@ -42,10 +42,7 @@ namespace Squidex.Config.Orleans
services.AddScopedAs<ActivationLimit>()
.As<IActivationLimit>();
services.AddInitializer<IJsonSerializer>("Serializer (Orleans)", serializer =>
{
J.DefaultSerializer = serializer;
}, -1);
services.AddScoped(x => x.GetRequiredService<IGrainActivationContext>().GrainIdentity);
});
builder.ConfigureApplicationParts(parts =>
@ -54,6 +51,11 @@ namespace Squidex.Config.Orleans
parts.AddApplicationPart(SquidexInfrastructure.Assembly);
});
builder.Configure<SerializationProviderOptions>(options =>
{
options.SerializationProviders.Add(typeof(JsonSerializer));
});
builder.Configure<SchedulingOptions>(options =>
{
options.TurnWarningLengthThreshold = TimeSpan.FromSeconds(5);
@ -75,10 +77,12 @@ namespace Squidex.Config.Orleans
options.HostSelf = false;
});
builder.AddOutgoingGrainCallFilter<ActivityPropagationOutgoingGrainCallFilter>();
builder.AddOutgoingGrainCallFilter<ActivityPropagationFilter>();
builder.AddOutgoingGrainCallFilter<CultureFilter>();
builder.AddIncomingGrainCallFilter<ExceptionWrapperFilter>();
builder.AddIncomingGrainCallFilter<ActivityPropagationIncomingGrainCallFilter>();
builder.AddIncomingGrainCallFilter<ActivityPropagationFilter>();
builder.AddIncomingGrainCallFilter<ActivationLimiterFilter>();
builder.AddIncomingGrainCallFilter<CultureFilter>();
builder.AddIncomingGrainCallFilter<LocalCacheFilter>();
builder.AddIncomingGrainCallFilter<LoggingFilter>();
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.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Xunit;
@ -117,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var grain = A.Fake<IAppGrain>();
A.CallTo(() => grain.GetStateAsync())
.Returns(app.AsJ());
.Returns(app);
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(app.Id.ToString(), null))
.Returns(grain);

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

@ -6,6 +6,7 @@
// ==========================================================================
using FakeItEasy;
using Orleans.Core;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
using Xunit;
@ -14,50 +15,51 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
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;
public AppUISettingsGrainTests()
{
sut = new AppUISettingsGrain(grainState);
sut = new AppUISettingsGrain(identity, state);
}
[Fact]
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 expected =
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();
}
[Fact]
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 expected =
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();
}
[Fact]
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");
@ -65,16 +67,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
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();
}
[Fact]
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();
@ -82,16 +84,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
new JsonObject().Add("root",
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();
}
[Fact]
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");
@ -101,18 +103,18 @@ namespace Squidex.Domain.Apps.Entities.Apps
new JsonObject().Add("root",
new JsonObject());
Assert.Equal(expected.ToString(), actual.Value.ToString());
Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => grainState.WriteAsync())
A.CallTo(() => state.WriteAsync())
.MustHaveHappenedTwiceExactly();
}
[Fact]
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]
@ -120,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
await sut.RemoveAsync("root.nested");
A.CallTo(() => grainState.WriteAsync())
A.CallTo(() => state.WriteAsync())
.MustNotHaveHappened();
}
@ -129,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
await sut.RemoveAsync("root");
A.CallTo(() => grainState.WriteAsync())
A.CallTo(() => state.WriteAsync())
.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.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Apps
@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var settings = new JsonObject();
A.CallTo(() => grain.GetAsync())
.Returns(settings.AsJ());
.Returns(settings);
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);
A.CallTo(() => grain.SetAsync("the.path", A<J<JsonValue>>.That.Matches(x => x.Value == value)))
A.CallTo(() => grain.SetAsync("the.path", value))
.MustHaveHappened();
}
@ -60,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
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();
}

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.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Validation;
using Xunit;
@ -90,7 +89,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
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));
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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Core.Apps;
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>>();
sut = new AppDomainObject(PersistenceFactory, log,
initialSettings,
appPlansProvider,
appPlansBillingManager,
userResolver);
#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
}

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

@ -6,6 +6,7 @@
// ==========================================================================
using FakeItEasy;
using Orleans.Core;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure;
@ -15,14 +16,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{
public class AppsCacheGrainTests
{
private readonly IGrainIdentity identity = A.Fake<IGrainIdentity>();
private readonly IAppRepository appRepository = A.Fake<IAppRepository>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly AppsCacheGrain sut;
public AppsCacheGrainTests()
{
sut = new AppsCacheGrain(appRepository);
sut.ActivateAsync(appId.ToString()).Wait();
A.CallTo(() => identity.PrimaryKeyString)
.Returns(appId.ToString());
sut = new AppsCacheGrain(identity, appRepository);
}
[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>();
A.CallTo(() => appGrain.GetStateAsync())
.Returns(J.Of(app));
.Returns(app);
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId.Id.ToString(), null))
.Returns(appGrain);

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

Loading…
Cancel
Save