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