Browse Source

App State

pull/169/head
Sebastian Stehle 8 years ago
parent
commit
da74af2f11
  1. BIN
      libs/orleans.providers.mongodb/2.0.0-preview2/orleans.providers.mongodb.2.0.0-preview2.nupkg
  2. 1
      libs/orleans.providers.mongodb/2.0.0-preview2/orleans.providers.mongodb.2.0.0-preview2.nupkg.sha512
  3. 29
      libs/orleans.providers.mongodb/2.0.0-preview2/orleans.providers.mongodb.nuspec
  4. 6
      src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs
  5. 6
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs
  6. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs
  7. 21
      src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs
  8. 25
      src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs
  9. 13
      src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs
  10. 3
      src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs
  11. 2
      src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs
  12. 2
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs
  13. 26
      src/Squidex.Domain.Apps.Read/EntityMapper.cs
  14. 32
      src/Squidex.Domain.Apps.Read/IAppState.cs
  15. 6
      src/Squidex.Domain.Apps.Read/IEntity.cs
  16. 3
      src/Squidex.Domain.Apps.Read/Squidex.Domain.Apps.Read.csproj
  17. 70
      src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs
  18. 17
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/EventMessage.cs
  19. 33
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs
  20. 24
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs
  21. 77
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs
  22. 276
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs
  23. 41
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs
  24. 35
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs
  25. 29
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonEntity.cs
  26. 31
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonRuleEntity.cs
  27. 57
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs
  28. 71
      src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs
  29. 8
      src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs
  30. 2
      src/Squidex.Infrastructure.MongoDb/MongoDb/MongoEntity.cs
  31. 2
      src/Squidex.Infrastructure/CQRS/Events/IEventNotifier.cs
  32. 30
      src/Squidex.Infrastructure/CQRS/Events/Orleans/OrleansClientEventNotifier.cs
  33. 8
      src/Squidex.Infrastructure/CQRS/Events/Orleans/OrleansEventNotifier.cs
  34. 13
      src/Squidex.Infrastructure/Log/ConsoleLogChannel.cs
  35. 2
      src/Squidex.Infrastructure/Log/FileChannel.cs
  36. 4
      src/Squidex.Infrastructure/Log/Internal/AnsiLogConsole.cs
  37. 4
      src/Squidex.Infrastructure/Log/Internal/ConsoleLogProcessor.cs
  38. 2
      src/Squidex.Infrastructure/Log/Internal/IConsole.cs
  39. 2
      src/Squidex.Infrastructure/Log/Internal/LogMessageEntry.cs
  40. 13
      src/Squidex.Infrastructure/Log/Internal/WindowsLogConsole.cs
  41. 1
      src/Squidex/AppServices.cs
  42. 1
      src/Squidex/Config/Domain/InfrastructureServices.cs
  43. 8
      src/Squidex/Config/Domain/ReadServices.cs
  44. 5
      src/Squidex/Config/Domain/SerializationServices.cs
  45. 8
      src/Squidex/Config/Orleans/ClientServices.cs
  46. 25
      src/Squidex/Config/Orleans/SiloServices.cs
  47. 2
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  48. 2
      src/Squidex/Program.cs
  49. 2
      src/Squidex/Squidex.csproj
  50. 19
      tests/Benchmarks/Benchmarks.csproj
  51. 27
      tests/Benchmarks/IBenchmark.cs
  52. 95
      tests/Benchmarks/Program.cs
  53. 8
      tests/Benchmarks/Properties/launchSettings.json
  54. 74
      tests/Benchmarks/Tests/AppendToEventStore.cs
  55. 73
      tests/Benchmarks/Tests/AppendToEventStoreWithManyWriters.cs
  56. 100
      tests/Benchmarks/Tests/HandleEvents.cs
  57. 107
      tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs
  58. 19
      tests/Benchmarks/Tests/TestData/MyEvent.cs
  59. 79
      tests/Benchmarks/Tests/TestData/MyEventConsumer.cs
  60. 1
      tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs

BIN
libs/orleans.providers.mongodb/2.0.0-preview2/orleans.providers.mongodb.2.0.0-preview2.nupkg

Binary file not shown.

1
libs/orleans.providers.mongodb/2.0.0-preview2/orleans.providers.mongodb.2.0.0-preview2.nupkg.sha512

@ -0,0 +1 @@
vjOVukk+3TGMSUoQkGnSjeD6k8j6x0WWqjwFntWpc4BijggTJYyND35YV9+Or63br+003NEpxYtdJfQwgwyA2Q==

29
libs/orleans.providers.mongodb/2.0.0-preview2/orleans.providers.mongodb.nuspec

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Orleans.Providers.MongoDB</id>
<version>2.0.0-preview2</version>
<authors>laredoza</authors>
<owners>laredoza</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://github.com/dotnet/Orleans#license</licenseUrl>
<projectUrl>https://github.com/OrleansContrib/Orleans.Providers.MongoDB</projectUrl>
<description>A MongoDb implementation of the Orleans Providers. This includes the Membership (IMembershipTable and IGatewayListProvider), Reminder (IReminderTable), MongoStatisticsPublisher and IStorageProvider providers.</description>
<releaseNotes>Binary serialization added to the Storage Provider by https://github.com/orthrus. The UseJsonFormat="true" parameter controls this.
Switching between formats while there is data in the storage tables will end in tears (data will be lost). So don't do it.
Feedback would be appreciated.</releaseNotes>
<copyright>MIT</copyright>
<tags>Orleans OrleansProviders MongoDB</tags>
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Microsoft.Orleans.OrleansProviders" version="2.0.0-beta1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.OrleansRuntime" version="2.0.0-beta1" exclude="Build,Analyzers" />
<dependency id="MongoDB.Driver" version="2.4.4" exclude="Build,Analyzers" />
<dependency id="System.Reflection.Metadata" version="1.5.0" exclude="Build,Analyzers" />
<dependency id="System.ValueTuple" version="4.4.0" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>

6
src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs

@ -9,6 +9,7 @@
using System;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Events.Schemas.Utils
{
@ -146,5 +147,10 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
{
schema.Unpublish();
}
public static void Apply(this Schema schema, ScriptsConfigured @event)
{
SimpleMapper.Map(@event, schema);
}
}
}

6
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs

