public ResponseStateMachine(IOptions <ServiceBusRabbitMQOptions> configBus, IOptions <WorkflowConfig> configWorkflow) { _configBus = configBus.Value; InstanceState(x => x.State); //Response events Event(() => ResponseCreated, x => x.CorrelateById(context => context.Message.Response.ResponseId)); Event(() => ResponseUpdated, x => x.CorrelateById(context => context.Message.Response.ResponseId)); Event(() => ResponseActionCallback, x => x.CorrelateById(context => context.Message.ResponseId)); Event(() => ResponseClosed, x => x.CorrelateById(context => context.Message.Response.ResponseId)); Event(() => ResponseEventClusterTagged, x => x.CorrelateById(context => context.Message.Response.ResponseId)); //The event is submitted from the Response endpoint Initially( When(ResponseCreated) .Then(context => context.Instance.CorrelationId = context.Data.Response.ResponseId) .Then(context => Console.WriteLine($"Response-{context.Instance.CorrelationId}: Saga Initiated.")) .Then(context => Console.WriteLine($"Response--{context.Instance.CorrelationId}: Event Received.")) .ThenPublishActions(ResponseUpdateType.NewResponse) //Run open actions .Then(context => Console.Out.WriteLine($"Response--{context.Instance.CorrelationId}: ResponseCreated.")) .TransitionTo(Waiting) ); //Waiting state During(Waiting, //Triggers when event clusters have been associated When(ResponseEventClusterTagged) .ThenAsync(context => context.Publish(new ResponseUIUpdateRequestedEvent() { ResponseId = context.Instance.CorrelationId, ResponseUI = new ResponseUIModel() { UpdateType = ResponseUpdateType.UpdateResponse.ToString(), Response = context.Data.Response, ResponseId = context.Data.Response.ResponseId } })), //Triggers when the response was closed When(ResponseClosed) .Then(context => Console.Out.WriteLine($"Response--{context.Instance.CorrelationId}: ResponseClosed.")) .ThenPublishActions(ResponseUpdateType.CloseResponse), //Run close actions //Triggers when the was updated with new actions When(ResponseUpdated) .Then(context => Console.Out.WriteLine($"Response--{context.Instance.CorrelationId}: ResponseActionsUpdated.")) .ThenPublishActions(ResponseUpdateType.UpdateResponseActions), //Run update action, //Triggers call back on actions, ends the saga if all close actions are performed successfully When(ResponseActionCallback) .Then(context => Console.Out.WriteLine($"Response--{context.Instance.CorrelationId}: ResponseActionCallback")) .ThenAsync(context => context.Publish(new ActionCallbackUIUpdatedRequestedEvent() //Track action callbacks { ResponseId = context.Data.ResponseId, ActionId = context.Data.ActionId, ActionCloseModel = new ActionCallbackUIModel() { UpdateType = ResponseUpdateType.ResponseActionCallback.ToString(), ActionId = context.Data.ActionId, Status = context.Data.Status, ErrorMessage = context.Data.ErrorMessage, StartDate = context.Data.StartDate, EndDate = context.Data.EndDate, ResponseId = context.Data.ResponseId } })) //Add 1 to the action completed count .If(new StateMachineCondition <ResponseState, IEventSagaReceiveResponseActionCallback>(bc => bc.Data.ActionCorrelationId == bc.Instance.ActionCorrelationId), x => x .Then(context => context.Instance.ActionsCompletedCount++)) //When the count is complete, we send a UI update .If(new StateMachineCondition <ResponseState, IEventSagaReceiveResponseActionCallback>(bc => bc.Data.ActionCorrelationId == bc.Instance.ActionCorrelationId && bc.Instance.ActionsCompletedCount >= bc.Instance.ActionsTotal), x => x .ThenAsync(context => context.Publish(new ResponseUIUpdateRequestedEvent() { ResponseId = context.Instance.CorrelationId, ResponseUI = new ResponseUIModel() { UpdateType = context.Instance.ActionUpdateType.ToString(), ResponseId = context.Instance.CorrelationId } }))) //If the update type was close response, we finalize the saga .If(new StateMachineCondition <ResponseState, IEventSagaReceiveResponseActionCallback>(bc => bc.Data.ActionCorrelationId == bc.Instance.ActionCorrelationId && bc.Instance.ActionsCompletedCount >= bc.Instance.ActionsTotal && bc.Instance.ActionUpdateType == ResponseUpdateType.CloseResponse), x => x .Finalize()) ); //Delete persisted saga after completion SetCompletedWhenFinalized(); }
public ServiceBusRabbitMQ(IOptions <ServiceBusRabbitMQOptions> config, ILogger <ServiceBusRabbitMQ> logger) { _logger = logger; _config = config.Value; }
public DeviceSynchronizationStateMachine(IOptions <ServiceBusRabbitMQOptions> configBus) { _configBus = configBus.Value; InstanceState(x => x.State); #region Initialization Events //Devices Updates Event(() => EventReceived, x => x.CorrelateById(context => context.Message.DeviceId).SelectId(context => Guid.NewGuid())); Event(() => EventDeviceDeleted, x => x.CorrelateById(context => context.Message.CorrelationId)); Event(() => DeviceCreatedOrUpdated, x => x.CorrelateById(context => context.Message.CorrelationId)); //UI Update notification, can be ignored if saga doesn't exist anymore Event(() => DeviceUIUpdated, x => { x.CorrelateById(context => context.Message.CorrelationId); x.OnMissingInstance(m => m.Discard()); }); Event(() => DeviceDeleteRequestedFault, x => x.CorrelateById(context => context.Message.Message.CorrelationId)); Event(() => DeviceCreateOrUpdateRequestedFault, x => x.CorrelateById(context => context.Message.Message.CorrelationId)); #endregion //The event is submitted from Azure Service Queue Initially( When(EventReceived) .Then(context => context.Instance.DeviceId = context.Data.DeviceId) .Then(context => context.Instance.ChangeType = context.Data.ChangeType) .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization-{context.Instance.CorrelationId}: Saga Initiated.")) .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization--{context.Instance.CorrelationId}: Event Received.")) .If(new StateMachineCondition <DeviceSynchronizationState, IEventSagaReceivedDeviceChange>(bc => bc.Instance.ChangeType == "twinChangeNotification" || bc.Instance.ChangeType == "createDeviceIdentity" || bc.Instance.ChangeType == "ping"), x => x .ThenAsync(context => context.Publish(new DeviceCreateOrUpdateRequestedEvent() { CorrelationId = context.Instance.CorrelationId, DeviceId = context.Data.DeviceId, ChangeType = context.Data.ChangeType, Date = context.Data.Date, Data = context.Data.Data })) .TransitionTo(Waiting) ) .If(new StateMachineCondition <DeviceSynchronizationState, IEventSagaReceivedDeviceChange>(bc => bc.Instance.ChangeType == "deleteDeviceIdentity"), x => x .ThenAsync(context => context.Publish(new DeviceDeleteRequestedEvent() { CorrelationId = context.Instance.CorrelationId, DeviceId = context.Data.DeviceId })) .TransitionTo(Waiting) )); //State while the Device is being created / updated During(Waiting, When(DeviceCreatedOrUpdated) .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization---{context.Instance.CorrelationId}: Device Updated: {context.Instance.ChangeType}.")) .If(new StateMachineCondition <DeviceSynchronizationState, IDeviceCreatedOrUpdated>(bc => bc.Data.NotifyUI), x => x .ThenAsync(context => context.Publish(new DeviceUIUpdateRequestedEvent() { CorrelationId = context.Instance.CorrelationId, DeviceUI = new DeviceUIModel() { DeviceId = context.Instance.DeviceId, Device = context.Data.Device, UpdateType = context.Instance.ChangeType == "twinChangeNotification" || context.Instance.ChangeType == "ping" ? DeviceUpdateType.UpdateDevice.ToString() : DeviceUpdateType.NewDevice.ToString() } })) ) .Finalize(), When(EventDeviceDeleted) .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization---{context.Instance.CorrelationId}: Device Deleted: {context.Instance.DeviceId}.")) .ThenAsync(context => context.Publish(new DeviceUIUpdateRequestedEvent() { CorrelationId = context.Instance.CorrelationId, DeviceUI = new DeviceUIModel() { DeviceId = context.Instance.DeviceId, UpdateType = DeviceUpdateType.DeleteDevice.ToString() } })) .Finalize() ); //Stateless DuringAny( When(DeviceDeleteRequestedFault) .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization---{context.Instance.CorrelationId}: !!FAULT!! Device Deleted: {context.Data.Message}")), When(DeviceCreateOrUpdateRequestedFault) .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization---{context.Instance.CorrelationId}: !!FAULT!! Device Updated: {context.Data.Message}")) // //UI Update acknoledgement // When(DeviceUIUpdated) // .ThenAsync(context => Console.Out.WriteLineAsync($"DeviceSynchronization---{context.Instance.CorrelationId}: UI Updated.")) ); //Delete persisted saga after completion SetCompletedWhenFinalized(); }
public EventProcessingStateMachine(IOptions <ServiceBusRabbitMQOptions> configBus, IOptions <WorkflowConfig> configWorkflow) { _configBus = configBus.Value; _configWorkflow = configWorkflow.Value.EventProcessingWorkflow; InstanceState(x => x.State); #region Initialization Events //Main events being added, only one will be processed at a time Event(() => EventReceived, x => x.CorrelateBy(context => context.SagaDeviceId, context => $"{context.Message.EventType}_{context.Message.DeviceId}").SelectId(context => Guid.NewGuid())); Event(() => EventCloseReceived, x => x.CorrelateBy(context => context.SagaDeviceId, context => $"{context.Message.EventType}_{context.Message.DeviceId}")); Event(() => EventClusterCreatedOrUpdated, x => x.CorrelateById(context => context.Message.EventCluster.EventClusterId)); Event(() => EventClusterClosed, x => x.CorrelateById(context => context.Message.EventCluster.EventClusterId)); Event(() => EventClusterCreateOrUpdateRequestedFault, x => x.CorrelateById(context => context.Message.Message.EventClusterId)); //UI Update notification, can be ignored if saga doesn't exist anymore //Event(() => EventClusterUIUpdated, x => { x.CorrelateById(context => context.Message.CorrelationId); x.OnMissingInstance(m => m.Discard()); }); Schedule(() => EventClusterLifespanElapsed, x => x.ExpirationId, x => { x.Delay = TimeSpan.FromMinutes(_configWorkflow.EventClusterLifespan); x.Received = e => e.CorrelateById(context => context.Message.EventClusterId); }); #endregion //The event is submitted from Azure Service Queue Initially( When(EventReceived) .Then(context => context.Instance.EventType = context.Data.EventType) .Then(context => context.Instance.DeviceId = context.Data.DeviceId) .Then(context => context.Instance.LastEventReceived = context.Data.Date) .Then(context => context.Instance.SagaDeviceId = $"{context.Data.EventType}_{context.Data.DeviceId}") .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing-{context.Instance.CorrelationId}: Saga Initiated.")) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing--{context.Instance.CorrelationId}: Event Received.")) .If(new StateMachineCondition <EventProcessingState, IEventSagaReceived>(bc => bc.Data.EventType != "message"), x => x .Schedule(EventClusterLifespanElapsed, p => new EventClusterLifespanElapsed() { EventClusterId = p.Instance.CorrelationId }) ) .ThenAsync(context => context.Publish(new EventClusterCreateOrUpdateRequestedEvent() { EventClusterId = context.Instance.CorrelationId, DeviceId = context.Data.DeviceId, EventType = context.Data.EventType, Date = context.Data.Date, Data = context.Data.Data })) .TransitionTo(ListeningToEvents) ); //State while the Event Cluster is being created During(ListeningToEvents, //Any new event will be sent to the same saga When(EventReceived) .Then(context => context.Instance.LastEventReceived = context.Data.Date) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing--{context.Instance.CorrelationId}: Event Received.")) .If(new StateMachineCondition <EventProcessingState, IEventSagaReceived>(bc => bc.Data.EventType != "message"), x => x .Schedule(EventClusterLifespanElapsed, p => new EventClusterLifespanElapsed() { EventClusterId = p.Instance.CorrelationId }) ) .ThenAsync(context => context.Publish(new EventClusterCreateOrUpdateRequestedEvent() { EventClusterId = context.Instance.CorrelationId, DeviceId = context.Data.DeviceId, EventType = context.Data.EventType, Date = context.Data.Date, Data = context.Data.Data })), //Result of an event being created or updated When(EventClusterCreatedOrUpdated) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing---{context.Instance.CorrelationId}: Event Cluster Created.")) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing---{context.Instance.CorrelationId}: Event Added in Cluster.")) //In case of a lot of events being sent at once, skip UI refresh on some events .If(new StateMachineCondition <EventProcessingState, IEventClusterCreatedOrUpdated>(bc => bc.Instance.LastEventReceived == bc.Data.LastEventDate || //If not new event before the reception, send update bc.Data.EventCluster.EventCount == 1 || //If first event, send update bc.Data.EventCluster.EventCount % 10 == 0) //If many events at the same time, send every 5 events , x => x .ThenAsync(context => context.Publish(new EventUIUpdateRequestedEvent() //Update UI { CorrelationId = context.Instance.CorrelationId, EventClusterUI = new EventClusterUIModel() { EventCluster = context.Data.EventCluster, UpdateType = context.Data.EventCluster.EventCount > 1 ? EventClusterUpdateType.UpdateEventCluster.ToString() : EventClusterUpdateType.NewEventCluster.ToString() } })) ) //Check response tagging, only for the first generated event .If(new StateMachineCondition <EventProcessingState, IEventClusterCreatedOrUpdated>(bc => bc.Data.EventCluster.EventCount == 1), x => x .ThenAsync(context => context.Publish(new ResponseTagNewEventClusterRequestedEvent() { EventClusterId = context.Instance.CorrelationId, EventClusterGeolocation = context.Data.EventCluster.Device.Geolocation }))), When(EventCloseReceived) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing--{context.Instance.CorrelationId}: Event Cluster closing requested.")) .ThenAsync(context => context.Publish(new EventClusterCloseRequestedEvent() { EventClusterId = context.Instance.CorrelationId, ClosureDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddMinutes(_configWorkflow.EventClusterCooldown) })), When(EventClusterClosed) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing---{context.Instance.CorrelationId}: Saga Closed.")) .ThenAsync(context => context.Publish(new EventUIUpdateRequestedEvent() { CorrelationId = context.Instance.CorrelationId, EventClusterUI = new EventClusterUIModel() { EventCluster = context.Data.EventCluster, UpdateType = EventClusterUpdateType.CloseEventCluster.ToString() } })) .Finalize() ); //Stateless DuringAny( //No new event after a while, request to close the cluster When(EventClusterLifespanElapsed.Received) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing--{context.Instance.CorrelationId}: Event Cluster closing requested.")) .ThenAsync(context => context.Publish(new EventClusterCloseRequestedEvent() { EventClusterId = context.Data.EventClusterId, ClosureDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddMinutes(_configWorkflow.EventClusterCooldown) })), //Fault while creating/adding an event to cluster When(EventClusterCreateOrUpdateRequestedFault) .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing---{context.Instance.CorrelationId}: !!FAULT!! Event Cluster: {context.Data.Message}")) //UI Update acknoledgement - Disabled for performance improvement //When(EventClusterUIUpdated) // .ThenAsync(context => Console.Out.WriteLineAsync($"EventProcessing---{context.Instance.CorrelationId}: UI Updated.")) ); //Delete persisted saga after completion SetCompletedWhenFinalized(); }