mirror of https://github.com/Squidex/squidex.git
21 changed files with 199 additions and 396 deletions
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// InfrastructureErrors.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace Squidex.Infrastructure.RabbitMq |
|||
{ |
|||
public class InfrastructureErrors |
|||
{ |
|||
public static readonly EventId EventHandlingFailed = new EventId(10001, "EventHandlingFailed"); |
|||
|
|||
public static readonly EventId EventDeserializationFailed = new EventId(10002, "EventDeserializationFailed"); |
|||
} |
|||
} |
|||
@ -1,135 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventChannel.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Text; |
|||
using Newtonsoft.Json; |
|||
using RabbitMQ.Client; |
|||
using RabbitMQ.Client.Events; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
// ReSharper disable InvertIf
|
|||
|
|||
namespace Squidex.Infrastructure.RabbitMq |
|||
{ |
|||
public sealed class RabbitMqEventBus : DisposableObject, IExternalSystem |
|||
{ |
|||
private readonly bool isPersistent; |
|||
private readonly string queueName; |
|||
private const string Exchange = "Squidex"; |
|||
private readonly ConnectionFactory connectionFactory; |
|||
private readonly Lazy<IConnection> connection; |
|||
private readonly Lazy<IModel> channel; |
|||
private EventingBasicConsumer consumer; |
|||
|
|||
public RabbitMqEventBus(ConnectionFactory connectionFactory, bool isPersistent, string queueName) |
|||
{ |
|||
Guard.NotNull(connectionFactory, nameof(connectionFactory)); |
|||
|
|||
this.queueName = queueName; |
|||
|
|||
this.connectionFactory = connectionFactory; |
|||
|
|||
connection = new Lazy<IConnection>(connectionFactory.CreateConnection); |
|||
channel = new Lazy<IModel>(() => CreateChannel(connection.Value)); |
|||
|
|||
this.isPersistent = isPersistent; |
|||
} |
|||
|
|||
protected override void DisposeObject(bool disposing) |
|||
{ |
|||
if (connection.IsValueCreated) |
|||
{ |
|||
connection.Value.Close(); |
|||
connection.Value.Dispose(); |
|||
} |
|||
} |
|||
|
|||
public void Publish(EventData eventData) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
channel.Value.BasicPublish(Exchange, string.Empty, null, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(eventData))); |
|||
} |
|||
|
|||
public void CheckConnection() |
|||
{ |
|||
try |
|||
{ |
|||
var currentConnection = connection.Value; |
|||
|
|||
if (!currentConnection.IsOpen) |
|||
{ |
|||
throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}"); |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}", e); |
|||
} |
|||
} |
|||
|
|||
public void Connect(string queuePrefix, Action<EventData> received) |
|||
{ |
|||
ThrowIfDisposed(); |
|||
ThrowIfConnected(); |
|||
|
|||
lock (connection) |
|||
{ |
|||
var currentChannel = channel.Value; |
|||
|
|||
ThrowIfConnected(); |
|||
|
|||
var fullQueueName = $"{queuePrefix}_"; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(queueName)) |
|||
{ |
|||
fullQueueName += queueName; |
|||
} |
|||
else |
|||
{ |
|||
fullQueueName += Environment.MachineName; |
|||
} |
|||
|
|||
currentChannel.QueueDeclare(fullQueueName, isPersistent, false, !isPersistent); |
|||
currentChannel.QueueBind(fullQueueName, Exchange, string.Empty); |
|||
|
|||
consumer = new EventingBasicConsumer(currentChannel); |
|||
|
|||
consumer.Received += (model, e) => |
|||
{ |
|||
var eventData = JsonConvert.DeserializeObject<EventData>(Encoding.UTF8.GetString(e.Body)); |
|||
|
|||
received(eventData); |
|||
}; |
|||
|
|||
currentChannel.BasicConsume(fullQueueName, true, consumer); |
|||
} |
|||
} |
|||
|
|||
private static IModel CreateChannel(IConnection connection, bool declareExchange = true) |
|||
{ |
|||
var channel = connection.CreateModel(); |
|||
|
|||
if (declareExchange) |
|||
{ |
|||
channel.ExchangeDeclare(Exchange, ExchangeType.Fanout, true); |
|||
} |
|||
|
|||
return channel; |
|||
} |
|||
|
|||
private void ThrowIfConnected() |
|||
{ |
|||
if (consumer != null) |
|||
{ |
|||
throw new InvalidOperationException("Already connected to channel."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
<?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>3c9ba12d-f5f2-4355-8d30-8289e4d0752d</ProjectGuid> |
|||
<RootNamespace>Squidex.Infrastructure.RabbitMq</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> |
|||
@ -1,31 +0,0 @@ |
|||
{ |
|||
"version": "1.0.0-*", |
|||
"dependencies": { |
|||
"Autofac": "4.3.0", |
|||
"Microsoft.Extensions.Logging": "1.1.0", |
|||
"Newtonsoft.Json": "9.0.2-beta2", |
|||
"NodaTime": "2.0.0-beta20170123", |
|||
"RabbitMQ.Client": "5.0.0-pre2", |
|||
"Squidex.Infrastructure": "1.0.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" |
|||
} |
|||
}, |
|||
"buildOptions": { |
|||
"embed": [ |
|||
"*.csv" |
|||
] |
|||
}, |
|||
"tooling": { |
|||
"defaultNamespace": "Squidex.Infrastructure.RabbitMq" |
|||
} |
|||
} |
|||
@ -1,63 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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 ex) |
|||
{ |
|||
logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, ex, "Failed to receive invalidation message."); |
|||
} |
|||
} |
|||
|
|||
public void NotifyEventsStored() |
|||
{ |
|||
try |
|||
{ |
|||
subscriber.Publish(Channel, RedisValue.Null); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, ex, "Failed to send invalidation message"); |
|||
} |
|||
} |
|||
|
|||
public void Subscribe(Action handler) |
|||
{ |
|||
inMemoryNotifier.Subscribe(handler); |
|||
} |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RedisExternalSystem.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Squidex.Infrastructure.Redis |
|||
{ |
|||
public sealed class RedisExternalSystem : IExternalSystem |
|||
{ |
|||
private readonly IConnectionMultiplexer redis; |
|||
|
|||
public RedisExternalSystem(IConnectionMultiplexer redis) |
|||
{ |
|||
Guard.NotNull(redis, nameof(redis)); |
|||
|
|||
this.redis = redis; |
|||
} |
|||
|
|||
public void CheckConnection() |
|||
{ |
|||
try |
|||
{ |
|||
redis.GetStatus(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new ConfigurationException($"Redis connection failed to connect to database {redis.Configuration}", ex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// ==========================================================================
|
|||
// RedisInvalidator2.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using Microsoft.Extensions.Logging; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Squidex.Infrastructure.Redis |
|||
{ |
|||
public class RedisPubSub : IPubSub, IExternalSystem |
|||
{ |
|||
private readonly ConnectionMultiplexer redis; |
|||
private readonly ConcurrentDictionary<string, RedisSubscription> subjects = new ConcurrentDictionary<string, RedisSubscription>(); |
|||
private readonly ILogger<RedisPubSub> logger; |
|||
private readonly ISubscriber subscriber; |
|||
|
|||
public RedisPubSub(ConnectionMultiplexer redis, ILogger<RedisPubSub> logger) |
|||
{ |
|||
Guard.NotNull(redis, nameof(redis)); |
|||
Guard.NotNull(logger, nameof(logger)); |
|||
|
|||
this.redis = redis; |
|||
|
|||
this.logger = logger; |
|||
|
|||
subscriber = redis.GetSubscriber(); |
|||
} |
|||
|
|||
public void Connect() |
|||
{ |
|||
try |
|||
{ |
|||
redis.GetStatus(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new ConfigurationException($"Redis connection failed to connect to database {redis.Configuration}", ex); |
|||
} |
|||
} |
|||
|
|||
public void Publish(string channelName, string token, bool notifySelf) |
|||
{ |
|||
Guard.NotNullOrEmpty(channelName, nameof(channelName)); |
|||
|
|||
subjects.GetOrAdd(channelName, c => new RedisSubscription(subscriber, c, logger)).Invalidate(token, notifySelf); |
|||
} |
|||
|
|||
public IDisposable Subscribe(string channelName, Action<string> handler) |
|||
{ |
|||
Guard.NotNullOrEmpty(channelName, nameof(channelName)); |
|||
|
|||
return subjects.GetOrAdd(channelName, c => new RedisSubscription(subscriber, c, logger)).Subscribe(handler); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// IInvalidator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public interface IPubSub |
|||
{ |
|||
void Publish(string channelName, string token, bool notifySelf); |
|||
|
|||
IDisposable Subscribe(string channelName, Action<string> handler); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// ==========================================================================
|
|||
// InMemoryInvalidator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Reactive.Subjects; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public sealed class InMemoryPubSub : IPubSub |
|||
{ |
|||
private readonly ConcurrentDictionary<string, Subject<string>> subjects = new ConcurrentDictionary<string, Subject<string>>(); |
|||
|
|||
public void Publish(string channelName, string token, bool notifySelf) |
|||
{ |
|||
if (notifySelf) |
|||
{ |
|||
subjects.GetOrAdd(channelName, k => new Subject<string>()).OnNext(token); |
|||
} |
|||
} |
|||
|
|||
public IDisposable Subscribe(string channelName, Action<string> handler) |
|||
{ |
|||
return subjects.GetOrAdd(channelName, k => new Subject<string>()).Subscribe(handler); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue