mirror of https://github.com/Squidex/squidex.git
33 changed files with 577 additions and 347 deletions
@ -1,40 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing |
|||
{ |
|||
public sealed class DefaultEventNotifier : IEventNotifier |
|||
{ |
|||
private static readonly string ChannelName = typeof(DefaultEventNotifier).Name; |
|||
|
|||
private readonly IPubSub pubsub; |
|||
|
|||
public sealed class EventNotification |
|||
{ |
|||
public string StreamName { get; set; } |
|||
} |
|||
|
|||
public DefaultEventNotifier(IPubSub pubsub) |
|||
{ |
|||
Guard.NotNull(pubsub, nameof(pubsub)); |
|||
|
|||
this.pubsub = pubsub; |
|||
} |
|||
|
|||
public void NotifyEventsStored(string streamName) |
|||
{ |
|||
pubsub.Publish(new EventNotification { StreamName = streamName }, true); |
|||
} |
|||
|
|||
public IDisposable Subscribe(Action<string> handler) |
|||
{ |
|||
return pubsub.Subscribe<EventNotification>(x => handler?.Invoke(x.StreamName)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Orleans; |
|||
using Orleans.Runtime; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
public sealed class EventConsumerBootstrap : ILifecycleParticipant<ISiloLifecycle> |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public EventConsumerBootstrap(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public void Participate(ISiloLifecycle lifecycle) |
|||
{ |
|||
lifecycle.Subscribe(SiloLifecycleStage.SiloActive, ct => |
|||
{ |
|||
var grain = grainFactory.GetGrain<IEventConsumerManagerGrain>("Default"); |
|||
|
|||
return grain.ActivateAsync(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,90 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.EventSourcing.Grains.Messages; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
public sealed class EventConsumerGrainManager : DisposableObjectBase, IRunnable |
|||
{ |
|||
private readonly IStateFactory factory; |
|||
private readonly IPubSub pubSub; |
|||
private readonly List<IEventConsumer> consumers; |
|||
private readonly List<IDisposable> subscriptions = new List<IDisposable>(); |
|||
|
|||
public EventConsumerGrainManager(IEnumerable<IEventConsumer> consumers, IPubSub pubSub, IStateFactory factory) |
|||
{ |
|||
Guard.NotNull(pubSub, nameof(pubSub)); |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
Guard.NotNull(consumers, nameof(consumers)); |
|||
|
|||
this.pubSub = pubSub; |
|||
this.factory = factory; |
|||
this.consumers = consumers.ToList(); |
|||
} |
|||
|
|||
public void Run() |
|||
{ |
|||
var actors = new Dictionary<string, EventConsumerGrain>(); |
|||
|
|||
foreach (var consumer in consumers) |
|||
{ |
|||
var actor = factory.CreateAsync<EventConsumerGrain>(consumer.Name).Result; |
|||
|
|||
actors[consumer.Name] = actor; |
|||
actor.Activate(consumer); |
|||
} |
|||
|
|||
subscriptions.Add(pubSub.Subscribe<StartConsumerMessage>(m => |
|||
{ |
|||
if (actors.TryGetValue(m.ConsumerName, out var actor)) |
|||
{ |
|||
actor.Start(); |
|||
} |
|||
})); |
|||
|
|||
subscriptions.Add(pubSub.Subscribe<StopConsumerMessage>(m => |
|||
{ |
|||
if (actors.TryGetValue(m.ConsumerName, out var actor)) |
|||
{ |
|||
actor.Stop(); |
|||
} |
|||
})); |
|||
|
|||
subscriptions.Add(pubSub.Subscribe<ResetConsumerMessage>(m => |
|||
{ |
|||
if (actors.TryGetValue(m.ConsumerName, out var actor)) |
|||
{ |
|||
actor.Reset(); |
|||
} |
|||
})); |
|||
|
|||
subscriptions.Add(pubSub.ReceiveAsync<GetStatesRequest, GetStatesResponse>(request => |
|||
{ |
|||
var states = actors.Values.Select(x => x.GetState()).ToArray(); |
|||
|
|||
return Task.FromResult(new GetStatesResponse { States = states }); |
|||
})); |
|||
} |
|||
|
|||
protected override void DisposeObject(bool disposing) |
|||
{ |
|||
if (disposing) |
|||
{ |
|||
foreach (var subscription in subscriptions) |
|||
{ |
|||
subscription.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Orleans.Concurrency; |
|||
using Orleans.Core; |
|||
using Orleans.Runtime; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
public class EventConsumerManagerGrain : Grain, IEventConsumerManagerGrain |
|||
{ |
|||
private readonly IEnumerable<IEventConsumer> eventConsumers; |
|||
|
|||
public EventConsumerManagerGrain(IEnumerable<IEventConsumer> eventConsumers) |
|||
: this(eventConsumers, null, null) |
|||
{ |
|||
} |
|||
|
|||
protected EventConsumerManagerGrain( |
|||
IEnumerable<IEventConsumer> eventConsumers, |
|||
IGrainIdentity identity, |
|||
IGrainRuntime runtime) |
|||
: base(identity, runtime) |
|||
{ |
|||
Guard.NotNull(eventConsumers, nameof(eventConsumers)); |
|||
|
|||
this.eventConsumers = eventConsumers; |
|||
} |
|||
|
|||
public Task ReceiveReminder(string reminderName, TickStatus status) |
|||
{ |
|||
return ActivateAsync(); |
|||
} |
|||
|
|||
public override Task OnActivateAsync() |
|||
{ |
|||
DelayDeactivation(TimeSpan.FromDays(1)); |
|||
|
|||
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10)); |
|||
RegisterTimer(x => ActivateAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(10)); |
|||
|
|||
return Task.FromResult(true); |
|||
} |
|||
|
|||
public Task ActivateAsync() |
|||
{ |
|||
var tasks = |
|||
eventConsumers |
|||
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name)) |
|||
.Select(c => c.ActivateAsync()); |
|||
|
|||
return Task.WhenAll(tasks); |
|||
} |
|||
|
|||
public Task WakeUpAsync(string streamName) |
|||
{ |
|||
var tasks = |
|||
eventConsumers |
|||
.Where(c => streamName == null || Regex.IsMatch(streamName, c.EventsFilter)) |
|||
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name)) |
|||
.Select(c => c.WakeUpAsync()); |
|||
|
|||
return Task.WhenAll(tasks); |
|||
} |
|||
|
|||
public Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync() |
|||
{ |
|||
var tasks = |
|||
eventConsumers |
|||
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name)) |
|||
.Select(c => c.GetStateAsync()); |
|||
|
|||
return Task.WhenAll(tasks).ContinueWith(x => new Immutable<List<EventConsumerInfo>>(x.Result.Select(r => r.Value).ToList())); |
|||
} |
|||
|
|||
public Task ResetAsync(string consumerName) |
|||
{ |
|||
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName); |
|||
|
|||
return eventConsumer.ResetAsync(); |
|||
} |
|||
|
|||
public Task StartAsync(string consumerName) |
|||
{ |
|||
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName); |
|||
|
|||
return eventConsumer.StartAsync(); |
|||
} |
|||
|
|||
public Task StopAsync(string consumerName) |
|||
{ |
|||
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName); |
|||
|
|||
return eventConsumer.StopAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Orleans.Concurrency; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
public interface IEventConsumerGrain : IGrainWithStringKey |
|||
{ |
|||
Task<Immutable<EventConsumerInfo>> GetStateAsync(); |
|||
|
|||
Task ActivateAsync(); |
|||
|
|||
Task StopAsync(); |
|||
|
|||
Task StartAsync(); |
|||
|
|||
Task ResetAsync(); |
|||
|
|||
Task WakeUpAsync(); |
|||
|
|||
Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent); |
|||
|
|||
Task OnErrorAsync(Immutable<IEventSubscription> subscription, Immutable<Exception> exception); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Orleans.Concurrency; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
public interface IEventConsumerManagerGrain : IGrainWithStringKey |
|||
{ |
|||
Task ActivateAsync(); |
|||
|
|||
Task WakeUpAsync(string streamName); |
|||
|
|||
Task StopAsync(string consumerName); |
|||
|
|||
Task StartAsync(string consumerName); |
|||
|
|||
Task ResetAsync(string consumerName); |
|||
|
|||
Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync(); |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages |
|||
{ |
|||
public sealed class GetStatesRequest |
|||
{ |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages |
|||
{ |
|||
public sealed class GetStatesResponse |
|||
{ |
|||
public EventConsumerInfo[] States { get; set; } |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages |
|||
{ |
|||
public sealed class ResetConsumerMessage |
|||
{ |
|||
public string ConsumerName { get; set; } |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages |
|||
{ |
|||
public sealed class StartConsumerMessage |
|||
{ |
|||
public string ConsumerName { get; set; } |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages |
|||
{ |
|||
public sealed class StopConsumerMessage |
|||
{ |
|||
public string ConsumerName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
public sealed class OrleansEventNotifier : IEventNotifier |
|||
{ |
|||
private readonly IEventConsumerManagerGrain eventConsumerManagerGrain; |
|||
|
|||
public OrleansEventNotifier(IGrainFactory factory) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
|
|||
eventConsumerManagerGrain = factory.GetGrain<IEventConsumerManagerGrain>("Default"); |
|||
} |
|||
|
|||
public void NotifyEventsStored(string streamName) |
|||
{ |
|||
eventConsumerManagerGrain.WakeUpAsync(streamName); |
|||
} |
|||
|
|||
public IDisposable Subscribe(Action<string> handler) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Orleans.Concurrency; |
|||
|
|||
namespace Squidex.Infrastructure.EventSourcing.Grains |
|||
{ |
|||
internal sealed class WrapperSubscription : IEventSubscriber |
|||
{ |
|||
private readonly IEventConsumerGrain grain; |
|||
private readonly TaskScheduler scheduler; |
|||
|
|||
public WrapperSubscription(IEventConsumerGrain grain, TaskScheduler scheduler) |
|||
{ |
|||
this.grain = grain; |
|||
|
|||
this.scheduler = scheduler ?? TaskScheduler.Default; |
|||
} |
|||
|
|||
public Task OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|||
{ |
|||
return Dispatch(() => grain.OnEventAsync(subscription.AsImmutable(), storedEvent.AsImmutable())); |
|||
} |
|||
|
|||
public Task OnErrorAsync(IEventSubscription subscription, Exception exception) |
|||
{ |
|||
return Dispatch(() => grain.OnErrorAsync(subscription.AsImmutable(), exception.AsImmutable())); |
|||
} |
|||
|
|||
private Task Dispatch(Func<Task> task) |
|||
{ |
|||
return Task<Task>.Factory.StartNew(() => task(), CancellationToken.None, TaskCreationOptions.None, scheduler).Unwrap(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.EventSourcing.Grains; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public static class ClientServices |
|||
{ |
|||
public static void AddAppClient(this IServiceCollection services) |
|||
{ |
|||
services.AddSingletonAs(c => c.GetRequiredService<IClusterClient>()) |
|||
.As<IGrainFactory>(); |
|||
|
|||
services.AddSingletonAs(c => |
|||
{ |
|||
var client = new ClientBuilder() |
|||
.ConfigureApplicationParts(builder => |
|||
{ |
|||
builder.AddApplicationPart(typeof(EventConsumerGrain).Assembly); |
|||
}) |
|||
.UseStaticGatewayListProvider(options => |
|||
{ |
|||
options.Gateways.Add(new Uri("gwy.tcp://127.0.0.1:40000/0")); |
|||
}) |
|||
.Build(); |
|||
|
|||
client.Connect().Wait(); |
|||
|
|||
return client; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
using Orleans; |
|||
using Orleans.Hosting; |
|||
using Orleans.Runtime.Configuration; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public static class SiloExtensions |
|||
{ |
|||
public static ISiloHostBuilder UseContentRoot(this ISiloHostBuilder builder, string path) |
|||
{ |
|||
builder.ConfigureAppConfiguration(config => |
|||
{ |
|||
config.SetBasePath(path); |
|||
}); |
|||
|
|||
return builder; |
|||
} |
|||
|
|||
public static ClusterConfiguration WithDashboard(this ClusterConfiguration config) |
|||
{ |
|||
config.RegisterDashboard(); |
|||
|
|||
return config; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Orleans; |
|||
using Orleans.Runtime; |
|||
using Orleans.Runtime.Configuration; |
|||
using Squidex.Infrastructure.EventSourcing.Grains; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public static class SiloServices |
|||
{ |
|||
public static void AddAppSiloServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
services.AddSingletonAs<EventConsumerBootstrap>() |
|||
.As<ILifecycleParticipant<ISiloLifecycle>>(); |
|||
|
|||
/* |
|||
var clusterConfiguration = |
|||
services.Where(x => x.ServiceType == typeof(ClusterConfiguration)) |
|||
.Select(x => x.ImplementationInstance) |
|||
.Select(x => (ClusterConfiguration)x) |
|||
.FirstOrDefault(); |
|||
|
|||
if (clusterConfiguration != null) |
|||
{ |
|||
clusterConfiguration.Globals.RegisterBootstrapProvider<EventConsumerBootstrap>("EventConsumers"); |
|||
|
|||
var ipConfig = config.GetRequiredValue("orleans:hostNameOrIPAddress"); |
|||
|
|||
if (ipConfig.Equals("Host", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
ipConfig = Dns.GetHostName(); |
|||
} |
|||
else if (ipConfig.Equals("FirstIPAddressOfHost")) |
|||
{ |
|||
var ips = Dns.GetHostAddressesAsync(Dns.GetHostName()).Result; |
|||
|
|||
ipConfig = ips.FirstOrDefault()?.ToString(); |
|||
} |
|||
|
|||
clusterConfiguration.Defaults.PropagateActivityId = true; |
|||
clusterConfiguration.Defaults.ProxyGatewayEndpoint = new IPEndPoint(IPAddress.Any, 40000); |
|||
clusterConfiguration.Defaults.HostNameOrIPAddress = ipConfig; |
|||
}*/ |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue