diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs index 646a3eb7..11e45684 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs @@ -17,6 +17,8 @@ using SimpleResults; using TransactionProcessor.BusinessLogic.Requests; using Grpc.Core; +using Xunit.Abstractions; +using Xunit.Sdk; namespace TransactionProcessor.BusinessLogic.Tests.DomainEventHandlers { diff --git a/TransactionProcessor.BusinessLogic/Common/PolicyFactory.cs b/TransactionProcessor.BusinessLogic/Common/PolicyFactory.cs index f9f29f7f..84eabacc 100644 --- a/TransactionProcessor.BusinessLogic/Common/PolicyFactory.cs +++ b/TransactionProcessor.BusinessLogic/Common/PolicyFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using EventStore.Client; using Grpc.Core; using Polly; @@ -17,15 +18,25 @@ public static IAsyncPolicy CreatePolicy(Int32 retryCount=5, TimeSpan? re AsyncRetryPolicy retryPolicy = CreateRetryPolicy(retryCount, retryDelayValue, policyTag); - return withFallBack switch { + IAsyncPolicy policyWrap = withFallBack switch { true => CreateFallbackPolicy(policyTag, retryPolicy), _ => retryPolicy }; + return policyWrap; + } + + public static async Task ExecuteWithPolicyAsync(Func> action, IAsyncPolicy policy, String policyTag = "") + { + Result result = await policy.ExecuteAsync(action); + + // Log success if no retries were required + Logger.LogWarning($"{policyTag} - Execution succeeded without retries."); + + return result; } private static AsyncRetryPolicy CreateRetryPolicy(int retryCount, TimeSpan retryDelay, String policyTag) { - // return Policy .Handle() .Or(ex => ex.StatusCode == StatusCode.DeadlineExceeded) @@ -39,15 +50,15 @@ private static AsyncRetryPolicy CreateRetryPolicy(int retryCount, TimeSp private static AsyncPolicyWrap CreateFallbackPolicy(String policyTag, AsyncRetryPolicy retryPolicy) { - AsyncFallbackPolicy fallbackPolicy = Policy - .Handle() - .Or() - .FallbackAsync(async (cancellationToken) => - { - Logger.LogWarning($"{policyTag} -All retries failed. Executing fallback action..."); - // Log failure, notify monitoring system, or enqueue for later processing - return Result.Failure("Fallback action executed"); - }); + var fallbackPolicy = Policy + .Handle() // Catch-all for exceptions that aren't retried + .FallbackAsync( + fallbackValue: Result.Failure("An error occurred, no retry required."), // Ensure a valid Result return + onFallbackAsync: (exception, context) => + { + Logger.LogWarning($"{policyTag} - Non-retryable exception encountered: {exception.GetType().Name}"); + return Task.CompletedTask; + }); return fallbackPolicy.WrapAsync(retryPolicy); } diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs index 6e338924..d93445ea 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs @@ -63,7 +63,7 @@ private async Task HandleSpecificDomainEvent(CallbackReceivedEnrichedEve { IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { if (domainEvent.TypeString == typeof(CallbackHandler.DataTransferObjects.Deposit).ToString()) { // Work out the merchant id from the reference field (second part, split on hyphen) String merchantReference = domainEvent.Reference.Split("-")[1]; @@ -80,7 +80,7 @@ private async Task HandleSpecificDomainEvent(CallbackReceivedEnrichedEve } return Result.Success(); - }); + }, retryPolicy, "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); } #endregion diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs index 7420a9b8..7c49efde 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs @@ -34,10 +34,10 @@ private async Task HandleSpecificDomainEvent(SettlementDomainEvents.Merc CancellationToken cancellationToken) { IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.FeeCalculatedDateTime, domainEvent.CalculatedValue, domainEvent.TransactionId, domainEvent.FeeId); return await this.Mediator.Send(command, cancellationToken); - }); + }, retryPolicy, "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); } } \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs index 28e20c0e..54ff3ead 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs @@ -54,22 +54,22 @@ private async Task HandleSpecificDomainEvent(MerchantStatementDomainEven IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantStatementDomainEventHandler - StatementGeneratedEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { MerchantStatementCommands.EmailMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.MerchantStatementId); return await this.Mediator.Send(command, cancellationToken); - }); + }, retryPolicy, "MerchantStatementDomainEventHandler - StatementGeneratedEvent"); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent, CancellationToken cancellationToken) { IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "MerchantStatementDomainEventHandler - TransactionHasBeenCompletedEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { MerchantStatementCommands.AddTransactionToMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.CompletedDateTime, domainEvent.TransactionAmount, domainEvent.IsAuthorised, domainEvent.TransactionId); return await this.Mediator.Send(command, cancellationToken); - }); + },retryPolicy, "MerchantStatementDomainEventHandler - TransactionHasBeenCompletedEvent"); } #endregion diff --git a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs index 18ffba17..a6f4eaf7 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs @@ -61,25 +61,25 @@ private async Task HandleSpecificDomainEvent(FloatDomainEvents.FloatCred IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - FloatCreditPurchasedEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { FloatActivityCommands.RecordCreditPurchaseCommand command = new(domainEvent.EstateId, domainEvent.FloatId, domainEvent.CreditPurchasedDateTime, domainEvent.Amount, domainEvent.EventId); return await this.Mediator.Send(command, cancellationToken); - }); + },retryPolicy, "TransactionDomainEventHandler - FloatCreditPurchasedEvent"); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionCostInformationRecordedEvent domainEvent, CancellationToken cancellationToken){ IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - TransactionCostInformationRecordedEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { FloatActivityCommands.RecordTransactionCommand command = new(domainEvent.EstateId, domainEvent.TransactionId); return await this.Mediator.Send(command, cancellationToken); - }); + }, retryPolicy, "TransactionDomainEventHandler - TransactionCostInformationRecordedEvent"); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent, @@ -93,25 +93,25 @@ private async Task HandleSpecificDomainEvent(TransactionDomainEvents.Mer CancellationToken cancellationToken) { IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - MerchantFeePendingSettlementAddedToTransactionEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { SettlementCommands.AddMerchantFeePendingSettlementCommand command = new SettlementCommands.AddMerchantFeePendingSettlementCommand(domainEvent.TransactionId, domainEvent.CalculatedValue, domainEvent.FeeCalculatedDateTime, (CalculationType)domainEvent.FeeCalculationType, domainEvent.FeeId, domainEvent.FeeValue, domainEvent.SettlementDueDate, domainEvent.MerchantId, domainEvent.EstateId); return await this.Mediator.Send(command, cancellationToken); - }); + }, retryPolicy, "TransactionDomainEventHandler - MerchantFeePendingSettlementAddedToTransactionEvent"); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent domainEvent, CancellationToken cancellationToken) { IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - SettledMerchantFeeAddedToTransactionEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { AddSettledFeeToSettlementCommand command = new AddSettledFeeToSettlementCommand( domainEvent.SettledDateTime.Date, domainEvent.MerchantId, domainEvent.EstateId, domainEvent.FeeId, domainEvent.TransactionId); return await this.Mediator.Send(command, cancellationToken); - }); + },retryPolicy, "TransactionDomainEventHandler - SettledMerchantFeeAddedToTransactionEvent"); } private async Task HandleSpecificDomainEvent(SettlementDomainEvents.MerchantFeeSettledEvent domainEvent, @@ -119,12 +119,12 @@ private async Task HandleSpecificDomainEvent(SettlementDomainEvents.Merc { IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(2, policyTag: "TransactionDomainEventHandler - MerchantFeeSettledEvent"); - return await retryPolicy.ExecuteAsync(async () => { + return await PolicyFactory.ExecuteWithPolicyAsync(async () => { AddSettledMerchantFeeCommand command = new(domainEvent.TransactionId, domainEvent.CalculatedValue, domainEvent.FeeCalculatedDateTime, (CalculationType)domainEvent.FeeCalculationType, domainEvent.FeeId, domainEvent.FeeValue, domainEvent.SettledDateTime, domainEvent.SettlementId); return await this.Mediator.Send(command, cancellationToken); - }); + }, retryPolicy, "TransactionDomainEventHandler - MerchantFeeSettledEvent"); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.CustomerEmailReceiptRequestedEvent domainEvent,