From 6250c6f60a49c18eb80e14445b622832c47d67d5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 10 Feb 2017 23:04:50 +0100 Subject: [PATCH] Redis systems --- Squidex.sln | 14 +-- .../EventStore/MongoEventStore.cs | 22 +---- .../EventStore/MongoStreamsRepository.cs | 11 --- .../MongoRepositoryBase.cs | 14 ++- .../RabbitMqEventBus.cs | 2 +- .../InfrastructureErrors.cs | 19 ++++ .../RedisEventNotifier.cs | 63 ++++++++++++ .../RedisExternalSystem.cs | 37 +++++++ .../RedisInvalidatingCache.cs | 56 +++++++++++ .../RedisInvalidator.cs | 96 +++++++++++++++++++ .../Squidex.Infrastructure.Redis.xproj | 21 ++++ .../WrapperCacheEntry.cs | 83 ++++++++++++++++ src/Squidex.Infrastructure.Redis/project.json | 27 ++++++ .../CQRS/Events/EventReceiver.cs | 26 +++-- .../CQRS/Events/InMemoryEventNotifier.cs | 28 ++++++ .../Timers/CompletionTimer.cs | 8 +- .../Apps/MongoAppEntity.cs | 6 +- .../Apps/MongoAppRepository.cs | 14 ++- .../Apps/MongoAppRepository_EventHandling.cs | 6 +- .../MongoDbStoresExternalSystem.cs | 38 -------- .../Schemas/MongoSchemaRepository.cs | 7 +- .../MongoSchemaRepository_EventHandling.cs | 8 +- .../Utils/MongoDbConsumerWrapper.cs | 14 +-- .../Apps/Repositories/IAppRepository.cs | 2 + .../Schemas/Repositories/ISchemaRepository.cs | 2 + src/Squidex/Config/Domain/ClusterModule.cs | 87 +++++++++++++++++ src/Squidex/Config/Domain/EventBusModule.cs | 81 ---------------- src/Squidex/Config/Domain/EventStoreModule.cs | 2 +- .../Config/Domain/InfrastructureModule.cs | 10 +- src/Squidex/Config/Domain/ReadModule.cs | 1 - .../Config/Domain/StoreMongoDbModule.cs | 38 ++++++-- src/Squidex/Config/Domain/Usages.cs | 27 +++++- src/Squidex/Program.cs | 30 +----- src/Squidex/Startup.cs | 2 +- src/Squidex/project.json | 3 +- 35 files changed, 662 insertions(+), 243 deletions(-) delete mode 100644 src/Squidex.Infrastructure.MongoDb/EventStore/MongoStreamsRepository.cs create mode 100644 src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs create mode 100644 src/Squidex.Infrastructure.Redis/RedisEventNotifier.cs create mode 100644 src/Squidex.Infrastructure.Redis/RedisExternalSystem.cs create mode 100644 src/Squidex.Infrastructure.Redis/RedisInvalidatingCache.cs create mode 100644 src/Squidex.Infrastructure.Redis/RedisInvalidator.cs create mode 100644 src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.xproj create mode 100644 src/Squidex.Infrastructure.Redis/WrapperCacheEntry.cs create mode 100644 src/Squidex.Infrastructure.Redis/project.json create mode 100644 src/Squidex.Infrastructure/CQRS/Events/InMemoryEventNotifier.cs delete mode 100644 src/Squidex.Read.MongoDb/MongoDbStoresExternalSystem.cs create mode 100644 src/Squidex/Config/Domain/ClusterModule.cs delete mode 100644 src/Squidex/Config/Domain/EventBusModule.cs diff --git a/Squidex.sln b/Squidex.sln index 3816dc81f..fbf5f53e3 100644 --- a/Squidex.sln +++ b/Squidex.sln @@ -36,10 +36,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Core.Tests", "tests EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.MongoDb", "src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.xproj", "{6A811927-3C37-430A-90F4-503E37123956}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.RabbitMq", "src\Squidex.Infrastructure.RabbitMq\Squidex.Infrastructure.RabbitMq.xproj", "{3C9BA12D-F5F2-4355-8D30-8289E4D0752D}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Read.Tests", "tests\Squidex.Read.Tests\Squidex.Read.Tests.xproj", "{8B074219-F69A-4E41-83C6-12EE1E647779}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.Redis", "src\Squidex.Infrastructure.Redis\Squidex.Infrastructure.Redis.xproj", "{D7166C56-178A-4457-B56A-C615C7450DEE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -90,14 +90,14 @@ Global {6A811927-3C37-430A-90F4-503E37123956}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A811927-3C37-430A-90F4-503E37123956}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A811927-3C37-430A-90F4-503E37123956}.Release|Any CPU.Build.0 = Release|Any CPU - {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Release|Any CPU.Build.0 = Release|Any CPU {8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.Build.0 = Release|Any CPU + {D7166C56-178A-4457-B56A-C615C7450DEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7166C56-178A-4457-B56A-C615C7450DEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7166C56-178A-4457-B56A-C615C7450DEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7166C56-178A-4457-B56A-C615C7450DEE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -113,7 +113,7 @@ Global {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} - {3C9BA12D-F5F2-4355-8D30-8289E4D0752D} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {8B074219-F69A-4E41-83C6-12EE1E647779} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} + {D7166C56-178A-4457-B56A-C615C7450DEE} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} EndGlobalSection EndGlobal diff --git a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs index fc54bfedb..46ffffa71 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs @@ -22,7 +22,7 @@ using Squidex.Infrastructure.Reflection; namespace Squidex.Infrastructure.MongoDb.EventStore { - public class MongoEventStore : MongoRepositoryBase, IEventStore, IExternalSystem + public class MongoEventStore : MongoRepositoryBase, IEventStore { private const int Retries = 500; private readonly IEventNotifier notifier; @@ -56,18 +56,6 @@ namespace Squidex.Infrastructure.MongoDb.EventStore eventsOffsetIndex = indexNames[0]; } - public void CheckConnection() - { - try - { - Database.ListCollections(); - } - catch (Exception e) - { - throw new ConfigurationException($"MongoDb Event Store failed to connect to database {Database.DatabaseNamespace.DatabaseName}", e); - } - } - public IObservable GetEventsAsync(string streamName) { Guard.NotNullOrEmpty(streamName, nameof(streamName)); @@ -100,14 +88,14 @@ namespace Squidex.Infrastructure.MongoDb.EventStore { foreach (var @event in commit.Events) { - if (position >= lastReceivedPosition) + position++; + + if (position > lastReceivedPosition) { var eventData = SimpleMapper.Map(@event, new EventData()); observer.OnNext(new StoredEvent(position, eventData)); } - - position++; } }, ct); }); @@ -187,7 +175,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore if (document != null) { - return document["EventsOffset"].ToInt64(); + return document["EventStreamOffset"].ToInt64(); } return -1; diff --git a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoStreamsRepository.cs b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoStreamsRepository.cs deleted file mode 100644 index b948e0e4e..000000000 --- a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoStreamsRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure.MongoDb.EventStore -{ - public class MongoStreamsRepository - { - } -} diff --git a/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs b/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs index 919d9d5bf..bf74dead1 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs @@ -13,7 +13,7 @@ using MongoDB.Driver; namespace Squidex.Infrastructure.MongoDb { - public abstract class MongoRepositoryBase + public abstract class MongoRepositoryBase : IExternalSystem { private const string CollectionFormat = "{0}Set"; private Lazy> mongoCollection; @@ -143,5 +143,17 @@ namespace Squidex.Infrastructure.MongoDb return false; } } + + public void CheckConnection() + { + try + { + Database.ListCollections(); + } + catch (Exception e) + { + throw new ConfigurationException($"MongoDb connection failed to connect to database {Database.DatabaseNamespace.DatabaseName}", e); + } + } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventBus.cs b/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventBus.cs index 5fbbb1468..db032c665 100644 --- a/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventBus.cs +++ b/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventBus.cs @@ -17,7 +17,7 @@ using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Infrastructure.RabbitMq { - public sealed class RabbitMqEventBus : DisposableObject, IEventPublisher, IEventStream, IExternalSystem + public sealed class RabbitMqEventBus : DisposableObject, IExternalSystem { private readonly bool isPersistent; private readonly string queueName; diff --git a/src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs b/src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs new file mode 100644 index 000000000..28bec8176 --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// InfrastructureErrors.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Microsoft.Extensions.Logging; + +namespace Squidex.Infrastructure.Redis +{ + public class InfrastructureErrors + { + public static readonly EventId InvalidatingReceivedFailed = new EventId(10001, "InvalidingReceivedFailed"); + + public static readonly EventId InvalidatingPublishedFailed = new EventId(10002, "InvalidatingPublishedFailed"); + } +} diff --git a/src/Squidex.Infrastructure.Redis/RedisEventNotifier.cs b/src/Squidex.Infrastructure.Redis/RedisEventNotifier.cs new file mode 100644 index 000000000..d9a753233 --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/RedisEventNotifier.cs @@ -0,0 +1,63 @@ +// ========================================================================== +// RedisEventNotifier.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Microsoft.Extensions.Logging; +using Squidex.Infrastructure.CQRS.Events; +using StackExchange.Redis; + +namespace Squidex.Infrastructure.Redis +{ + public sealed class RedisEventNotifier : IEventNotifier + { + private const string Channel = "SquidexEventNotifications"; + private readonly InMemoryEventNotifier inMemoryNotifier = new InMemoryEventNotifier(); + private readonly ISubscriber subscriber; + private readonly ILogger logger; + + public RedisEventNotifier(IConnectionMultiplexer redis, ILogger logger) + { + Guard.NotNull(redis, nameof(redis)); + Guard.NotNull(logger, nameof(logger)); + + subscriber = redis.GetSubscriber(); + subscriber.Subscribe(Channel, (channel, value) => HandleInvalidation()); + + this.logger = logger; + } + + private void HandleInvalidation() + { + try + { + inMemoryNotifier.NotifyEventsStored(); + } + catch (Exception e) + { + logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, e, "Failed to receive invalidation message."); + } + } + + public void NotifyEventsStored() + { + try + { + subscriber.Publish(Channel, RedisValue.Null); + } + catch (Exception e) + { + logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, e, "Failed to send invalidation message"); + } + } + + public void Subscribe(Action handler) + { + inMemoryNotifier.Subscribe(handler); + } + } +} diff --git a/src/Squidex.Infrastructure.Redis/RedisExternalSystem.cs b/src/Squidex.Infrastructure.Redis/RedisExternalSystem.cs new file mode 100644 index 000000000..161bf942c --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/RedisExternalSystem.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// RedisExternalSystem.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using StackExchange.Redis; + +namespace Squidex.Infrastructure.Redis +{ + public class RedisExternalSystem + { + private readonly IConnectionMultiplexer redis; + + public RedisExternalSystem(IConnectionMultiplexer redis) + { + Guard.NotNull(redis, nameof(redis)); + + this.redis = redis; + } + + public void CheckConnection() + { + try + { + redis.GetStatus(); + } + catch (Exception e) + { + throw new ConfigurationException($"Redis connection failed to connect to database {redis.Configuration}", e); + } + } + } +} diff --git a/src/Squidex.Infrastructure.Redis/RedisInvalidatingCache.cs b/src/Squidex.Infrastructure.Redis/RedisInvalidatingCache.cs new file mode 100644 index 000000000..f8d0f2269 --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/RedisInvalidatingCache.cs @@ -0,0 +1,56 @@ +// ========================================================================== +// RedisInvalidatingCache.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; + +namespace Squidex.Infrastructure.Redis +{ + public class RedisInvalidatingCache : IMemoryCache + { + private readonly IMemoryCache inner; + private readonly RedisInvalidator invalidator; + + public RedisInvalidatingCache(IMemoryCache inner, IConnectionMultiplexer redis, ILogger logger) + { + Guard.NotNull(redis, nameof(redis)); + Guard.NotNull(inner, nameof(inner)); + Guard.NotNull(logger, nameof(logger)); + + this.inner = inner; + + invalidator = new RedisInvalidator(redis, inner, logger); + } + + public void Dispose() + { + inner.Dispose(); + } + + public bool TryGetValue(object key, out object value) + { + return inner.TryGetValue(key, out value); + } + + public void Remove(object key) + { + inner.Remove(key); + + if (key is string) + { + invalidator.Invalidate(key.ToString()); + } + } + + public ICacheEntry CreateEntry(object key) + { + return new WrapperCacheEntry(inner.CreateEntry(key), invalidator); + } + } +} diff --git a/src/Squidex.Infrastructure.Redis/RedisInvalidator.cs b/src/Squidex.Infrastructure.Redis/RedisInvalidator.cs new file mode 100644 index 000000000..89e630fa5 --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/RedisInvalidator.cs @@ -0,0 +1,96 @@ +// ========================================================================== +// RedisInvalidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; + +// ReSharper disable InvertIf +// ReSharper disable ArrangeThisQualifier + +namespace Squidex.Infrastructure.Redis +{ + internal sealed class RedisInvalidator + { + private const string Channel = "SquidexChannelInvalidations"; + private readonly Guid instanceId = Guid.NewGuid(); + private readonly ISubscriber subscriber; + private readonly IMemoryCache cache; + private readonly ILogger logger; + private int invalidationsReceived; + + public int InvalidationsReceived + { + get + { + return invalidationsReceived; + } + } + + public RedisInvalidator(IConnectionMultiplexer redis, IMemoryCache cache, ILogger logger) + { + this.cache = cache; + + subscriber = redis.GetSubscriber(); + subscriber.Subscribe(Channel, (channel, value) => HandleInvalidation(value)); + + this.logger = logger; + } + + private void HandleInvalidation(string value) + { + try + { + if (string.IsNullOrWhiteSpace(value)) + { + return; + } + + var parts = value.Split('#'); + + if (parts.Length != 2) + { + return; + } + + Guid sender; + + if (!Guid.TryParse(parts[0], out sender)) + { + return; + } + + if (sender != instanceId) + { + invalidationsReceived++; + + cache.Remove(parts[1]); + } + } + catch (Exception e) + { + logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, e, "Failed to receive invalidation message."); + } + } + + public void Invalidate(string key) + { + try + { + var message = string.Join("#", instanceId.ToString()); + + subscriber.Publish(Channel, message); + } + catch (Exception e) + { + logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, e, "Failed to send invalidation message {0}", key); + } + } + } +} diff --git a/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.xproj b/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.xproj new file mode 100644 index 000000000..27ec46604 --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + d7166c56-178a-4457-b56a-c615c7450dee + Squidex.Infrastructure.Redis + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/Squidex.Infrastructure.Redis/WrapperCacheEntry.cs b/src/Squidex.Infrastructure.Redis/WrapperCacheEntry.cs new file mode 100644 index 000000000..4f4c02458 --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/WrapperCacheEntry.cs @@ -0,0 +1,83 @@ +// ========================================================================== +// WrapperCacheEntry.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; + +namespace Squidex.Infrastructure.Redis +{ + internal sealed class WrapperCacheEntry : ICacheEntry + { + private readonly ICacheEntry inner; + private readonly RedisInvalidator invalidator; + + public object Key + { + get { return inner.Key; } + } + + public IList ExpirationTokens + { + get { return inner.ExpirationTokens; } + } + + public IList PostEvictionCallbacks + { + get { return inner.PostEvictionCallbacks; } + } + + public DateTimeOffset? AbsoluteExpiration + { + get { return inner.AbsoluteExpiration; } + set { inner.AbsoluteExpiration = value; } + } + + public TimeSpan? AbsoluteExpirationRelativeToNow + { + get { return inner.AbsoluteExpirationRelativeToNow; } + set { inner.AbsoluteExpirationRelativeToNow = value; } + } + + public TimeSpan? SlidingExpiration + { + get { return inner.SlidingExpiration; } + set { inner.SlidingExpiration = value; } + } + + public CacheItemPriority Priority + { + get { return inner.Priority; } + set { inner.Priority = value; } + } + + public object Value + { + get { return inner.Value; } + set { inner.Value = value; } + } + + public WrapperCacheEntry(ICacheEntry inner, RedisInvalidator invalidator) + { + this.inner = inner; + + this.invalidator = invalidator; + } + + public void Dispose() + { + if (Key is string) + { + invalidator.Invalidate(Key?.ToString()); + } + + inner.Dispose(); + } + } +} diff --git a/src/Squidex.Infrastructure.Redis/project.json b/src/Squidex.Infrastructure.Redis/project.json new file mode 100644 index 000000000..c6bb7bcef --- /dev/null +++ b/src/Squidex.Infrastructure.Redis/project.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Autofac": "4.3.0", + "Microsoft.Extensions.Caching.Abstractions": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Newtonsoft.Json": "9.0.2-beta2", + "NodaTime": "2.0.0-beta20170123", + "Squidex.Infrastructure": "1.0.0-*", + "StackExchange.Redis.StrongName": "1.2.0", + "System.Linq": "4.3.0", + "System.Reactive": "3.1.1", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Security.Claims": "4.3.0" + }, + "frameworks": { + "netstandard1.6": { + "dependencies": { + "NETStandard.Library": "1.6.1" + }, + "imports": "dnxcore50" + } + }, + "tooling": { + "defaultNamespace": "Squidex.Infrastructure.Redis" + } +} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs index eabd950fe..645e08689 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs @@ -7,11 +7,11 @@ // ========================================================================== using System; -using System.Reactive.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Squidex.Infrastructure.Timers; +// ReSharper disable MethodSupportsCancellation // ReSharper disable ConvertIfStatementToConditionalTernaryExpression // ReSharper disable InvertIf @@ -22,7 +22,6 @@ namespace Squidex.Infrastructure.CQRS.Events private readonly EventDataFormatter formatter; private readonly IEventStore eventStore; private readonly IEventNotifier eventNotifier; - private readonly IEventCatchConsumer eventConsumer; private readonly ILogger logger; private CompletionTimer timer; @@ -30,20 +29,17 @@ namespace Squidex.Infrastructure.CQRS.Events EventDataFormatter formatter, IEventStore eventStore, IEventNotifier eventNotifier, - IEventCatchConsumer eventConsumer, ILogger logger) { Guard.NotNull(logger, nameof(logger)); Guard.NotNull(formatter, nameof(formatter)); Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventNotifier, nameof(eventNotifier)); - Guard.NotNull(eventConsumer, nameof(eventConsumer)); this.logger = logger; this.formatter = formatter; this.eventStore = eventStore; this.eventNotifier = eventNotifier; - this.eventConsumer = eventConsumer; } protected override void DisposeObject(bool disposing) @@ -54,8 +50,10 @@ namespace Squidex.Infrastructure.CQRS.Events } } - public void Subscribe(int delay = 5000) + public void Subscribe(IEventCatchConsumer eventConsumer, int delay = 5000) { + Guard.NotNull(eventConsumer, nameof(eventConsumer)); + if (timer != null) { return; @@ -70,14 +68,26 @@ namespace Squidex.Infrastructure.CQRS.Events lastReceivedPosition = await eventConsumer.GetLastHandledEventNumber(); } - await eventStore.GetEventsAsync(lastReceivedPosition).ForEachAsync(async storedEvent => + var tcs = new TaskCompletionSource(); + + eventStore.GetEventsAsync(lastReceivedPosition).Subscribe(storedEvent => { var @event = ParseEvent(storedEvent.Data); @event.SetEventNumber(storedEvent.EventNumber); - await DispatchConsumer(@event, eventConsumer, storedEvent.EventNumber); + DispatchConsumer(@event, eventConsumer, storedEvent.EventNumber).Wait(); + + lastReceivedPosition++; + }, ex => + { + tcs.SetException(ex); + }, () => + { + tcs.SetResult(true); }, ct); + + await tcs.Task; }); eventNotifier.Subscribe(timer.Trigger); diff --git a/src/Squidex.Infrastructure/CQRS/Events/InMemoryEventNotifier.cs b/src/Squidex.Infrastructure/CQRS/Events/InMemoryEventNotifier.cs new file mode 100644 index 000000000..0dbd490e7 --- /dev/null +++ b/src/Squidex.Infrastructure/CQRS/Events/InMemoryEventNotifier.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// InMemoryEventNotifier.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Reactive.Subjects; + +namespace Squidex.Infrastructure.CQRS.Events +{ + public sealed class InMemoryEventNotifier : IEventNotifier + { + private readonly Subject subject = new Subject(); + + public void NotifyEventsStored() + { + subject.OnNext(null); + } + + public void Subscribe(Action handler) + { + subject.Subscribe(_ => handler()); + } + } +} diff --git a/src/Squidex.Infrastructure/Timers/CompletionTimer.cs b/src/Squidex.Infrastructure/Timers/CompletionTimer.cs index e430353da..bcfb6b38c 100644 --- a/src/Squidex.Infrastructure/Timers/CompletionTimer.cs +++ b/src/Squidex.Infrastructure/Timers/CompletionTimer.cs @@ -35,15 +35,15 @@ namespace Squidex.Infrastructure.Timers try { await callback(disposeCancellationTokenSource.Token).ConfigureAwait(false); + + delayCancellationSource = new CancellationTokenSource(); + + await Task.Delay(delay, delayCancellationSource.Token).ConfigureAwait(false); } catch (TaskCanceledException) { Console.WriteLine("Task in TriggerTimer has been cancelled."); } - - delayCancellationSource = new CancellationTokenSource(); - - await Task.Delay(delay, delayCancellationSource.Token).ConfigureAwait(false); } } diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs index 0400a395d..6d280253c 100644 --- a/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs +++ b/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs @@ -27,15 +27,15 @@ namespace Squidex.Read.MongoDb.Apps [BsonRequired] [BsonElement] - public HashSet Languages { get; } = new HashSet(); + public HashSet Languages { get; set; } = new HashSet(); [BsonRequired] [BsonElement] - public Dictionary Clients { get; } = new Dictionary(); + public Dictionary Clients { get; set; } = new Dictionary(); [BsonRequired] [BsonElement] - public Dictionary Contributors { get; } = new Dictionary(); + public Dictionary Contributors { get; set; } = new Dictionary(); IReadOnlyCollection IAppEntity.Clients { diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs index 62fe7f90e..39a89de66 100644 --- a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs +++ b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs @@ -10,25 +10,18 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using MongoDB.Driver; -using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; using Squidex.Read.Apps; using Squidex.Read.Apps.Repositories; -using Squidex.Read.Apps.Services; namespace Squidex.Read.MongoDb.Apps { public partial class MongoAppRepository : MongoRepositoryBase, IAppRepository, IEventConsumer { - private readonly IAppProvider appProvider; - - public MongoAppRepository(IMongoDatabase database, IAppProvider appProvider) + public MongoAppRepository(IMongoDatabase database) : base(database) { - Guard.NotNull(appProvider, nameof(appProvider)); - - this.appProvider = appProvider; } protected override string CollectionName() @@ -41,6 +34,11 @@ namespace Squidex.Read.MongoDb.Apps return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name)); } + protected override MongoCollectionSettings CollectionSettings() + { + return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority }; + } + public async Task> QueryAllAsync(string subjectId) { var entities = diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs index 674e0679e..6e88f5d0d 100644 --- a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs @@ -20,6 +20,8 @@ namespace Squidex.Read.MongoDb.Apps { public partial class MongoAppRepository { + public event Action AppSaved; + public Task On(Envelope @event) { return this.DispatchActionAsync(@event.Payload, @event.Headers); @@ -32,7 +34,7 @@ namespace Squidex.Read.MongoDb.Apps SimpleMapper.Map(@event, a); }); - appProvider.Remove(headers.AggregateId()); + AppSaved?.Invoke(headers.AggregateId()); } protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers) @@ -105,7 +107,7 @@ namespace Squidex.Read.MongoDb.Apps { await Collection.UpdateAsync(headers, updater); - appProvider.Remove(headers.AggregateId()); + AppSaved?.Invoke(headers.AggregateId()); } } } diff --git a/src/Squidex.Read.MongoDb/MongoDbStoresExternalSystem.cs b/src/Squidex.Read.MongoDb/MongoDbStoresExternalSystem.cs deleted file mode 100644 index 17c1dd1c4..000000000 --- a/src/Squidex.Read.MongoDb/MongoDbStoresExternalSystem.cs +++ /dev/null @@ -1,38 +0,0 @@ -// ========================================================================== -// MongoDbStoresExternalSystem.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using MongoDB.Driver; -using Squidex.Infrastructure; - -namespace Squidex.Read.MongoDb -{ - public sealed class MongoDbStoresExternalSystem : IExternalSystem - { - private readonly IMongoDatabase database; - - public MongoDbStoresExternalSystem(IMongoDatabase database) - { - Guard.NotNull(database, nameof(database)); - - this.database = database; - } - - public void CheckConnection() - { - try - { - database.ListCollections(); - } - catch (Exception e) - { - throw new ConfigurationException($"MongoDb Event Store failed to connect to database {database.DatabaseNamespace.DatabaseName}", e); - } - } - } -} diff --git a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs index 28089ce7d..cd1da50e4 100644 --- a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs +++ b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs @@ -18,7 +18,6 @@ using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; using Squidex.Read.Schemas; using Squidex.Read.Schemas.Repositories; -using Squidex.Read.Schemas.Services; namespace Squidex.Read.MongoDb.Schemas { @@ -26,18 +25,16 @@ namespace Squidex.Read.MongoDb.Schemas { private readonly SchemaJsonSerializer serializer; private readonly FieldRegistry registry; - private readonly ISchemaProvider schemaProvider; - public MongoSchemaRepository(IMongoDatabase database, SchemaJsonSerializer serializer, FieldRegistry registry, ISchemaProvider schemaProvider) + public MongoSchemaRepository(IMongoDatabase database, SchemaJsonSerializer serializer, FieldRegistry registry) : base(database) { Guard.NotNull(registry, nameof(registry)); Guard.NotNull(serializer, nameof(serializer)); - Guard.NotNull(schemaProvider, nameof(schemaProvider)); this.registry = registry; + this.serializer = serializer; - this.schemaProvider = schemaProvider; } protected override string CollectionName() diff --git a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs index 26309d078..50974b44d 100644 --- a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs +++ b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs @@ -21,6 +21,8 @@ namespace Squidex.Read.MongoDb.Schemas { public partial class MongoSchemaRepository { + public event Action SchemaSaved; + public Task On(Envelope @event) { return this.DispatchActionAsync(@event.Payload, @event.Headers); @@ -32,7 +34,7 @@ namespace Squidex.Read.MongoDb.Schemas await Collection.CreateAsync(headers, s => { UpdateSchema(s, schema); SimpleMapper.Map(@event, s); }); - schemaProvider.Remove(headers.AggregateId()); + SchemaSaved?.Invoke(headers.AggregateId()); } protected Task On(FieldDeleted @event, EnvelopeHeaders headers) @@ -89,14 +91,14 @@ namespace Squidex.Read.MongoDb.Schemas { await Collection.UpdateAsync(headers, s => s.IsDeleted = true); - schemaProvider.Remove(headers.AggregateId()); + SchemaSaved?.Invoke(headers.AggregateId()); } private async Task UpdateSchema(EnvelopeHeaders headers, Func updater) { await Collection.UpdateAsync(headers, e => UpdateSchema(e, updater)); - schemaProvider.Remove(headers.AggregateId()); + SchemaSaved?.Invoke(headers.AggregateId()); } private void UpdateSchema(MongoSchemaEntity entity, Func updater) diff --git a/src/Squidex.Read.MongoDb/Utils/MongoDbConsumerWrapper.cs b/src/Squidex.Read.MongoDb/Utils/MongoDbConsumerWrapper.cs index 221a938b9..186e180fa 100644 --- a/src/Squidex.Read.MongoDb/Utils/MongoDbConsumerWrapper.cs +++ b/src/Squidex.Read.MongoDb/Utils/MongoDbConsumerWrapper.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS; @@ -18,8 +19,12 @@ namespace Squidex.Read.MongoDb.Utils { public sealed class EventPosition { + [BsonId] + [BsonRepresentation(BsonType.String)] public string Name { get; set; } + [BsonElement] + [BsonRequired] public long EventNumber { get; set; } } @@ -36,7 +41,7 @@ namespace Squidex.Read.MongoDb.Utils this.eventConsumer = eventConsumer; - eventStoreName = GetType().Name; + eventStoreName = eventConsumer.GetType().Name; } protected override string CollectionName() @@ -44,11 +49,6 @@ namespace Squidex.Read.MongoDb.Utils return "EventPositions"; } - protected override Task SetupCollectionAsync(IMongoCollection collection) - { - return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name), new CreateIndexOptions { Unique = true }); - } - public async Task On(Envelope @event, long eventNumber) { await eventConsumer.On(@event); @@ -65,7 +65,7 @@ namespace Squidex.Read.MongoDb.Utils { var collectionPosition = await Collection - .Find(new BsonDocument()).SortByDescending(x => x.EventNumber).Limit(1) + .Find(x => x.Name == eventStoreName).SortByDescending(x => x.EventNumber).Limit(1) .FirstOrDefaultAsync(); return collectionPosition?.EventNumber ?? -1; diff --git a/src/Squidex.Read/Apps/Repositories/IAppRepository.cs b/src/Squidex.Read/Apps/Repositories/IAppRepository.cs index 2b473cd8b..232d3d6b2 100644 --- a/src/Squidex.Read/Apps/Repositories/IAppRepository.cs +++ b/src/Squidex.Read/Apps/Repositories/IAppRepository.cs @@ -14,6 +14,8 @@ namespace Squidex.Read.Apps.Repositories { public interface IAppRepository { + event Action AppSaved; + Task> QueryAllAsync(string subjectId); Task FindAppAsync(Guid appId); diff --git a/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs b/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs index 9212ac6f7..ef86664aa 100644 --- a/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs +++ b/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs @@ -14,6 +14,8 @@ namespace Squidex.Read.Schemas.Repositories { public interface ISchemaRepository { + event Action SchemaSaved; + Task> QueryAllAsync(Guid appId); Task> QueryAllWithSchemaAsync(Guid appId); diff --git a/src/Squidex/Config/Domain/ClusterModule.cs b/src/Squidex/Config/Domain/ClusterModule.cs new file mode 100644 index 000000000..55f636e50 --- /dev/null +++ b/src/Squidex/Config/Domain/ClusterModule.cs @@ -0,0 +1,87 @@ +// ========================================================================== +// ClusterModule.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Autofac; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Redis; +using StackExchange.Redis; + +namespace Squidex.Config.Domain +{ + public class ClusterModule : Module + { + public IConfiguration Configuration { get; } + + public ClusterModule(IConfiguration configuration) + { + Configuration = configuration; + } + + protected override void Load(ContainerBuilder builder) + { + var canCatch = Configuration.GetValue("squidex:catch"); + + if (canCatch) + { + builder.RegisterType() + .AsSelf() + .InstancePerDependency(); + } + + var clustererType = Configuration.GetValue("squidex:clusterer:type"); + + if (string.IsNullOrWhiteSpace(clustererType)) + { + throw new ConfigurationException("You must specify the clusterer type in the 'squidex:clusterer:type' configuration section."); + } + + if (string.Equals(clustererType, "Slack", StringComparison.OrdinalIgnoreCase)) + { + var connectionString = Configuration.GetValue("squidex:clusterer:redis:connectionString"); + + if (string.IsNullOrWhiteSpace(connectionString) || !Uri.IsWellFormedUriString(connectionString, UriKind.Absolute)) + { + throw new ConfigurationException("You must specify the Redis connection string in the 'squidex:clusterer:redis:connectionString' configuration section."); + } + + builder.Register(c => ConnectionMultiplexer.Connect(connectionString)) + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.Register(c => + { + var inner = new MemoryCache(c.Resolve>()); + + return new RedisInvalidatingCache(inner, + c.Resolve(), + c.Resolve>()); + }) + .As() + .SingleInstance(); + } + else + { + throw new ConfigurationException($"Unsupported clusterer type '{clustererType}' for key 'squidex:clusterer:type', supported: Redis."); + } + } + } +} diff --git a/src/Squidex/Config/Domain/EventBusModule.cs b/src/Squidex/Config/Domain/EventBusModule.cs deleted file mode 100644 index d03c266af..000000000 --- a/src/Squidex/Config/Domain/EventBusModule.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ========================================================================== -// EventStoreModule.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Autofac; -using Microsoft.Extensions.Configuration; -using RabbitMQ.Client; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.RabbitMq; - -namespace Squidex.Config.Domain -{ - public class EventBusModule : Module - { - public IConfiguration Configuration { get; } - - public EventBusModule(IConfiguration configuration) - { - Configuration = configuration; - } - - protected override void Load(ContainerBuilder builder) - { - var eventBusType = Configuration.GetValue("squidex:eventBus:type"); - - if (string.IsNullOrWhiteSpace(eventBusType)) - { - throw new ConfigurationException("You must specify the event bus type in the 'squidex:eventBus:type' configuration section."); - } - - var canCatch = Configuration.GetValue("squidex:eventBus:catch"); - - builder.RegisterType() - .WithParameter(new NamedParameter("canCatch", canCatch)) - .AsSelf() - .SingleInstance(); - - if (string.Equals(eventBusType, "Memory", StringComparison.OrdinalIgnoreCase)) - { - builder.RegisterType() - .As() - .As() - .SingleInstance(); - } - else if (string.Equals(eventBusType, "RabbitMq", StringComparison.OrdinalIgnoreCase)) - { - var connectionString = Configuration.GetValue("squidex:eventBus:rabbitMq:connectionString"); - - if (string.IsNullOrWhiteSpace(connectionString) || !Uri.IsWellFormedUriString(connectionString, UriKind.Absolute)) - { - throw new ConfigurationException("You must specify the RabbitMq connection string in the 'squidex:eventBus:rabbitMq:connectionString' configuration section."); - } - - var queueName = Configuration.GetValue("squidex:eventBus:rabbitMq:queueName"); - - builder.Register(c => - { - var connectionFactory = new ConnectionFactory(); - - connectionFactory.SetUri(new Uri(connectionString)); - - return new RabbitMqEventBus(connectionFactory, canCatch, queueName); - }) - .As() - .As() - .As() - .SingleInstance(); - } - else - { - throw new ConfigurationException($"Unsupported store type '{eventBusType}' for key 'squidex:eventStore:type', supported: Memory, RabbmitMq."); - } - } - } -} diff --git a/src/Squidex/Config/Domain/EventStoreModule.cs b/src/Squidex/Config/Domain/EventStoreModule.cs index 627cc4500..2214a3667 100644 --- a/src/Squidex/Config/Domain/EventStoreModule.cs +++ b/src/Squidex/Config/Domain/EventStoreModule.cs @@ -55,7 +55,7 @@ namespace Squidex.Config.Domain var mongoDbClient = new MongoClient(connectionString); var mongoDatabase = mongoDbClient.GetDatabase(databaseName); - var eventStore = new MongoEventStore(mongoDatabase); + var eventStore = new MongoEventStore(mongoDatabase, c.Resolve()); return eventStore; }) diff --git a/src/Squidex/Config/Domain/InfrastructureModule.cs b/src/Squidex/Config/Domain/InfrastructureModule.cs index 66945ced5..abfa0b5d2 100644 --- a/src/Squidex/Config/Domain/InfrastructureModule.cs +++ b/src/Squidex/Config/Domain/InfrastructureModule.cs @@ -12,10 +12,8 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Configuration; using Squidex.Core.Schemas; using Squidex.Core.Schemas.Json; -using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.CQRS.Replay; namespace Squidex.Config.Domain { @@ -54,12 +52,12 @@ namespace Squidex.Config.Domain .As() .SingleInstance(); - builder.RegisterType() - .As() + builder.RegisterType() + .As() .SingleInstance(); - builder.RegisterType() - .As() + builder.RegisterType() + .As() .SingleInstance(); builder.RegisterType() diff --git a/src/Squidex/Config/Domain/ReadModule.cs b/src/Squidex/Config/Domain/ReadModule.cs index 487af56c4..980b63975 100644 --- a/src/Squidex/Config/Domain/ReadModule.cs +++ b/src/Squidex/Config/Domain/ReadModule.cs @@ -8,7 +8,6 @@ using Autofac; using Microsoft.Extensions.Configuration; -using Squidex.Infrastructure.CQRS.Events; using Squidex.Read.Apps; using Squidex.Read.Apps.Services; using Squidex.Read.Apps.Services.Implementations; diff --git a/src/Squidex/Config/Domain/StoreMongoDbModule.cs b/src/Squidex/Config/Domain/StoreMongoDbModule.cs index be6caeb91..d846794cd 100644 --- a/src/Squidex/Config/Domain/StoreMongoDbModule.cs +++ b/src/Squidex/Config/Domain/StoreMongoDbModule.cs @@ -18,13 +18,13 @@ using Squidex.Infrastructure.CQRS.Events; using Squidex.Read.Apps.Repositories; using Squidex.Read.Contents.Repositories; using Squidex.Read.History.Repositories; -using Squidex.Read.MongoDb; using Squidex.Read.MongoDb.Apps; using Squidex.Read.MongoDb.Contents; using Squidex.Read.MongoDb.History; using Squidex.Read.MongoDb.Infrastructure; using Squidex.Read.MongoDb.Schemas; using Squidex.Read.MongoDb.Users; +using Squidex.Read.MongoDb.Utils; using Squidex.Read.Schemas.Repositories; using Squidex.Read.Users.Repositories; @@ -88,11 +88,6 @@ namespace Squidex.Config.Domain .As() .InstancePerLifetimeScope(); - builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) - .As() - .InstancePerLifetimeScope(); - builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) .As() @@ -101,24 +96,49 @@ namespace Squidex.Config.Domain builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) .As() - .As() + .As() + .AsSelf() .SingleInstance(); builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) .As() - .As() + .As() + .AsSelf() .SingleInstance(); builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) .As() - .As() + .As() + .AsSelf() .SingleInstance(); builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) .As() + .As() + .AsSelf() + .SingleInstance(); + + builder.Register(c => + new MongoDbConsumerWrapper( + c.ResolveNamed(MongoDatabaseName), + c.Resolve())) + .As() + .SingleInstance(); + + builder.Register(c => + new MongoDbConsumerWrapper( + c.ResolveNamed(MongoDatabaseName), + c.Resolve())) + .As() + .SingleInstance(); + + builder.Register(c => + new MongoDbConsumerWrapper( + c.ResolveNamed(MongoDatabaseName), + c.Resolve())) .As() .SingleInstance(); } diff --git a/src/Squidex/Config/Domain/Usages.cs b/src/Squidex/Config/Domain/Usages.cs index 03ea56e57..661c15793 100644 --- a/src/Squidex/Config/Domain/Usages.cs +++ b/src/Squidex/Config/Domain/Usages.cs @@ -11,6 +11,10 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; +using Squidex.Read.Apps.Repositories; +using Squidex.Read.Apps.Services; +using Squidex.Read.Schemas.Repositories; +using Squidex.Read.Schemas.Services; namespace Squidex.Config.Domain { @@ -18,7 +22,28 @@ namespace Squidex.Config.Domain { public static IApplicationBuilder UseMyEventStore(this IApplicationBuilder app) { - app.ApplicationServices.GetService().Subscribe(); + var catchConsumers = app.ApplicationServices.GetServices(); + + foreach (var catchConsumer in catchConsumers) + { + var receiver = app.ApplicationServices.GetService(); + + receiver?.Subscribe(catchConsumer); + } + + var appProvider = app.ApplicationServices.GetRequiredService(); + + app.ApplicationServices.GetRequiredService().AppSaved += id => + { + appProvider.Remove(id); + }; + + var schemaProvider = app.ApplicationServices.GetRequiredService(); + + app.ApplicationServices.GetRequiredService().SchemaSaved += id => + { + schemaProvider.Remove(id); + }; return app; } diff --git a/src/Squidex/Program.cs b/src/Squidex/Program.cs index 7f5bc9fde..b7bedeb12 100644 --- a/src/Squidex/Program.cs +++ b/src/Squidex/Program.cs @@ -6,13 +6,8 @@ // All rights reserved. // ========================================================================== -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure; // ReSharper disable InvertIf @@ -22,32 +17,13 @@ namespace Squidex { public static void Main(string[] args) { - var host = new WebHostBuilder() + new WebHostBuilder() .UseKestrel(k => { k.AddServerHeader = false; }) .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() - .Build(); - - if (args.Length > 0) - { - var commands = host.Services.GetService>(); - - foreach (var command in commands) - { - if (string.Equals(args[0], command.Name, StringComparison.OrdinalIgnoreCase)) - { - command.Execute(args.Skip(1).ToArray()); - return; - } - } - - Console.WriteLine("Unknown command: {0}", args[0]); - } - else - { - host.Run(); - } + .Build() + .Run(); } } } diff --git a/src/Squidex/Startup.cs b/src/Squidex/Startup.cs index 05d74a14c..fb0d2bfc8 100644 --- a/src/Squidex/Startup.cs +++ b/src/Squidex/Startup.cs @@ -76,7 +76,7 @@ namespace Squidex var builder = new ContainerBuilder(); builder.Populate(services); - builder.RegisterModule(new EventBusModule(Configuration)); + builder.RegisterModule(new ClusterModule(Configuration)); builder.RegisterModule(new EventStoreModule(Configuration)); builder.RegisterModule(new InfrastructureModule(Configuration)); builder.RegisterModule(new ReadModule(Configuration)); diff --git a/src/Squidex/project.json b/src/Squidex/project.json index 7c1f490bc..7f3be446a 100644 --- a/src/Squidex/project.json +++ b/src/Squidex/project.json @@ -32,10 +32,11 @@ "Squidex.Events": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure.MongoDb": "1.0.0-*", - "Squidex.Infrastructure.RabbitMq": "1.0.0-*", + "Squidex.Infrastructure.Redis": "1.0.0-*", "Squidex.Read": "1.0.0-*", "Squidex.Read.MongoDb": "1.0.0-*", "Squidex.Write": "1.0.0-*", + "StackExchange.Redis.StrongName": "1.2.0", "System.Linq": "4.3.0" },