@ -7,7 +7,6 @@
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Infrastructure.MongoDb;
@ -50,10 +49,5 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps
[BsonElement]
[BsonJson]
public LanguagesConfig LanguagesConfig { get; set; }
public PartitionResolver PartitionResolver
{
get { return LanguagesConfig.ToResolver(); }
}
}
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs

@ -19,7 +19,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{
public sealed class MongoContentEntity : IContentEntity, IMongoEntity
public sealed class MongoContentEntity : IContentEntity
{
private NamedContentData data;

21
src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs

@ -12,6 +12,8 @@ using NodaTime;
using Squidex.Domain.Apps.Read.History;
using Squidex.Infrastructure;
#pragma warning disable RECS0029 // Warns about property or indexer setters and event adders or removers that do not use the value parameter
namespace Squidex.Domain.Apps.Read.MongoDb.History
{
internal sealed class ParsedHistoryEvent : IHistoryEventEntity
@ -22,26 +24,29 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
public Guid Id
{
get { return inner.Id; }
set { }
}
public Guid EventId
public Instant Created
{
get { return inner.Id; }
get { return inner.Created; }
set { }
}
public RefToken Actor
public Instant LastModified
{
get { return inner.Actor; }
get { return inner.LastModified; }
set { }
}
public Instant Created
public RefToken Actor
{
get { return inner.Created; }
get { return inner.Actor; }
}
public Instant LastModified
public Guid EventId
{
get { return inner.LastModified; }
get { return inner.Id; }
}
public long Version

25
src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs

@ -18,25 +18,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb
{
public static class MongoCollectionExtensions
{
public static Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IMongoEntity, new()
public static Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new()
{
var entity = EntityMapper.Create<T>(@event, headers);
updater(entity);
var entity = EntityMapper.Create<T>(@event, headers, updater);
return collection.InsertOneIfNotExistsAsync(entity);
}
public static async Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Func<T, Task> updater) where T : class, IMongoEntity, new()
{
var entity = EntityMapper.Create<T>(@event, headers);
await updater(entity);
await collection.InsertOneIfNotExistsAsync(entity);
}
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IMongoEntity, new()
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new()
{
var entity =
await collection.Find(t => t.Id == headers.AggregateId())
@ -50,7 +39,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb
await collection.UpdateAsync(@event, headers, entity, updater);
}
public static async Task<bool> TryUpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IMongoEntity, new()
public static async Task<bool> TryUpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new()
{
var entity =
await collection.Find(t => t.Id == headers.AggregateId())
@ -76,11 +65,9 @@ namespace Squidex.Domain.Apps.Read.MongoDb
return false;
}
private static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, T entity, Action<T> updater) where T : class, IMongoEntity, new()
private static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, T entity, Action<T> updater) where T : class, IEntity, new()
{
EntityMapper.Update(@event, headers, entity);
updater(entity);
entity.Update(@event, headers, updater);
await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity);
}

13
tests/Benchmarks/Utils/Helper.cs → src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs

@ -1,21 +1,20 @@
// ==========================================================================
// Helper.cs
// AppEntityExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Domain.Apps.Core;
namespace Benchmarks.Utils
namespace Squidex.Domain.Apps.Read.Apps
{
public static class Helper
public static class AppEntityExtensions
{
public static EventData CreateEventData()
public static PartitionResolver PartitionResolver(this IAppEntity entity)
{
return new EventData { EventId = Guid.NewGuid(), Metadata = "EventMetdata", Payload = "EventPayload", Type = "MyEvent" };
return entity.LanguagesConfig.ToResolver();
}
}
}

3
src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs

@ -6,7 +6,6 @@
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Domain.Apps.Read.Apps
@ -24,7 +23,5 @@ namespace Squidex.Domain.Apps.Read.Apps
AppContributors Contributors { get; }
LanguagesConfig LanguagesConfig { get; }
PartitionResolver PartitionResolver { get; }
}
}

2
src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Read.Contents.Edm
{
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(60);
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver);
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver());
});
return result;

2
src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl;
partitionResolver = app.PartitionResolver;
partitionResolver = app.PartitionResolver();
assetType = new AssetGraphType(this);
assetListType = new ListGraphType(new NonNullGraphType(assetType));

26
src/Squidex.Domain.Apps.Read.MongoDb/EntityMapper.cs → src/Squidex.Domain.Apps.Read/EntityMapper.cs

@ -6,15 +6,15 @@
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb
namespace Squidex.Domain.Apps.Read
{
public static class EntityMapper
{
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers) where T : IMongoEntity, new()
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : IEntity, new()
{
var entity = new T();
@ -26,34 +26,36 @@ namespace Squidex.Domain.Apps.Read.MongoDb
SetAppId(@event, entity);
return Update(@event, headers, entity);
return entity.Update(@event, headers, updater);
}
public static T Update<T>(SquidexEvent @event, EnvelopeHeaders headers, T entity) where T : IMongoEntity, new()
public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : IEntity, new()
{
SetVersion(headers, entity);
SetLastModified(headers, entity);
SetLastModifiedBy(@event, entity);
updater(entity);
return entity;
}
private static void SetId(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetId(EnvelopeHeaders headers, IEntity entity)
{
entity.Id = headers.AggregateId();
}
private static void SetCreated(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetCreated(EnvelopeHeaders headers, IEntity entity)
{
entity.Created = headers.Timestamp();
}
private static void SetLastModified(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetLastModified(EnvelopeHeaders headers, IEntity entity)
{
entity.LastModified = headers.Timestamp();
}
private static void SetVersion(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetVersion(EnvelopeHeaders headers, IEntity entity)
{
if (entity is IEntityWithVersion withVersion)
{
@ -61,7 +63,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb
}
}
private static void SetCreatedBy(SquidexEvent @event, IMongoEntity entity)
private static void SetCreatedBy(SquidexEvent @event, IEntity entity)
{
if (entity is IEntityWithCreatedBy withCreatedBy)
{
@ -69,7 +71,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb
}
}
private static void SetLastModifiedBy(SquidexEvent @event, IMongoEntity entity)
private static void SetLastModifiedBy(SquidexEvent @event, IEntity entity)
{
if (entity is IEntityWithLastModifiedBy withModifiedBy)
{
@ -77,7 +79,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb
}
}
private static void SetAppId(SquidexEvent @event, IMongoEntity entity)
private static void SetAppId(SquidexEvent @event, IEntity entity)
{
if (entity is IAppRefEntity app && @event is AppEvent appEvent)
{

32
src/Squidex.Domain.Apps.Read/IAppState.cs

@ -0,0 +1,32 @@
// ==========================================================================
// IAppState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
namespace Squidex.Domain.Apps.Read
{
public interface IAppState
{
Task<IAppEntity> GetAppAsync(Guid appId);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool provideDeleted = false);
Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId);
Task<List<IRuleEntity>> GetRulesAsync(Guid appId);
Task<List<IAppEntity>> GetUserApps(string userId);
}
}

6
src/Squidex.Domain.Apps.Read/IEntity.cs

@ -13,10 +13,10 @@ namespace Squidex.Domain.Apps.Read
{
public interface IEntity
{
Guid Id { get; }
Guid Id { get; set; }
Instant Created { get; }
Instant Created { get; set; }
Instant LastModified { get; }
Instant LastModified { get; set; }
}
}

3
src/Squidex.Domain.Apps.Read/Squidex.Domain.Apps.Read.csproj

@ -15,6 +15,9 @@
<ItemGroup>
<PackageReference Include="GraphQL" Version="0.17.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.0" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.0.0-beta1" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.0.0-beta1" />
<PackageReference Include="Microsoft.Orleans.OrleansCodeGenerator.Build" Version="2.0.0-beta1-fix" />
<PackageReference Include="NodaTime" Version="2.2.1" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

70
src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs

@ -0,0 +1,70 @@
// ==========================================================================
// StateEventConsumer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Read.State.Orleans.Grains;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.State
{
public sealed class AppStateEventConsumer : IEventConsumer
{
private readonly IGrainFactory factory;
public string Name
{
get { return typeof(AppStateEventConsumer).Name; }
}
public string EventsFilter
{
get { return @"(^app-)|(^schema-)|(^rule\-)"; }
}
public AppStateEventConsumer(IGrainFactory factory)
{
Guard.NotNull(factory, nameof(factory));
this.factory = factory;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public async Task On(Envelope<IEvent> @event)
{
if (@event.Payload is AppEvent appEvent)
{
var appGrain = factory.GetGrain<IAppStateGrain>(appEvent.AppId.Id);
await appGrain.HandleAsync(new EventMessage { Event = @event });
}
if (@event.Payload is AppContributorAssigned contributorAssigned)
{
var userGrain = factory.GetGrain<IAppUserGrain>(contributorAssigned.ContributorId);
await userGrain.AddSchemaAsync(contributorAssigned.AppId.Id);
}
if (@event.Payload is AppContributorRemoved contributorRemoved)
{
var userGrain = factory.GetGrain<IAppUserGrain>(contributorRemoved.ContributorId);
await userGrain.RemoveSchemaAsync(contributorRemoved.AppId.Id);
}
}
}
}

17
src/Squidex.Infrastructure.MongoDb/MongoDb/IMongoEntity.cs → src/Squidex.Domain.Apps.Read/State/Orleans/Grains/EventMessage.cs

@ -1,22 +1,19 @@
// ==========================================================================
// IMongoEntity.cs
// EventMessage.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using NodaTime;
using Orleans.Concurrency;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Infrastructure.MongoDb
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public interface IMongoEntity
[Immutable]
public sealed class EventMessage
{
Guid Id { get; set; }
Instant Created { get; set; }
Instant LastModified { get; set; }
public Envelope<IEvent> Event { get; set; }
}
}

33
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs

@ -0,0 +1,33 @@
// ==========================================================================
// IAppStateGrain.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public interface IAppStateGrain : IGrainWithGuidKey
{
Task<IAppEntity> GetAppAsync();
Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false);
Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false);
Task<List<ISchemaEntity>> GetSchemasAsync();
Task<List<IRuleEntity>> GetRulesAsync();
Task HandleAsync(EventMessage message);
}
}

24
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs

@ -0,0 +1,24 @@
// ==========================================================================
// IAppUserGrain.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public interface IAppUserGrain : IGrainWithStringKey
{
Task<List<Guid>> GetSchemaIdsAsync();
Task AddSchemaAsync(Guid schemaId);
Task RemoveSchemaAsync(Guid schemaId);
}
}

77
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs

@ -0,0 +1,77 @@
// ==========================================================================
// AppStateGrain.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Orleans.Providers;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
[StorageProvider(ProviderName = "Default")]
public sealed class AppStateGrain : Grain<AppStateGrainState>, IAppStateGrain
{
private readonly FieldRegistry fieldRegistry;
public AppStateGrain(FieldRegistry fieldRegistry)
{
Guard.NotNull(fieldRegistry, nameof(fieldRegistry));
this.fieldRegistry = fieldRegistry;
}
public Task<IAppEntity> GetAppAsync()
{
var value = State.App;
return Task.FromResult<IAppEntity>(value);
}
public Task<List<IRuleEntity>> GetRulesAsync()
{
var value = State.Rules.Values.OfType<IRuleEntity>().ToList();
return Task.FromResult(value);
}
public Task<List<ISchemaEntity>> GetSchemasAsync()
{
var value = State.Schemas.Values.Where(x => !x.IsDeleted).OfType<ISchemaEntity>().ToList();
return Task.FromResult(value);
}
public Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false)
{
var value = State.Schemas.Values.FirstOrDefault(x => x.Id == id && (!x.IsDeleted || provideDeleted));
return Task.FromResult<ISchemaEntity>(value);
}
public Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false)
{
var value = State.Schemas.Values.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted));
return Task.FromResult<ISchemaEntity>(value);
}
public Task HandleAsync(EventMessage message)
{
State.Apply(message.Event, fieldRegistry);
return WriteStateAsync();
}
}
}

276
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs

@ -0,0 +1,276 @@
// ==========================================================================
// AppStateGrainState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Apps.Utils;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Domain.Apps.Events.Rules.Utils;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Domain.Apps.Events.Schemas.Old;
using Squidex.Domain.Apps.Events.Schemas.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Reflection;
#pragma warning disable CS0612 // Type or member is obsolete
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
public sealed class AppStateGrainState
{
[JsonProperty]
public JsonAppEntity App { get; set; }
[JsonProperty]
public Dictionary<Guid, JsonRuleEntity> Rules { get; set; }
[JsonProperty]
public Dictionary<Guid, JsonSchemaEntity> Schemas { get; set; }
public void Reset()
{
Rules = new Dictionary<Guid, JsonRuleEntity>();
Schemas = new Dictionary<Guid, JsonSchemaEntity>();
}
public void Apply(Envelope<IEvent> envelope, FieldRegistry registry)
{
switch (envelope.Payload)
{
case AppCreated @event:
{
Reset();
App = EntityMapper.Create<JsonAppEntity>(@event, envelope.Headers, a =>
{
SimpleMapper.Map(envelope, a);
a.Clients = new AppClients();
a.Contributors = new AppContributors();
a.LanguagesConfig = LanguagesConfig.Build(Language.EN);
});
break;
}
case AppPlanChanged @event:
App.Update(@event, envelope.Headers, a =>
{
SimpleMapper.Map(envelope, a);
});
break;
case AppClientAttached @event:
App.Update(@event, envelope.Headers, a =>
{
a.Clients.Apply(@event);
});
break;
case AppClientRevoked @event:
App.Update(@event, envelope.Headers, a =>
{
a.Clients.Apply(@event);
});
break;
case AppClientRenamed @event:
App.Update(@event, envelope.Headers, a =>
{
a.Clients.Apply(@event);
});
break;
case AppClientUpdated @event:
App.Update(@event, envelope.Headers, a =>
{
a.Clients.Apply(@event);
});
break;
case AppContributorRemoved @event:
App.Update(@event, envelope.Headers, a =>
{
a.Contributors.Apply(@event);
});
break;
case AppContributorAssigned @event:
App.Update(@event, envelope.Headers, a =>
{
a.Contributors.Apply(@event);
});
break;
case AppLanguageAdded @event:
App.Update(@event, envelope.Headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
break;
case AppLanguageRemoved @event:
App.Update(@event, envelope.Headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
break;
case AppLanguageUpdated @event:
App.Update(@event, envelope.Headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
break;
case RuleCreated @event:
Rules[@event.RuleId] = EntityMapper.Create<JsonRuleEntity>(@event, envelope.Headers, r =>
{
r.Rule = RuleEventDispatcher.Create(@event);
});
break;
case RuleUpdated @event:
Rules[@event.RuleId].Update(@event, envelope.Headers, r =>
{
r.Rule.Apply(@event);
});
break;
case RuleEnabled @event:
Rules[@event.RuleId].Update(@event, envelope.Headers, r =>
{
r.Rule.Apply(@event);
});
break;
case RuleDisabled @event:
Rules.Remove(@event.RuleId);
break;
case SchemaCreated @event:
Schemas[@event.SchemaId.Id] = EntityMapper.Create<JsonSchemaEntity>(@event, envelope.Headers, s =>
{
s.SchemaDef = SchemaEventDispatcher.Create(@event, registry);
SimpleMapper.Map(@event, s);
});
break;
case FieldAdded @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event, registry);
});
break;
case FieldDeleted @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case FieldLocked @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case FieldHidden @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case FieldShown @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case FieldDisabled @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case FieldEnabled @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case FieldUpdated @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case SchemaFieldsReordered @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case SchemaUpdated @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case SchemaPublished @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case ScriptsConfigured @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
s.SchemaDef.Apply(@event);
});
break;
case SchemaDeleted @event:
Schemas.Remove(@event.SchemaId.Id);
break;
case WebhookAdded @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
/* NOOP */
});
break;
case WebhookDeleted @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
/* NOOP */
});
break;
}
}
}
}

41
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs

@ -0,0 +1,41 @@
// ==========================================================================
// AppUserGrain.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Orleans.Providers;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
[StorageProvider(ProviderName = "Default")]
public sealed class AppUserGrain : Grain<HashSet<Guid>>, IAppUserGrain
{
public Task AddSchemaAsync(Guid schemaId)
{
State.Add(schemaId);
return TaskHelper.Done;
}
public Task RemoveSchemaAsync(Guid schemaId)
{
State.Remove(schemaId);
return TaskHelper.Done;
}
public Task<List<Guid>> GetSchemaIdsAsync()
{
return Task.FromResult(State.ToList());
}
}
}

35
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs

@ -0,0 +1,35 @@
// ==========================================================================
// JsonAppEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
public sealed class JsonAppEntity : JsonEntity, IAppEntity
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public string PlanId { get; set; }
[JsonProperty]
public string PlanOwner { get; set; }
[JsonProperty]
public AppClients Clients { get; set; }
[JsonProperty]
public AppContributors Contributors { get; set; }
[JsonProperty]
public LanguagesConfig LanguagesConfig { get; set; }
}
}

29
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonEntity.cs

@ -0,0 +1,29 @@
// ==========================================================================
// JsonEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using NodaTime;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
public abstract class JsonEntity
{
[JsonProperty]
public Guid Id { get; set; }
[JsonProperty]
public Instant Created { get; set; }
[JsonProperty]
public Instant LastModified { get; set; }
[JsonProperty]
public long Version { get; set; }
}
}

31
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonRuleEntity.cs

@ -0,0 +1,31 @@
// ==========================================================================
// JsonRuleEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
public sealed class JsonRuleEntity : JsonEntity, IRuleEntity
{
[JsonProperty]
public Guid AppId { get; set; }
[JsonProperty]
public RefToken CreatedBy { get; set; }
[JsonProperty]
public RefToken LastModifiedBy { get; set; }
[JsonProperty]
public Rule Rule { get; set; }
}
}

57
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs

@ -0,0 +1,57 @@
// ==========================================================================
// JsonSchemaEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
public sealed class JsonSchemaEntity : JsonEntity, ISchemaEntity
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public Guid AppId { get; set; }
[JsonProperty]
public RefToken CreatedBy { get; set; }
[JsonProperty]
public RefToken LastModifiedBy { get; set; }
[JsonProperty]
public bool IsDeleted { get; set; }
[JsonProperty]
public string ScriptQuery { get; set; }
[JsonProperty]
public string ScriptCreate { get; set; }
[JsonProperty]
public string ScriptUpdate { get; set; }
[JsonProperty]
public string ScriptDelete { get; set; }
[JsonProperty]
public string ScriptChange { get; set; }
[JsonProperty]
public Schema SchemaDef { get; set; }
public bool IsPublished
{
get { return SchemaDef.IsPublished; }
}
}
}

71
src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs

@ -0,0 +1,71 @@
// ==========================================================================
// OrleansAppState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public sealed class OrleansAppState : IAppState
{
private readonly IGrainFactory factory;
public OrleansAppState(IGrainFactory factory)
{
Guard.NotNull(factory, nameof(factory));
this.factory = factory;
}
public Task<IAppEntity> GetAppAsync(Guid appId)
{
return factory.GetGrain<IAppStateGrain>(appId).GetAppAsync();
}
public Task<List<IRuleEntity>> GetRulesAsync(Guid appId)
{
return factory.GetGrain<IAppStateGrain>(appId).GetRulesAsync();
}
public Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false)
{
return factory.GetGrain<IAppStateGrain>(appId).GetSchemaAsync(id, provideDeleted);
}
public Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool provideDeleted = false)
{
return factory.GetGrain<IAppStateGrain>(appId).GetSchemaAsync(name, provideDeleted);
}
public Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId)
{
return factory.GetGrain<IAppStateGrain>(appId).GetSchemasAsync();
}
public async Task<List<IAppEntity>> GetUserApps(string userId)
{
var schemaIds = await factory.GetGrain<IAppUserGrain>(userId).GetSchemaIdsAsync();
var tasks =
schemaIds
.Select(x => factory.GetGrain<IAppStateGrain>(x))
.Select(x => x.GetAppAsync());
var apps = await Task.WhenAll(tasks);
return apps.Where(a => a != null).ToList();
}
}
}

8
src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs

@ -23,6 +23,8 @@ using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
#pragma warning disable IDE0017 // Simplify object initialization
namespace Squidex.Domain.Apps.Write.Contents
{
public sealed class ContentOperationContext
@ -69,7 +71,7 @@ namespace Squidex.Domain.Apps.Write.Contents
{
if (command is ContentDataCommand dataCommand)
{
dataCommand.Data.Enrich(schemaEntity.SchemaDef, appEntity.PartitionResolver);
dataCommand.Data.Enrich(schemaEntity.SchemaDef, appEntity.PartitionResolver());
}
return TaskHelper.Done;
@ -96,11 +98,11 @@ namespace Squidex.Domain.Apps.Write.Contents
if (partial)
{
await dataCommand.Data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver, errors);
await dataCommand.Data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), errors);
}
else
{
await dataCommand.Data.ValidateAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver, errors);
await dataCommand.Data.ValidateAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), errors);
}
if (errors.Count > 0)

2
src/Squidex.Infrastructure.MongoDb/MongoDb/MongoEntity.cs

