Skip to content

Commit 45eb430

Browse files
Merge pull request #746 from TransactionProcessing/task/#725_merchant_domain)event_hander_retry
Task/#725 merchant domain)event hander retry
2 parents 996c5af + a12f4ce commit 45eb430

File tree

8 files changed

+38
-79
lines changed

8 files changed

+38
-79
lines changed

TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public async Task MerchantSettlementDomainEventHandler_Handle_MerchantFeeSettled
4545
public async Task MerchantSettlementDomainEventHandler_Handle_MerchantFeeSettledEvent_Retry_EventIsHandled()
4646
{
4747
this.Mediator.SetupSequence(m => m.Send(It.IsAny<IRequest<Result>>(), It.IsAny<CancellationToken>()))
48-
.ThrowsAsync(new WrongExpectedVersionException("Stream1", StreamRevision.None, StreamRevision.None))
49-
.ThrowsAsync(new RpcException(new Status(StatusCode.DeadlineExceeded, "Deadline Exceeded")))
48+
.ReturnsAsync(Result.Failure(new List<String>() { "Append failed due to WrongExpectedVersion"}))
49+
.ReturnsAsync(Result.Failure(new List<String>() { "DeadlineExceeded"}))
5050
.ReturnsAsync(Result.Success());
5151

5252
Result result = await this.EventHandler.Handle(TestData.DomainEvents.MerchantFeeSettledEvent, CancellationToken.None);

TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Threading;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
24
using System.Threading.Tasks;
35
using EventStore.Client;
46
using Grpc.Core;
@@ -36,8 +38,8 @@ public async Task MerchantStatementDomainEventHandler_Handle_StatementGeneratedE
3638
public async Task MerchantStatementDomainEventHandler_Handle_StatementGeneratedEvent_Retry_EventIsHandled()
3739
{
3840
this.Mediator.SetupSequence(m => m.Send(It.IsAny<IRequest<Result>>(), It.IsAny<CancellationToken>()))
39-
.ThrowsAsync(new WrongExpectedVersionException("Stream1", StreamRevision.None, StreamRevision.None))
40-
.ThrowsAsync(new RpcException(new Status(StatusCode.DeadlineExceeded, "Deadline Exceeded")))
41+
.ReturnsAsync(Result.Failure(new List<String>() { "Append failed due to WrongExpectedVersion" }))
42+
.ReturnsAsync(Result.Failure(new List<String>() { "DeadlineExceeded" }))
4143
.ReturnsAsync(Result.Success());
4244

4345
Result result = await this.EventHandler.Handle(TestData.DomainEvents.StatementGeneratedEvent, CancellationToken.None);
@@ -58,8 +60,8 @@ public async Task MerchantStatementDomainEventHandler_Handle_TransactionHasBeenC
5860
public async Task MerchantStatementDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_Retry_EventIsHandled()
5961
{
6062
this.Mediator.SetupSequence(m => m.Send(It.IsAny<IRequest<Result>>(), It.IsAny<CancellationToken>()))
61-
.ThrowsAsync(new WrongExpectedVersionException("Stream1", StreamRevision.None, StreamRevision.None))
62-
.ThrowsAsync(new RpcException(new Status(StatusCode.DeadlineExceeded, "Deadline Exceeded")))
63+
.ReturnsAsync(Result.Failure(new List<String>() { "Append failed due to WrongExpectedVersion" }))
64+
.ReturnsAsync(Result.Failure(new List<String>() { "DeadlineExceeded" }))
6365
.ReturnsAsync(Result.Success());
6466

6567
Result result = await this.EventHandler.Handle(TestData.DomainEvents.TransactionHasBeenCompletedEvent, CancellationToken.None);

TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77

88
namespace TransactionProcessor.BusinessLogic.Tests.DomainEventHandlers
99
{
10-
using System;
11-
using System.Threading;
12-
using System.Threading.Tasks;
1310
using EventHandling;
1411
using Microsoft.Extensions.Configuration;
1512
using Moq;
1613
using Shared.DomainDrivenDesign.EventSourcing;
1714
using Shared.General;
1815
using Shared.Logger;
1916
using Shouldly;
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Threading;
20+
using System.Threading.Tasks;
2021
using Testing;
2122
using Xunit;
2223

@@ -139,8 +140,8 @@ public async Task TransactionDomainEventHandler_EventPassedIn_EventIsHandled(Typ
139140
public async Task TransactionDomainEventHandler_EventPassedIn_Retry_EventIsHandled(Type eventType)
140141
{
141142
this.Mediator.SetupSequence(m => m.Send(It.IsAny<IRequest<Result>>(), It.IsAny<CancellationToken>()))
142-
.ThrowsAsync(new WrongExpectedVersionException("Stream1", StreamRevision.None, StreamRevision.None))
143-
.ThrowsAsync(new RpcException(new Status(StatusCode.DeadlineExceeded, "Deadline Exceeded")))
143+
.ReturnsAsync(Result.Failure(new List<String>() { "Append failed due to WrongExpectedVersion" }))
144+
.ReturnsAsync(Result.Failure(new List<String>() { "DeadlineExceeded" }))
144145
.ReturnsAsync(Result.Success());
145146

146147
DomainEvent domainEvent = eventType.Name switch
@@ -159,37 +160,6 @@ public async Task TransactionDomainEventHandler_EventPassedIn_Retry_EventIsHandl
159160
var result = await this.TransactionDomainEventHandler.Handle(domainEvent, CancellationToken.None);
160161
result.IsSuccess.ShouldBeTrue();
161162
}
162-
163-
[Theory]
164-
[InlineData(typeof(FloatDomainEvents.FloatCreditPurchasedEvent))]
165-
[InlineData(typeof(TransactionDomainEvents.TransactionCostInformationRecordedEvent))]
166-
//[InlineData(typeof(TransactionDomainEvents.TransactionHasBeenCompletedEvent))]
167-
[InlineData(typeof(TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent))]
168-
[InlineData(typeof(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent))]
169-
[InlineData(typeof(SettlementDomainEvents.MerchantFeeSettledEvent))]
170-
//[InlineData(typeof(CustomerEmailReceiptRequestedEvent))]
171-
//[InlineData(typeof(CustomerEmailReceiptResendRequestedEvent))]
172-
public async Task TransactionDomainEventHandler_EventPassedIn_ExceptionNotRetrid_EventIsHandled(Type eventType)
173-
{
174-
this.Mediator.SetupSequence(m => m.Send(It.IsAny<IRequest<Result>>(), It.IsAny<CancellationToken>()))
175-
.ThrowsAsync(new ApplicationException());
176-
177-
DomainEvent domainEvent = eventType.Name switch
178-
{
179-
nameof(FloatDomainEvents.FloatCreditPurchasedEvent) => new FloatDomainEvents.FloatCreditPurchasedEvent(TestData.FloatAggregateId, TestData.EstateId, TestData.CreditPurchasedDateTime, TestData.FloatCreditAmount, TestData.FloatCreditCostPrice),
180-
nameof(TransactionDomainEvents.TransactionCostInformationRecordedEvent) => TestData.TransactionCostInformationRecordedEvent,
181-
nameof(TransactionDomainEvents.TransactionHasBeenCompletedEvent) => TestData.DomainEvents.TransactionHasBeenCompletedEvent,
182-
nameof(TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent) => new TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent(TestData.TransactionId, TestData.EstateId, TestData.MerchantId, TestData.CalculatedFeeValue, 0, TestData.TransactionFeeId, TestData.TransactionFeeValue, TestData.TransactionFeeCalculateDateTime, TestData.TransactionFeeSettlementDueDate, TestData.TransactionDateTime),
183-
nameof(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent) => TestData.SettledMerchantFeeAddedToTransactionEvent(TestData.SettlementDate),
184-
nameof(SettlementDomainEvents.MerchantFeeSettledEvent) => new SettlementDomainEvents.MerchantFeeSettledEvent(TestData.SettlementAggregateId, TestData.EstateId, TestData.MerchantId, TestData.TransactionId, TestData.CalculatedFeeValue, 0, TestData.TransactionFeeId, TestData.TransactionFeeValue, TestData.TransactionFeeCalculateDateTime, TestData.SettlementDate),
185-
//nameof(TransactionDomainEvents.CustomerEmailReceiptRequestedEvent) => TestData.CustomerEmailReceiptRequestedEvent,
186-
//nameof(TransactionDomainEvents.CustomerEmailReceiptResendRequestedEvent) => TestData.CustomerEmailReceiptResendRequestedEvent,
187-
_ => throw new NotSupportedException($"Event {eventType.Name} not supported")
188-
};
189-
190-
var result = await this.TransactionDomainEventHandler.Handle(domainEvent, CancellationToken.None);
191-
result.IsFailed.ShouldBeTrue();
192-
}
193163
}
194164
}
195165

TransactionProcessor.BusinessLogic/Common/PolicyFactory.cs

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@
1313
namespace TransactionProcessor.BusinessLogic.Common;
1414

1515
public static class PolicyFactory{
16-
public static IAsyncPolicy<Result> CreatePolicy(Int32 retryCount=5, TimeSpan? retryDelay = null, String policyTag="", Boolean withFallBack=true) {
16+
public static IAsyncPolicy<Result> CreatePolicy(Int32 retryCount=5, TimeSpan? retryDelay = null, String policyTag="", Boolean withFallBack=false) {
1717

18-
TimeSpan retryDelayValue = retryDelay.GetValueOrDefault(TimeSpan.FromSeconds(1));
18+
TimeSpan retryDelayValue = retryDelay.GetValueOrDefault(TimeSpan.FromSeconds(2));
1919

2020
AsyncRetryPolicy<Result> retryPolicy = CreateRetryPolicy(retryCount, retryDelayValue, policyTag);
2121

22-
IAsyncPolicy<Result> policyWrap = withFallBack switch {
23-
true => CreateFallbackPolicy(policyTag, retryPolicy),
24-
_ => retryPolicy
25-
};
26-
return policyWrap;
22+
return retryPolicy;
2723
}
2824

2925
public static async Task<Result> ExecuteWithPolicyAsync(Func<Task<Result>> action, IAsyncPolicy<Result> policy, String policyTag = "")
@@ -52,29 +48,15 @@ public static async Task<Result> ExecuteWithPolicyAsync(Func<Task<Result>> actio
5248
private static AsyncRetryPolicy<Result> CreateRetryPolicy(int retryCount, TimeSpan retryDelay, String policyTag)
5349
{
5450
return Policy<Result>
55-
.Handle<WrongExpectedVersionException>()
56-
.Or<RpcException>(ex => ex.StatusCode == StatusCode.DeadlineExceeded)
51+
.HandleResult(result => !result.IsSuccess && String.Join("|",result.Errors).Contains("Append failed due to WrongExpectedVersion")) // Retry if the result is not successful
52+
.OrResult(result => !result.IsSuccess && String.Join("|", result.Errors).Contains("DeadlineExceeded")) // Retry if the result is not successful
5753
.WaitAndRetryAsync(retryCount,
5854
_ => retryDelay, // Fixed delay
59-
(exception, timeSpan, retryCount, context) =>
55+
(result, timeSpan, retryCount, context) =>
6056
{
6157
context["RetryCount"] = retryCount;
62-
Logger.LogWarning($"{policyTag} - Retry {retryCount} due to {exception.Exception.GetType().Name}. Waiting {timeSpan} before retrying...");
63-
});
64-
}
65-
66-
private static AsyncPolicyWrap<Result> CreateFallbackPolicy(String policyTag, AsyncRetryPolicy<Result> retryPolicy)
67-
{
68-
var fallbackPolicy = Policy<Result>
69-
.Handle<Exception>() // Catch-all for exceptions that aren't retried
70-
.FallbackAsync(
71-
fallbackValue: Result.Failure("An error occurred, no retry required."), // Ensure a valid Result return
72-
onFallbackAsync: (exception, context) =>
73-
{
74-
Logger.LogWarning($"{policyTag} - Non-retryable exception encountered: {exception.Exception.GetType().Name}");
75-
return Task.CompletedTask;
58+
Logger.LogWarning($"{policyTag} - Retry {retryCount} due to unsuccessful result {String.Join(".",result.Result.Errors)}. Waiting {timeSpan} before retrying...");
7659
});
7760

78-
return fallbackPolicy.WrapAsync(retryPolicy);
7961
}
8062
}

TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public async Task<Result> Handle(IDomainEvent domainEvent,
6161
private async Task<Result> HandleSpecificDomainEvent(CallbackReceivedEnrichedEvent domainEvent,
6262
CancellationToken cancellationToken)
6363
{
64-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent");
64+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent");
6565

6666
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
6767
if (domainEvent.TypeString == typeof(CallbackHandler.DataTransferObjects.Deposit).ToString()) {

TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public async Task<Result> Handle(IDomainEvent domainEvent,
3232

3333
private async Task<Result> HandleSpecificDomainEvent(SettlementDomainEvents.MerchantFeeSettledEvent domainEvent,
3434
CancellationToken cancellationToken) {
35-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent");
35+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent");
3636

3737
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
3838
MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.FeeCalculatedDateTime, domainEvent.CalculatedValue, domainEvent.TransactionId, domainEvent.FeeId);

TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public async Task<Result> Handle(IDomainEvent domainEvent,
5252
private async Task<Result> HandleSpecificDomainEvent(MerchantStatementDomainEvents.StatementGeneratedEvent domainEvent,
5353
CancellationToken cancellationToken) {
5454

55-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantStatementDomainEventHandler - StatementGeneratedEvent");
55+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantStatementDomainEventHandler - StatementGeneratedEvent");
5656

5757
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
5858
MerchantStatementCommands.EmailMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.MerchantStatementId);
@@ -63,7 +63,7 @@ private async Task<Result> HandleSpecificDomainEvent(MerchantStatementDomainEven
6363

6464
private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent,
6565
CancellationToken cancellationToken) {
66-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantStatementDomainEventHandler - TransactionHasBeenCompletedEvent");
66+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantStatementDomainEventHandler - TransactionHasBeenCompletedEvent");
6767

6868
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
6969
MerchantStatementCommands.AddTransactionToMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.CompletedDateTime, domainEvent.TransactionAmount, domainEvent.IsAuthorised, domainEvent.TransactionId);

TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public async Task<Result> Handle(IDomainEvent domainEvent,
5959
private async Task<Result> HandleSpecificDomainEvent(FloatDomainEvents.FloatCreditPurchasedEvent domainEvent,
6060
CancellationToken cancellationToken) {
6161

62-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - FloatCreditPurchasedEvent");
62+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - FloatCreditPurchasedEvent");
6363

6464
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
6565
FloatActivityCommands.RecordCreditPurchaseCommand command =
@@ -72,7 +72,7 @@ private async Task<Result> HandleSpecificDomainEvent(FloatDomainEvents.FloatCred
7272

7373
private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.TransactionCostInformationRecordedEvent domainEvent, CancellationToken cancellationToken){
7474

75-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - TransactionCostInformationRecordedEvent");
75+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - TransactionCostInformationRecordedEvent");
7676

7777
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
7878
FloatActivityCommands.RecordTransactionCommand command = new(domainEvent.EstateId,
@@ -84,14 +84,19 @@ private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.Tra
8484

8585
private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent,
8686
CancellationToken cancellationToken) {
87-
CalculateFeesForTransactionCommand command = new(domainEvent.TransactionId, domainEvent.CompletedDateTime, domainEvent.EstateId, domainEvent.MerchantId);
87+
88+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - TransactionHasBeenCompletedEvent");
8889

89-
return await this.Mediator.Send(command, cancellationToken);
90+
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
91+
CalculateFeesForTransactionCommand command = new(domainEvent.TransactionId, domainEvent.CompletedDateTime, domainEvent.EstateId, domainEvent.MerchantId);
92+
93+
return await this.Mediator.Send(command, cancellationToken);
94+
}, retryPolicy, "TransactionDomainEventHandler - TransactionHasBeenCompletedEvent");
9095
}
9196

9297
private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent domainEvent,
9398
CancellationToken cancellationToken) {
94-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - MerchantFeePendingSettlementAddedToTransactionEvent");
99+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - MerchantFeePendingSettlementAddedToTransactionEvent");
95100

96101
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
97102
SettlementCommands.AddMerchantFeePendingSettlementCommand command =
@@ -105,7 +110,7 @@ private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.Mer
105110

106111
private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent domainEvent,
107112
CancellationToken cancellationToken) {
108-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - SettledMerchantFeeAddedToTransactionEvent");
113+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - SettledMerchantFeeAddedToTransactionEvent");
109114

110115
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
111116
AddSettledFeeToSettlementCommand command = new AddSettledFeeToSettlementCommand(
@@ -117,7 +122,7 @@ private async Task<Result> HandleSpecificDomainEvent(TransactionDomainEvents.Set
117122
private async Task<Result> HandleSpecificDomainEvent(SettlementDomainEvents.MerchantFeeSettledEvent domainEvent,
118123
CancellationToken cancellationToken)
119124
{
120-
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - MerchantFeeSettledEvent");
125+
IAsyncPolicy<Result> retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - MerchantFeeSettledEvent");
121126

122127
return await PolicyFactory.ExecuteWithPolicyAsync(async () => {
123128
AddSettledMerchantFeeCommand command = new(domainEvent.TransactionId, domainEvent.CalculatedValue,

0 commit comments

Comments
 (0)