private static async Task HandleUnhandledObjectOfTypeAsync <TObject>( FileStandardStream stream, string concern, HandleRecordAsyncSpecificProtocolBase <TObject> handler) { var streamRecordHandlingProtocol = stream.GetStreamRecordHandlingProtocols <TObject>(); var recordToHandle = await streamRecordHandlingProtocol.ExecuteAsync(new TryHandleRecordOp <TObject>(concern)); if (recordToHandle != null) { try { var handleRecordOp = new HandleRecordOp <TObject>(recordToHandle); await handler.ExecuteAsync(handleRecordOp); stream.GetStreamRecordHandlingProtocols().Execute(new CompleteRunningHandleRecordOp(recordToHandle.InternalRecordId, concern)); } catch (SelfCancelRunningExecutionException ex) { stream.GetStreamRecordHandlingProtocols().Execute(new SelfCancelRunningHandleRecordOp(recordToHandle.InternalRecordId, concern, ex.Details)); } catch (Exception ex) { stream.GetStreamRecordHandlingProtocols().Execute(new FailRunningHandleRecordOp(recordToHandle.InternalRecordId, concern, ex.ToString())); throw; } } }
/// <inheritdoc /> public override void Execute( HandleRecordOp <ExecuteOpRequestedEvent <TOperation> > operation) { var operationToExecute = operation.RecordToHandle.Payload.Operation; this.executeOperationProtocol.Execute(operationToExecute); Thread.Sleep(this.waitTimeBeforeQueuing); var operationClone = operation.RecordToHandle.Payload.DeepClone(); this.requeueStream.Put( operationClone, existingRecordStrategy: this.existingRecordStrategyOnRequeue, recordRetentionCount: this.recordRetentionCountOnRequeue); }
/// <inheritdoc /> public override async Task ExecuteAsync( HandleRecordOp <ExecuteOpRequestedEvent <long, UploadFileToSlackOp> > operation) { new { operation }.AsArg().Must().NotBeNull(); // Pull some info out of the operation. var slackOperationTrackingCodeId = operation.RecordToHandle.Payload.Id; var uploadFileToSlackOp = operation.RecordToHandle.Payload.Operation; var inheritableTags = operation.RecordToHandle.Metadata.Tags; // Write the request to the event stream var uploadFileToSlackRequestedEvent = new UploadFileToSlackRequestedEvent <long>(slackOperationTrackingCodeId, DateTime.UtcNow, uploadFileToSlackOp.UploadFileToSlackRequest); var tags = this.buildUploadFileToSlackRequestedEventTagsProtocol.ExecuteBuildTags(slackOperationTrackingCodeId, uploadFileToSlackRequestedEvent, inheritableTags); await this.slackEventStream.PutWithIdAsync(slackOperationTrackingCodeId, uploadFileToSlackRequestedEvent, tags, ExistingRecordStrategy.DoNotWriteIfFoundById); // Execute the operation var uploadFileToSlackResponse = await this.uploadFileToSlackProtocol.ExecuteAsync(uploadFileToSlackOp); // Put response to event stream based on success/failure UploadFileToSlackResponseEventBase <long> uploadFileToSlackResponseEvent; if (uploadFileToSlackResponse.UploadFileToSlackResult == UploadFileToSlackResult.Succeeded) { uploadFileToSlackResponseEvent = new SucceededInUploadingFileToSlackEvent <long>(slackOperationTrackingCodeId, DateTime.UtcNow, uploadFileToSlackResponse); } else { uploadFileToSlackResponseEvent = new FailedToUploadFileToSlackEvent <long>(slackOperationTrackingCodeId, DateTime.UtcNow, uploadFileToSlackResponse); } tags = this.buildUploadFileToSlackResponseEventTagsProtocol.ExecuteBuildTags(slackOperationTrackingCodeId, uploadFileToSlackResponseEvent, inheritableTags); await this.slackEventStream.PutWithIdAsync(slackOperationTrackingCodeId, uploadFileToSlackResponseEvent, tags, ExistingRecordStrategy.DoNotWriteIfFoundByIdAndType); }
/// <inheritdoc /> public override async Task ExecuteAsync( HandleRecordOp <ExecuteOpRequestedEvent <long, SendEmailOp> > operation) { new { operation }.AsArg().Must().NotBeNull(); // Pull some info out of the operation. var emailTrackingCodeId = operation.RecordToHandle.Payload.Id; var sendEmailOp = operation.RecordToHandle.Payload.Operation; var inheritableTags = operation.RecordToHandle.Metadata.Tags; // Write the request to the event stream var sendEmailRequestedEvent = new SendEmailRequestedEvent <long>(emailTrackingCodeId, DateTime.UtcNow, sendEmailOp.SendEmailRequest); var tags = this.buildSendEmailRequestedEventTagsProtocol.ExecuteBuildTags(emailTrackingCodeId, sendEmailRequestedEvent, inheritableTags); await this.emailEventStream.PutWithIdAsync(emailTrackingCodeId, sendEmailRequestedEvent, tags, ExistingRecordStrategy.DoNotWriteIfFoundById); // Execute the operation var emailResponse = await this.sendEmailProtocol.ExecuteAsync(sendEmailOp); // Put response to event stream based on success/failure SendEmailResponseEventBase <long> emailResponseEventBase; if (emailResponse.SendEmailResult == SendEmailResult.Success) { emailResponseEventBase = new SucceededInSendingEmailEvent <long>(emailTrackingCodeId, DateTime.UtcNow, emailResponse); } else { emailResponseEventBase = new FailedToSendEmailEvent <long>(emailTrackingCodeId, DateTime.UtcNow, emailResponse); } tags = this.buildSendEmailResponseEventTagsProtocol.ExecuteBuildTags(emailTrackingCodeId, emailResponseEventBase, inheritableTags); await this.emailEventStream.PutWithIdAsync(emailTrackingCodeId, emailResponseEventBase, tags, ExistingRecordStrategy.DoNotWriteIfFoundByIdAndType); }
/// <inheritdoc /> public override async Task ExecuteAsync( HandleRecordOp <ExecuteOpRequestedEvent <long, ProcessSendNotificationSagaOp> > operation) { new { operation }.AsArg().Must().NotBeNull(); // Pull some info out of the operation. var executeOpRequestedEvent = operation.RecordToHandle.Payload; var processSendNotificationSagaOp = executeOpRequestedEvent.Operation; var inheritableTags = (operation.RecordToHandle.Metadata.Tags?.DeepClone() ?? new List <NamedValue <string> >()).ToList(); // Poll for failure event var channels = processSendNotificationSagaOp.ChannelToOperationsMonitoringInfoMap.Keys; var channelToOperationsOutcomeInfoMap = new Dictionary <IDeliveryChannel, IReadOnlyCollection <ChannelOperationOutcomeInfo> >(); foreach (var channel in channels) { new { this.channelToEventStreamMap }.AsOp().Must().ContainKey(channel, Invariant($"Looking for success/failure events on event stream for chanel {channel.GetType().ToStringReadable()} but there is no event stream associated with that channel.")); var eventStream = this.channelToEventStreamMap[channel]; var operationsMonitoringInfo = processSendNotificationSagaOp.ChannelToOperationsMonitoringInfoMap[channel]; var operationsOutcomeInfo = new List <ChannelOperationOutcomeInfo>(); channelToOperationsOutcomeInfoMap.Add(channel, operationsOutcomeInfo); foreach (var operationMonitoringInfo in operationsMonitoringInfo) { ChannelOperationOutcomeInfo operationOutcomeInfo; var failureEventMetadata = await eventStream.GetLatestRecordMetadataByIdAsync( operationMonitoringInfo.ChannelOperationTrackingCodeId, operationMonitoringInfo.FailedEventType, recordNotFoundStrategy : RecordNotFoundStrategy.ReturnDefault); if (failureEventMetadata != null) { // Merge-in the tags on the failure event. AddMissingTags(inheritableTags, failureEventMetadata.Tags); operationOutcomeInfo = new ChannelOperationOutcomeInfo(operationMonitoringInfo.ChannelOperationTrackingCodeId, operationMonitoringInfo.FailedEventType, ChannelOperationOutcome.Failed); } else { var successEventMetadata = await eventStream.GetLatestRecordMetadataByIdAsync( operationMonitoringInfo.ChannelOperationTrackingCodeId, operationMonitoringInfo.SucceededEventType, recordNotFoundStrategy : RecordNotFoundStrategy.ReturnDefault); if (successEventMetadata != null) { // Merge-in the tags on the success event. AddMissingTags(inheritableTags, successEventMetadata.Tags); operationOutcomeInfo = new ChannelOperationOutcomeInfo(operationMonitoringInfo.ChannelOperationTrackingCodeId, operationMonitoringInfo.SucceededEventType, ChannelOperationOutcome.Succeeded); } else { // Neither succeeded nor failed, cancel the run and let this handler try again in the future. throw new SelfCancelRunningExecutionException(Invariant($"Notification '{executeOpRequestedEvent.Id}' is being sent on the '{channel.GetType().ToStringReadable()}' channel via channel-operation '{operationMonitoringInfo.ChannelOperationTrackingCodeId}', however the events that indicate whether the operation succeeded ('{operationMonitoringInfo.SucceededEventType}') or failed ('{operationMonitoringInfo.FailedEventType}') do not exist in the channel event stream. Is the operation still executing? Cancelling the handling of this Saga operation.")); } } operationsOutcomeInfo.Add(operationOutcomeInfo); } } // Write the AttemptToSendNotificationEventBase to the Event Stream. var notificationTrackingCodeId = processSendNotificationSagaOp.NotificationTrackingCodeId; var attemptToSendNotificationResult = new AttemptToSendNotificationResult(channelToOperationsOutcomeInfoMap); var attemptToSendNotificationOutcome = attemptToSendNotificationResult.GetOutcome(); AttemptToSendNotificationEventBase attemptToSendNotificationEvent; switch (attemptToSendNotificationOutcome) { case AttemptToSendNotificationOutcome.SentOnAllPreparedChannels: attemptToSendNotificationEvent = new SentOnAllPreparedChannelsEvent(notificationTrackingCodeId, DateTime.UtcNow, attemptToSendNotificationResult); break; case AttemptToSendNotificationOutcome.SentOnSomePreparedChannels: attemptToSendNotificationEvent = new SentOnSomePreparedChannelsEvent(notificationTrackingCodeId, DateTime.UtcNow, attemptToSendNotificationResult); break; case AttemptToSendNotificationOutcome.CouldNotSendOnAnyPreparedChannel: attemptToSendNotificationEvent = new CouldNotSendOnAnyPreparedChannelEvent(notificationTrackingCodeId, DateTime.UtcNow, attemptToSendNotificationResult); break; default: throw new NotSupportedException(Invariant($"This {nameof(AttemptToSendNotificationOutcome)} is not supported: {attemptToSendNotificationOutcome}.")); } var tags = this.buildAttemptToSendNotificationEventTagsProtocol.ExecuteBuildTags(notificationTrackingCodeId, attemptToSendNotificationEvent, inheritableTags); await this.notificationEventStream.PutWithIdAsync(notificationTrackingCodeId, attemptToSendNotificationEvent, tags, ExistingRecordStrategy.DoNotWriteIfFoundByIdAndType); }
/// <inheritdoc /> public override async Task ExecuteAsync( HandleRecordOp <ExecuteOpRequestedEvent <long, SendNotificationOp> > operation) { new { operation }.AsArg().Must().NotBeNull(); // Pull some info out of the operation. var trackingCodeId = operation.RecordToHandle.Payload.Id; var sendNotificationOp = operation.RecordToHandle.Payload.Operation; var notification = sendNotificationOp.Notification; var tags = operation.RecordToHandle.Metadata.Tags; // Write SendNotificationRequestedEvent to the Event Stream (creates the Notification Aggregate). await this.PutSendNotificationRequestedEventAsync(sendNotificationOp, trackingCodeId, tags); // Get the audience and write CannotGetOrUseAudienceEvent // to the Event Stream if there is none of if we should stop on failures. var getAudienceResult = await this.GetAudienceAsync(notification); var getAudienceOutcome = getAudienceResult.GetOutcome(); if ((getAudienceOutcome != GetAudienceOutcome.GotAudienceWithNoFailuresReported) && (getAudienceOutcome != GetAudienceOutcome.GotAudienceWithReportedFailuresIgnored)) { await this.PutCannotGetOrUseAudienceEventAsync(getAudienceResult, trackingCodeId, tags); return; } // Get the delivery channel configs and write CannotGetOrUseDeliveryChannelConfigsEvent // to the Event Stream if there are none or if we should stop on failures. var audience = getAudienceResult.Audience; var getDeliveryChannelConfigsResult = await this.GetDeliveryChannelConfigsAsync(notification, getAudienceResult.Audience); var getDeliveryChannelConfigsOutcome = getDeliveryChannelConfigsResult.GetOutcome(); if ((getDeliveryChannelConfigsOutcome != GetDeliveryChannelConfigsOutcome.GotDeliveryChannelConfigsWithNoFailuresReported) && (getDeliveryChannelConfigsOutcome != GetDeliveryChannelConfigsOutcome.GotDeliveryChannelConfigsWithReportedFailuresIgnored)) { await this.PutCannotGetOrUseDeliveryChannelConfigsEventAsync(getAudienceResult, getDeliveryChannelConfigsResult, trackingCodeId, tags); return; } // Prepare the notification to be sent to the audience on all channels configured for sending. var channelConfigs = getDeliveryChannelConfigsResult.Configs; var channelToPrepareToSendOnChannelResultMap = new Dictionary <IDeliveryChannel, PrepareToSendOnChannelResult>(); var channelToOperationInstructionsMap = new Dictionary <IDeliveryChannel, IReadOnlyList <ChannelOperationInstruction> >(); foreach (var channelConfig in channelConfigs) { if (channelConfig.Action == DeliveryChannelAction.AudienceOptedOut) { continue; } else if (channelConfig.Action == DeliveryChannelAction.SendOnChannel) { // no-op } else { throw new NotSupportedException(Invariant($"This {nameof(DeliveryChannelAction)} is not supported: {channelConfig.Action}.")); } var channel = channelConfig.Channel; var prepareToSendOnChannelResult = await this.PrepareToSendOnChannelAsync(notification, audience, channel, tags); var prepareToSendOnChannelOutcome = prepareToSendOnChannelResult.GetOutcome(); channelToPrepareToSendOnChannelResultMap.Add(channel, prepareToSendOnChannelResult); if ((prepareToSendOnChannelOutcome == PrepareToSendOnChannelOutcome.PreparedToSendOnChannelWithNoFailuresReported) || (prepareToSendOnChannelOutcome == PrepareToSendOnChannelOutcome.PreparedToSendOnChannelWithReportedFailuresIgnored)) { new { this.channelToOperationStreamMap }.AsOp().Must().ContainKey(channel, Invariant($"Staging to send on channel {channel.GetType().ToStringReadable()} but there is no operation stream associated with that channel.")); channelToOperationInstructionsMap.Add(channel, prepareToSendOnChannelResult.ChannelOperationInstructions); } else { if (this.cannotPrepareToSendOnChannelAction == CannotPrepareToSendOnChannelAction.ContinueAndAttemptPreparingToSendOnNextChannel) { // no-op } else if (this.cannotPrepareToSendOnChannelAction == CannotPrepareToSendOnChannelAction.StopAndNotDoNotSendOnAnyChannel) { // discard all channel operations channelToOperationInstructionsMap = new Dictionary <IDeliveryChannel, IReadOnlyList <ChannelOperationInstruction> >(); break; } else { throw new NotSupportedException(Invariant($"This {nameof(CannotPrepareToSendOnChannelAction)} is not supported: {this.cannotPrepareToSendOnChannelAction}.")); } } } var prepareToSendNotificationResult = new PrepareToSendNotificationResult(channelToPrepareToSendOnChannelResultMap, this.cannotPrepareToSendOnChannelAction, channelToOperationInstructionsMap.Keys.ToList()); var prepareToSendNotificationOutcome = prepareToSendNotificationResult.GetOutcome(); await this.PutPrepareToSendNotificationEventAsync(prepareToSendNotificationOutcome, getAudienceResult, getDeliveryChannelConfigsResult, prepareToSendNotificationResult, trackingCodeId, tags); // Is at least one channel is prepared for sending? Continue. if ((prepareToSendNotificationOutcome != PrepareToSendNotificationOutcome.PreparedToSendOnAllChannels) && (prepareToSendNotificationOutcome != PrepareToSendNotificationOutcome.PreparedToSendOnSomeChannels)) { return; } // Create a Saga to track channel operations. await this.PutSagaAsync(trackingCodeId, channelToOperationInstructionsMap, tags); // Push the channel-operations to their streams. foreach (var channel in channelToOperationInstructionsMap.Keys) { var channelOperationInstructions = channelToOperationInstructionsMap[channel]; await this.PutChannelOperationsAsync(channel, channelOperationInstructions); } }