using Microsoft.Extensions.Logging; using Dapr.Workflow; using Dapr.Workflow.Client; using MaksIT.Results; using MaksIT.Core.Extensions; namespace MaksIT.Dapr.Services; /// /// Schedules and manages Dapr workflow instances with outcomes. /// public interface IDaprWorkflowService { /// /// Schedules a new workflow instance. Returns the instance id. /// Task> ScheduleAsync( string workflowName, object? input = null, string? instanceId = null, DateTimeOffset? startTime = null, CancellationToken cancellationToken = default); /// /// Gets the current state of a workflow instance. /// Task> GetStateAsync(string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellationToken = default); /// /// Waits until the workflow has started. /// Task> WaitForStartAsync(string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellationToken = default); /// /// Waits until the workflow has completed. /// Task> WaitForCompletionAsync(string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellationToken = default); /// /// Raises an external event to a waiting workflow. /// Task RaiseEventAsync(string instanceId, string eventName, object? eventPayload = null, CancellationToken cancellationToken = default); /// /// Terminates a running workflow instance. /// Task TerminateAsync(string instanceId, object? output = null, CancellationToken cancellationToken = default); /// /// Suspends a running workflow instance. /// Task SuspendAsync(string instanceId, string? reason = null, CancellationToken cancellationToken = default); /// /// Resumes a suspended workflow instance. /// Task ResumeAsync(string instanceId, string? reason = null, CancellationToken cancellationToken = default); /// /// Purges history for a completed workflow instance. /// Task PurgeAsync(string instanceId, CancellationToken cancellationToken = default); /// /// Lists workflow instance IDs with optional pagination. /// Task> ListInstanceIdsAsync( string? continuationToken = null, int? pageSize = null, CancellationToken cancellationToken = default); /// /// Gets the full execution history of a workflow instance. /// Task>> GetInstanceHistoryAsync( string instanceId, CancellationToken cancellationToken = default); /// /// Reruns a workflow from a history event, returning the new instance id. /// Task> RerunFromEventAsync( string sourceInstanceId, uint eventId, RerunWorkflowFromEventOptions? options = null, CancellationToken cancellationToken = default); } /// /// Default using . /// public class DaprWorkflowService : IDaprWorkflowService { private const string ErrorMessage = "MaksIT.Dapr - Workflow error"; private readonly IDaprWorkflowClient _client; private readonly ILogger _logger; /// /// Creates a workflow facade backed by . /// public DaprWorkflowService(ILogger logger, IDaprWorkflowClient client) { _logger = logger; _client = client; } /// public async Task> ScheduleAsync( string workflowName, object? input = null, string? instanceId = null, DateTimeOffset? startTime = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(workflowName)) return Result.BadRequest(default!, "workflowName is required."); try { var id = await _client.ScheduleNewWorkflowAsync(workflowName, instanceId, input, startTime, cancellationToken); return Result.Ok(id); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> GetStateAsync( string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return Result.BadRequest(default!, validation.Messages.ToArray()); try { var state = await _client.GetWorkflowStateAsync(instanceId, getInputsAndOutputs, cancellationToken); return Result.Ok(state); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> WaitForStartAsync( string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return Result.BadRequest(default!, validation.Messages.ToArray()); try { var state = await _client.WaitForWorkflowStartAsync(instanceId, getInputsAndOutputs, cancellationToken); return Result.Ok(state); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> WaitForCompletionAsync( string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return Result.BadRequest(default!, validation.Messages.ToArray()); try { var state = await _client.WaitForWorkflowCompletionAsync(instanceId, getInputsAndOutputs, cancellationToken); return Result.Ok(state); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task RaiseEventAsync( string instanceId, string eventName, object? eventPayload = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(instanceId) || string.IsNullOrWhiteSpace(eventName)) return Result.BadRequest("instanceId and eventName are required."); try { await _client.RaiseEventAsync(instanceId, eventName, eventPayload, cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task TerminateAsync( string instanceId, object? output = null, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return validation; try { await _client.TerminateWorkflowAsync(instanceId, output, cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task SuspendAsync( string instanceId, string? reason = null, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return validation; try { await _client.SuspendWorkflowAsync(instanceId, reason, cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task ResumeAsync( string instanceId, string? reason = null, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return validation; try { await _client.ResumeWorkflowAsync(instanceId, reason, cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task PurgeAsync(string instanceId, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return validation; try { await _client.PurgeInstanceAsync(instanceId, cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> ListInstanceIdsAsync( string? continuationToken = null, int? pageSize = null, CancellationToken cancellationToken = default) { try { var page = await _client.ListInstanceIdsAsync(continuationToken, pageSize, cancellationToken); return Result.Ok(page); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task>> GetInstanceHistoryAsync( string instanceId, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(instanceId); if (!validation.IsSuccess) return Result>.BadRequest(default!, validation.Messages.ToArray()); try { var history = await _client.GetInstanceHistoryAsync(instanceId, cancellationToken); return Result>.Ok(history); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result>.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> RerunFromEventAsync( string sourceInstanceId, uint eventId, RerunWorkflowFromEventOptions? options = null, CancellationToken cancellationToken = default) { var validation = ValidateInstanceId(sourceInstanceId); if (!validation.IsSuccess) return Result.BadRequest(default!, validation.Messages.ToArray()); try { var id = await _client.RerunWorkflowFromEventAsync(sourceInstanceId, eventId, options, cancellationToken); return Result.Ok(id); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } private static Result ValidateInstanceId(string instanceId) { if (string.IsNullOrWhiteSpace(instanceId)) return Result.BadRequest("instanceId is required."); return Result.Ok(); } }