-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTransactionScript.cs
More file actions
151 lines (123 loc) · 5.54 KB
/
TransactionScript.cs
File metadata and controls
151 lines (123 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
namespace PatternKit.Application.TransactionScript;
/// <summary>Application service operation that executes one request workflow as an explicit transaction script.</summary>
public interface ITransactionScript<TRequest, TResponse>
{
string Name { get; }
ValueTask<TransactionScriptResult<TResponse>> ExecuteAsync(TRequest request, CancellationToken cancellationToken = default);
}
/// <summary>Fluent Transaction Script implementation for request/application workflows.</summary>
public sealed class TransactionScript<TRequest, TResponse> : ITransactionScript<TRequest, TResponse>
{
private readonly Func<TRequest, IEnumerable<TransactionScriptError>> _validator;
private readonly Func<TRequest, CancellationToken, ValueTask<TResponse>> _handler;
private TransactionScript(
string name,
Func<TRequest, IEnumerable<TransactionScriptError>> validator,
Func<TRequest, CancellationToken, ValueTask<TResponse>> handler)
{
Name = name;
_validator = validator;
_handler = handler;
}
public string Name { get; }
public static Builder Create(string name)
=> new(name);
public async ValueTask<TransactionScriptResult<TResponse>> ExecuteAsync(TRequest request, CancellationToken cancellationToken = default)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
cancellationToken.ThrowIfCancellationRequested();
var errors = (_validator(request) ?? Array.Empty<TransactionScriptError>())
.Where(static error => error is not null)
.ToArray();
if (errors.Length > 0)
return TransactionScriptResult<TResponse>.Rejected(errors);
try
{
var response = await _handler(request, cancellationToken).ConfigureAwait(false);
return TransactionScriptResult<TResponse>.Completed(response);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return TransactionScriptResult<TResponse>.Failed(ex);
}
}
public sealed class Builder
{
private readonly string _name;
private Func<TRequest, IEnumerable<TransactionScriptError>> _validator = static _ => Array.Empty<TransactionScriptError>();
private Func<TRequest, CancellationToken, ValueTask<TResponse>>? _handler;
internal Builder(string name)
{
_name = string.IsNullOrWhiteSpace(name)
? throw new ArgumentException("Transaction script name is required.", nameof(name))
: name;
}
public Builder Validate(Func<TRequest, IEnumerable<TransactionScriptError>> validator)
{
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
return this;
}
public Builder Execute(Func<TRequest, CancellationToken, ValueTask<TResponse>> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
return this;
}
public TransactionScript<TRequest, TResponse> Build()
=> new(_name, _validator, _handler ?? throw new InvalidOperationException("Transaction script handler is required."));
}
}
/// <summary>Result returned by a Transaction Script execution.</summary>
public sealed class TransactionScriptResult<TResponse>
{
private TransactionScriptResult(
TResponse? response,
TransactionScriptStatus status,
IReadOnlyList<TransactionScriptError> errors,
Exception? exception)
{
Response = response;
Status = status;
Errors = errors;
Exception = exception;
}
public TResponse? Response { get; }
public TransactionScriptStatus Status { get; }
public IReadOnlyList<TransactionScriptError> Errors { get; }
public Exception? Exception { get; }
public bool Succeeded => Status == TransactionScriptStatus.Completed;
public static TransactionScriptResult<TResponse> Completed(TResponse response)
=> new(response, TransactionScriptStatus.Completed, Array.Empty<TransactionScriptError>(), null);
public static TransactionScriptResult<TResponse> Rejected(IReadOnlyList<TransactionScriptError> errors)
{
if (errors is null)
throw new ArgumentNullException(nameof(errors));
if (errors.Count == 0)
throw new ArgumentException("Transaction script rejection requires at least one error.", nameof(errors));
return new(default, TransactionScriptStatus.Rejected, errors, null);
}
public static TransactionScriptResult<TResponse> Failed(Exception exception)
=> new(default, TransactionScriptStatus.Failed, Array.Empty<TransactionScriptError>(), exception ?? throw new ArgumentNullException(nameof(exception)));
}
/// <summary>Execution status for a Transaction Script.</summary>
public enum TransactionScriptStatus
{
Completed,
Rejected,
Failed
}
/// <summary>Validation or precondition failure reported before a Transaction Script handler runs.</summary>
public sealed class TransactionScriptError
{
public TransactionScriptError(string code, string message)
{
Code = string.IsNullOrWhiteSpace(code)
? throw new ArgumentException("Transaction script error code is required.", nameof(code))
: code;
Message = string.IsNullOrWhiteSpace(message)
? throw new ArgumentException("Transaction script error message is required.", nameof(message))
: message;
}
public string Code { get; }
public string Message { get; }
}