mirror of https://github.com/Squidex/squidex.git
19 changed files with 207 additions and 547 deletions
@ -1,115 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// DispatchEventBlock.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using System.Threading.Tasks.Dataflow; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
|
||||
{ |
|
||||
internal sealed class DispatchEventBlock : IEventReceiverBlock |
|
||||
{ |
|
||||
private readonly ISemanticLog log; |
|
||||
private readonly IEventConsumer eventConsumer; |
|
||||
private readonly TransformBlock<Envelope<IEvent>, Envelope<IEvent>> transformBlock; |
|
||||
private long lastReceivedEventNumber = -1; |
|
||||
private bool isRunning = true; |
|
||||
|
|
||||
public Action<Exception> OnError { get; set; } |
|
||||
|
|
||||
public ITargetBlock<Envelope<IEvent>> Target |
|
||||
{ |
|
||||
get { return transformBlock; } |
|
||||
} |
|
||||
|
|
||||
public DispatchEventBlock(IEventConsumer eventConsumer, ISemanticLog log) |
|
||||
{ |
|
||||
this.eventConsumer = eventConsumer; |
|
||||
|
|
||||
this.log = log; |
|
||||
|
|
||||
var nullHandler = new ActionBlock<Envelope<IEvent>>(x => { }); |
|
||||
|
|
||||
transformBlock = |
|
||||
new TransformBlock<Envelope<IEvent>, Envelope<IEvent>>(x => HandleAsync(x), |
|
||||
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }); |
|
||||
transformBlock.LinkTo(nullHandler, x => x == null); |
|
||||
} |
|
||||
|
|
||||
public void LinkTo(ITargetBlock<Envelope<IEvent>> target) |
|
||||
{ |
|
||||
transformBlock.LinkTo(target, new DataflowLinkOptions { PropagateCompletion = true }); |
|
||||
} |
|
||||
|
|
||||
public void Stop() |
|
||||
{ |
|
||||
isRunning = false; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
isRunning = true; |
|
||||
|
|
||||
lastReceivedEventNumber = -1; |
|
||||
} |
|
||||
|
|
||||
private async Task<Envelope<IEvent>> HandleAsync(Envelope<IEvent> input) |
|
||||
{ |
|
||||
var eventNumber = input.Headers.EventNumber(); |
|
||||
|
|
||||
if (eventNumber <= lastReceivedEventNumber || !isRunning) |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
var consumerName = eventConsumer.Name; |
|
||||
|
|
||||
var eventId = input.Headers.EventId().ToString(); |
|
||||
var eventType = input.Payload.GetType().Name; |
|
||||
try |
|
||||
{ |
|
||||
log.LogInformation(w => w |
|
||||
.WriteProperty("action", "HandleEvent") |
|
||||
.WriteProperty("actionId", eventId) |
|
||||
.WriteProperty("state", "Started") |
|
||||
.WriteProperty("eventId", eventId) |
|
||||
.WriteProperty("eventType", eventType) |
|
||||
.WriteProperty("eventConsumer", consumerName)); |
|
||||
|
|
||||
await eventConsumer.On(input); |
|
||||
|
|
||||
log.LogInformation(w => w |
|
||||
.WriteProperty("action", "HandleEvent") |
|
||||
.WriteProperty("actionId", eventId) |
|
||||
.WriteProperty("state", "Completed") |
|
||||
.WriteProperty("eventId", eventId) |
|
||||
.WriteProperty("eventType", eventType) |
|
||||
.WriteProperty("eventConsumer", consumerName)); |
|
||||
|
|
||||
lastReceivedEventNumber = eventNumber; |
|
||||
|
|
||||
return input; |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
OnError?.Invoke(ex); |
|
||||
|
|
||||
log.LogError(ex, w => w |
|
||||
.WriteProperty("action", "HandleEvent") |
|
||||
.WriteProperty("actionId", eventId) |
|
||||
.WriteProperty("state", "Started") |
|
||||
.WriteProperty("eventId", eventId) |
|
||||
.WriteProperty("eventType", eventType) |
|
||||
.WriteProperty("eventConsumer", consumerName)); |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// IEventReceiverBlock.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
|
||||
{ |
|
||||
public interface IEventReceiverBlock |
|
||||
{ |
|
||||
Action<Exception> OnError { get; set; } |
|
||||
|
|
||||
void Reset(); |
|
||||
|
|
||||
void Stop(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,98 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ParseEventBlock.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using System.Threading.Tasks.Dataflow; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
|
||||
{ |
|
||||
internal sealed class ParseEventBlock : IEventReceiverBlock |
|
||||
{ |
|
||||
private readonly EventDataFormatter formatter; |
|
||||
private readonly ISemanticLog log; |
|
||||
private readonly TransformBlock<StoredEvent, Envelope<IEvent>> transformBlock; |
|
||||
private long lastReceivedEventNumber = -1; |
|
||||
private bool isRunning = true; |
|
||||
|
|
||||
public Action<Exception> OnError { get; set; } |
|
||||
|
|
||||
public ParseEventBlock(EventDataFormatter formatter, ISemanticLog log) |
|
||||
{ |
|
||||
this.formatter = formatter; |
|
||||
this.log = log; |
|
||||
|
|
||||
var nullHandler = new ActionBlock<Envelope<IEvent>>(x => { }); |
|
||||
|
|
||||
transformBlock = |
|
||||
new TransformBlock<StoredEvent, Envelope<IEvent>>(x => HandleAsync(x), |
|
||||
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }); |
|
||||
transformBlock.LinkTo(nullHandler, x => x == null); |
|
||||
} |
|
||||
|
|
||||
public Task NextAsync(StoredEvent input) |
|
||||
{ |
|
||||
return transformBlock.SendAsync(input); |
|
||||
} |
|
||||
|
|
||||
public void LinkTo(ITargetBlock<Envelope<IEvent>> target) |
|
||||
{ |
|
||||
transformBlock.LinkTo(target, new DataflowLinkOptions { PropagateCompletion = true }); |
|
||||
} |
|
||||
|
|
||||
public void Complete() |
|
||||
{ |
|
||||
transformBlock.Complete(); |
|
||||
} |
|
||||
|
|
||||
public void Stop() |
|
||||
{ |
|
||||
isRunning = false; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
isRunning = true; |
|
||||
|
|
||||
lastReceivedEventNumber = -1; |
|
||||
} |
|
||||
|
|
||||
private Envelope<IEvent> HandleAsync(StoredEvent input) |
|
||||
{ |
|
||||
var eventNumber = input.EventNumber; |
|
||||
|
|
||||
if (eventNumber <= lastReceivedEventNumber || !isRunning) |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
var result = formatter.Parse(input.Data); |
|
||||
|
|
||||
result.SetEventNumber(input.EventNumber); |
|
||||
result.SetEventStreamNumber(input.EventStreamNumber); |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
OnError?.Invoke(ex); |
|
||||
|
|
||||
log.LogFatal(ex, w => w |
|
||||
.WriteProperty("action", "ParseEvent") |
|
||||
.WriteProperty("state", "Failed") |
|
||||
.WriteProperty("eventId", input.Data.EventId.ToString()) |
|
||||
.WriteProperty("eventNumber", input.EventNumber)); |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,161 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// QueryEventsBlock.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using System.Threading.Tasks.Dataflow; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
|
||||
{ |
|
||||
internal sealed class QueryEventsBlock : IEventReceiverBlock |
|
||||
{ |
|
||||
private readonly ISemanticLog log; |
|
||||
private readonly IEventConsumerInfoRepository eventConsumerInfoRepository; |
|
||||
private readonly IEventConsumer eventConsumer; |
|
||||
private readonly IEventStore eventStore; |
|
||||
private readonly ActionBlock<Envelope<IEvent>> actionBlock; |
|
||||
private bool isRunning = true; |
|
||||
private bool isStarted; |
|
||||
|
|
||||
public Action<Exception> OnError { get; set; } |
|
||||
|
|
||||
public Action OnReset { get; set; } |
|
||||
|
|
||||
public Func<StoredEvent, Task> OnEvent { get; set; } |
|
||||
|
|
||||
public ITargetBlock<Envelope<IEvent>> Target |
|
||||
{ |
|
||||
get { return actionBlock; } |
|
||||
} |
|
||||
|
|
||||
public Task Completion |
|
||||
{ |
|
||||
get { return actionBlock.Completion; } |
|
||||
} |
|
||||
|
|
||||
public QueryEventsBlock(IEventConsumerInfoRepository eventConsumerInfoRepository, IEventConsumer eventConsumer, IEventStore eventStore, ISemanticLog log) |
|
||||
{ |
|
||||
this.eventConsumerInfoRepository = eventConsumerInfoRepository; |
|
||||
this.eventConsumer = eventConsumer; |
|
||||
this.eventStore = eventStore; |
|
||||
|
|
||||
this.log = log; |
|
||||
|
|
||||
actionBlock = |
|
||||
new ActionBlock<Envelope<IEvent>>(HandleAsync, |
|
||||
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }); |
|
||||
} |
|
||||
|
|
||||
public void Complete() |
|
||||
{ |
|
||||
actionBlock.Complete(); |
|
||||
} |
|
||||
|
|
||||
public void NextOrThrowAway() |
|
||||
{ |
|
||||
actionBlock.Post(null); |
|
||||
} |
|
||||
|
|
||||
public void Stop() |
|
||||
{ |
|
||||
isRunning = false; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
isRunning = true; |
|
||||
} |
|
||||
|
|
||||
private async Task HandleAsync(object input) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
if (!isStarted) |
|
||||
{ |
|
||||
await eventConsumerInfoRepository.CreateAsync(eventConsumer.Name); |
|
||||
|
|
||||
isStarted = true; |
|
||||
} |
|
||||
|
|
||||
var status = await eventConsumerInfoRepository.FindAsync(eventConsumer.Name); |
|
||||
|
|
||||
var lastReceivedEventNumber = status.LastHandledEventNumber; |
|
||||
|
|
||||
if (status.IsResetting) |
|
||||
{ |
|
||||
await ResetAsync(); |
|
||||
|
|
||||
Reset(); |
|
||||
} |
|
||||
|
|
||||
if (!status.IsStopped || !isRunning) |
|
||||
{ |
|
||||
var ct = new CancellationTokenSource(); |
|
||||
|
|
||||
await eventStore.GetEventsAsync(async storedEvent => |
|
||||
{ |
|
||||
if (!isRunning) |
|
||||
{ |
|
||||
ct.Cancel(); |
|
||||
} |
|
||||
|
|
||||
var onEvent = OnEvent; |
|
||||
|
|
||||
if (onEvent != null) |
|
||||
{ |
|
||||
await onEvent(storedEvent); |
|
||||
} |
|
||||
}, ct.Token, null, lastReceivedEventNumber); |
|
||||
} |
|
||||
} |
|
||||
catch (OperationCanceledException) |
|
||||
{ |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
OnError?.Invoke(ex); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task ResetAsync() |
|
||||
{ |
|
||||
var consumerName = eventConsumer.Name; |
|
||||
|
|
||||
var actionId = Guid.NewGuid().ToString(); |
|
||||
try |
|
||||
{ |
|
||||
log.LogInformation(w => w |
|
||||
.WriteProperty("action", "EventConsumerReset") |
|
||||
.WriteProperty("actionId", actionId) |
|
||||
.WriteProperty("state", "Started") |
|
||||
.WriteProperty("eventConsumer", consumerName)); |
|
||||
|
|
||||
await eventConsumer.ClearAsync(); |
|
||||
await eventConsumerInfoRepository.SetLastHandledEventNumberAsync(consumerName, -1); |
|
||||
|
|
||||
log.LogInformation(w => w |
|
||||
.WriteProperty("action", "EventConsumerReset") |
|
||||
.WriteProperty("actionId", actionId) |
|
||||
.WriteProperty("state", "Completed") |
|
||||
.WriteProperty("eventConsumer", consumerName)); |
|
||||
|
|
||||
OnReset?.Invoke(); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
log.LogFatal(ex, w => w |
|
||||
.WriteProperty("action", "EventConsumerReset") |
|
||||
.WriteProperty("actionId", actionId) |
|
||||
.WriteProperty("state", "Completed") |
|
||||
.WriteProperty("eventConsumer", consumerName)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,86 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// UpdateStateBlock.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using System.Threading.Tasks.Dataflow; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
|
||||
{ |
|
||||
internal sealed class UpdateStateBlock : IEventReceiverBlock |
|
||||
{ |
|
||||
private readonly ISemanticLog log; |
|
||||
private readonly IEventConsumerInfoRepository eventConsumerInfoRepository; |
|
||||
private readonly IEventConsumer eventConsumer; |
|
||||
private readonly ActionBlock<Envelope<IEvent>> actionBlock; |
|
||||
private long lastReceivedEventNumber = -1; |
|
||||
private bool isRunning = true; |
|
||||
|
|
||||
public Action<Exception> OnError { get; set; } |
|
||||
|
|
||||
public ITargetBlock<Envelope<IEvent>> Target |
|
||||
{ |
|
||||
get { return actionBlock; } |
|
||||
} |
|
||||
|
|
||||
public Task Completion |
|
||||
{ |
|
||||
get { return actionBlock.Completion; } |
|
||||
} |
|
||||
|
|
||||
public UpdateStateBlock(IEventConsumerInfoRepository eventConsumerInfoRepository, IEventConsumer eventConsumer, ISemanticLog log) |
|
||||
{ |
|
||||
this.eventConsumerInfoRepository = eventConsumerInfoRepository; |
|
||||
this.eventConsumer = eventConsumer; |
|
||||
|
|
||||
this.log = log; |
|
||||
|
|
||||
actionBlock = |
|
||||
new ActionBlock<Envelope<IEvent>>(HandleAsync, |
|
||||
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }); |
|
||||
} |
|
||||
|
|
||||
public void Stop() |
|
||||
{ |
|
||||
isRunning = false; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
isRunning = true; |
|
||||
|
|
||||
lastReceivedEventNumber = -1; |
|
||||
} |
|
||||
|
|
||||
private async Task HandleAsync(Envelope<IEvent> input) |
|
||||
{ |
|
||||
var eventNumber = input.Headers.EventNumber(); |
|
||||
|
|
||||
if (eventNumber <= lastReceivedEventNumber || !isRunning) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
await eventConsumerInfoRepository.SetLastHandledEventNumberAsync(eventConsumer.Name, eventNumber); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
OnError?.Invoke(ex); |
|
||||
|
|
||||
log.LogFatal(ex, w => w |
|
||||
.WriteProperty("action", "UpdateState") |
|
||||
.WriteProperty("state", "Failed") |
|
||||
.WriteProperty("eventId", input.Headers.EventId().ToString()) |
|
||||
.WriteProperty("eventNumber", input.Headers.EventNumber())); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue