Browse Source

Redis systems

pull/1/head
Sebastian 9 years ago
parent
commit
6250c6f60a
  1. 14
      Squidex.sln
  2. 22
      src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
  3. 11
      src/Squidex.Infrastructure.MongoDb/EventStore/MongoStreamsRepository.cs
  4. 14
      src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs
  5. 2
      src/Squidex.Infrastructure.RabbitMq/RabbitMqEventBus.cs
  6. 19
      src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs
  7. 63
      src/Squidex.Infrastructure.Redis/RedisEventNotifier.cs
  8. 37
      src/Squidex.Infrastructure.Redis/RedisExternalSystem.cs
  9. 56
      src/Squidex.Infrastructure.Redis/RedisInvalidatingCache.cs
  10. 96
      src/Squidex.Infrastructure.Redis/RedisInvalidator.cs
  11. 21
      src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.xproj
  12. 83
      src/Squidex.Infrastructure.Redis/WrapperCacheEntry.cs
  13. 27
      src/Squidex.Infrastructure.Redis/project.json
  14. 26
      src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
  15. 28
      src/Squidex.Infrastructure/CQRS/Events/InMemoryEventNotifier.cs
  16. 8
      src/Squidex.Infrastructure/Timers/CompletionTimer.cs
  17. 6
      src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs
  18. 14
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs
  19. 6
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  20. 38
      src/Squidex.Read.MongoDb/MongoDbStoresExternalSystem.cs
  21. 7
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  22. 8
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  23. 14
      src/Squidex.Read.MongoDb/Utils/MongoDbConsumerWrapper.cs
  24. 2
      src/Squidex.Read/Apps/Repositories/IAppRepository.cs
  25. 2
      src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs
  26. 87
      src/Squidex/Config/Domain/ClusterModule.cs
  27. 81
      src/Squidex/Config/Domain/EventBusModule.cs
  28. 2
      src/Squidex/Config/Domain/EventStoreModule.cs
  29. 10
      src/Squidex/Config/Domain/InfrastructureModule.cs
  30. 1
      src/Squidex/Config/Domain/ReadModule.cs
  31. 38
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  32. 27
      src/Squidex/Config/Domain/Usages.cs
  33. 30
      src/Squidex/Program.cs
  34. 2
      src/Squidex/Startup.cs
  35. 3
      src/Squidex/project.json

14
Squidex.sln

@ -36,10 +36,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Core.Tests", "tests
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.MongoDb", "src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.xproj", "{6A811927-3C37-430A-90F4-503E37123956}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.MongoDb", "src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.xproj", "{6A811927-3C37-430A-90F4-503E37123956}"
EndProject 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}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Read.Tests", "tests\Squidex.Read.Tests\Squidex.Read.Tests.xproj", "{8B074219-F69A-4E41-83C6-12EE1E647779}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{6A811927-3C37-430A-90F4-503E37123956}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -113,7 +113,7 @@ Global
{7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {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} {8B074219-F69A-4E41-83C6-12EE1E647779} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{D7166C56-178A-4457-B56A-C615C7450DEE} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

22
src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs

@ -22,7 +22,7 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Infrastructure.MongoDb.EventStore namespace Squidex.Infrastructure.MongoDb.EventStore
{ {
public class MongoEventStore : MongoRepositoryBase<MongoEventCommit>, IEventStore, IExternalSystem public class MongoEventStore : MongoRepositoryBase<MongoEventCommit>, IEventStore
{ {
private const int Retries = 500; private const int Retries = 500;
private readonly IEventNotifier notifier; private readonly IEventNotifier notifier;
@ -56,18 +56,6 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
eventsOffsetIndex = indexNames[0]; 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<StoredEvent> GetEventsAsync(string streamName) public IObservable<StoredEvent> GetEventsAsync(string streamName)
{ {
Guard.NotNullOrEmpty(streamName, nameof(streamName)); Guard.NotNullOrEmpty(streamName, nameof(streamName));
@ -100,14 +88,14 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{ {
foreach (var @event in commit.Events) foreach (var @event in commit.Events)
{ {
if (position >= lastReceivedPosition) position++;
if (position > lastReceivedPosition)
{ {
var eventData = SimpleMapper.Map(@event, new EventData()); var eventData = SimpleMapper.Map(@event, new EventData());
observer.OnNext(new StoredEvent(position, eventData)); observer.OnNext(new StoredEvent(position, eventData));
} }
position++;
} }
}, ct); }, ct);
}); });
@ -187,7 +175,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
if (document != null) if (document != null)
{ {
return document["EventsOffset"].ToInt64(); return document["EventStreamOffset"].ToInt64();
} }
return -1; return -1;

11
src/Squidex.Infrastructure.MongoDb/EventStore/MongoStreamsRepository.cs

@ -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
{
}
}

14
src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs

@ -13,7 +13,7 @@ using MongoDB.Driver;
namespace Squidex.Infrastructure.MongoDb namespace Squidex.Infrastructure.MongoDb
{ {
public abstract class MongoRepositoryBase<TEntity> public abstract class MongoRepositoryBase<TEntity> : IExternalSystem
{ {
private const string CollectionFormat = "{0}Set"; private const string CollectionFormat = "{0}Set";
private Lazy<IMongoCollection<TEntity>> mongoCollection; private Lazy<IMongoCollection<TEntity>> mongoCollection;
@ -143,5 +143,17 @@ namespace Squidex.Infrastructure.MongoDb
return false; 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);
}
}
} }
} }

2
src/Squidex.Infrastructure.RabbitMq/RabbitMqEventBus.cs

@ -17,7 +17,7 @@ using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Infrastructure.RabbitMq namespace Squidex.Infrastructure.RabbitMq
{ {
public sealed class RabbitMqEventBus : DisposableObject, IEventPublisher, IEventStream, IExternalSystem public sealed class RabbitMqEventBus : DisposableObject, IExternalSystem
{ {
private readonly bool isPersistent; private readonly bool isPersistent;
private readonly string queueName; private readonly string queueName;

19
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");
}
}

63
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<RedisEventNotifier> logger;
public RedisEventNotifier(IConnectionMultiplexer redis, ILogger<RedisEventNotifier> 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);
}
}
}

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

56
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<RedisInvalidatingCache> 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);
}
}
}

96
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<RedisInvalidatingCache> logger;
private int invalidationsReceived;
public int InvalidationsReceived
{
get
{
return invalidationsReceived;
}
}
public RedisInvalidator(IConnectionMultiplexer redis, IMemoryCache cache, ILogger<RedisInvalidatingCache> 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);
}
}
}
}

21
src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.xproj

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>d7166c56-178a-4457-b56a-c615c7450dee</ProjectGuid>
<RootNamespace>Squidex.Infrastructure.Redis</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

83
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<IChangeToken> ExpirationTokens
{
get { return inner.ExpirationTokens; }
}
public IList<PostEvictionCallbackRegistration> 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();
}
}
}

27
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"
}
}

26
src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs

@ -7,11 +7,11 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Infrastructure.Timers; using Squidex.Infrastructure.Timers;
// ReSharper disable MethodSupportsCancellation
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression // ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable InvertIf // ReSharper disable InvertIf
@ -22,7 +22,6 @@ namespace Squidex.Infrastructure.CQRS.Events
private readonly EventDataFormatter formatter; private readonly EventDataFormatter formatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventNotifier eventNotifier; private readonly IEventNotifier eventNotifier;
private readonly IEventCatchConsumer eventConsumer;
private readonly ILogger<EventReceiver> logger; private readonly ILogger<EventReceiver> logger;
private CompletionTimer timer; private CompletionTimer timer;
@ -30,20 +29,17 @@ namespace Squidex.Infrastructure.CQRS.Events
EventDataFormatter formatter, EventDataFormatter formatter,
IEventStore eventStore, IEventStore eventStore,
IEventNotifier eventNotifier, IEventNotifier eventNotifier,
IEventCatchConsumer eventConsumer,
ILogger<EventReceiver> logger) ILogger<EventReceiver> logger)
{ {
Guard.NotNull(logger, nameof(logger)); Guard.NotNull(logger, nameof(logger));
Guard.NotNull(formatter, nameof(formatter)); Guard.NotNull(formatter, nameof(formatter));
Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventNotifier, nameof(eventNotifier)); Guard.NotNull(eventNotifier, nameof(eventNotifier));
Guard.NotNull(eventConsumer, nameof(eventConsumer));
this.logger = logger; this.logger = logger;
this.formatter = formatter; this.formatter = formatter;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventNotifier = eventNotifier; this.eventNotifier = eventNotifier;
this.eventConsumer = eventConsumer;
} }
protected override void DisposeObject(bool disposing) 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) if (timer != null)
{ {
return; return;
@ -70,14 +68,26 @@ namespace Squidex.Infrastructure.CQRS.Events
lastReceivedPosition = await eventConsumer.GetLastHandledEventNumber(); lastReceivedPosition = await eventConsumer.GetLastHandledEventNumber();
} }
await eventStore.GetEventsAsync(lastReceivedPosition).ForEachAsync(async storedEvent => var tcs = new TaskCompletionSource<bool>();
eventStore.GetEventsAsync(lastReceivedPosition).Subscribe(storedEvent =>
{ {
var @event = ParseEvent(storedEvent.Data); var @event = ParseEvent(storedEvent.Data);
@event.SetEventNumber(storedEvent.EventNumber); @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); }, ct);
await tcs.Task;
}); });
eventNotifier.Subscribe(timer.Trigger); eventNotifier.Subscribe(timer.Trigger);

28
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<object> subject = new Subject<object>();
public void NotifyEventsStored()
{
subject.OnNext(null);
}
public void Subscribe(Action handler)
{
subject.Subscribe(_ => handler());
}
}
}

8
src/Squidex.Infrastructure/Timers/CompletionTimer.cs

@ -35,15 +35,15 @@ namespace Squidex.Infrastructure.Timers
try try
{ {
await callback(disposeCancellationTokenSource.Token).ConfigureAwait(false); await callback(disposeCancellationTokenSource.Token).ConfigureAwait(false);
delayCancellationSource = new CancellationTokenSource();
await Task.Delay(delay, delayCancellationSource.Token).ConfigureAwait(false);
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
Console.WriteLine("Task in TriggerTimer has been cancelled."); Console.WriteLine("Task in TriggerTimer has been cancelled.");
} }
delayCancellationSource = new CancellationTokenSource();
await Task.Delay(delay, delayCancellationSource.Token).ConfigureAwait(false);
} }
} }

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

@ -27,15 +27,15 @@ namespace Squidex.Read.MongoDb.Apps
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public HashSet<string> Languages { get; } = new HashSet<string>(); public HashSet<string> Languages { get; set; } = new HashSet<string>();
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public Dictionary<string, MongoAppClientEntity> Clients { get; } = new Dictionary<string, MongoAppClientEntity>(); public Dictionary<string, MongoAppClientEntity> Clients { get; set; } = new Dictionary<string, MongoAppClientEntity>();
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public Dictionary<string, MongoAppContributorEntity> Contributors { get; } = new Dictionary<string, MongoAppContributorEntity>(); public Dictionary<string, MongoAppContributorEntity> Contributors { get; set; } = new Dictionary<string, MongoAppContributorEntity>();
IReadOnlyCollection<IAppClientEntity> IAppEntity.Clients IReadOnlyCollection<IAppClientEntity> IAppEntity.Clients
{ {

14
src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs

@ -10,25 +10,18 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps; using Squidex.Read.Apps;
using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Repositories;
using Squidex.Read.Apps.Services;
namespace Squidex.Read.MongoDb.Apps namespace Squidex.Read.MongoDb.Apps
{ {
public partial class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, IEventConsumer public partial class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, IEventConsumer
{ {
private readonly IAppProvider appProvider; public MongoAppRepository(IMongoDatabase database)
public MongoAppRepository(IMongoDatabase database, IAppProvider appProvider)
: base(database) : base(database)
{ {
Guard.NotNull(appProvider, nameof(appProvider));
this.appProvider = appProvider;
} }
protected override string CollectionName() protected override string CollectionName()
@ -41,6 +34,11 @@ namespace Squidex.Read.MongoDb.Apps
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name)); return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name));
} }
protected override MongoCollectionSettings CollectionSettings()
{
return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId) public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId)
{ {
var entities = var entities =

6
src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -20,6 +20,8 @@ namespace Squidex.Read.MongoDb.Apps
{ {
public partial class MongoAppRepository public partial class MongoAppRepository
{ {
public event Action<Guid> AppSaved;
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)
{ {
return this.DispatchActionAsync(@event.Payload, @event.Headers); return this.DispatchActionAsync(@event.Payload, @event.Headers);
@ -32,7 +34,7 @@ namespace Squidex.Read.MongoDb.Apps
SimpleMapper.Map(@event, a); SimpleMapper.Map(@event, a);
}); });
appProvider.Remove(headers.AggregateId()); AppSaved?.Invoke(headers.AggregateId());
} }
protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers) protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers)
@ -105,7 +107,7 @@ namespace Squidex.Read.MongoDb.Apps
{ {
await Collection.UpdateAsync(headers, updater); await Collection.UpdateAsync(headers, updater);
appProvider.Remove(headers.AggregateId()); AppSaved?.Invoke(headers.AggregateId());
} }
} }
} }

38
src/Squidex.Read.MongoDb/MongoDbStoresExternalSystem.cs

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

7
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs

@ -18,7 +18,6 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Schemas; using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Schemas.Services;
namespace Squidex.Read.MongoDb.Schemas namespace Squidex.Read.MongoDb.Schemas
{ {
@ -26,18 +25,16 @@ namespace Squidex.Read.MongoDb.Schemas
{ {
private readonly SchemaJsonSerializer serializer; private readonly SchemaJsonSerializer serializer;
private readonly FieldRegistry registry; 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) : base(database)
{ {
Guard.NotNull(registry, nameof(registry)); Guard.NotNull(registry, nameof(registry));
Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(schemaProvider, nameof(schemaProvider));
this.registry = registry; this.registry = registry;
this.serializer = serializer; this.serializer = serializer;
this.schemaProvider = schemaProvider;
} }
protected override string CollectionName() protected override string CollectionName()

8
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -21,6 +21,8 @@ namespace Squidex.Read.MongoDb.Schemas
{ {
public partial class MongoSchemaRepository public partial class MongoSchemaRepository
{ {
public event Action<Guid> SchemaSaved;
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)
{ {
return this.DispatchActionAsync(@event.Payload, @event.Headers); 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); }); 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) protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
@ -89,14 +91,14 @@ namespace Squidex.Read.MongoDb.Schemas
{ {
await Collection.UpdateAsync(headers, s => s.IsDeleted = true); await Collection.UpdateAsync(headers, s => s.IsDeleted = true);
schemaProvider.Remove(headers.AggregateId()); SchemaSaved?.Invoke(headers.AggregateId());
} }
private async Task UpdateSchema(EnvelopeHeaders headers, Func<Schema, Schema> updater) private async Task UpdateSchema(EnvelopeHeaders headers, Func<Schema, Schema> updater)
{ {
await Collection.UpdateAsync(headers, e => UpdateSchema(e, updater)); await Collection.UpdateAsync(headers, e => UpdateSchema(e, updater));
schemaProvider.Remove(headers.AggregateId()); SchemaSaved?.Invoke(headers.AggregateId());
} }
private void UpdateSchema(MongoSchemaEntity entity, Func<Schema, Schema> updater) private void UpdateSchema(MongoSchemaEntity entity, Func<Schema, Schema> updater)

14
src/Squidex.Read.MongoDb/Utils/MongoDbConsumerWrapper.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
@ -18,8 +19,12 @@ namespace Squidex.Read.MongoDb.Utils
{ {
public sealed class EventPosition public sealed class EventPosition
{ {
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Name { get; set; } public string Name { get; set; }
[BsonElement]
[BsonRequired]
public long EventNumber { get; set; } public long EventNumber { get; set; }
} }
@ -36,7 +41,7 @@ namespace Squidex.Read.MongoDb.Utils
this.eventConsumer = eventConsumer; this.eventConsumer = eventConsumer;
eventStoreName = GetType().Name; eventStoreName = eventConsumer.GetType().Name;
} }
protected override string CollectionName() protected override string CollectionName()
@ -44,11 +49,6 @@ namespace Squidex.Read.MongoDb.Utils
return "EventPositions"; return "EventPositions";
} }
protected override Task SetupCollectionAsync(IMongoCollection<EventPosition> collection)
{
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name), new CreateIndexOptions { Unique = true });
}
public async Task On(Envelope<IEvent> @event, long eventNumber) public async Task On(Envelope<IEvent> @event, long eventNumber)
{ {
await eventConsumer.On(@event); await eventConsumer.On(@event);
@ -65,7 +65,7 @@ namespace Squidex.Read.MongoDb.Utils
{ {
var collectionPosition = var collectionPosition =
await Collection await Collection
.Find(new BsonDocument()).SortByDescending(x => x.EventNumber).Limit(1) .Find(x => x.Name == eventStoreName).SortByDescending(x => x.EventNumber).Limit(1)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
return collectionPosition?.EventNumber ?? -1; return collectionPosition?.EventNumber ?? -1;

2
src/Squidex.Read/Apps/Repositories/IAppRepository.cs

@ -14,6 +14,8 @@ namespace Squidex.Read.Apps.Repositories
{ {
public interface IAppRepository public interface IAppRepository
{ {
event Action<Guid> AppSaved;
Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId); Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId);
Task<IAppEntity> FindAppAsync(Guid appId); Task<IAppEntity> FindAppAsync(Guid appId);

2
src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs

@ -14,6 +14,8 @@ namespace Squidex.Read.Schemas.Repositories
{ {
public interface ISchemaRepository public interface ISchemaRepository
{ {
event Action<Guid> SchemaSaved;
Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId); Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId);
Task<IReadOnlyList<ISchemaEntityWithSchema>> QueryAllWithSchemaAsync(Guid appId); Task<IReadOnlyList<ISchemaEntityWithSchema>> QueryAllWithSchemaAsync(Guid appId);

87
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<bool>("squidex:catch");
if (canCatch)
{
builder.RegisterType<EventReceiver>()
.AsSelf()
.InstancePerDependency();
}
var clustererType = Configuration.GetValue<string>("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<string>("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<IConnectionMultiplexer>()
.SingleInstance();
builder.RegisterType<RedisEventNotifier>()
.As<IEventNotifier>()
.SingleInstance();
builder.RegisterType<RedisExternalSystem>()
.As<IExternalSystem>()
.SingleInstance();
builder.Register(c =>
{
var inner = new MemoryCache(c.Resolve<IOptions<MemoryCacheOptions>>());
return new RedisInvalidatingCache(inner,
c.Resolve<IConnectionMultiplexer>(),
c.Resolve<ILogger<RedisInvalidatingCache>>());
})
.As<IMemoryCache>()
.SingleInstance();
}
else
{
throw new ConfigurationException($"Unsupported clusterer type '{clustererType}' for key 'squidex:clusterer:type', supported: Redis.");
}
}
}
}

81
src/Squidex/Config/Domain/EventBusModule.cs

@ -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<string>("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<bool>("squidex:eventBus:catch");
builder.RegisterType<EventReceiver>()
.WithParameter(new NamedParameter("canCatch", canCatch))
.AsSelf()
.SingleInstance();
if (string.Equals(eventBusType, "Memory", StringComparison.OrdinalIgnoreCase))
{
builder.RegisterType<InMemoryEventBus>()
.As<IEventStream>()
.As<IEventPublisher>()
.SingleInstance();
}
else if (string.Equals(eventBusType, "RabbitMq", StringComparison.OrdinalIgnoreCase))
{
var connectionString = Configuration.GetValue<string>("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<string>("squidex:eventBus:rabbitMq:queueName");
builder.Register(c =>
{
var connectionFactory = new ConnectionFactory();
connectionFactory.SetUri(new Uri(connectionString));
return new RabbitMqEventBus(connectionFactory, canCatch, queueName);
})
.As<IEventStream>()
.As<IEventPublisher>()
.As<IExternalSystem>()
.SingleInstance();
}
else
{
throw new ConfigurationException($"Unsupported store type '{eventBusType}' for key 'squidex:eventStore:type', supported: Memory, RabbmitMq.");
}
}
}
}

2
src/Squidex/Config/Domain/EventStoreModule.cs

@ -55,7 +55,7 @@ namespace Squidex.Config.Domain
var mongoDbClient = new MongoClient(connectionString); var mongoDbClient = new MongoClient(connectionString);
var mongoDatabase = mongoDbClient.GetDatabase(databaseName); var mongoDatabase = mongoDbClient.GetDatabase(databaseName);
var eventStore = new MongoEventStore(mongoDatabase); var eventStore = new MongoEventStore(mongoDatabase, c.Resolve<IEventNotifier>());
return eventStore; return eventStore;
}) })

10
src/Squidex/Config/Domain/InfrastructureModule.cs

@ -12,10 +12,8 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Core.Schemas.Json; using Squidex.Core.Schemas.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Replay;
namespace Squidex.Config.Domain namespace Squidex.Config.Domain
{ {
@ -54,12 +52,12 @@ namespace Squidex.Config.Domain
.As<ICommandBus>() .As<ICommandBus>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<DefaultNameResolver>() builder.RegisterType<InMemoryEventNotifier>()
.As<IStreamNameResolver>() .As<IEventNotifier>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<ReplayGenerator>() builder.RegisterType<DefaultNameResolver>()
.As<ICliCommand>() .As<IStreamNameResolver>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<EventDataFormatter>() builder.RegisterType<EventDataFormatter>()

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

@ -8,7 +8,6 @@
using Autofac; using Autofac;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Apps; using Squidex.Read.Apps;
using Squidex.Read.Apps.Services; using Squidex.Read.Apps.Services;
using Squidex.Read.Apps.Services.Implementations; using Squidex.Read.Apps.Services.Implementations;

38
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -18,13 +18,13 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Repositories;
using Squidex.Read.Contents.Repositories; using Squidex.Read.Contents.Repositories;
using Squidex.Read.History.Repositories; using Squidex.Read.History.Repositories;
using Squidex.Read.MongoDb;
using Squidex.Read.MongoDb.Apps; using Squidex.Read.MongoDb.Apps;
using Squidex.Read.MongoDb.Contents; using Squidex.Read.MongoDb.Contents;
using Squidex.Read.MongoDb.History; using Squidex.Read.MongoDb.History;
using Squidex.Read.MongoDb.Infrastructure; using Squidex.Read.MongoDb.Infrastructure;
using Squidex.Read.MongoDb.Schemas; using Squidex.Read.MongoDb.Schemas;
using Squidex.Read.MongoDb.Users; using Squidex.Read.MongoDb.Users;
using Squidex.Read.MongoDb.Utils;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Users.Repositories; using Squidex.Read.Users.Repositories;
@ -88,11 +88,6 @@ namespace Squidex.Config.Domain
.As<IUserRepository>() .As<IUserRepository>()
.InstancePerLifetimeScope(); .InstancePerLifetimeScope();
builder.RegisterType<MongoDbStoresExternalSystem>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<IExternalSystem>()
.InstancePerLifetimeScope();
builder.RegisterType<MongoPersistedGrantStore>() builder.RegisterType<MongoPersistedGrantStore>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName)) .WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<IPersistedGrantStore>() .As<IPersistedGrantStore>()
@ -101,24 +96,49 @@ namespace Squidex.Config.Domain
builder.RegisterType<MongoContentRepository>() builder.RegisterType<MongoContentRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName)) .WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<IContentRepository>() .As<IContentRepository>()
.As<IEventCatchConsumer>() .As<IExternalSystem>()
.AsSelf()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoHistoryEventRepository>() builder.RegisterType<MongoHistoryEventRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName)) .WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<IHistoryEventRepository>() .As<IHistoryEventRepository>()
.As<IEventCatchConsumer>() .As<IExternalSystem>()
.AsSelf()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoSchemaRepository>() builder.RegisterType<MongoSchemaRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName)) .WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<ISchemaRepository>() .As<ISchemaRepository>()
.As<IEventCatchConsumer>() .As<IExternalSystem>()
.AsSelf()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoAppRepository>() builder.RegisterType<MongoAppRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName)) .WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<IAppRepository>() .As<IAppRepository>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.Register(c =>
new MongoDbConsumerWrapper(
c.ResolveNamed<IMongoDatabase>(MongoDatabaseName),
c.Resolve<MongoContentRepository>()))
.As<IEventCatchConsumer>()
.SingleInstance();
builder.Register(c =>
new MongoDbConsumerWrapper(
c.ResolveNamed<IMongoDatabase>(MongoDatabaseName),
c.Resolve<MongoSchemaRepository>()))
.As<IEventCatchConsumer>()
.SingleInstance();
builder.Register(c =>
new MongoDbConsumerWrapper(
c.ResolveNamed<IMongoDatabase>(MongoDatabaseName),
c.Resolve<MongoAppRepository>()))
.As<IEventCatchConsumer>() .As<IEventCatchConsumer>()
.SingleInstance(); .SingleInstance();
} }

27
src/Squidex/Config/Domain/Usages.cs

@ -11,6 +11,10 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events; 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 namespace Squidex.Config.Domain
{ {
@ -18,7 +22,28 @@ namespace Squidex.Config.Domain
{ {
public static IApplicationBuilder UseMyEventStore(this IApplicationBuilder app) public static IApplicationBuilder UseMyEventStore(this IApplicationBuilder app)
{ {
app.ApplicationServices.GetService<EventReceiver>().Subscribe(); var catchConsumers = app.ApplicationServices.GetServices<IEventCatchConsumer>();
foreach (var catchConsumer in catchConsumers)
{
var receiver = app.ApplicationServices.GetService<EventReceiver>();
receiver?.Subscribe(catchConsumer);
}
var appProvider = app.ApplicationServices.GetRequiredService<IAppProvider>();
app.ApplicationServices.GetRequiredService<IAppRepository>().AppSaved += id =>
{
appProvider.Remove(id);
};
var schemaProvider = app.ApplicationServices.GetRequiredService<ISchemaProvider>();
app.ApplicationServices.GetRequiredService<ISchemaRepository>().SchemaSaved += id =>
{
schemaProvider.Remove(id);
};
return app; return app;
} }

30
src/Squidex/Program.cs

@ -6,13 +6,8 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure;
// ReSharper disable InvertIf // ReSharper disable InvertIf
@ -22,32 +17,13 @@ namespace Squidex
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var host = new WebHostBuilder() new WebHostBuilder()
.UseKestrel(k => { k.AddServerHeader = false; }) .UseKestrel(k => { k.AddServerHeader = false; })
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration() .UseIISIntegration()
.UseStartup<Startup>() .UseStartup<Startup>()
.Build(); .Build()
.Run();
if (args.Length > 0)
{
var commands = host.Services.GetService<IEnumerable<ICliCommand>>();
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();
}
} }
} }
} }

2
src/Squidex/Startup.cs

@ -76,7 +76,7 @@ namespace Squidex
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.Populate(services); builder.Populate(services);
builder.RegisterModule(new EventBusModule(Configuration)); builder.RegisterModule(new ClusterModule(Configuration));
builder.RegisterModule(new EventStoreModule(Configuration)); builder.RegisterModule(new EventStoreModule(Configuration));
builder.RegisterModule(new InfrastructureModule(Configuration)); builder.RegisterModule(new InfrastructureModule(Configuration));
builder.RegisterModule(new ReadModule(Configuration)); builder.RegisterModule(new ReadModule(Configuration));

3
src/Squidex/project.json

@ -32,10 +32,11 @@
"Squidex.Events": "1.0.0-*", "Squidex.Events": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"Squidex.Infrastructure.MongoDb": "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": "1.0.0-*",
"Squidex.Read.MongoDb": "1.0.0-*", "Squidex.Read.MongoDb": "1.0.0-*",
"Squidex.Write": "1.0.0-*", "Squidex.Write": "1.0.0-*",
"StackExchange.Redis.StrongName": "1.2.0",
"System.Linq": "4.3.0" "System.Linq": "4.3.0"
}, },

Loading…
Cancel
Save