mirror of https://github.com/Squidex/squidex.git
8 changed files with 335 additions and 210 deletions
@ -1,124 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// EventReceiverBlock.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
using System.Threading.Tasks.Dataflow; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
|
||||
{ |
|
||||
public abstract class EventReceiverBlock<TInput, TOutput> |
|
||||
{ |
|
||||
private long lastEventNumber = -1; |
|
||||
|
|
||||
protected ISemanticLog Log { get; } |
|
||||
|
|
||||
protected IEventConsumer EventConsumer { get; } |
|
||||
|
|
||||
protected IEventConsumerInfoRepository EventConsumerInfoRepository { get; } |
|
||||
|
|
||||
public ITargetBlock<TInput> Target { get; } |
|
||||
|
|
||||
public Task Completion |
|
||||
{ |
|
||||
get { return Target.Completion; } |
|
||||
} |
|
||||
|
|
||||
protected EventReceiverBlock(bool transform, IEventConsumer eventConsumer, IEventConsumerInfoRepository eventConsumerInfoRepository, ISemanticLog log) |
|
||||
{ |
|
||||
EventConsumer = eventConsumer; |
|
||||
EventConsumerInfoRepository = eventConsumerInfoRepository; |
|
||||
|
|
||||
Log = log; |
|
||||
|
|
||||
if (transform) |
|
||||
{ |
|
||||
var nullHandlerBlock = |
|
||||
new ActionBlock<TOutput>(_ => { }); |
|
||||
|
|
||||
var transformBlock = |
|
||||
new TransformBlock<TInput, TOutput>(new Func<TInput, Task<TOutput>>(HandleAsync), |
|
||||
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }); |
|
||||
transformBlock.LinkTo(nullHandlerBlock, new DataflowLinkOptions { PropagateCompletion = true }, x => x == null); |
|
||||
|
|
||||
Target = transformBlock; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
Target = |
|
||||
new ActionBlock<TInput>(HandleAsync, |
|
||||
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 }); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public Task NextAsync(TInput input) |
|
||||
{ |
|
||||
return Target.SendAsync(input); |
|
||||
} |
|
||||
|
|
||||
public void NextOrThrowAway(TInput input) |
|
||||
{ |
|
||||
Target.Post(input); |
|
||||
} |
|
||||
|
|
||||
public void Complete() |
|
||||
{ |
|
||||
Target.Complete(); |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
lastEventNumber = -1; |
|
||||
} |
|
||||
|
|
||||
public void LinkTo(ITargetBlock<TOutput> other) |
|
||||
{ |
|
||||
if (Target is TransformBlock<TInput, TOutput> transformBlock) |
|
||||
{ |
|
||||
transformBlock.LinkTo(other, new DataflowLinkOptions { PropagateCompletion = true }, e => e != null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected abstract Task<TOutput> On(TInput input); |
|
||||
|
|
||||
protected abstract long GetEventNumber(TInput input); |
|
||||
|
|
||||
private async Task<TOutput> HandleAsync(TInput input) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var eventNumber = GetEventNumber(input); |
|
||||
|
|
||||
if (eventNumber > lastEventNumber) |
|
||||
{ |
|
||||
var envelope = await On(input); |
|
||||
|
|
||||
lastEventNumber = eventNumber; |
|
||||
|
|
||||
return envelope; |
|
||||
} |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
Log.LogFatal(ex, w => w.WriteProperty("action", "EventHandlingFailed")); |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
await EventConsumerInfoRepository.StopAsync(EventConsumer.Name, ex.ToString()); |
|
||||
} |
|
||||
catch (Exception ex2) |
|
||||
{ |
|
||||
Log.LogFatal(ex2, w => w.WriteProperty("action", "EventHandlingFailed")); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return default(TOutput); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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,33 +1,86 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// UpdateStateBlock.cs
|
// DispatchEventBlock.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
using System.Threading.Tasks.Dataflow; |
||||
using Squidex.Infrastructure.Log; |
using Squidex.Infrastructure.Log; |
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events.Internal |
namespace Squidex.Infrastructure.CQRS.Events.Internal |
||||
{ |
{ |
||||
public sealed class UpdateStateBlock : EventReceiverBlock<Envelope<IEvent>, Envelope<IEvent>> |
internal sealed class UpdateStateBlock : IEventReceiverBlock |
||||
{ |
{ |
||||
public UpdateStateBlock(IEventConsumer eventConsumer, IEventConsumerInfoRepository eventConsumerInfoRepository, ISemanticLog log) |
private readonly ISemanticLog log; |
||||
: base(false, eventConsumer, eventConsumerInfoRepository, 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; |
||||
} |
} |
||||
|
|
||||
protected override async Task<Envelope<IEvent>> On(Envelope<IEvent> input) |
public void Reset() |
||||
{ |
{ |
||||
await EventConsumerInfoRepository.SetLastHandledEventNumberAsync(EventConsumer.Name, input.Headers.EventNumber()); |
isRunning = true; |
||||
|
|
||||
return input; |
lastReceivedEventNumber = -1; |
||||
} |
} |
||||
|
|
||||
protected override long GetEventNumber(Envelope<IEvent> input) |
private async Task HandleAsync(Envelope<IEvent> input) |
||||
{ |
{ |
||||
return input.Headers.EventNumber(); |
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