mirror of https://github.com/Squidex/squidex.git
16 changed files with 88 additions and 305 deletions
@ -1,249 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventReceiver.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Timers; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public sealed class EventReceiver : DisposableObjectBase |
|||
{ |
|||
private readonly EventDataFormatter formatter; |
|||
private readonly IEventStore eventStore; |
|||
private readonly IEventConsumerInfoRepository eventConsumerInfoRepository; |
|||
private readonly ISemanticLog log; |
|||
private IEventSubscription currentSubscription; |
|||
private CompletionTimer timer; |
|||
|
|||
public EventReceiver( |
|||
EventDataFormatter formatter, |
|||
IEventStore eventStore, |
|||
IEventConsumerInfoRepository eventConsumerInfoRepository, |
|||
ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(log, nameof(log)); |
|||
Guard.NotNull(formatter, nameof(formatter)); |
|||
Guard.NotNull(eventStore, nameof(eventStore)); |
|||
Guard.NotNull(eventConsumerInfoRepository, nameof(eventConsumerInfoRepository)); |
|||
|
|||
this.log = log; |
|||
this.formatter = formatter; |
|||
this.eventStore = eventStore; |
|||
this.eventConsumerInfoRepository = eventConsumerInfoRepository; |
|||
} |
|||
|
|||
protected override void DisposeObject(bool disposing) |
|||
{ |
|||
if (disposing) |
|||
{ |
|||
try |
|||
{ |
|||
currentSubscription?.Dispose(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogWarning(ex, w => w |
|||
.WriteProperty("action", "DisposeEventReceiver") |
|||
.WriteProperty("state", "Failed")); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
timer?.StopAsync().Wait(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogWarning(ex, w => w |
|||
.WriteProperty("action", "DisposeEventReceiver") |
|||
.WriteProperty("state", "Failed")); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Refresh() |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
timer?.SkipCurrentDelay(); |
|||
} |
|||
|
|||
public void Subscribe(IEventConsumer eventConsumer) |
|||
{ |
|||
Guard.NotNull(eventConsumer, nameof(eventConsumer)); |
|||
|
|||
ThrowIfDisposed(); |
|||
|
|||
if (timer != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var consumerName = eventConsumer.Name; |
|||
var consumerStarted = false; |
|||
|
|||
timer = new CompletionTimer(5000, async ct => |
|||
{ |
|||
if (!consumerStarted) |
|||
{ |
|||
await eventConsumerInfoRepository.CreateAsync(consumerName); |
|||
|
|||
consumerStarted = true; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var status = await eventConsumerInfoRepository.FindAsync(consumerName); |
|||
|
|||
var position = status.Position; |
|||
|
|||
if (status.IsResetting) |
|||
{ |
|||
currentSubscription?.Dispose(); |
|||
currentSubscription = null; |
|||
|
|||
position = null; |
|||
|
|||
await ResetAsync(eventConsumer); |
|||
} |
|||
else if (status.IsStopped) |
|||
{ |
|||
currentSubscription?.Dispose(); |
|||
currentSubscription = null; |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (currentSubscription == null) |
|||
{ |
|||
await SubscribeAsync(eventConsumer, position); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogFatal(ex, w => w.WriteProperty("action", "EventHandlingFailed")); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private async Task SubscribeAsync(IEventConsumer eventConsumer, string position) |
|||
{ |
|||
var consumerName = eventConsumer.Name; |
|||
|
|||
var subscription = eventStore.CreateSubscription(eventConsumer.EventsFilter, position); |
|||
|
|||
await subscription.SubscribeAsync(async storedEvent => |
|||
{ |
|||
await DispatchConsumerAsync(ParseEvent(storedEvent), eventConsumer, eventConsumer.Name); |
|||
|
|||
await eventConsumerInfoRepository.SetPositionAsync(eventConsumer.Name, storedEvent.EventPosition, false); |
|||
}, async exception => |
|||
{ |
|||
await eventConsumerInfoRepository.StopAsync(consumerName, exception.ToString()); |
|||
|
|||
subscription.Dispose(); |
|||
}); |
|||
|
|||
currentSubscription = subscription; |
|||
} |
|||
|
|||
private async Task ResetAsync(IEventConsumer eventConsumer) |
|||
{ |
|||
var actionId = Guid.NewGuid().ToString(); |
|||
try |
|||
{ |
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Started") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
await eventConsumer.ClearAsync(); |
|||
await eventConsumerInfoRepository.SetPositionAsync(eventConsumer.Name, null, true); |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogFatal(ex, w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventConsumer", eventConsumer.GetType().Name)); |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
|
|||
private async Task DispatchConsumerAsync(Envelope<IEvent> @event, IEventConsumer eventConsumer, string consumerName) |
|||
{ |
|||
var eventId = @event.Headers.EventId().ToString(); |
|||
var eventType = @event.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(@event); |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("actionId", eventId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventId", eventId) |
|||
.WriteProperty("eventType", eventType) |
|||
.WriteProperty("eventConsumer", consumerName)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogError(ex, w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("actionId", eventId) |
|||
.WriteProperty("state", "Started") |
|||
.WriteProperty("eventId", eventId) |
|||
.WriteProperty("eventType", eventType) |
|||
.WriteProperty("eventConsumer", consumerName)); |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
|
|||
private Envelope<IEvent> ParseEvent(StoredEvent storedEvent) |
|||
{ |
|||
try |
|||
{ |
|||
var @event = formatter.Parse(storedEvent.Data); |
|||
|
|||
@event.SetEventPosition(storedEvent.EventPosition); |
|||
@event.SetEventStreamNumber(storedEvent.EventStreamNumber); |
|||
|
|||
return @event; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogFatal(ex, w => w |
|||
.WriteProperty("action", "ParseEvent") |
|||
.WriteProperty("state", "Failed") |
|||
.WriteProperty("eventId", storedEvent.Data.EventId.ToString()) |
|||
.WriteProperty("eventPosition", storedEvent.EventPosition)); |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue