From e776721703ea12073b338f94f491e3e06e2d95dd Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Sat, 19 Apr 2025 16:17:06 +0100 Subject: [PATCH] some performance changes --- ...rchantSettlementDomainEventHandlerTests.cs | 12 --- ...erchantStatementDomainEventHandlerTests.cs | 25 +---- .../TransactionDomainEventHandlerTests.cs | 37 +------ .../MerchantDomainEventHandler.cs | 31 +++--- .../MerchantSettlementDomainEventHandler.cs | 29 ++++-- .../MerchantStatementDomainEventHandler.cs | 44 ++++++--- .../TransactionDomainEventHandler.cs | 97 ++++++++----------- .../Services/AggregateService.cs | 6 +- .../Services/FloatDomainService.cs | 12 ++- .../Services/IdGenerationService.cs | 8 ++ .../Services/TransactionDomainService.cs | 57 ++--------- 11 files changed, 136 insertions(+), 222 deletions(-) diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs index 53715b4c..510894b1 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantSettlementDomainEventHandlerTests.cs @@ -40,17 +40,5 @@ public async Task MerchantSettlementDomainEventHandler_Handle_MerchantFeeSettled Result result = await this.EventHandler.Handle(TestData.DomainEvents.MerchantFeeSettledEvent, CancellationToken.None); result.IsSuccess.ShouldBeTrue(); } - - [Fact] - public async Task MerchantSettlementDomainEventHandler_Handle_MerchantFeeSettledEvent_Retry_EventIsHandled() - { - this.Mediator.SetupSequence(m => m.Send(It.IsAny>(), It.IsAny())) - .ReturnsAsync(Result.Failure(new List() { "Append failed due to WrongExpectedVersion"})) - .ReturnsAsync(Result.Failure(new List() { "DeadlineExceeded"})) - .ReturnsAsync(Result.Success()); - - Result result = await this.EventHandler.Handle(TestData.DomainEvents.MerchantFeeSettledEvent, CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - } } } diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs index a744aafd..66c8ccf4 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs @@ -33,19 +33,7 @@ public async Task MerchantStatementDomainEventHandler_Handle_StatementGeneratedE Result result = await this.EventHandler.Handle(TestData.DomainEvents.StatementGeneratedEvent, CancellationToken.None); result.IsSuccess.ShouldBeTrue(); } - - [Fact] - public async Task MerchantStatementDomainEventHandler_Handle_StatementGeneratedEvent_Retry_EventIsHandled() - { - this.Mediator.SetupSequence(m => m.Send(It.IsAny>(), It.IsAny())) - .ReturnsAsync(Result.Failure(new List() { "Append failed due to WrongExpectedVersion" })) - .ReturnsAsync(Result.Failure(new List() { "DeadlineExceeded" })) - .ReturnsAsync(Result.Success()); - - Result result = await this.EventHandler.Handle(TestData.DomainEvents.StatementGeneratedEvent, CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - } - + [Fact] public async Task MerchantStatementDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_EventIsHandled() { @@ -56,15 +44,4 @@ public async Task MerchantStatementDomainEventHandler_Handle_TransactionHasBeenC result.IsSuccess.ShouldBeTrue(); } - [Fact] - public async Task MerchantStatementDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_Retry_EventIsHandled() - { - this.Mediator.SetupSequence(m => m.Send(It.IsAny>(), It.IsAny())) - .ReturnsAsync(Result.Failure(new List() { "Append failed due to WrongExpectedVersion" })) - .ReturnsAsync(Result.Failure(new List() { "DeadlineExceeded" })) - .ReturnsAsync(Result.Success()); - - Result result = await this.EventHandler.Handle(TestData.DomainEvents.TransactionHasBeenCompletedEvent, CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - } } \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs index 91b5fea9..aa9b150f 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/TransactionDomainEventHandlerTests.cs @@ -109,8 +109,8 @@ public TransactionDomainEventHandlerTests(ITestOutputHelper testOutputHelper) : [InlineData(typeof(TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent))] [InlineData(typeof(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent))] [InlineData(typeof(SettlementDomainEvents.MerchantFeeSettledEvent))] - //[InlineData(typeof(CustomerEmailReceiptRequestedEvent))] - //[InlineData(typeof(CustomerEmailReceiptResendRequestedEvent))] + [InlineData(typeof(TransactionDomainEvents.CustomerEmailReceiptRequestedEvent))] + [InlineData(typeof(TransactionDomainEvents.CustomerEmailReceiptResendRequestedEvent))] public async Task TransactionDomainEventHandler_EventPassedIn_EventIsHandled(Type eventType) { DomainEvent domainEvent = eventType.Name switch { nameof(FloatDomainEvents.FloatCreditPurchasedEvent) => new FloatDomainEvents.FloatCreditPurchasedEvent(TestData.FloatAggregateId, TestData.EstateId, TestData.CreditPurchasedDateTime, TestData.FloatCreditAmount, TestData.FloatCreditCostPrice), @@ -127,39 +127,6 @@ public async Task TransactionDomainEventHandler_EventPassedIn_EventIsHandled(Typ var result = await this.TransactionDomainEventHandler.Handle(domainEvent, CancellationToken.None); result.IsSuccess.ShouldBeTrue(); } - - [Theory] - [InlineData(typeof(FloatDomainEvents.FloatCreditPurchasedEvent))] - [InlineData(typeof(TransactionDomainEvents.TransactionCostInformationRecordedEvent))] - //[InlineData(typeof(TransactionDomainEvents.TransactionHasBeenCompletedEvent))] - [InlineData(typeof(TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent))] - [InlineData(typeof(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent))] - [InlineData(typeof(SettlementDomainEvents.MerchantFeeSettledEvent))] - //[InlineData(typeof(CustomerEmailReceiptRequestedEvent))] - //[InlineData(typeof(CustomerEmailReceiptResendRequestedEvent))] - public async Task TransactionDomainEventHandler_EventPassedIn_Retry_EventIsHandled(Type eventType) - { - this.Mediator.SetupSequence(m => m.Send(It.IsAny>(), It.IsAny())) - .ReturnsAsync(Result.Failure(new List() { "Append failed due to WrongExpectedVersion" })) - .ReturnsAsync(Result.Failure(new List() { "DeadlineExceeded" })) - .ReturnsAsync(Result.Success()); - - DomainEvent domainEvent = eventType.Name switch - { - nameof(FloatDomainEvents.FloatCreditPurchasedEvent) => new FloatDomainEvents.FloatCreditPurchasedEvent(TestData.FloatAggregateId, TestData.EstateId, TestData.CreditPurchasedDateTime, TestData.FloatCreditAmount, TestData.FloatCreditCostPrice), - nameof(TransactionDomainEvents.TransactionCostInformationRecordedEvent) => TestData.TransactionCostInformationRecordedEvent, - nameof(TransactionDomainEvents.TransactionHasBeenCompletedEvent) => TestData.DomainEvents.TransactionHasBeenCompletedEvent, - 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), - nameof(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent) => TestData.SettledMerchantFeeAddedToTransactionEvent(TestData.SettlementDate), - 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), - //nameof(TransactionDomainEvents.CustomerEmailReceiptRequestedEvent) => TestData.CustomerEmailReceiptRequestedEvent, - //nameof(TransactionDomainEvents.CustomerEmailReceiptResendRequestedEvent) => TestData.CustomerEmailReceiptResendRequestedEvent, - _ => throw new NotSupportedException($"Event {eventType.Name} not supported") - }; - - var result = await this.TransactionDomainEventHandler.Handle(domainEvent, CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - } } } diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs index f28da5e2..b28b5543 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs @@ -55,28 +55,23 @@ public async Task Handle(IDomainEvent domainEvent, } private async Task HandleSpecificDomainEvent(CallbackReceivedEnrichedEvent domainEvent, - CancellationToken cancellationToken) - { - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); - - 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]; + CancellationToken cancellationToken) { + 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]; - Result result = await this.EstateReportingRepository.GetMerchantFromReference(domainEvent.EstateId, merchantReference, cancellationToken); - if (result.IsFailed) - return ResultHelpers.CreateFailure(result); + Result result = await this.EstateReportingRepository.GetMerchantFromReference(domainEvent.EstateId, merchantReference, cancellationToken); + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); - // We now need to deserialise the message from the callback - CallbackHandler.DataTransferObjects.Deposit callbackMessage = JsonConvert.DeserializeObject(domainEvent.CallbackMessage); + // We now need to deserialise the message from the callback + CallbackHandler.DataTransferObjects.Deposit callbackMessage = JsonConvert.DeserializeObject(domainEvent.CallbackMessage); - MerchantCommands.MakeMerchantDepositCommand command = new(domainEvent.EstateId, result.Data.MerchantId, DataTransferObjects.Requests.Merchant.MerchantDepositSource.Automatic, new MakeMerchantDepositRequest { DepositDateTime = callbackMessage.DateTime, Reference = callbackMessage.Reference, Amount = callbackMessage.Amount, }); - return await this.Mediator.Send(command, cancellationToken); - } + MerchantCommands.MakeMerchantDepositCommand command = new(domainEvent.EstateId, result.Data.MerchantId, DataTransferObjects.Requests.Merchant.MerchantDepositSource.Automatic, new MakeMerchantDepositRequest { DepositDateTime = callbackMessage.DateTime, Reference = callbackMessage.Reference, Amount = callbackMessage.Amount, }); + return await this.Mediator.Send(command, cancellationToken); + } - return Result.Success(); - }, retryPolicy, "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); + return Result.Success(); } #endregion diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs index 9d1c494e..906fa34a 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs @@ -3,10 +3,14 @@ using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.EventHandling; using SimpleResults; +using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Prometheus; using TransactionProcessor.BusinessLogic.Common; using TransactionProcessor.BusinessLogic.Requests; +using TransactionProcessor.BusinessLogic.Services; using TransactionProcessor.DomainEvents; namespace TransactionProcessor.BusinessLogic.EventHandling; @@ -20,24 +24,31 @@ public MerchantSettlementDomainEventHandler(IMediator mediator) { public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) { + + Stopwatch sw = Stopwatch.StartNew(); + String g = domainEvent.GetType().Name; + String m = this.GetType().Name; + Counter counterCalls = AggregateService.GetCounterMetric($"{m}_{g}_events_processed"); + Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}"); + counterCalls.Inc(); + Task t = domainEvent switch { SettlementDomainEvents.MerchantFeeSettledEvent mfse => this.HandleSpecificDomainEvent(mfse, cancellationToken), _ => null }; + + Result result = Result.Success(); if (t != null) - return await t; - - return Result.Success(); + result = await t; + sw.Stop(); + histogramMetric.Observe(sw.Elapsed.TotalSeconds); + return result; } private async Task HandleSpecificDomainEvent(SettlementDomainEvents.MerchantFeeSettledEvent domainEvent, CancellationToken cancellationToken) { - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantSettlementDomainEventHandler - MerchantFeeSettledEvent"); - - return await PolicyFactory.ExecuteWithPolicyAsync(async () => { - MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.FeeCalculatedDateTime, domainEvent.CalculatedValue, domainEvent.TransactionId, domainEvent.FeeId); + 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"); + return await this.Mediator.Send(command, cancellationToken); } } \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs index 2332088a..64a7dc08 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs @@ -1,12 +1,16 @@ -using MediatR; +using System; +using MediatR; using Polly; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.EventHandling; using SimpleResults; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Prometheus; using TransactionProcessor.BusinessLogic.Common; using TransactionProcessor.BusinessLogic.Requests; +using TransactionProcessor.BusinessLogic.Services; using TransactionProcessor.DomainEvents; namespace TransactionProcessor.BusinessLogic.EventHandling @@ -41,7 +45,26 @@ public MerchantStatementDomainEventHandler(IMediator mediator) public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) { - return await this.HandleSpecificDomainEvent((dynamic)domainEvent, cancellationToken); + Stopwatch sw = Stopwatch.StartNew(); + String g = domainEvent.GetType().Name; + String m = this.GetType().Name; + Counter counterCalls = AggregateService.GetCounterMetric($"{m}_{g}_events_processed"); + Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}"); + counterCalls.Inc(); + + Task t = domainEvent switch + { + MerchantStatementDomainEvents.StatementGeneratedEvent de => this.HandleSpecificDomainEvent(de, cancellationToken), + TransactionDomainEvents.TransactionHasBeenCompletedEvent de => this.HandleSpecificDomainEvent(de, cancellationToken), + _ => null + }; + + Result result = Result.Success(); + if (t != null) + result = await t; + sw.Stop(); + histogramMetric.Observe(sw.Elapsed.TotalSeconds); + return result; } /// @@ -51,25 +74,16 @@ public async Task Handle(IDomainEvent domainEvent, /// The cancellation token. private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.StatementGeneratedEvent domainEvent, CancellationToken cancellationToken) { + MerchantStatementCommands.EmailMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.MerchantStatementId); - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantStatementDomainEventHandler - StatementGeneratedEvent"); - - 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"); + return await this.Mediator.Send(command, cancellationToken); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent, CancellationToken cancellationToken) { - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "MerchantStatementDomainEventHandler - TransactionHasBeenCompletedEvent"); - - return await PolicyFactory.ExecuteWithPolicyAsync(async () => { - MerchantStatementCommands.AddTransactionToMerchantStatementCommand command = new(domainEvent.EstateId, domainEvent.MerchantId, domainEvent.CompletedDateTime, domainEvent.TransactionAmount, domainEvent.IsAuthorised, domainEvent.TransactionId); + 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"); + return await this.Mediator.Send(command, cancellationToken); } #endregion diff --git a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs index 4b46782f..7578970a 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/TransactionDomainEventHandler.cs @@ -1,6 +1,8 @@ using MediatR; using SimpleResults; +using System; using System.Diagnostics; +using Prometheus; using TransactionProcessor.BusinessLogic.Requests; using TransactionProcessor.DomainEvents; using TransactionProcessor.Models.Contract; @@ -15,6 +17,7 @@ namespace TransactionProcessor.BusinessLogic.EventHandling using System.Threading; using System.Threading.Tasks; using TransactionProcessor.BusinessLogic.Common; + using TransactionProcessor.BusinessLogic.Services; using static TransactionProcessor.BusinessLogic.Requests.SettlementCommands; using static TransactionProcessor.BusinessLogic.Requests.TransactionCommands; @@ -46,90 +49,72 @@ public TransactionDomainEventHandler(IMediator mediator) { public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) { - - Logger.LogWarning($"|{domainEvent.EventId}|Transaction Domain Event Handler - Inside Handle {domainEvent.EventType}"); Stopwatch sw = Stopwatch.StartNew(); - var result = await this.HandleSpecificDomainEvent((dynamic)domainEvent, cancellationToken); + String g = domainEvent.GetType().Name; + String m = this.GetType().Name; + Counter counterCalls = AggregateService.GetCounterMetric($"{m}_{g}_events_processed"); + Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}"); + counterCalls.Inc(); + + Task t = domainEvent switch + { + FloatDomainEvents.FloatCreditPurchasedEvent fcpe => this.HandleSpecificDomainEvent(fcpe, cancellationToken), + TransactionDomainEvents.TransactionCostInformationRecordedEvent tcire => this.HandleSpecificDomainEvent(tcire, cancellationToken), + TransactionDomainEvents.TransactionHasBeenCompletedEvent thbce => this.HandleSpecificDomainEvent(thbce, cancellationToken), + TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent mfpse => this.HandleSpecificDomainEvent(mfpse, cancellationToken), + TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent smfate => this.HandleSpecificDomainEvent(smfate, cancellationToken), + TransactionDomainEvents.CustomerEmailReceiptRequestedEvent ce => this.HandleSpecificDomainEvent(ce, cancellationToken), + TransactionDomainEvents.CustomerEmailReceiptResendRequestedEvent cer => this.HandleSpecificDomainEvent(cer, cancellationToken), + _ => null + }; + + Result result = Result.Success(); + if (t != null) + result = await t; sw.Stop(); - Logger.LogWarning($"|{domainEvent.EventId}|Transaction Domain Event Handler - after HandleSpecificDomainEvent {domainEvent.EventType} time {sw.ElapsedMilliseconds}ms"); - + histogramMetric.Observe(sw.Elapsed.TotalSeconds); return result; } private async Task HandleSpecificDomainEvent(FloatDomainEvents.FloatCreditPurchasedEvent domainEvent, - CancellationToken cancellationToken) { - - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - FloatCreditPurchasedEvent"); - - return await PolicyFactory.ExecuteWithPolicyAsync(async () => { - FloatActivityCommands.RecordCreditPurchaseCommand command = - new(domainEvent.EstateId, domainEvent.FloatId, - domainEvent.CreditPurchasedDateTime, domainEvent.Amount, domainEvent.EventId); + CancellationToken cancellationToken) { + FloatActivityCommands.RecordCreditPurchaseCommand command = new(domainEvent.EstateId, domainEvent.FloatId, domainEvent.CreditPurchasedDateTime, domainEvent.Amount, domainEvent.EventId); - return await this.Mediator.Send(command, cancellationToken); - },retryPolicy, "TransactionDomainEventHandler - FloatCreditPurchasedEvent"); + return await this.Mediator.Send(command, cancellationToken); } - private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionCostInformationRecordedEvent domainEvent, CancellationToken cancellationToken){ - - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - TransactionCostInformationRecordedEvent"); + private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionCostInformationRecordedEvent domainEvent, + CancellationToken cancellationToken) { - return await PolicyFactory.ExecuteWithPolicyAsync(async () => { - FloatActivityCommands.RecordTransactionCommand command = new(domainEvent.EstateId, - domainEvent.TransactionId); + FloatActivityCommands.RecordTransactionCommand command = new(domainEvent.EstateId, domainEvent.TransactionId); - return await this.Mediator.Send(command, cancellationToken); - }, retryPolicy, "TransactionDomainEventHandler - TransactionCostInformationRecordedEvent"); + return await this.Mediator.Send(command, cancellationToken); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent, CancellationToken cancellationToken) { - - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - TransactionHasBeenCompletedEvent"); - return await PolicyFactory.ExecuteWithPolicyAsync(async () => { - CalculateFeesForTransactionCommand command = new(domainEvent.TransactionId, domainEvent.CompletedDateTime, domainEvent.EstateId, domainEvent.MerchantId); + CalculateFeesForTransactionCommand command = new(domainEvent.TransactionId, domainEvent.CompletedDateTime, domainEvent.EstateId, domainEvent.MerchantId); - return await this.Mediator.Send(command, cancellationToken); - }, retryPolicy, "TransactionDomainEventHandler - TransactionHasBeenCompletedEvent"); + return await this.Mediator.Send(command, cancellationToken); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.MerchantFeePendingSettlementAddedToTransactionEvent domainEvent, CancellationToken cancellationToken) { - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - MerchantFeePendingSettlementAddedToTransactionEvent"); - - 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"); + 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); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent domainEvent, CancellationToken cancellationToken) { - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - SettledMerchantFeeAddedToTransactionEvent"); - - 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"); + AddSettledFeeToSettlementCommand command = new AddSettledFeeToSettlementCommand(domainEvent.SettledDateTime.Date, domainEvent.MerchantId, domainEvent.EstateId, domainEvent.FeeId, domainEvent.TransactionId); + return await this.Mediator.Send(command, cancellationToken); } private async Task HandleSpecificDomainEvent(SettlementDomainEvents.MerchantFeeSettledEvent domainEvent, - CancellationToken cancellationToken) - { - IAsyncPolicy retryPolicy = PolicyFactory.CreatePolicy(policyTag: "TransactionDomainEventHandler - MerchantFeeSettledEvent"); - - 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"); + CancellationToken cancellationToken) { + 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); } private async Task HandleSpecificDomainEvent(TransactionDomainEvents.CustomerEmailReceiptRequestedEvent domainEvent, diff --git a/TransactionProcessor.BusinessLogic/Services/AggregateService.cs b/TransactionProcessor.BusinessLogic/Services/AggregateService.cs index 1ce03b0b..c56d6400 100644 --- a/TransactionProcessor.BusinessLogic/Services/AggregateService.cs +++ b/TransactionProcessor.BusinessLogic/Services/AggregateService.cs @@ -73,6 +73,8 @@ public AggregateService(IAggregateRepositoryResolver aggregateRepositoryResolver .RegisterPostEvictionCallback(AggregateService.EvictionCallback); this.AggregateTypes.Add((typeof(EstateAggregate), memoryCacheEntryOptions, new Object())); + this.AggregateTypes.Add((typeof(ContractAggregate), memoryCacheEntryOptions, new Object())); + this.AggregateTypes.Add((typeof(OperatorAggregate), memoryCacheEntryOptions, new Object())); } internal static void EvictionCallback(Object key, @@ -191,7 +193,7 @@ public async Task> Get(Guid aggregateId, String g = typeof(TAggregate).Name; String m = $"AggregateService"; Counter counterCalls = AggregateService.GetCounterMetric($"{m}_{g}_times_rehydrated"); - Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}"); + Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}_rehydrated"); counterCalls.Inc(); TAggregate aggregate = null; @@ -220,7 +222,7 @@ public async Task Save(TAggregate aggregate, String g = typeof(TAggregate).Name; String m = $"AggregateService"; Counter counterCalls = AggregateService.GetCounterMetric($"{m}_{g}_times_saved"); - Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}"); + Histogram histogramMetric = AggregateService.GetHistogramMetric($"{m}_{g}_saved"); counterCalls.Inc(); diff --git a/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs b/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs index ccb2c580..59d043c6 100644 --- a/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs @@ -153,25 +153,33 @@ public async Task RecordCreditPurchase(FloatCommands.RecordCreditPurchas public async Task RecordCreditPurchase(FloatActivityCommands.RecordCreditPurchaseCommand command, CancellationToken cancellationToken) { + + // Generate the id for the activity aggregate + Guid floatActivityAggregateId = IdGenerationService.GenerateFloatActivityAggregateId(command.EstateId, command.FloatId, command.CreditPurchasedDateTime.Date); + Result result = await ApplyFloatActivityUpdates((floatAggregate) => { floatAggregate.RecordCreditPurchase(command.EstateId, command.CreditPurchasedDateTime, command.Amount, command.CreditId); return Result.Success(); - }, command.FloatId, cancellationToken,false); + }, floatActivityAggregateId, cancellationToken,false); return result; } public async Task RecordTransaction(FloatActivityCommands.RecordTransactionCommand command, CancellationToken cancellationToken) { + Result getTransactionResult = await this.AggregateService.GetLatest(command.TransactionId, cancellationToken); if (getTransactionResult.IsFailed) return ResultHelpers.CreateFailure(getTransactionResult); Guid floatId = IdGenerationService.GenerateFloatAggregateId(command.EstateId, getTransactionResult.Data.ContractId, getTransactionResult.Data.ProductId); + // Generate the id for the activity aggregate + Guid floatActivityAggregateId = IdGenerationService.GenerateFloatActivityAggregateId(command.EstateId, floatId, getTransactionResult.Data.TransactionDateTime.Date); + Result result = await ApplyFloatActivityUpdates((floatAggregate) => { floatAggregate.RecordTransactionAgainstFloat(command.EstateId, getTransactionResult.Data.TransactionDateTime, getTransactionResult.Data.TransactionAmount.GetValueOrDefault(), command.TransactionId); return Result.Success(); - }, floatId, cancellationToken, false); + }, floatActivityAggregateId, cancellationToken, false); return result; } } diff --git a/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs b/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs index a3c8c21e..4fdbf2a7 100644 --- a/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs +++ b/TransactionProcessor.BusinessLogic/Services/IdGenerationService.cs @@ -44,5 +44,13 @@ public static Guid GenerateFloatAggregateId(Guid estateId, Guid contractId, Guid contractId, productId }); + + public static Guid GenerateFloatActivityAggregateId(Guid estateId, Guid floatId, DateTime dateTime) => + IdGenerationService.GenerateUniqueId(new + { + estateId, + floatId, + dateTime + }); } } diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index 50105ae6..27956a21 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -515,58 +515,17 @@ internal static DateTime CalculateSettlementDate(Models.Merchant.SettlementSched private async Task> GetTransactionFeesForCalculation(TransactionAggregate transactionAggregate, CancellationToken cancellationToken) { // TODO: convert to result?? - - Boolean contractProductFeeCacheEnabled; - String contractProductFeeCacheEnabledValue = ConfigurationReader.GetValue("ContractProductFeeCacheEnabled"); - if (String.IsNullOrEmpty(contractProductFeeCacheEnabledValue)) { - contractProductFeeCacheEnabled = false; - } - else { - contractProductFeeCacheEnabled = Boolean.Parse(contractProductFeeCacheEnabledValue); - } - - Boolean feesInCache; - Result> feesForProduct = null; - if (contractProductFeeCacheEnabled == false) { - feesInCache = false; - } - else { - // Ok we should have filtered out the not applicable transactions - // Check if we have fees for this product in the cache - feesInCache = this.MemoryCache.TryGetValue((transactionAggregate.EstateId, transactionAggregate.ContractId, transactionAggregate.ProductId), out feesForProduct); + // Get the fees to be calculated + Result> feesForProduct = await this.GetTransactionFeesForProduct(transactionAggregate.ContractId, transactionAggregate.ProductId, cancellationToken); + + if (feesForProduct.IsFailed) { + Logger.LogWarning($"Failed to get fees {feesForProduct.Message}"); + return null; } - if (feesInCache == false) { - Logger.LogInformation($"Fees for Key: Estate Id {transactionAggregate.EstateId} Contract Id {transactionAggregate.ContractId} ProductId {transactionAggregate.ProductId} not found in the cache"); - - // Nothing in cache so we need to make a remote call - // Get the fees to be calculated - feesForProduct = await this.GetTransactionFeesForProduct(transactionAggregate.ContractId, transactionAggregate.ProductId, cancellationToken); - - - if (feesForProduct.IsFailed) { - Logger.LogWarning($"Failed to get fees {feesForProduct.Message}"); - return null; - } - - Logger.LogInformation($"After getting Fees {feesForProduct.Data.Count} returned"); - - if (contractProductFeeCacheEnabled == true) { - // Now add this the result to the cache - String contractProductFeeCacheExpiryInHours = ConfigurationReader.GetValue("ContractProductFeeCacheExpiryInHours"); - if (String.IsNullOrEmpty(contractProductFeeCacheExpiryInHours)) { - contractProductFeeCacheExpiryInHours = "168"; // 7 Days default - } - - this.MemoryCache.Set((transactionAggregate.EstateId, transactionAggregate.ContractId, transactionAggregate.ProductId), feesForProduct.Data, new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(Int32.Parse(contractProductFeeCacheExpiryInHours)) }); - Logger.LogInformation($"Fees for Key: Estate Id {transactionAggregate.EstateId} Contract Id {transactionAggregate.ContractId} ProductId {transactionAggregate.ProductId} added to cache"); - } - } - else { - Logger.LogInformation($"Fees for Key: Estate Id {transactionAggregate.EstateId} Contract Id {transactionAggregate.ContractId} ProductId {transactionAggregate.ProductId} found in the cache"); - } + Logger.LogInformation($"After getting Fees {feesForProduct.Data.Count} returned"); - List feesForCalculation = new List(); + List feesForCalculation = new(); foreach (Models.Contract.ContractProductTransactionFee contractProductTransactionFee in feesForProduct.Data) { TransactionFeeToCalculate transactionFeeToCalculate = new TransactionFeeToCalculate { FeeId = contractProductTransactionFee.TransactionFeeId, Value = contractProductTransactionFee.Value, FeeType = (TransactionProcessor.Models.Contract.FeeType)contractProductTransactionFee.FeeType, CalculationType = (TransactionProcessor.Models.Contract.CalculationType)contractProductTransactionFee.CalculationType };