From 91df742d860df44107647c5e15b30cccf770d025 Mon Sep 17 00:00:00 2001 From: Andrzej Bakun Date: Mon, 8 Apr 2024 21:49:21 +0200 Subject: [PATCH 1/8] Move repository interfaces from .Core project to .Application project --- .../Fitnet.Contracts.Application}/IContractsRepository.cs | 4 +++- .../Sign/SignContractCommandHandler.cs | 1 - .../Database/Repositories/ContractsRepository.cs | 1 + .../Database/Repositories/RepositoriesModule.cs | 4 ++-- .../IBindingContractsRepository.cs | 4 +++- .../Fitnet.Contracts.Application}/IContractsRepository.cs | 6 ++++-- .../SignContract/SignContractCommandHandler.cs | 1 - .../TerminateBindingContractCommandHandler.cs | 1 - .../Database/Repositories/BindingContractsRepository.cs | 1 + .../Database/Repositories/ContractsRepository.cs | 1 + .../Database/Repositories/RepositoriesModule.cs | 2 +- 11 files changed, 16 insertions(+), 10 deletions(-) rename {Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core => Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application}/IContractsRepository.cs (83%) rename Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/{Fitnet.Contracts.Core => Fitnet.Contracts.Application}/IBindingContractsRepository.cs (80%) rename {Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core => Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application}/IContractsRepository.cs (83%) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IContractsRepository.cs b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs similarity index 83% rename from Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IContractsRepository.cs rename to Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs index e8f1f571..846ed047 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IContractsRepository.cs +++ b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs @@ -1,4 +1,6 @@ -namespace EvolutionaryArchitecture.Fitnet.Contracts.Core; +namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; + +using Core; public interface IContractsRepository { diff --git a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/Sign/SignContractCommandHandler.cs b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/Sign/SignContractCommandHandler.cs index bb682028..c2fe6f05 100644 --- a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/Sign/SignContractCommandHandler.cs +++ b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/Sign/SignContractCommandHandler.cs @@ -1,7 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.Sign; using Common.Api.ErrorHandling; -using Core; using IntegrationEvents; using MassTransit; diff --git a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs index ab290f1a..e995e7ff 100644 --- a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs +++ b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs @@ -1,5 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; +using Application; using Core; using Microsoft.EntityFrameworkCore; diff --git a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs index ae38c117..e3569dd4 100644 --- a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs +++ b/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs @@ -1,6 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; -using Core; +using Application; using Microsoft.Extensions.DependencyInjection; internal static class RepositoriesModule @@ -11,4 +11,4 @@ internal static IServiceCollection AddRepositories(this IServiceCollection servi return services; } -} \ No newline at end of file +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IBindingContractsRepository.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IBindingContractsRepository.cs similarity index 80% rename from Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IBindingContractsRepository.cs rename to Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IBindingContractsRepository.cs index 42cc1db2..95bd38b5 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IBindingContractsRepository.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IBindingContractsRepository.cs @@ -1,4 +1,6 @@ -namespace EvolutionaryArchitecture.Fitnet.Contracts.Core; +namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; + +using Core; public interface IBindingContractsRepository { diff --git a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IContractsRepository.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs similarity index 83% rename from Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IContractsRepository.cs rename to Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs index e4f55a7f..846ed047 100644 --- a/Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core/IContractsRepository.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs @@ -1,4 +1,6 @@ -namespace EvolutionaryArchitecture.Fitnet.Contracts.Core; +namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; + +using Core; public interface IContractsRepository { @@ -6,4 +8,4 @@ public interface IContractsRepository Task GetPreviousForCustomerAsync(Guid customerId, CancellationToken cancellationToken = default); Task AddAsync(Contract contract, CancellationToken cancellationToken = default); Task CommitAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs index 84f93a27..d7c3d8d4 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommandHandler.cs @@ -1,7 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.SignContract; using Common.Api.ErrorHandling; -using Core; using IntegrationEvents; using MassTransit; diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/TerminateBindingContract/TerminateBindingContractCommandHandler.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/TerminateBindingContract/TerminateBindingContractCommandHandler.cs index 94a9272e..915bda6f 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/TerminateBindingContract/TerminateBindingContractCommandHandler.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/TerminateBindingContract/TerminateBindingContractCommandHandler.cs @@ -1,7 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.TerminateBindingContract; using Common.Api.ErrorHandling; -using Core; [UsedImplicitly] internal sealed class TerminateBindingContractCommandHandler( diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/BindingContractsRepository.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/BindingContractsRepository.cs index 9caded37..6cb00fe0 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/BindingContractsRepository.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/BindingContractsRepository.cs @@ -1,5 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; +using Application; using Core; using Microsoft.EntityFrameworkCore; diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs index a3913fc8..dae5f18c 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/ContractsRepository.cs @@ -1,5 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; +using Application; using Core; using Microsoft.EntityFrameworkCore; diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs index 5892cc6b..6a414551 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs @@ -1,6 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; -using Core; +using Application; using Microsoft.Extensions.DependencyInjection; internal static class RepositoriesModule From e8ffe4168a39acb98d508fe47de821ff53ce0651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20B=C4=85czek?= <74410956+kamilbaczek@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:00:58 +0200 Subject: [PATCH 2/8] refactor: use domain events in contracts module --- ...BeAddedToActiveBindingContractRuleTests.cs | 2 +- .../Common/Builders/ContractBuilder.cs | 34 ++++++++++++++++++ .../Common/Builders/SignContractBuilder.cs | 24 +++++++++++++ .../Common/EntityExtensions.cs | 9 +++++ .../Common/FakeContractDates.cs | 7 ++++ ...tractCanBePreparedOnlyForAdultRuleTests.cs | 2 +- ...eSmallerThanMaximumHeightLimitRuleTests.cs | 2 +- .../PreviousContractHasToBeSignedRuleTests.cs | 2 +- .../PrepareContract/PrepareContractTests.cs | 22 ++++++++++++ ...nedWithin30DaysFromPreparationRuleTests.cs | 2 +- ...ContractMustNotBeAlreadySignedRuleTests.cs | 2 +- .../SignContract/SignContractTests.cs | 35 ++++++++----------- .../SignContract/SignedContractBuilder.cs | 28 +++++++++++++++ ...ibleOnlyAfterThreeMonthsHavePassedTests.cs | 2 +- .../TerminateBindingContractTests.cs | 23 ++++++++++++ .../Fitnet.Contracts.Core/BindingContract.cs | 6 +++- .../Src/Fitnet.Contracts.Core/Contract.cs | 5 --- 17 files changed, 173 insertions(+), 34 deletions(-) create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/ContractBuilder.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/EntityExtensions.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/FakeContractDates.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractTests.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/TerminateBindingContractTests.cs diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/AddAnnex/BusinessRules/AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRuleTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/AddAnnex/BusinessRules/AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRuleTests.cs index 295ced49..0b4e380d 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/AddAnnex/BusinessRules/AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRuleTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/AddAnnex/BusinessRules/AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRuleTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.AddAnnex.BusinessRules; -using Common.Core.BusinessRules; using Core.AddAnnex.BusinessRules; +using Fitnet.Common.Core.BusinessRules; public sealed class AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRuleTests { diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/ContractBuilder.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/ContractBuilder.cs new file mode 100644 index 00000000..584a58b9 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/ContractBuilder.cs @@ -0,0 +1,34 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract; + +using Common; +using SignContract; + +internal sealed class ContractBuilder +{ + internal static ContractBuilder Prepared() => new(); + + private DateTimeOffset? _preparedAt; + + public ContractBuilder PreparedAt(DateTimeOffset preparedAt) + { + _preparedAt = preparedAt; + return this; + } + + public SignedContractBuilder Signed() => new(Prepare()); + + private Contract Prepare() + { + var preparedAt = _preparedAt ?? FakeContractDates.PreparedAt; + var prepareContractParameters = PrepareContractParameters.GetValid(); + var contract = Contract.Prepare( + Guid.NewGuid(), + prepareContractParameters.MaxAge, + prepareContractParameters.MaxHeight, + preparedAt); + + return contract; + } + + public static implicit operator Contract(ContractBuilder builder) => builder.Prepare(); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs new file mode 100644 index 00000000..200564a5 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/Builders/SignContractBuilder.cs @@ -0,0 +1,24 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract; + +internal sealed class SignContractBuilder(Contract parentBuilder) +{ + private DateTimeOffset _signDay; + private DateTimeOffset _fakeToday; + + public SignContractBuilder SignedOn(DateTimeOffset signDay, DateTimeOffset fakeToday) + { + _signDay = signDay; + _fakeToday = fakeToday; + + return this; + } + + private BindingContract Build() + { + var bindingContract = parentBuilder.Sign(_signDay, _fakeToday); + + return bindingContract; + } + + public static implicit operator BindingContract(SignContractBuilder builder) => builder.Build(); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/EntityExtensions.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/EntityExtensions.cs new file mode 100644 index 00000000..1962d7da --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/EntityExtensions.cs @@ -0,0 +1,9 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.Common; + +using DomainDrivenDesign.BuildingBlocks; + +internal static class EntityExtensions +{ + public static TEvent? GetPublishedEvent(this Entity entity) where TEvent : class, IDomainEvent => entity.Events.OfType() + .MinBy(domainEvent => domainEvent.OccuredAt); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/FakeContractDates.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/FakeContractDates.cs new file mode 100644 index 00000000..4ebf8e6c --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/FakeContractDates.cs @@ -0,0 +1,7 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.Common; + +internal static class FakeContractDates +{ + public static DateTimeOffset PreparedAt => new(2022, 2, 3, 1, 1, 1, TimeSpan.Zero); + public static DateTimeOffset SignDay => new(2022, 1, 3, 1, 1, 1, TimeSpan.Zero); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRuleTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRuleTests.cs index 8d526dae..990ef5ca 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRuleTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRuleTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract.BusinessRules; -using Common.Core.BusinessRules; using Core.PrepareContract.BusinessRules; +using Fitnet.Common.Core.BusinessRules; public sealed class ContractCanBePreparedOnlyForAdultRuleTests { diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs index b1d89425..71cba5ab 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract.BusinessRules; -using Common.Core.BusinessRules; using Core.PrepareContract.BusinessRules; +using Fitnet.Common.Core.BusinessRules; public sealed class CustomerMustBeSmallerThanMaximumHeightLimitRuleTests { diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRuleTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRuleTests.cs index 67ca924c..88c5301b 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRuleTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRuleTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract.BusinessRules; -using Common.Core.BusinessRules; using Core.PrepareContract.BusinessRules; +using Fitnet.Common.Core.BusinessRules; public sealed class PreviousContractHasToBeSignedRuleTests { diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractTests.cs new file mode 100644 index 00000000..5831ae85 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractTests.cs @@ -0,0 +1,22 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract; + +using Common; +using Core.PrepareContract; + +public sealed class PrepareContractTests +{ + private readonly Guid _customerId = Guid.NewGuid(); + private const int CustomerAge = 25; + private const int CustomerHeight = 170; + private readonly DateTimeOffset _preparedAt = new(2022, 2, 3, 1, 1, 1, TimeSpan.Zero); + + [Fact] + internal void Given_prepare_contract_Then_should_raise_contract_prepared_event() + { + var contract = Contract.Prepare(_customerId, CustomerAge, CustomerHeight, _preparedAt); + + var @event = contract.GetPublishedEvent(); + @event?.CustomerId.Should().Be(_customerId); + @event?.PreparedAt.Should().Be(_preparedAt); + } +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractCanOnlyBeSignedWithin30DaysFromPreparationRuleTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractCanOnlyBeSignedWithin30DaysFromPreparationRuleTests.cs index 2f0bc316..1f705c40 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractCanOnlyBeSignedWithin30DaysFromPreparationRuleTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractCanOnlyBeSignedWithin30DaysFromPreparationRuleTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract.BusinessRules; -using Common.Core.BusinessRules; using Core.SignContract.BusinessRules; +using Fitnet.Common.Core.BusinessRules; public sealed class ContractCanOnlyBeSignedWithin30DaysFromPreparationRuleTests { diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractMustNotBeAlreadySignedRuleTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractMustNotBeAlreadySignedRuleTests.cs index 133d6c36..c07945d1 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractMustNotBeAlreadySignedRuleTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/BusinessRules/ContractMustNotBeAlreadySignedRuleTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract.BusinessRules; using Core.SignContract.BusinessRules; -using EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; +using Fitnet.Common.Core.BusinessRules; public sealed class ContractMustNotBeAlreadySignedRuleTests { diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs index 5af34e71..e62f87b6 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignContractTests.cs @@ -1,8 +1,10 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract; +using Common; +using Core.SignContract; using PrepareContract; -public class SignContractTests +public sealed class SignContractTests { [Theory] [ClassData(typeof(SignContractTestData))] @@ -13,42 +15,33 @@ internal void Given_sign_contract_Then_expiration_date_is_set_to_contract_durati DateTimeOffset expectedExpirationDate) { // Arrange - var contract = PrepareContract(preparedAt); + Contract contract = ContractBuilder + .Prepared() + .PreparedAt(preparedAt); // Act var bindingContract = contract.Sign(signedAt, fakeNow); // Assert - bindingContract.ExpiringAt.Should().Be(expectedExpirationDate); + var @event = bindingContract.GetPublishedEvent(); + @event?.ExpiringAt.Should().Be(expectedExpirationDate); } - private static readonly DateTimeOffset PreparedAt = new(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); - private static readonly DateTimeOffset FakeNow = PreparedAt.AddDays(1); - private static readonly DateTimeOffset SignedAt = PreparedAt.AddDays(1); + private static readonly DateTimeOffset FakeNow = FakeContractDates.PreparedAt.AddDays(1); + private static readonly DateTimeOffset SignedAt = FakeContractDates.PreparedAt.AddDays(1); [Fact] internal void Given_sign_contract_Then_contracts_becomes_binding_contract() { // Arrange - var contract = PrepareContract(PreparedAt); + Contract contract = ContractBuilder + .Prepared(); // Act var bindingContract = contract.Sign(SignedAt, FakeNow); // Assert - bindingContract.Should().NotBeNull(); - bindingContract.Should().BeOfType(); - } - - private static Contract PrepareContract(DateTimeOffset preparedAt) - { - var prepareContractParameters = PrepareContractParameters.GetValid(); - var contract = Contract.Prepare( - Guid.NewGuid(), - prepareContractParameters.MaxAge, - prepareContractParameters.MaxHeight, - preparedAt); - - return contract; + var @event = bindingContract.GetPublishedEvent(); + @event?.BindingFrom.Should().Be(SignedAt); } } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs new file mode 100644 index 00000000..9fc0bb50 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/SignContract/SignedContractBuilder.cs @@ -0,0 +1,28 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.SignContract; + +using Common; + +internal sealed class SignedContractBuilder(Contract parentBuilder) +{ + private DateTimeOffset? _signDay; + private DateTimeOffset? _fakeToday; + + public SignedContractBuilder SignedOn(DateTimeOffset signDay, DateTimeOffset fakeToday) + { + _signDay = signDay; + _fakeToday = fakeToday; + + return this; + } + + private BindingContract Build() + { + var signDay = _signDay ?? FakeContractDates.SignDay; + var fakeToday = _fakeToday ?? FakeContractDates.SignDay; + var bindingContract = parentBuilder.Sign(signDay, fakeToday); + + return bindingContract; + } + + public static implicit operator BindingContract(SignedContractBuilder builder) => builder.Build(); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/BusinessRules/TerminationIsPossibleOnlyAfterThreeMonthsHavePassedTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/BusinessRules/TerminationIsPossibleOnlyAfterThreeMonthsHavePassedTests.cs index 1795326d..c383e504 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/BusinessRules/TerminationIsPossibleOnlyAfterThreeMonthsHavePassedTests.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/BusinessRules/TerminationIsPossibleOnlyAfterThreeMonthsHavePassedTests.cs @@ -1,7 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.TerminateBindingContract.BusinessRules; -using Common.Core.BusinessRules; using Core.TerminateBindingContract.BusinessRules; +using Fitnet.Common.Core.BusinessRules; using TerminationIsPossibleOnlyAfterThreeMonthsHavePassed.TestData; public sealed class TerminationIsPossibleOnlyAfterThreeMonthsHavePassedTests diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/TerminateBindingContractTests.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/TerminateBindingContractTests.cs new file mode 100644 index 00000000..3a5c46c7 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/TerminateBindingContract/TerminateBindingContractTests.cs @@ -0,0 +1,23 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.TerminateBindingContract; + +using Common; +using Core.TerminateBindingContract; +using PrepareContract; + +public sealed class TerminateBindingContractTests +{ + private readonly DateTimeOffset _terminatedAt = new(2023, 3, 3, 1, 1, 1, TimeSpan.Zero); + + [Fact] + internal void Given_terminate_binding_contracts_Then_should_raise_binding_contracts() + { + BindingContract bindingContract = ContractBuilder + .Prepared() + .Signed(); + + bindingContract.Terminate(_terminatedAt); + + var @event = bindingContract.GetPublishedEvent(); + @event?.TerminatedAt.Should().Be(_terminatedAt); + } +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs index 61ce40b2..e82ffaf4 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs @@ -62,8 +62,12 @@ public void Terminate(DateTimeOffset terminatedAt) } } +public record struct ContractId(Guid Value) +{ + internal static ContractId Create() => new(Guid.NewGuid()); +} + public readonly record struct BindingContractId(Guid Value) { internal static BindingContractId Create() => new(Guid.NewGuid()); } - diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs index 7b08d2da..32e2c2ad 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Contract.cs @@ -68,8 +68,3 @@ public BindingContract Sign(DateTimeOffset signedAt, DateTimeOffset now) return bindingContract; } } - -public readonly record struct ContractId(Guid Value) -{ - internal static ContractId Create() => new(Guid.NewGuid()); -} From fb03b810b70296ad0b96bfbb67fd7ca7861beb32 Mon Sep 17 00:00:00 2001 From: Maciej Jedrzejewski Date: Fri, 12 Apr 2024 13:58:50 +0200 Subject: [PATCH 3/8] fix: add private constructor that is required by EF --- .../Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs index fe779404..80445f33 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs @@ -9,6 +9,9 @@ public sealed class Annex : Entity public BindingContractId BindingContractId { get; init; } public DateTimeOffset ValidFrom { get; init; } + // EF needs this constructor to create non-primitive types + private Annex() { } + private Annex(BindingContractId bindingContractId, DateTimeOffset validFrom) { Id = AnnexId.Create(); From a91af488b0d56278eacbf4ea7dabc49cfa6069c9 Mon Sep 17 00:00:00 2001 From: Maciej Jedrzejewski Date: Fri, 12 Apr 2024 14:01:23 +0200 Subject: [PATCH 4/8] feat: register annexes as a part of binding contract aggregate --- .../Fitnet.Contracts.Core/BindingContract.cs | 6 +- .../BindingContractEntityConfiguration.cs | 18 +++ .../20240412114229_AddAnnexes.Designer.cs | 110 ++++++++++++++++++ .../Migrations/20240412114229_AddAnnexes.cs | 37 ++++++ .../ContractsPersistenceModelSnapshot.cs | 28 ++++- 5 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.cs diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs index e82ffaf4..a7664bbe 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs @@ -16,6 +16,7 @@ public sealed class BindingContract : Entity public DateTimeOffset TerminatedAt { get; set; } public DateTimeOffset BindingFrom { get; init; } public DateTimeOffset ExpiringAt { get; init; } + public ICollection RegisteredAnnexes { get; } private BindingContract( ContractId contractId, @@ -30,6 +31,7 @@ private BindingContract( Duration = duration; ExpiringAt = expiringAt; BindingFrom = bindingFrom; + RegisteredAnnexes = []; var @event = BindingContractStartedEvent.Raise(BindingFrom, ExpiringAt); RecordEvent(@event); @@ -42,12 +44,12 @@ internal static BindingContract Start( DateTimeOffset bindingFrom, DateTimeOffset expiringAt) => new(id, customerId, duration, bindingFrom, expiringAt); - public Annex AddAnnex(DateTimeOffset validFrom, DateTimeOffset now) + public void AddAnnex(DateTimeOffset validFrom, DateTimeOffset now) { BusinessRuleValidator.Validate( new AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRule(TerminatedAt, ExpiringAt, now)); - return Annex.Add(Id, validFrom); + RegisteredAnnexes.Add(Annex.Add(Id, validFrom)); } public void Terminate(DateTimeOffset terminatedAt) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs index df0899b2..7c36ba3f 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs @@ -27,5 +27,23 @@ public void Configure(EntityTypeBuilder builder) builder.Property(contract => contract.TerminatedAt).IsRequired(); builder.Property(contract => contract.BindingFrom).IsRequired(); builder.Property(contract => contract.ExpiringAt).IsRequired(); + + builder.OwnsMany(nameof(BindingContract.RegisteredAnnexes), annex => + { + annex.WithOwner().HasForeignKey(a => a.BindingContractId); + annex.ToTable("Annexes"); + annex + .Property(annex => annex.Id) + .HasConversion( + id => id.Value, + value => new AnnexId(value)); + annex.Property(annex => annex.Id).IsRequired(); + annex.Property(annex => annex.BindingContractId) + .HasConversion( + id => id.Value, + value => new BindingContractId(value)); + annex.Property(annex => annex.BindingContractId).IsRequired(); + annex.Property(annex => annex.ValidFrom).IsRequired(); + }); } } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs new file mode 100644 index 00000000..1ed67071 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs @@ -0,0 +1,110 @@ +// +using System; +using EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Migrations +{ + [DbContext(typeof(ContractsPersistence))] + [Migration("20240412114229_AddAnnexes")] + partial class AddAnnexes + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Contracts") + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BindingFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("ContractId") + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("interval"); + + b.Property("ExpiringAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TerminatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("BindingContracts", "Contracts"); + }); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("interval"); + + b.Property("ExpiringAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PreparedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SignedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "Contracts"); + }); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "RegisteredAnnexes", b1 => + { + b1.Property("BindingContractId") + .HasColumnType("uuid"); + + b1.Property("Id") + .HasColumnType("uuid"); + + b1.Property("ValidFrom") + .HasColumnType("timestamp with time zone"); + + b1.HasKey("BindingContractId", "Id"); + + b1.ToTable("Annexes", "Contracts"); + + b1.WithOwner() + .HasForeignKey("BindingContractId"); + }); + + b.Navigation("RegisteredAnnexes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.cs new file mode 100644 index 00000000..1a8c5972 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.cs @@ -0,0 +1,37 @@ +#nullable disable + +namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Migrations; + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +/// +public partial class AddAnnexes : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) => migrationBuilder.CreateTable( + name: "Annexes", + schema: "Contracts", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + BindingContractId = table.Column(type: "uuid", nullable: false), + ValidFrom = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Annexes", x => new { x.BindingContractId, x.Id }); + table.ForeignKey( + name: "FK_Annexes_BindingContracts_BindingContractId", + column: x => x.BindingContractId, + principalSchema: "Contracts", + principalTable: "BindingContracts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + /// + protected override void Down(MigrationBuilder migrationBuilder) => migrationBuilder.DropTable( + name: "Annexes", + schema: "Contracts"); +} diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs index b493bb02..84da575c 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/ContractsPersistenceModelSnapshot.cs @@ -26,6 +26,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uuid"); b.Property("BindingFrom") @@ -40,8 +41,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Duration") .HasColumnType("interval"); - b.Property("ExpiringAt") - .IsRequired() + b.Property("ExpiringAt") .HasColumnType("timestamp with time zone"); b.Property("TerminatedAt") @@ -77,6 +77,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Contracts", "Contracts"); }); + + modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => + { + b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "RegisteredAnnexes", b1 => + { + b1.Property("BindingContractId") + .HasColumnType("uuid"); + + b1.Property("Id") + .HasColumnType("uuid"); + + b1.Property("ValidFrom") + .HasColumnType("timestamp with time zone"); + + b1.HasKey("BindingContractId", "Id"); + + b1.ToTable("Annexes", "Contracts"); + + b1.WithOwner() + .HasForeignKey("BindingContractId"); + }); + + b.Navigation("RegisteredAnnexes"); + }); #pragma warning restore 612, 618 } } From e2bd24c0c02b9189e2eec642576453cd376dac4d Mon Sep 17 00:00:00 2001 From: Maciej Jedrzejewski Date: Sat, 13 Apr 2024 09:49:23 +0200 Subject: [PATCH 5/8] refactor: change the wording to attach as it is more popular than add --- .../Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs | 2 +- .../Src/Fitnet.Contracts.Core/BindingContract.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs index 80445f33..e7f7dc75 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Annex.cs @@ -22,7 +22,7 @@ private Annex(BindingContractId bindingContractId, DateTimeOffset validFrom) RecordEvent(@event); } - internal static Annex Add(BindingContractId bindingContractId, DateTimeOffset validFrom) => + internal static Annex Attach(BindingContractId bindingContractId, DateTimeOffset validFrom) => new(bindingContractId, validFrom); } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs index a7664bbe..28755792 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs @@ -44,12 +44,12 @@ internal static BindingContract Start( DateTimeOffset bindingFrom, DateTimeOffset expiringAt) => new(id, customerId, duration, bindingFrom, expiringAt); - public void AddAnnex(DateTimeOffset validFrom, DateTimeOffset now) + public void AttachAnnex(DateTimeOffset validFrom, DateTimeOffset now) { BusinessRuleValidator.Validate( new AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRule(TerminatedAt, ExpiringAt, now)); - RegisteredAnnexes.Add(Annex.Add(Id, validFrom)); + RegisteredAnnexes.Add(Annex.Attach(Id, validFrom)); } public void Terminate(DateTimeOffset terminatedAt) From de5a38e72f244d81f3a134ab20e60f05d1525a15 Mon Sep 17 00:00:00 2001 From: Maciej Jedrzejewski Date: Sat, 13 Apr 2024 10:04:15 +0200 Subject: [PATCH 6/8] refactor: rename to attached annexes --- .../Src/Fitnet.Contracts.Core/BindingContract.cs | 6 +++--- .../Configurations/BindingContractEntityConfiguration.cs | 2 +- .../Migrations/20240412114229_AddAnnexes.Designer.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs index 28755792..c4defd8b 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/BindingContract.cs @@ -16,7 +16,7 @@ public sealed class BindingContract : Entity public DateTimeOffset TerminatedAt { get; set; } public DateTimeOffset BindingFrom { get; init; } public DateTimeOffset ExpiringAt { get; init; } - public ICollection RegisteredAnnexes { get; } + public ICollection AttachedAnnexes { get; } private BindingContract( ContractId contractId, @@ -31,7 +31,7 @@ private BindingContract( Duration = duration; ExpiringAt = expiringAt; BindingFrom = bindingFrom; - RegisteredAnnexes = []; + AttachedAnnexes = []; var @event = BindingContractStartedEvent.Raise(BindingFrom, ExpiringAt); RecordEvent(@event); @@ -49,7 +49,7 @@ public void AttachAnnex(DateTimeOffset validFrom, DateTimeOffset now) BusinessRuleValidator.Validate( new AnnexCanOnlyBeAddedOnlyBeAddedToActiveBindingContractRule(TerminatedAt, ExpiringAt, now)); - RegisteredAnnexes.Add(Annex.Attach(Id, validFrom)); + AttachedAnnexes.Add(Annex.Attach(Id, validFrom)); } public void Terminate(DateTimeOffset terminatedAt) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs index 7c36ba3f..a849c704 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs @@ -28,7 +28,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(contract => contract.BindingFrom).IsRequired(); builder.Property(contract => contract.ExpiringAt).IsRequired(); - builder.OwnsMany(nameof(BindingContract.RegisteredAnnexes), annex => + builder.OwnsMany(nameof(BindingContract.AttachedAnnexes), annex => { annex.WithOwner().HasForeignKey(a => a.BindingContractId); annex.ToTable("Annexes"); diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs index 1ed67071..973e36f9 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Migrations/20240412114229_AddAnnexes.Designer.cs @@ -83,7 +83,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("EvolutionaryArchitecture.Fitnet.Contracts.Core.BindingContract", b => { - b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "RegisteredAnnexes", b1 => + b.OwnsMany("EvolutionaryArchitecture.Fitnet.Contracts.Core.Annex", "AttachedAnnexes", b1 => { b1.Property("BindingContractId") .HasColumnType("uuid"); @@ -102,7 +102,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("BindingContractId"); }); - b.Navigation("RegisteredAnnexes"); + b.Navigation("AttachedAnnexes"); }); #pragma warning restore 612, 618 } From 100a1554c6ebf1781048922ae51fc33047e2d939 Mon Sep 17 00:00:00 2001 From: Maciej Jedrzejewski Date: Sat, 13 Apr 2024 10:05:22 +0200 Subject: [PATCH 7/8] refactor: extract attached annexes configuration to a separated method --- .../Configurations/BindingContractEntityConfiguration.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs index a849c704..e11f6a2f 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs @@ -28,6 +28,10 @@ public void Configure(EntityTypeBuilder builder) builder.Property(contract => contract.BindingFrom).IsRequired(); builder.Property(contract => contract.ExpiringAt).IsRequired(); + ConfigureAttachedAnnexes(builder); + } + + private static void ConfigureAttachedAnnexes(EntityTypeBuilder builder) => builder.OwnsMany(nameof(BindingContract.AttachedAnnexes), annex => { annex.WithOwner().HasForeignKey(a => a.BindingContractId); @@ -45,5 +49,4 @@ public void Configure(EntityTypeBuilder builder) annex.Property(annex => annex.BindingContractId).IsRequired(); annex.Property(annex => annex.ValidFrom).IsRequired(); }); - } } From 7fdf054fab767f8d8c98f3789e7653ffeb0b1e0c Mon Sep 17 00:00:00 2001 From: Maciej Jedrzejewski Date: Sat, 13 Apr 2024 10:08:29 +0200 Subject: [PATCH 8/8] refactor: add registration of annexes as extension method --- .../BindingContractEntityConfiguration.cs | 21 +-------------- .../BindingContractExtensions.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractExtensions.cs diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs index e11f6a2f..e1c78048 100644 --- a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractEntityConfiguration.cs @@ -28,25 +28,6 @@ public void Configure(EntityTypeBuilder builder) builder.Property(contract => contract.BindingFrom).IsRequired(); builder.Property(contract => contract.ExpiringAt).IsRequired(); - ConfigureAttachedAnnexes(builder); + builder.RegisterAnnexes(); } - - private static void ConfigureAttachedAnnexes(EntityTypeBuilder builder) => - builder.OwnsMany(nameof(BindingContract.AttachedAnnexes), annex => - { - annex.WithOwner().HasForeignKey(a => a.BindingContractId); - annex.ToTable("Annexes"); - annex - .Property(annex => annex.Id) - .HasConversion( - id => id.Value, - value => new AnnexId(value)); - annex.Property(annex => annex.Id).IsRequired(); - annex.Property(annex => annex.BindingContractId) - .HasConversion( - id => id.Value, - value => new BindingContractId(value)); - annex.Property(annex => annex.BindingContractId).IsRequired(); - annex.Property(annex => annex.ValidFrom).IsRequired(); - }); } diff --git a/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractExtensions.cs b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractExtensions.cs new file mode 100644 index 00000000..fb901ca6 --- /dev/null +++ b/Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Configurations/BindingContractExtensions.cs @@ -0,0 +1,27 @@ +namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Configurations; + +using Core; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +internal static class BindingContractExtensions +{ + internal static void RegisterAnnexes(this EntityTypeBuilder builder) => builder.OwnsMany( + nameof(BindingContract.AttachedAnnexes), annex => + { + annex.WithOwner().HasForeignKey(a => a.BindingContractId); + annex.ToTable("Annexes"); + annex + .Property(annex => annex.Id) + .HasConversion( + id => id.Value, + value => new AnnexId(value)); + annex.Property(annex => annex.Id).IsRequired(); + annex.Property(annex => annex.BindingContractId) + .HasConversion( + id => id.Value, + value => new BindingContractId(value)); + annex.Property(annex => annex.BindingContractId).IsRequired(); + annex.Property(annex => annex.ValidFrom).IsRequired(); + }); +}