diff --git a/TransactionProcessor.Aggregates.Tests/FloatAggregateTests.cs b/TransactionProcessor.Aggregates.Tests/FloatAggregateTests.cs index f3c90d3..c4473b6 100644 --- a/TransactionProcessor.Aggregates.Tests/FloatAggregateTests.cs +++ b/TransactionProcessor.Aggregates.Tests/FloatAggregateTests.cs @@ -1,4 +1,5 @@ using Shouldly; +using SimpleResults; using TransactionProcessor.Testing; namespace TransactionProcessor.Aggregates.Tests @@ -17,7 +18,8 @@ public void FloatAggregate_CanBeCreated_IsCreated() public void FloatAggregate_CreateFloat_IsCreated() { Aggregates.FloatAggregate aggregate = Aggregates.FloatAggregate.Create(TestData.FloatAggregateId); - aggregate.CreateFloat(TestData.EstateId, TestData.ContractId, TestData.ProductId, TestData.FloatCreatedDateTime); + Result result = aggregate.CreateFloat(TestData.EstateId, TestData.ContractId, TestData.ProductId, TestData.FloatCreatedDateTime); + result.IsSuccess.ShouldBeTrue(); aggregate.AggregateId.ShouldBe(TestData.FloatAggregateId); aggregate.EstateId.ShouldBe(TestData.EstateId); @@ -27,14 +29,12 @@ public void FloatAggregate_CreateFloat_IsCreated() } [Fact] - public void FloatAggregate_CreateFloat_FloatAlreadyCreated_ErrorThrown() - { + public void FloatAggregate_CreateFloat_FloatAlreadyCreated_NoErrorThrown() { Aggregates.FloatAggregate aggregate = Aggregates.FloatAggregate.Create(TestData.FloatAggregateId); aggregate.CreateFloat(TestData.EstateId, TestData.ContractId, TestData.ProductId, TestData.FloatCreatedDateTime); - Should.Throw(() => { - aggregate.CreateFloat(TestData.EstateId, TestData.ContractId, TestData.ProductId, TestData.FloatCreatedDateTime); - }); + Result result = aggregate.CreateFloat(TestData.EstateId, TestData.ContractId, TestData.ProductId, TestData.FloatCreatedDateTime); + result.IsSuccess.ShouldBeTrue(); } [Fact] @@ -42,8 +42,8 @@ public void FloatAggregate_RecordCreditPurchase_CreditPurchaseIsRecorded() { Aggregates.FloatAggregate aggregate = Aggregates.FloatAggregate.Create(TestData.FloatAggregateId); aggregate.CreateFloat(TestData.EstateId, TestData.ContractId, TestData.ProductId, TestData.FloatCreatedDateTime); - aggregate.RecordCreditPurchase(DateTime.Now, 1000, 900); - + Result result = aggregate.RecordCreditPurchase(DateTime.Now, 1000, 900); + result.IsSuccess.ShouldBeTrue(); aggregate.NumberOfCreditPurchases.ShouldBe(1); aggregate.TotalCostPrice.ShouldBe(900); aggregate.UnitCostPrice.ShouldBe(0.9m); @@ -54,10 +54,11 @@ public void FloatAggregate_RecordCreditPurchase_CreditPurchaseIsRecorded() public void FloatAggregate_RecordCreditPurchase_FloatNotCreated_ErrorThrown() { Aggregates.FloatAggregate aggregate = Aggregates.FloatAggregate.Create(TestData.FloatAggregateId); + + Result result = aggregate.RecordCreditPurchase(DateTime.Now, 1000, 900); + result.IsFailed.ShouldBeTrue(); + result.Status.ShouldBe(ResultStatus.Invalid); - Should.Throw(() => { - aggregate.RecordCreditPurchase(DateTime.Now, 1000, 900); - }); } [Fact] @@ -101,9 +102,11 @@ public void FloatAggregate_RecordCreditPurchase_DuplicateCreditPurchase_ErrorThr DateTime purchaseDateTime = DateTime.Now; aggregate.RecordCreditPurchase(purchaseDateTime, 1000, 900); - Should.Throw(() => { - aggregate.RecordCreditPurchase(purchaseDateTime, 1000, 900); - }); + Result result = aggregate.RecordCreditPurchase(purchaseDateTime, 1000, 900); + + result.IsFailed.ShouldBeTrue(); + result.Status.ShouldBe(ResultStatus.Invalid); + } } } \ No newline at end of file diff --git a/TransactionProcessor.Aggregates/FloatAggregate.cs b/TransactionProcessor.Aggregates/FloatAggregate.cs index 7182792..3a20d0e 100644 --- a/TransactionProcessor.Aggregates/FloatAggregate.cs +++ b/TransactionProcessor.Aggregates/FloatAggregate.cs @@ -2,6 +2,7 @@ using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.Aggregate; using Shared.General; +using SimpleResults; using TransactionProcessor.DomainEvents; namespace TransactionProcessor.Aggregates @@ -26,29 +27,41 @@ public static void PlayEvent(this FloatAggregate aggregate, FloatDomainEvents.Fl aggregate.Credits.Add((domainEvent.CreditPurchasedDateTime, domainEvent.Amount, domainEvent.CostPrice)); } - public static void CreateFloat(this FloatAggregate aggregate, + public static Result CreateFloat(this FloatAggregate aggregate, Guid estateId, Guid contractId, Guid productId, DateTime createdDateTime) { - aggregate.ValidateFloatIsNotAlreadyCreated(); + if (aggregate.IsCreated) { + return Result.Success(); // Idempotent + } FloatDomainEvents.FloatCreatedForContractProductEvent floatCreatedForContractProductEvent = new(aggregate.AggregateId, estateId, contractId, productId, createdDateTime); aggregate.ApplyAndAppend(floatCreatedForContractProductEvent); + + return Result.Success(); } - public static void RecordCreditPurchase(this FloatAggregate aggregate, DateTime creditPurchasedDate, Decimal amount, Decimal costPrice) + public static Result RecordCreditPurchase(this FloatAggregate aggregate, DateTime creditPurchasedDate, Decimal amount, Decimal costPrice) { - aggregate.ValidateFloatIsAlreadyCreated(); - aggregate.ValidateCreditIsNotADuplicate(creditPurchasedDate, amount, costPrice); + Result result = aggregate.ValidateFloatIsAlreadyCreated(); + if (result.IsFailed) { + return result; + } + result = aggregate.ValidateCreditIsNotADuplicate(creditPurchasedDate, amount, costPrice); + if (result.IsFailed) { + return result; + } FloatDomainEvents.FloatCreditPurchasedEvent floatCreditPurchasedEvent = new(aggregate.AggregateId, aggregate.EstateId, creditPurchasedDate, amount, costPrice); aggregate.ApplyAndAppend(floatCreditPurchasedEvent); + + return Result.Success(); } public static Decimal GetUnitCostPrice(this FloatAggregate aggregate) @@ -56,29 +69,24 @@ public static Decimal GetUnitCostPrice(this FloatAggregate aggregate) return Math.Round(aggregate.UnitCostPrice, 4); } - public static void ValidateFloatIsAlreadyCreated(this FloatAggregate aggregate) - { - if (aggregate.IsCreated == false) - { - throw new InvalidOperationException($"Float Aggregate Id {aggregate.AggregateId} must be created to perform this operation"); + public static Result ValidateFloatIsAlreadyCreated(this FloatAggregate aggregate) { + if (aggregate.IsCreated == false) { + return Result.Invalid($"Float Aggregate Id {aggregate.AggregateId} must be created to perform this operation"); } - } - public static void ValidateFloatIsNotAlreadyCreated(this FloatAggregate aggregate) - { - if (aggregate.IsCreated == true) - { - throw new InvalidOperationException($"Float Aggregate Id {aggregate.AggregateId} must not be created to perform this operation"); - } + return Result.Success(); } - public static void ValidateCreditIsNotADuplicate(this FloatAggregate aggregate, DateTime creditPurchasedDate, Decimal amount, Decimal costPrice) - { + public static Result ValidateCreditIsNotADuplicate(this FloatAggregate aggregate, + DateTime creditPurchasedDate, + Decimal amount, + Decimal costPrice) { Boolean isDuplicate = aggregate.Credits.Any(c => c.costPrice == costPrice && c.amount == amount && c.creditPurchasedDate == creditPurchasedDate); - if (isDuplicate == true) - { - throw new InvalidOperationException($"Float Aggregate Id {aggregate.AggregateId} already has a credit with this information recorded"); + if (isDuplicate) { + return Result.Invalid($"Float Aggregate Id {aggregate.AggregateId} already has a credit with this information recorded"); } + + return Result.Success(); } } diff --git a/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs b/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs index dea1fdb..1402b4e 100644 --- a/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/FloatDomainService.cs @@ -90,7 +90,9 @@ public async Task CreateFloatForContractProduct(FloatCommands.CreateFloa FloatAggregate floatAggregate = getFloatResult.Data; - floatAggregate.CreateFloat(command.EstateId, command.ContractId, command.ProductId, command.CreateDateTime); + Result stateResult = floatAggregate.CreateFloat(command.EstateId, command.ContractId, command.ProductId, command.CreateDateTime); + if (stateResult.IsFailed) + return ResultHelpers.CreateFailure(stateResult); Result saveResult = await this.AggregateService.Save(floatAggregate, cancellationToken); if (saveResult.IsFailed) @@ -112,7 +114,9 @@ public async Task RecordCreditPurchase(FloatCommands.RecordCreditPurchas FloatAggregate floatAggregate = getFloatResult.Data; - floatAggregate.RecordCreditPurchase(command.PurchaseDateTime, command.CreditAmount, command.CostPrice); + Result stateResult = floatAggregate.RecordCreditPurchase(command.PurchaseDateTime, command.CreditAmount, command.CostPrice); + if (stateResult.IsFailed) + return ResultHelpers.CreateFailure(stateResult); Result saveResult = await this.AggregateService.Save(floatAggregate, cancellationToken); if (saveResult.IsFailed)