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