mirror of https://github.com/Squidex/squidex.git
18 changed files with 594 additions and 0 deletions
@ -1,6 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<configuration> |
|||
<packageSources> |
|||
<add key="localfeed" value="libs" /> |
|||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> |
|||
</packageSources> |
|||
</configuration> |
|||
Binary file not shown.
@ -0,0 +1 @@ |
|||
5W20j9jiNog4dHUEt+cCnePb8z6jFEMnkwO4XilajM7FCnen3KTnN/G8PAUGuQieSlTI9MRe0sRYcafLJl900w== |
|||
@ -0,0 +1,23 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> |
|||
<metadata> |
|||
<id>Microsoft.Orleans.OrleansCodeGenerator.Build</id> |
|||
<version>2.0.0-beta1-fix</version> |
|||
<title>Microsoft Orleans Build-time Code Generator</title> |
|||
<authors>Microsoft</authors> |
|||
<owners>Microsoft</owners> |
|||
<requireLicenseAcceptance>false</requireLicenseAcceptance> |
|||
<developmentDependency>true</developmentDependency> |
|||
<licenseUrl>https://github.com/dotnet/Orleans#license</licenseUrl> |
|||
<projectUrl>https://github.com/dotnet/Orleans</projectUrl> |
|||
<iconUrl>https://raw.githubusercontent.com/dotnet/orleans/gh-pages/assets/logo_128.png</iconUrl> |
|||
<description>Microsoft Orleans build-time code generator to install in all grain interface & implementation projects.</description> |
|||
<copyright>© Microsoft Corporation. All rights reserved.</copyright> |
|||
<tags>Orleans Cloud-Computing Actor-Model Actors Distributed-Systems C# .NET</tags> |
|||
<repository type="git" url="https://github.com/dotnet/Orleans" /> |
|||
<dependencies> |
|||
<group targetFramework=".NETFramework4.6.1" /> |
|||
<group targetFramework=".NETCoreApp2.0" /> |
|||
</dependencies> |
|||
</metadata> |
|||
</package> |
|||
Binary file not shown.
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerInfo.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains |
|||
{ |
|||
public sealed class EventConsumerInfo |
|||
{ |
|||
public bool IsStopped { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public string Error { get; set; } |
|||
|
|||
public string Position { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// ==========================================================================
|
|||
// IEventConsumerGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains |
|||
{ |
|||
public interface IEventConsumerGrain : IGrainWithStringKey, IEventSubscriber |
|||
{ |
|||
Task<EventConsumerInfo> GetStateAsync(); |
|||
|
|||
Task StopAsync(); |
|||
|
|||
Task StartAsync(); |
|||
|
|||
Task ResetAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// ==========================================================================
|
|||
// IEventConsumerRegistryGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains |
|||
{ |
|||
public interface IEventConsumerRegistryGrain : IGrainWithStringKey |
|||
{ |
|||
Task RegisterAsync(string consumerName); |
|||
|
|||
Task StopAsync(string consumerName); |
|||
|
|||
Task StartAsync(string consumerName); |
|||
|
|||
Task ResetAsync(string consumerName); |
|||
|
|||
Task<List<EventConsumerInfo>> GetConsumersAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,280 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains |
|||
{ |
|||
public class EventConsumerGrain : Grain<EventConsumerInfo>, IEventSubscriber, IEventConsumerGrain |
|||
{ |
|||
private readonly EventDataFormatter eventFormatter; |
|||
private readonly EventConsumerFactory eventConsumerFactory; |
|||
private readonly IEventStore eventStore; |
|||
private readonly ISemanticLog log; |
|||
private TaskFactory dispatcher; |
|||
private IEventSubscription currentSubscription; |
|||
private IEventConsumer eventConsumer; |
|||
|
|||
public EventConsumerGrain( |
|||
EventDataFormatter eventFormatter, |
|||
EventConsumerFactory eventConsumerFactory, |
|||
IEventStore eventStore, |
|||
ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(log, nameof(log)); |
|||
Guard.NotNull(eventStore, nameof(eventStore)); |
|||
Guard.NotNull(eventFormatter, nameof(eventFormatter)); |
|||
Guard.NotNull(eventConsumerFactory, nameof(eventConsumerFactory)); |
|||
|
|||
this.log = log; |
|||
|
|||
this.eventStore = eventStore; |
|||
this.eventFormatter = eventFormatter; |
|||
this.eventConsumerFactory = eventConsumerFactory; |
|||
} |
|||
|
|||
public override async Task OnActivateAsync() |
|||
{ |
|||
dispatcher = new TaskFactory(TaskScheduler.Current); |
|||
|
|||
await GrainFactory.GetGrain<IEventConsumerRegistryGrain>(string.Empty).RegisterAsync(this.IdentityString); |
|||
|
|||
eventConsumer = eventConsumerFactory(this.IdentityString); |
|||
|
|||
if (!State.IsStopped) |
|||
{ |
|||
Subscribe(State.Position); |
|||
} |
|||
} |
|||
|
|||
protected virtual IEventSubscription CreateSubscription(IEventStore eventStore, string streamFilter, string position) |
|||
{ |
|||
return new RetrySubscription(eventStore, this, streamFilter, position); |
|||
} |
|||
|
|||
private Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|||
{ |
|||
if (subscription != currentSubscription) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(async () => |
|||
{ |
|||
var @event = ParseKnownEvent(storedEvent); |
|||
|
|||
if (@event != null) |
|||
{ |
|||
await DispatchConsumerAsync(@event); |
|||
} |
|||
|
|||
State.Error = null; |
|||
State.Position = storedEvent.EventPosition; |
|||
}); |
|||
} |
|||
|
|||
private Task HandleErrorAsync(IEventSubscription subscription, Exception exception) |
|||
{ |
|||
if (subscription != currentSubscription) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(() => |
|||
{ |
|||
Unsubscribe(); |
|||
|
|||
State.Error = exception.ToString(); |
|||
State.IsStopped = true; |
|||
}); |
|||
} |
|||
|
|||
public Task<EventConsumerInfo> GetStateAsync() |
|||
{ |
|||
return Task.FromResult(State); |
|||
} |
|||
|
|||
public Task StartAsync() |
|||
{ |
|||
if (!State.IsStopped) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(() => |
|||
{ |
|||
Subscribe(State.Position); |
|||
|
|||
State.Error = null; |
|||
State.IsStopped = false; |
|||
}); |
|||
} |
|||
|
|||
public Task StopAsync() |
|||
{ |
|||
if (State.IsStopped) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(() => |
|||
{ |
|||
Unsubscribe(); |
|||
|
|||
State.Error = null; |
|||
State.IsStopped = true; |
|||
}); |
|||
} |
|||
|
|||
public Task ResetAsync() |
|||
{ |
|||
return DoAndUpdateStateAsync(async () => |
|||
{ |
|||
Unsubscribe(); |
|||
|
|||
await ClearAsync(); |
|||
|
|||
Subscribe(null); |
|||
|
|||
State.Error = null; |
|||
State.Position = null; |
|||
State.IsStopped = false; |
|||
}); |
|||
} |
|||
|
|||
Task IEventSubscriber.OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|||
{ |
|||
return dispatcher.StartNew(() => this.HandleEventAsync(subscription, storedEvent)).Unwrap(); |
|||
} |
|||
|
|||
Task IEventSubscriber.OnErrorAsync(IEventSubscription subscription, Exception exception) |
|||
{ |
|||
return dispatcher.StartNew(() => this.HandleErrorAsync(subscription, exception)).Unwrap(); |
|||
} |
|||
|
|||
private Task DoAndUpdateStateAsync(Action action) |
|||
{ |
|||
return DoAndUpdateStateAsync(() => { action(); return TaskHelper.Done; }); |
|||
} |
|||
|
|||
private async Task DoAndUpdateStateAsync(Func<Task> action) |
|||
{ |
|||
try |
|||
{ |
|||
await action(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
try |
|||
{ |
|||
Unsubscribe(); |
|||
} |
|||
catch (Exception unsubscribeException) |
|||
{ |
|||
ex = new AggregateException(ex, unsubscribeException); |
|||
} |
|||
|
|||
log.LogFatal(ex, w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("state", "Failed") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
State.Error = ex.ToString(); |
|||
State.IsStopped = true; |
|||
} |
|||
|
|||
await WriteStateAsync(); |
|||
} |
|||
|
|||
private async Task ClearAsync() |
|||
{ |
|||
var actionId = Guid.NewGuid().ToString(); |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Started") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name))) |
|||
{ |
|||
await eventConsumer.ClearAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task DispatchConsumerAsync(Envelope<IEvent> @event) |
|||
{ |
|||
var eventId = @event.Headers.EventId().ToString(); |
|||
var eventType = @event.Payload.GetType().Name; |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("actionId", eventId) |
|||
.WriteProperty("state", "Started") |
|||
.WriteProperty("eventId", eventId) |
|||
.WriteProperty("eventType", eventType) |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("actionId", eventId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventId", eventId) |
|||
.WriteProperty("eventType", eventType) |
|||
.WriteProperty("eventConsumer", eventConsumer.Name))) |
|||
{ |
|||
await eventConsumer.On(@event); |
|||
} |
|||
} |
|||
|
|||
private void Unsubscribe() |
|||
{ |
|||
if (currentSubscription != null) |
|||
{ |
|||
currentSubscription.StopAsync().Forget(); |
|||
currentSubscription = null; |
|||
} |
|||
} |
|||
|
|||
private void Subscribe(string position) |
|||
{ |
|||
if (currentSubscription == null) |
|||
{ |
|||
currentSubscription?.StopAsync().Forget(); |
|||
currentSubscription = CreateSubscription(eventStore, eventConsumer.EventsFilter, position); |
|||
} |
|||
} |
|||
|
|||
private Envelope<IEvent> ParseKnownEvent(StoredEvent message) |
|||
{ |
|||
try |
|||
{ |
|||
var @event = eventFormatter.Parse(message.Data); |
|||
|
|||
@event.SetEventPosition(message.EventPosition); |
|||
@event.SetEventStreamNumber(message.EventStreamNumber); |
|||
|
|||
return @event; |
|||
} |
|||
catch (TypeNameNotFoundException) |
|||
{ |
|||
log.LogDebug(w => w.WriteProperty("oldEventFound", message.Data.Type)); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerRegistryGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains.Implementation |
|||
{ |
|||
public sealed class EventConsumerRegistryGrain : Grain<EventConsumerRegistryGrainState>, IEventConsumerRegistryGrain |
|||
{ |
|||
public Task<List<EventConsumerInfo>> GetConsumersAsync() |
|||
{ |
|||
var tasks = |
|||
State.EventConsumerNames |
|||
.Select(n => GrainFactory.GetGrain<IEventConsumerGrain>(n)) |
|||
.Select(c => c.GetStateAsync()); |
|||
|
|||
return Task.WhenAll(tasks).ContinueWith(x => x.Result.ToList()); |
|||
} |
|||
|
|||
public Task RegisterAsync(string consumerName) |
|||
{ |
|||
State.EventConsumerNames.Add(consumerName); |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
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,22 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerRegistryGrainState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains.Implementation |
|||
{ |
|||
public sealed class EventConsumerRegistryGrainState |
|||
{ |
|||
public HashSet<string> EventConsumerNames { get; set; } |
|||
|
|||
public EventConsumerRegistryGrainState() |
|||
{ |
|||
EventConsumerNames = new HashSet<string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public class IOrleansRunner |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// ==========================================================================
|
|||
// OrleansModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Squidex.Config.Domain; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public sealed class OrleansModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public OrleansModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var storeType = Configuration.GetValue<string>("orleans:type"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(storeType)) |
|||
{ |
|||
throw new ConfigurationException("Configure Orleans type with 'orleans:type'."); |
|||
} |
|||
|
|||
if (string.Equals(storeType, "MongoDB", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
builder.RegisterModule(new StoreMongoDbModule(Configuration)); |
|||
} |
|||
else |
|||
{ |
|||
throw new ConfigurationException($"Unsupported value '{storeType}' for 'stores:type', supported: MongoDb."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Options; |
|||
using Orleans.Providers.MongoDB; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public sealed class OrleansMongoDbModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public OrleansMongoDbModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var mongoConfig = Configuration.GetSection("orleans:mongoDb"); |
|||
|
|||
builder.RegisterInstance(Options.Create(mongoConfig.Get<MongoDBGatewayListProviderOptions>())) |
|||
.As<IOptions<MongoDBGatewayListProviderOptions>>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterInstance(Options.Create(mongoConfig.Get<MongoDBMembershipTableOptions>())) |
|||
.As<IOptions<MongoDBMembershipTableOptions>>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterInstance(Options.Create(mongoConfig.Get<MongoDBRemindersOptions>())) |
|||
.As<IOptions<MongoDBRemindersOptions>>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterInstance(Options.Create(mongoConfig.Get<MongoDBStatisticsOptions>())) |
|||
.As<IOptions<MongoDBStatisticsOptions>>() |
|||
.SingleInstance(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// ==========================================================================
|
|||
// OrleansSilo.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Orleans.Hosting; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public sealed class OrleansSilo |
|||
{ |
|||
private readonly IServiceProvider serviceProvider; |
|||
|
|||
public Task RunAsync() |
|||
{ |
|||
new SiloHostBuilder() |
|||
.ConfigureLocalHostPrimarySilo(33333) |
|||
.ConfigureSiloName("Squidex") |
|||
.UseServiceProviderFactory() |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue