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();
}
}