@ -13,7 +13,7 @@ using NodaTime;
namespace Squidex.Infrastructure.MongoDb
{
public abstract class MongoEntity : IMongoEntity
public abstract class MongoEntity
{
[BsonId]
[BsonElement]

2
src/Squidex.Infrastructure/CQRS/Events/IEventNotifier.cs

@ -6,8 +6,6 @@
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Infrastructure.CQRS.Events
{
public interface IEventNotifier

30
src/Squidex.Infrastructure/CQRS/Events/Orleans/OrleansClientEventNotifier.cs

@ -1,30 +0,0 @@
// ==========================================================================
// OrleansClientEventNotifier.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Orleans;
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains;
namespace Squidex.Infrastructure.CQRS.Events.Orleans
{
public sealed class OrleansClientEventNotifier : IEventNotifier
{
private readonly IEventConsumerRegistryGrain eventConsumerRegistryGrain;
public OrleansClientEventNotifier(IClusterClient orleans)
{
Guard.NotNull(orleans, nameof(orleans));
eventConsumerRegistryGrain = orleans.GetGrain<IEventConsumerRegistryGrain>("Default");
}
public void NotifyEventsStored(string streamName)
{
eventConsumerRegistryGrain.ActivateAsync(streamName);
}
}
}

8
src/Squidex.Infrastructure/CQRS/Events/Orleans/OrleansSiloEventNotifier.cs → src/Squidex.Infrastructure/CQRS/Events/Orleans/OrleansEventNotifier.cs

@ -11,15 +11,15 @@ using Squidex.Infrastructure.CQRS.Events.Orleans.Grains;
namespace Squidex.Infrastructure.CQRS.Events.Orleans
{
public sealed class OrleansSiloEventNotifier : IEventNotifier
public sealed class OrleansEventNotifier : IEventNotifier
{
private readonly IEventConsumerRegistryGrain eventConsumerRegistryGrain;
public OrleansSiloEventNotifier(IGrainFactory orleans)
public OrleansEventNotifier(IGrainFactory factory)
{
Guard.NotNull(orleans, nameof(orleans));
Guard.NotNull(factory, nameof(factory));
eventConsumerRegistryGrain = orleans.GetGrain<IEventConsumerRegistryGrain>("Default");
eventConsumerRegistryGrain = factory.GetGrain<IEventConsumerRegistryGrain>("Default");
}
public void NotifyEventsStored(string streamName)

13
src/Squidex.Infrastructure/Log/ConsoleLogChannel.cs

@ -22,7 +22,18 @@ namespace Squidex.Infrastructure.Log
public void Log(SemanticLogLevel logLevel, string message)
{
processor.EnqueueMessage(new LogMessageEntry { Message = message, IsError = logLevel >= SemanticLogLevel.Error });
var color = 0;
if (logLevel == SemanticLogLevel.Warning)
{
color = 0xffff00;
}
else if (logLevel >= SemanticLogLevel.Error)
{
color = 0xff0000;
}
processor.EnqueueMessage(new LogMessageEntry { Message = message, Color = color });
}
}
}

2
src/Squidex.Infrastructure/Log/FileChannel.cs

@ -31,7 +31,7 @@ namespace Squidex.Infrastructure.Log
public void Log(SemanticLogLevel logLevel, string message)
{
processor.EnqueueMessage(new LogMessageEntry { Message = message, IsError = logLevel >= SemanticLogLevel.Error });
processor.EnqueueMessage(new LogMessageEntry { Message = message });
}
public void Connect()

4
src/Squidex.Infrastructure/Log/Internal/AnsiLogConsole.cs

@ -19,9 +19,9 @@ namespace Squidex.Infrastructure.Log.Internal
this.logToStdError = logToStdError;
}
public void WriteLine(bool isError, string message)
public void WriteLine(int color, string message)
{
if (isError && logToStdError)
if (color != 0 && logToStdError)
{
Console.Error.WriteLine(message);
}

4
src/Squidex.Infrastructure/Log/Internal/ConsoleLogProcessor.cs

@ -24,7 +24,7 @@ namespace Squidex.Infrastructure.Log.Internal
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
console = new WindowsLogConsole(false);
console = new WindowsLogConsole(true);
}
else
{
@ -43,7 +43,7 @@ namespace Squidex.Infrastructure.Log.Internal
{
foreach (var entry in messageQueue.GetConsumingEnumerable())
{
console.WriteLine(entry.IsError, entry.Message);
console.WriteLine(entry.Color, entry.Message);
}
}

2
src/Squidex.Infrastructure/Log/Internal/IConsole.cs

@ -10,6 +10,6 @@ namespace Squidex.Infrastructure.Log.Internal
{
public interface IConsole
{
void WriteLine(bool isError, string message);
void WriteLine(int color, string message);
}
}

2
src/Squidex.Infrastructure/Log/Internal/LogMessageEntry.cs

@ -10,7 +10,7 @@ namespace Squidex.Infrastructure.Log.Internal
{
public struct LogMessageEntry
{
public bool IsError;
public int Color;
public string Message;
}

13
src/Squidex.Infrastructure/Log/Internal/WindowsLogConsole.cs

@ -19,13 +19,20 @@ namespace Squidex.Infrastructure.Log.Internal
this.logToStdError = logToStdError;
}
public void WriteLine(bool isError, string message)
public void WriteLine(int color, string message)
{
if (isError)
if (color != 0)
{
try
{
Console.ForegroundColor = ConsoleColor.Red;
if (color == 0xffff00)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
}
if (logToStdError)
{

1
src/Squidex/AppServices.cs

@ -11,7 +11,6 @@ using Microsoft.Extensions.DependencyInjection;
using Squidex.Config;
using Squidex.Config.Domain;
using Squidex.Config.Identity;
using Squidex.Config.Orleans;
using Squidex.Config.Swagger;
using Squidex.Config.Web;

1
src/Squidex/Config/Domain/InfrastructureServices.cs

@ -21,7 +21,6 @@ using Squidex.Infrastructure.Assets.ImageSharp;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Events.Orleans;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.UsageTracking;
using Squidex.Pipeline;

8
src/Squidex/Config/Domain/ReadServices.cs

@ -25,10 +25,12 @@ using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations;
using Squidex.Domain.Apps.Read.State;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Events.Orleans;
using Squidex.Pipeline;
namespace Squidex.Config.Domain
@ -77,9 +79,15 @@ namespace Squidex.Config.Domain
services.AddSingleton<NoopAppPlanBillingManager>()
.As<IAppPlanBillingManager>();
services.AddSingleton<OrleansEventNotifier>()
.As<IEventNotifier>();
services.AddSingleton<RuleDequeuer>()
.As<IExternalSystem>();
services.AddSingleton<AppStateEventConsumer>()
.As<IEventConsumer>();
services.AddSingleton<RuleEnqueuer>()
.As<IEventConsumer>();

5
src/Squidex/Config/Domain/SerializationServices.cs

@ -29,6 +29,11 @@ namespace Squidex.Config.Domain
private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings();
private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry);
public static JsonSerializerSettings DefaultJsonSettings
{
get { return SerializerSettings; }
}
private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
{
settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry);

8
src/Squidex/Config/Orleans/ClientServices.cs

@ -9,9 +9,8 @@
using Microsoft.Extensions.DependencyInjection;
using Orleans;
using Orleans.Runtime.Configuration;
using Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations;
using Squidex.Domain.Users.DataProtection.Orleans.Grains.Implementations;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Events.Orleans;
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation;
namespace Squidex.Config.Orleans
@ -20,13 +19,14 @@ namespace Squidex.Config.Orleans
{
public static void AddAppClient(this IServiceCollection services)
{
services.AddSingleton<OrleansClientEventNotifier>()
.As<IEventNotifier>();
services.AddSingleton(c => c.GetRequiredService<IClusterClient>())
.As<IGrainFactory>();
services.AddSingleton(c =>
{
var client = new ClientBuilder()
.UseConfiguration(ClientConfiguration.LocalhostSilo())
.AddApplicationPartsFromReferences(typeof(AppStateGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly)
.Build();

25
src/Squidex/Config/Orleans/SiloServices.cs

@ -9,16 +9,26 @@
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Orleans;
using Orleans.Providers;
using Orleans.Providers.MongoDB.StorageProviders;
using Orleans.Runtime.Configuration;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Events.Orleans;
using Squidex.Config.Domain;
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains;
namespace Squidex.Config.Orleans
{
public static class SiloServices
{
public sealed class CustomMongoDbStorageProvider : MongoStorageProvider
{
protected override JsonSerializerSettings ReturnSerializerSettings(IProviderRuntime providerRuntime, IProviderConfiguration config)
{
return SerializationServices.DefaultJsonSettings;
}
}
public static void AddAppSiloServices(this IServiceCollection services, IConfiguration config)
{
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration");
@ -41,7 +51,7 @@ namespace Squidex.Config.Orleans
{
if (clusterConfiguration != null)
{
clusterConfiguration.AddMongoDBStorageProvider("Default", c =>
clusterConfiguration.AddMongoDBStorageProvider<CustomMongoDbStorageProvider>("Default", c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
@ -57,21 +67,21 @@ namespace Squidex.Config.Orleans
});
}
services.UseMongoDBGatewayListProvider(c =>
services.AddMongoDBGatewayListProvider(c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
c.DatabaseName = mongoDatabaseName;
});
services.UseMongoDBMembershipTable(c =>
services.AddMongoDBMembershipTable(c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
c.DatabaseName = mongoDatabaseName;
});
services.UseMongoDBReminders(c =>
services.AddMongoDBReminders(c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
@ -79,9 +89,6 @@ namespace Squidex.Config.Orleans
});
}
});
services.AddSingleton<OrleansSiloEventNotifier>()
.As<IEventNotifier>();
}
}
}

2
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -62,7 +62,7 @@ namespace Squidex.Controllers.ContentApi.Generator
foreach (var schema in schemas.Where(x => x.IsPublished).Select(x => x.SchemaDef))
{
new SchemaSwaggerGenerator(document, appBasePath, schema, AppendSchema, app.PartitionResolver).GenerateSchemaOperations();
new SchemaSwaggerGenerator(document, appBasePath, schema, AppendSchema, app.PartitionResolver()).GenerateSchemaOperations();
}
}

2
src/Squidex/Program.cs

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Hosting;
using Orleans.Hosting;
using Orleans.Runtime.Configuration;
using Squidex.Config.Orleans;
using Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations;
using Squidex.Domain.Users.DataProtection.Orleans.Grains.Implementations;
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation;
using Squidex.Infrastructure.Log.Adapter;
@ -23,6 +24,7 @@ namespace Squidex
public static void Main(string[] args)
{
var silo = new SiloHostBuilder()
.AddApplicationPartsFromReferences(typeof(AppStateGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly)
.UseConfiguration(ClusterConfiguration.LocalhostPrimarySilo(33333))

2
src/Squidex/Squidex.csproj

@ -73,7 +73,7 @@
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="11.10.0" />
<PackageReference Include="OpenCover" Version="4.6.519" />
<PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.0-preview1" />
<PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.0-preview2" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" />
<PackageReference Include="ReportGenerator" Version="3.0.2" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />

19
tests/Benchmarks/Benchmarks.csproj

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj" />
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

27
tests/Benchmarks/IBenchmark.cs

@ -1,27 +0,0 @@
// ==========================================================================
// IBenchmark.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Benchmarks
{
public interface IBenchmark
{
string Id { get; }
string Name { get; }
void Initialize();
void RunInitialize();
long Run();
void RunCleanup();
void Cleanup();
}
}

95
tests/Benchmarks/Program.cs

@ -1,95 +0,0 @@
// ==========================================================================
// Program.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Benchmarks.Tests;
namespace Benchmarks
{
public static class Program
{
private static readonly List<IBenchmark> Benchmarks = new List<IBenchmark>
{
new AppendToEventStore(),
new AppendToEventStoreWithManyWriters(),
new HandleEvents(),
new HandleEventsWithManyWriters()
};
public static void Main(string[] args)
{
var id = args.Length > 0 ? args[0] : string.Empty;
var benchmark = Benchmarks.Find(x => x.Id == id);
if (benchmark == null)
{
Console.WriteLine($"'{id}' is not a valid benchmark, please try: ");
var longestId = Benchmarks.Max(x => x.Id.Length);
foreach (var b in Benchmarks)
{
Console.WriteLine($" * {b.Id.PadRight(longestId)}: {b.Name}");
}
}
else
{
const int numRuns = 3;
try
{
var elapsed = 0d;
var count = 0L;
Console.WriteLine($"{benchmark.Name}: Initialized");
benchmark.Initialize();
for (var run = 0; run < numRuns; run++)
{
try
{
benchmark.RunInitialize();
var watch = Stopwatch.StartNew();
count += benchmark.Run();
watch.Stop();
elapsed += watch.ElapsedMilliseconds;
Console.WriteLine($"{benchmark.Name}: Run {run + 1} finished");
}
finally
{
benchmark.RunCleanup();
}
}
var averageElapsed = TimeSpan.FromMilliseconds(elapsed / numRuns);
var averageSeconds = Math.Round(count / (numRuns * averageElapsed.TotalSeconds), 2);
Console.WriteLine($"{benchmark.Name}: Completed after {averageElapsed}, {averageSeconds} items/s");
}
catch (Exception e)
{
Console.WriteLine($"Benchmark failed with '{e.Message}'");
}
finally
{
benchmark.Cleanup();
}
}
}
}
}

8
tests/Benchmarks/Properties/launchSettings.json

@ -1,8 +0,0 @@
{
"profiles": {
"Benchmarks": {
"commandName": "Project",
"commandLineArgs": "handleEvents"
}
}
}

74
tests/Benchmarks/Tests/AppendToEventStore.cs

@ -1,74 +0,0 @@
// ==========================================================================
// AppendToEventStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Benchmarks.Utils;
using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Benchmarks.Tests
{
public sealed class AppendToEventStore : IBenchmark
{
private IMongoClient mongoClient;
private IMongoDatabase mongoDatabase;
private IEventStore eventStore;
public string Id
{
get { return "appendToEventStore"; }
}
public string Name
{
get { return "Append events"; }
}
public void Initialize()
{
mongoClient = new MongoClient("mongodb://localhost");
}
public void RunInitialize()
{
mongoDatabase = mongoClient.GetDatabase(Guid.NewGuid().ToString());
eventStore = new MongoEventStore(mongoDatabase, new DefaultEventNotifier(new InMemoryPubSub()));
}
public long Run()
{
const long numCommits = 100;
const long numStreams = 20;
for (var streamId = 0; streamId < numStreams; streamId++)
{
var eventOffset = -1;
var streamName = streamId.ToString();
for (var commitId = 0; commitId < numCommits; commitId++)
{
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventOffset, new[] { Helper.CreateEventData() }).Wait();
eventOffset++;
}
}
return numCommits * numStreams;
}
public void RunCleanup()
{
mongoClient.DropDatabase(mongoDatabase.DatabaseNamespace.DatabaseName);
}
public void Cleanup()
{
}
}
}

73
tests/Benchmarks/Tests/AppendToEventStoreWithManyWriters.cs

@ -1,73 +0,0 @@
// ==========================================================================
// AppendToEventStoreWithManyWriters.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Benchmarks.Utils;
using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Benchmarks.Tests
{
public sealed class AppendToEventStoreWithManyWriters : IBenchmark
{
private IMongoClient mongoClient;
private IMongoDatabase mongoDatabase;
private IEventStore eventStore;
public string Id
{
get { return "appendToEventStoreParallel"; }
}
public string Name
{
get { return "Append events parallel"; }
}
public void Initialize()
{
mongoClient = new MongoClient("mongodb://localhost");
}
public void RunInitialize()
{
mongoDatabase = mongoClient.GetDatabase(Guid.NewGuid().ToString());
eventStore = new MongoEventStore(mongoDatabase, new DefaultEventNotifier(new InMemoryPubSub()));
}
public long Run()
{
const long numCommits = 200;
const long numStreams = 100;
Parallel.For(0, numStreams, streamId =>
{
var streamName = streamId.ToString();
for (var commitId = 0; commitId < numCommits; commitId++)
{
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, new[] { Helper.CreateEventData() }).Wait();
}
});
return numCommits * numStreams;
}
public void RunCleanup()
{
mongoClient.DropDatabase(mongoDatabase.DatabaseNamespace.DatabaseName);
}
public void Cleanup()
{
}
}
}

100
tests/Benchmarks/Tests/HandleEvents.cs

@ -1,100 +0,0 @@
// ==========================================================================
// HandleEvents.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Benchmarks.Tests.TestData;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Events.Actors;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
namespace Benchmarks.Tests
{
public sealed class HandleEvents : IBenchmark
{
private const int NumEvents = 5000;
private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry().Map(typeof(MyEvent));
private readonly EventDataFormatter formatter;
private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
private IMongoClient mongoClient;
private IMongoDatabase mongoDatabase;
private IEventStore eventStore;
private IEventNotifier eventNotifier;
private IEventConsumerInfoRepository eventConsumerInfos;
private EventConsumerActor eventConsumerActor;
private MyEventConsumer eventConsumer;
public string Id
{
get { return "handleEvents"; }
}
public string Name
{
get { return "Handle Events"; }
}
public HandleEvents()
{
serializerSettings.Converters.Add(new PropertiesBagConverter());
formatter = new EventDataFormatter(typeNameRegistry, serializerSettings);
}
public void Initialize()
{
mongoClient = new MongoClient("mongodb://localhost");
}
public void RunInitialize()
{
mongoDatabase = mongoClient.GetDatabase(Guid.NewGuid().ToString());
var log = new SemanticLog(new ILogChannel[0], new ILogAppender[0], () => new JsonLogWriter(Formatting.Indented, true));
eventConsumerInfos = new MongoEventConsumerInfoRepository(mongoDatabase);
eventConsumer = new MyEventConsumer(NumEvents);
eventNotifier = new DefaultEventNotifier(new InMemoryPubSub());
eventStore = new MongoEventStore(mongoDatabase, eventNotifier);
eventConsumerActor = new EventConsumerActor(formatter, eventStore, eventConsumerInfos, log);
eventConsumerActor.SubscribeAsync(eventConsumer);
}
public long Run()
{
var streamName = Guid.NewGuid().ToString();
for (var eventId = 0; eventId < NumEvents; eventId++)
{
var eventData = formatter.ToEventData(new Envelope<IEvent>(new MyEvent { EventNumber = eventId + 1 }), Guid.NewGuid());
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventId - 1, new[] { eventData }).Wait();
}
eventConsumer.WaitAndVerify();
return NumEvents;
}
public void RunCleanup()
{
mongoClient.DropDatabase(mongoDatabase.DatabaseNamespace.DatabaseName);
eventConsumerActor.Dispose();
}
public void Cleanup()
{
}
}
}

107
tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs

@ -1,107 +0,0 @@
// ==========================================================================
// HandleEventsWithManyWriters.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Benchmarks.Tests.TestData;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Events.Actors;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
namespace Benchmarks.Tests
{
public sealed class HandleEventsWithManyWriters : IBenchmark
{
private const int NumCommits = 200;
private const int NumStreams = 10;
private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry().Map(typeof(MyEvent));
private readonly EventDataFormatter formatter;
private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
private IMongoClient mongoClient;
private IMongoDatabase mongoDatabase;
private IEventStore eventStore;
private IEventNotifier eventNotifier;
private IEventConsumerInfoRepository eventConsumerInfos;
private EventConsumerActor eventConsumerActor;
private MyEventConsumer eventConsumer;
public string Id
{
get { return "handleEventsParallel"; }
}
public string Name
{
get { return "Handle events parallel"; }
}
public HandleEventsWithManyWriters()
{
serializerSettings.Converters.Add(new PropertiesBagConverter());
formatter = new EventDataFormatter(typeNameRegistry, serializerSettings);
}
public void Initialize()
{
mongoClient = new MongoClient("mongodb://localhost");
}
public void RunInitialize()
{
mongoDatabase = mongoClient.GetDatabase(Guid.NewGuid().ToString());
var log = new SemanticLog(new ILogChannel[0], new ILogAppender[0], () => new JsonLogWriter(Formatting.Indented, true));
eventConsumerInfos = new MongoEventConsumerInfoRepository(mongoDatabase);
eventConsumer = new MyEventConsumer(NumStreams * NumCommits);
eventNotifier = new DefaultEventNotifier(new InMemoryPubSub());
eventStore = new MongoEventStore(mongoDatabase, eventNotifier);
eventConsumerActor = new EventConsumerActor(formatter, eventStore, eventConsumerInfos, log);
eventConsumerActor.SubscribeAsync(eventConsumer);
}
public long Run()
{
Parallel.For(0, NumStreams, streamId =>
{
var eventOffset = -1;
var streamName = streamId.ToString();
for (var commitId = 0; commitId < NumCommits; commitId++)
{
var eventData = formatter.ToEventData(new Envelope<IEvent>(new MyEvent()), Guid.NewGuid());
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventOffset - 1, new[] { eventData }).Wait();
eventOffset++;
}
});
eventConsumer.WaitAndVerify();
return NumStreams * NumCommits;
}
public void RunCleanup()
{
mongoClient.DropDatabase(mongoDatabase.DatabaseNamespace.DatabaseName);
eventConsumerActor.Dispose();
}
public void Cleanup()
{
}
}
}

19
tests/Benchmarks/Tests/TestData/MyEvent.cs

@ -1,19 +0,0 @@
// ==========================================================================
// MyEvent.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Benchmarks.Tests.TestData
{
[TypeName("MyEvent")]
public sealed class MyEvent : IEvent
{
public int EventNumber { get; set; }
}
}

79
tests/Benchmarks/Tests/TestData/MyEventConsumer.cs

@ -1,79 +0,0 @@
// ==========================================================================
// MyEventConsumer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Benchmarks.Tests.TestData
{
public sealed class MyEventConsumer : IEventConsumer
{
private readonly TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
private readonly int numEvents;
public List<int> EventNumbers { get; } = new List<int>();
public string Name
{
get { return typeof(MyEventConsumer).Name; }
}
public string EventsFilter
{
get { return string.Empty; }
}
public MyEventConsumer(int numEvents)
{
this.numEvents = numEvents;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public void WaitAndVerify()
{
completion.Task.Wait();
if (EventNumbers.Count != numEvents)
{
throw new InvalidOperationException($"{EventNumbers.Count} Events have been handled");
}
for (var i = 0; i < EventNumbers.Count; i++)
{
var value = EventNumbers[i];
if (value != i + 1)
{
throw new InvalidOperationException($"Event[{i}] != value");
}
}
}
public Task On(Envelope<IEvent> @event)
{
if (@event.Payload is MyEvent myEvent)
{
EventNumbers.Add(myEvent.EventNumber);
if (myEvent.EventNumber == numEvents)
{
completion.SetResult(true);
}
}
return TaskHelper.Done;
}
}
}

1
tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs

@ -9,7 +9,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Infrastructure.Tasks

Loading…
Cancel
Save