├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── action.yml ├── dependabot.yml └── workflows │ ├── chapter-1-workflow.yml │ ├── chapter-2-workflow.yml │ ├── chapter-3-contracts-package-workflow.yml │ ├── chapter-3-contracts-workflow.yml │ ├── chapter-3-package-workflow.yml │ ├── chapter-3-workflow.yml │ ├── chapter-4-contracts-workflow.yml │ ├── chapter-4-package-workflow.yml │ └── chapter-4-workflow.yml ├── .gitignore ├── Assets ├── discord.png ├── ea_banner_dark.png ├── ea_banner_light.png ├── ice_panel_black.png ├── nuget-feed-credentials-vs.png ├── nuget_feed_credentials_rider.png ├── project_paradox.png ├── subdomain_contracts_logic.jpg ├── subdomain_offers_logic.jpg ├── subdomain_passes_logic.jpg ├── subdomain_reports.jpg ├── subdomains.png ├── subdomains_communication.png ├── subdomains_processes.png └── subdomains_theory.png ├── Chapter-1-initial-architecture ├── Assets │ ├── multiple_subdomains_single_bounded_context.jpg │ └── projects_division.png ├── Docs │ ├── ArchitectureDecisionLog │ │ ├── 0001-record-architecture-decisions.adoc │ │ ├── 0002-use-one-project.adoc │ │ ├── 0003-use-modules.adoc │ │ ├── 0004-use-separate-database-schemas.adoc │ │ ├── 0005-use-vertical-slices.adoc │ │ ├── 0006-use-docker.adoc │ │ ├── 0007-use-in-memory-event-bus.adoc │ │ ├── 0008-use-internal-sealed-as-default.adoc │ │ ├── 0009-select-testing-strategy.adoc │ │ ├── 0010-select-integration-style-between-passes-and-offers-modules.adoc │ │ ├── 0011-turn-on-static-code-analysis.adoc │ │ └── 0012-select-assertion-framework.adoc │ ├── Contracts │ │ └── Api │ │ │ ├── Contracts.http │ │ │ └── http-client.env.json │ ├── Passes │ │ └── Api │ │ │ ├── Passes.http │ │ │ └── http-client.env.json │ └── Reports │ │ └── Api │ │ ├── Reports.http │ │ └── http-client.env.json ├── README.adoc └── Src │ ├── .editorconfig │ ├── Directory.Build.props │ ├── Dockerfile │ ├── Fitnet.ArchitectureTests │ ├── BussinessRulesArchitectureTests.cs │ ├── Common │ │ ├── Modules.cs │ │ ├── Predicates │ │ │ └── PredicatesExtensions.cs │ │ └── Solution.cs │ ├── ContractsArchitectureTests.cs │ ├── Conventions │ │ ├── EndpointsConventionsTests.cs │ │ └── InterfacesConventionsTests.cs │ ├── Fitnet.ArchitectureTests.csproj │ ├── GlobalUsings.cs │ ├── OffersArchitectureTests.cs │ ├── PassesArchitectureTests.cs │ └── ReportsArchitectureTests.cs │ ├── Fitnet.IntegrationTests │ ├── Common │ │ ├── Events │ │ │ └── EventBus │ │ │ │ └── InMemory │ │ │ │ ├── FakeEvent.cs │ │ │ │ ├── FakeEventConsumer.cs │ │ │ │ └── InMemoryEventBusTests.cs │ │ └── TestEngine │ │ │ ├── Configuration │ │ │ ├── ConfigurationExtensions.cs │ │ │ └── ConfigurationKeys.cs │ │ │ ├── DatabaseContainer.cs │ │ │ ├── IntegrationEvents │ │ │ └── Handlers │ │ │ │ └── IntegrationEventHandlerScope.cs │ │ │ └── Time │ │ │ ├── FakeTimeProvider.cs │ │ │ └── TimeExtensions.cs │ ├── Contracts │ │ ├── PrepareContract │ │ │ ├── PrepareContractRequestFaker.cs │ │ │ ├── PrepareContractRequestParameters.cs │ │ │ └── PrepareContractTests.cs │ │ └── SignContract │ │ │ ├── SignContractRequestParameters.cs │ │ │ └── SignContractTests.cs │ ├── Fitnet.IntegrationTests.csproj │ ├── GlobalUsings.cs │ ├── Offers │ │ └── Prepare │ │ │ ├── PassExpiredEventFaker.cs │ │ │ └── PrepareOfferTests.cs │ ├── Passes │ │ ├── MarkPassAsExpired │ │ │ └── MarkPassAsExpiredTests.cs │ │ └── RegisterPass │ │ │ ├── ContractSignedEventFaker.cs │ │ │ └── RegisterPassTests.cs │ └── Reports │ │ └── GenerateNewPassesPerMonthReport │ │ ├── GenerateNewPassesPerMonthReportTests.Given_valid_generate_new_report_request_Then_should_return_correct_data.verified.txt │ │ ├── GenerateNewPassesPerMonthReportTests.cs │ │ └── TestData │ │ ├── PassRegistrationDateRange.cs │ │ └── ReportTestCases.cs │ ├── Fitnet.UnitTests │ ├── BusinessRulesEngine │ │ ├── BusinessRuleValidatorTests.cs │ │ └── FakeBusinessRule.cs │ ├── Contracts │ │ ├── PrepareContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRuleTests.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs │ │ │ │ └── PreviousContractHasToBeSignedRuleTests.cs │ │ │ ├── PrepareContractParameters.cs │ │ │ └── RequestValidator │ │ │ │ ├── InvalidPrepareContractRequestTestCases.cs │ │ │ │ └── PrepareContractRequestValidatorTests.cs │ │ └── SignContract │ │ │ ├── BusinessRules │ │ │ └── ContractCanOnlyBeSignedWithin30DaysFromPreparationTests.cs │ │ │ ├── RequestValidator │ │ │ └── SignContractRequestValidatorTests.cs │ │ │ ├── SignContractTestData.cs │ │ │ └── SignContractTests.cs │ ├── Fitnet.UnitTests.csproj │ ├── GlobalExceptionHandlerTests.cs │ └── GlobalUsings.cs │ ├── Fitnet.slnx │ ├── Fitnet │ ├── .dockerignore │ ├── ApiPaths.cs │ ├── Common │ │ ├── BusinessRulesEngine │ │ │ ├── BusinessRuleValidationException.cs │ │ │ ├── BusinessRuleValidator.cs │ │ │ └── IBusinessRule.cs │ │ ├── Clock │ │ │ └── ClockModule.cs │ │ ├── Documentation │ │ │ └── SwaggerDocumentationExtensions.cs │ │ ├── ErrorHandling │ │ │ ├── ErrorHandlingExtensions.cs │ │ │ └── GlobalExceptionHandler.cs │ │ ├── Events │ │ │ ├── EventBus │ │ │ │ ├── EventBusModule.cs │ │ │ │ ├── IEventBus.cs │ │ │ │ └── InMemory │ │ │ │ │ ├── InMemoryEventBus.cs │ │ │ │ │ └── InMemoryEventBusModule.cs │ │ │ ├── IIntegrationEvent.cs │ │ │ └── IIntegrationEventHandler.cs │ │ └── Validation │ │ │ └── Requests │ │ │ ├── EndpointBuilderExtensions.cs │ │ │ ├── RequestValidationApiFilter.cs │ │ │ └── RequestValidationsExtensions.cs │ ├── Contracts │ │ ├── ContractsApiPaths.cs │ │ ├── ContractsEndpoints.cs │ │ ├── ContractsModule.cs │ │ ├── Data │ │ │ ├── Contract.cs │ │ │ └── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── ContractEntityConfiguration.cs │ │ │ │ ├── ContractsPersistence.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ └── Migrations │ │ │ │ ├── 20230503180334_CreateContractsTable.Designer.cs │ │ │ │ ├── 20230503180334_CreateContractsTable.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230608060456_MakeSignedAtColumnNotRequired.Designer.cs │ │ │ │ ├── 20230608060456_MakeSignedAtColumnNotRequired.cs │ │ │ │ ├── 20230608060545_AddCustomerIdColumntoContractsTable.Designer.cs │ │ │ │ ├── 20230608060545_AddCustomerIdColumntoContractsTable.cs │ │ │ │ ├── 20230618131127_ContractsAddColumnsToSupportContractExpiration.Designer.cs │ │ │ │ ├── 20230618131127_ContractsAddColumnsToSupportContractExpiration.cs │ │ │ │ └── ContractsPersistenceModelSnapshot.cs │ │ ├── PrepareContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRule.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRule.cs │ │ │ │ └── PreviousContractHasToBeSignedRule.cs │ │ │ ├── PrepareContractEndpoint.cs │ │ │ ├── PrepareContractRequest.cs │ │ │ └── PrepareContractRequestValidator.cs │ │ └── SignContract │ │ │ ├── BusinessRules │ │ │ └── ContractCanOnlyBeSignedWithin30DaysFromPreparation.cs │ │ │ ├── Events │ │ │ └── ContractSignedEvent.cs │ │ │ ├── SignContractEndpoint.cs │ │ │ ├── SignContractRequest.cs │ │ │ └── SignContractRequestValidator.cs │ ├── Fitnet.csproj │ ├── GlobalUsings.cs │ ├── Offers │ │ ├── Data │ │ │ ├── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ ├── Migrations │ │ │ │ │ ├── 20230503180337_Create_Offers_table.Designer.cs │ │ │ │ │ ├── 20230503180337_Create_Offers_table.cs │ │ │ │ │ └── OffersPersistenceModelSnapshot.cs │ │ │ │ ├── OfferEntityConfiguration.cs │ │ │ │ └── OffersPersistence.cs │ │ │ └── Offer.cs │ │ ├── OffersModule.cs │ │ └── Prepare │ │ │ ├── OfferPrepareEvent.cs │ │ │ └── PassExpiredEventHandler.cs │ ├── Passes │ │ ├── Data │ │ │ ├── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ ├── Migrations │ │ │ │ │ ├── 20230503180338_CreatePassesTable.Designer.cs │ │ │ │ │ ├── 20230503180338_CreatePassesTable.cs │ │ │ │ │ └── PassesPersistenceModelSnapshot.cs │ │ │ │ ├── PassEntityConfiguration.cs │ │ │ │ └── PassesPersistence.cs │ │ │ └── Pass.cs │ │ ├── GetAllPasses │ │ │ ├── GetAllPassesEndpoint.cs │ │ │ └── GetAllPassesResponse.cs │ │ ├── MarkPassAsExpired │ │ │ ├── Events │ │ │ │ └── PassExpiredEvent.cs │ │ │ └── MarkPassAsExpiredEndpoint.cs │ │ ├── PassesApiPaths.cs │ │ ├── PassesEndpoints.cs │ │ ├── PassesModule.cs │ │ └── RegisterPass │ │ │ ├── Events │ │ │ └── PassRegisteredEvent.cs │ │ │ ├── RegisterEndpoint.cs │ │ │ └── RegisterPassRequest.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Reports │ │ ├── DataAccess │ │ │ ├── DatabaseAccessModule.cs │ │ │ ├── DatabaseConnectionFactory.cs │ │ │ └── IDatabaseConnectionFactory.cs │ │ ├── GenerateNewPassesRegistrationsPerMonthReport │ │ │ ├── DataRetriever │ │ │ │ ├── INewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ │ └── NewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ ├── Dtos │ │ │ │ ├── NewPassesRegistrationsPerMonthDto.cs │ │ │ │ └── NewPassesRegistrationsPerMonthResponse.cs │ │ │ ├── GenerateNewPassesPerMonthReportEndpoint.cs │ │ │ └── GenerateNewPassesPerMonthReportModule.cs │ │ ├── ReportsApiPaths.cs │ │ ├── ReportsEndpoints.cs │ │ └── ReportsModule.cs │ ├── appsettings.Development.json │ └── appsettings.json │ └── docker-compose.yml ├── Chapter-2-modules-separation ├── Assets │ ├── architectural_patterns.png │ └── subdomain_types.png ├── Docs │ ├── ArchitectureDecisionLog │ │ ├── 0001-record-architecture-decisions.adoc │ │ ├── 0002-use-one-project.adoc │ │ ├── 0003-use-modules.adoc │ │ ├── 0004-use-separate-database-schemas.adoc │ │ ├── 0005-use-vertical-slices.adoc │ │ ├── 0006-use-docker.adoc │ │ ├── 0007-use-in-memory-event-bus.adoc │ │ ├── 0008-use-internal-sealed-as-default.adoc │ │ ├── 0009-select-testing-strategy.adoc │ │ ├── 0010-select-integration-style-between-passes-and-offers-modules.adoc │ │ ├── 0011-turn-on-static-code-analysis.adoc │ │ ├── 0012-applying-clean-architecture-approach-to-contracts-module.adoc │ │ ├── 0013-applying-layered-approach-to-passes-module.adoc │ │ ├── 0014-use-shared-integration-events.adoc │ │ ├── 0015-applying-command-query-separation-to-contracts-module.adoc │ │ ├── 0016-leveraging-disabling-configuration-to-isolate-integration-tests.adoc │ │ ├── 0017-selection-of-microsoft-feature-management-tool.adoc │ │ └── 0018-select-assertion-framework.adoc │ ├── Contracts │ │ └── Api │ │ │ ├── Contracts.http │ │ │ └── http-client.env.json │ ├── Passes │ │ └── Api │ │ │ ├── Passes.http │ │ │ └── http-client.env.json │ └── Reports │ │ └── Api │ │ ├── Reports.http │ │ └── http-client.env.json ├── README.adoc └── Src │ ├── .editorconfig │ ├── Common │ ├── Fitnet.Common.Api │ │ ├── ApiPaths.cs │ │ ├── Documentation │ │ │ └── SwaggerDocumentationExtensions.cs │ │ ├── ErrorHandling │ │ │ ├── ErrorHandlingExtensions.cs │ │ │ ├── GlobalExceptionHandler.cs │ │ │ └── ResourceNotFoundException.cs │ │ ├── Fitnet.Common.Api.csproj │ │ └── Validation │ │ │ └── Requests │ │ │ ├── EndpointBuilderExtensions.cs │ │ │ ├── RequestValidationApiFilter.cs │ │ │ └── RequestValidationsExtensions.cs │ ├── Fitnet.Common.Core │ │ ├── BusinessRules │ │ │ ├── BusinessRuleValidationException.cs │ │ │ ├── BusinessRuleValidator.cs │ │ │ └── IBusinessRule.cs │ │ └── Fitnet.Common.Core.csproj │ ├── Fitnet.Common.Infrastructure │ │ ├── Clock │ │ │ └── ClockModule.cs │ │ ├── CommonInfrastructureModule.cs │ │ ├── Events │ │ │ ├── EventBus │ │ │ │ ├── EventBusModule.cs │ │ │ │ ├── IEventBus.cs │ │ │ │ └── InMemory │ │ │ │ │ ├── InMemoryEventBus.cs │ │ │ │ │ └── InMemoryEventBusModule.cs │ │ │ ├── IIntegrationEvent.cs │ │ │ └── IIntegrationEventHandler.cs │ │ ├── Fitnet.Common.Infrastructure.csproj │ │ ├── Mediator │ │ │ └── MediatorModule.cs │ │ └── Modules │ │ │ └── ModuleAvailabilityChecker.cs │ ├── Fitnet.Common.IntegrationTests │ │ ├── Fitnet.Common.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ └── TestEngine │ │ │ ├── Configuration │ │ │ └── ConfigurationExtensions.cs │ │ │ ├── Database │ │ │ ├── DatabaseContainer.cs │ │ │ └── IDatabaseConfiguration.cs │ │ │ ├── FitnetWebApplicationFactory.cs │ │ │ ├── IntegrationEvents │ │ │ └── Handlers │ │ │ │ └── IntegrationEventHandlerScope.cs │ │ │ └── Time │ │ │ ├── FakeTimeProvider.cs │ │ │ └── TimeExtensions.cs │ └── Tests │ │ ├── Fitnet.Common.Api.UnitTests │ │ ├── Fitnet.Common.Api.UnitTests.csproj │ │ ├── GlobalExceptionHandlerTests.cs │ │ └── GlobalUsings.cs │ │ ├── Fitnet.Common.Core.UnitTests │ │ ├── BusinessRulesEngine │ │ │ ├── BusinessRuleValidatorTests.cs │ │ │ └── FakeBusinessRule.cs │ │ ├── Fitnet.Common.Core.UnitTests.csproj │ │ └── GlobalUsings.cs │ │ └── Fitnet.Common.Infrastructure.IntegrationTests │ │ ├── Events │ │ └── EventBus │ │ │ └── InMemory │ │ │ ├── FakeEvent.cs │ │ │ ├── FakeEventConsumer.cs │ │ │ └── InMemoryEventBusTests.cs │ │ ├── Fitnet.Common.Infrastructure.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ └── appsettings.IntegrationTests.json │ ├── Contracts │ ├── Fitnet.Contracts.Api │ │ ├── ContractsApiPaths.cs │ │ ├── ContractsEndpoints.cs │ │ ├── ContractsModule.cs │ │ ├── Fitnet.Contracts.Api.csproj │ │ ├── Prepare │ │ │ ├── PrepareContractEndpoint.cs │ │ │ ├── PrepareContractRequest.cs │ │ │ └── PrepareContractRequestValidator.cs │ │ └── Sign │ │ │ ├── SignContractEndpoint.cs │ │ │ ├── SignContractRequest.cs │ │ │ └── SignContractRequestValidator.cs │ ├── Fitnet.Contracts.Application │ │ ├── Fitnet.Contracts.Application.csproj │ │ ├── GlobalUsings.cs │ │ ├── ICommand.cs │ │ ├── IContractsModule.cs │ │ ├── Prepare │ │ │ ├── PrepareContractCommand.cs │ │ │ └── PrepareContractCommandHandler.cs │ │ └── Sign │ │ │ ├── SignContractCommand.cs │ │ │ └── SignContractCommandHandler.cs │ ├── Fitnet.Contracts.Core │ │ ├── Contract.cs │ │ ├── Fitnet.Contracts.Core.csproj │ │ ├── IContractsRepository.cs │ │ ├── PrepareContract │ │ │ └── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRule.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRule.cs │ │ │ │ └── PreviousContractHasToBeSignedRule.cs │ │ └── SignContract │ │ │ └── BusinessRules │ │ │ └── ContractCanOnlyBeSignedWithin30DaysFromPreparation.cs │ ├── Fitnet.Contracts.Infrastructure │ │ ├── ContractsModule.cs │ │ ├── Database │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ ├── ContractEntityConfiguration.cs │ │ │ ├── ContractsPersistence.cs │ │ │ ├── DatabaseModule.cs │ │ │ ├── Migrations │ │ │ │ ├── 20230503180334_CreateContractsTable.Designer.cs │ │ │ │ ├── 20230503180334_CreateContractsTable.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230601184153_MakeSignedAtColumnNullable.Designer.cs │ │ │ │ ├── 20230601184153_MakeSignedAtColumnNullable.cs │ │ │ │ ├── 20230601184328_AddCustomerIdColumn.Designer.cs │ │ │ │ ├── 20230601184328_AddCustomerIdColumn.cs │ │ │ │ ├── 20230624171216_ContractsAddColumnsToSupportContractExpiration.Designer.cs │ │ │ │ ├── 20230624171216_ContractsAddColumnsToSupportContractExpiration.cs │ │ │ │ └── ContractsPersistenceModelSnapshot.cs │ │ │ └── Repositories │ │ │ │ ├── ContractsRepository.cs │ │ │ │ └── RepositoriesModule.cs │ │ ├── Fitnet.Contracts.Infrastructure.csproj │ │ ├── GlobalUsings.cs │ │ ├── InfrastructureModule.cs │ │ └── Mediation │ │ │ └── MediationModule.cs │ ├── Fitnet.Contracts.IntegrationEvents │ │ ├── ContractSignedEvent.cs │ │ └── Fitnet.Contracts.IntegrationEvents.csproj │ └── Tests │ │ ├── Fitnet.Contracts.Core.UnitTests │ │ ├── Fitnet.Contracts.Core.UnitTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRuleTests.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs │ │ │ │ └── PreviousContractHasToBeSignedRuleTests.cs │ │ │ ├── PrepareContractParameters.cs │ │ │ └── RequestValidator │ │ │ │ ├── InvalidPrepareContractRequestTestCases.cs │ │ │ │ └── PrepareContractRequestValidatorTests.cs │ │ └── SignContract │ │ │ ├── BusinessRules │ │ │ └── ContractCanOnlyBeSignedWithin30DaysFromPreparationTests.cs │ │ │ ├── RequestValidator │ │ │ └── SignContractRequestValidatorTests.cs │ │ │ ├── SignContractTestData.cs │ │ │ └── SignContractTests.cs │ │ └── Fitnet.Contracts.IntegrationTests │ │ ├── ContractsDatabaseConfiguration.cs │ │ ├── Fitnet.Contracts.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ ├── PrepareContractRequestFaker.cs │ │ ├── PrepareContractRequestParameters.cs │ │ └── PrepareContractTests.cs │ │ ├── SignContract │ │ ├── SignContractRequestParameters.cs │ │ └── SignContractTests.cs │ │ └── appsettings.IntegrationTests.json │ ├── Directory.Build.props │ ├── Dockerfile │ ├── Fitnet.slnx │ ├── Fitnet │ ├── .dockerignore │ ├── Fitnet.csproj │ ├── GlobalUsings.cs │ ├── Modules │ │ ├── Module.cs │ │ └── ModulesRegistry.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json │ ├── Offers │ ├── Fitnet.Offers.Api │ │ ├── Fitnet.Offers.Api.csproj │ │ ├── OffersModule.cs │ │ └── Prepare │ │ │ ├── OfferPrepareEvent.cs │ │ │ └── PassExpiredEventHandler.cs │ ├── Fitnet.Offers.DataAccess │ │ ├── DataAccessModule.cs │ │ ├── Database │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ ├── DatabaseModule.cs │ │ │ ├── Migrations │ │ │ │ ├── 20230503180337_CreateOfferTable.Designer.cs │ │ │ │ ├── 20230503180337_CreateOfferTable.cs │ │ │ │ └── OffersPersistenceModelSnapshot.cs │ │ │ ├── OfferEntityConfiguration.cs │ │ │ └── OffersPersistence.cs │ │ ├── Fitnet.Offers.DataAccess.csproj │ │ ├── GlobalUsings.cs │ │ └── Offer.cs │ └── Tests │ │ └── Fitnet.Offers.IntegrationTests │ │ ├── Fitnet.Offers.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── OffersDatabaseConfiguration.cs │ │ ├── Prepare │ │ ├── PassExpiredEventFaker.cs │ │ └── PrepareOfferTests.cs │ │ └── appsettings.IntegrationTests.json │ ├── Passes │ ├── Fitnet.Passes.Api │ │ ├── Fitnet.Passes.Api.csproj │ │ ├── GetAllPasses │ │ │ ├── GetAllPassesEndpoint.cs │ │ │ └── GetAllPassesResponse.cs │ │ ├── MarkPassAsExpired │ │ │ └── MarkPassAsExpiredEndpoint.cs │ │ ├── PassesApiPaths.cs │ │ ├── PassesEndpoints.cs │ │ ├── PassesModule.cs │ │ └── RegisterPass │ │ │ ├── ContractSignedEventHandler.cs │ │ │ └── PassRegisteredEvent.cs │ ├── Fitnet.Passes.DataAccess │ │ ├── DataAccessModule.cs │ │ ├── Database │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ ├── DatabaseModule.cs │ │ │ ├── Migrations │ │ │ │ ├── 20230503180338_CreatePassesTable.Designer.cs │ │ │ │ ├── 20230503180338_CreatePassesTable.cs │ │ │ │ └── PassesPersistenceModelSnapshot.cs │ │ │ ├── PassEntityConfiguration.cs │ │ │ └── PassesPersistence.cs │ │ ├── Fitnet.Passes.DataAccess.csproj │ │ ├── GlobalUsings.cs │ │ └── Pass.cs │ ├── Fitnet.Passes.IntegrationEvents │ │ ├── Fitnet.Passes.IntegrationEvents.csproj │ │ └── PassExpiredEvent.cs │ └── Tests │ │ └── Fitnet.Passes.IntegrationTests │ │ ├── Fitnet.Passes.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── MarkPassAsExpired │ │ └── MarkPassAsExpiredTests.cs │ │ ├── PassesDatabaseConfiguration.cs │ │ ├── RegisterPass │ │ ├── ContractSignedEventFaker.cs │ │ └── RegisterPassTests.cs │ │ └── appsettings.IntegrationTests.json │ ├── Reports │ ├── Fitnet.Reports │ │ ├── DataAccess │ │ │ ├── DatabaseAccessModule.cs │ │ │ ├── DatabaseConnectionFactory.cs │ │ │ └── IDatabaseConnectionFactory.cs │ │ ├── Fitnet.Reports.csproj │ │ ├── GenerateNewPassesRegistrationsPerMonthReport │ │ │ ├── DataRetriever │ │ │ │ ├── INewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ │ └── NewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ ├── Dtos │ │ │ │ ├── NewPassesRegistrationsPerMonthDto.cs │ │ │ │ └── NewPassesRegistrationsPerMonthResponse.cs │ │ │ ├── GenerateNewPassesPerMonthReportEndpoint.cs │ │ │ └── GenerateNewPassesPerMonthReportModule.cs │ │ ├── ReportsApiPaths.cs │ │ ├── ReportsEndpoints.cs │ │ └── ReportsModule.cs │ └── Tests │ │ └── Fitnet.Reports.IntegrationTests │ │ ├── Fitnet.Reports.IntegrationTests.csproj │ │ ├── GenerateNewPassesPerMonthReport │ │ ├── ContractSignedEventFaker.cs │ │ ├── GenerateNewPassesPerMonthReportTests.Given_valid_generate_new_report_request_Then_should_return_correct_data.verified.txt │ │ ├── GenerateNewPassesPerMonthReportTests.cs │ │ └── TestData │ │ │ ├── PassRegistrationDateRange.cs │ │ │ └── ReportTestCases.cs │ │ ├── GlobalUsings.cs │ │ ├── ReportsDatabaseConfiguration.cs │ │ └── appsettings.IntegrationTests.json │ └── docker-compose.yml ├── Chapter-3-microservice-extraction ├── Assets │ ├── communication.png │ └── components.png ├── Docs │ ├── ArchitectureDecisionLog │ │ ├── 0001-record-architecture-decisions.adoc │ │ ├── 0002-use-one-project.adoc │ │ ├── 0003-use-modules.adoc │ │ ├── 0004-use-separate-database-schemas.adoc │ │ ├── 0005-use-vertical-slices.adoc │ │ ├── 0006-use-docker.adoc │ │ ├── 0007-use-in-memory-event-bus.adoc │ │ ├── 0008-use-internal-sealed-as-default.adoc │ │ ├── 0009-select-testing-strategy.adoc │ │ ├── 0010-select-integration-style-between-passes-and-offers-modules.adoc │ │ ├── 0011-turn-on-static-code-analysis.adoc │ │ ├── 0012-applying-clean-architecture-approach-to-contracts-module.adoc │ │ ├── 0013-applying-layered-approach-to-passes-module.adoc │ │ ├── 0014-use-shared-integration-events.adoc │ │ ├── 0015-applying-command-query-separation-to-contracts-module.adoc │ │ ├── 0016-leveraging-disabling-configuration-to-isolate-integration-tests.adoc │ │ ├── 0017-selection-of-microsoft-feature-management-tool.adoc │ │ ├── 0018-extraction-contracts-module.adoc │ │ ├── 0019-adopt-out-of-process-message-queue.adoc │ │ ├── 0020-implementing-rabbitmq-as-the-inter-service-communication-solution-for-microservices.adoc │ │ ├── 0021-transitioning-fully-from-inmemory -to-outoprocess -event-bus.adoc │ │ ├── 0022-introducing-inbox-outbox-pattern-for-reliability-contracts-and-passes.adoc │ │ └── 0023-implementation-of-Inbox-Outbox-Pattern-with-MassTransit.adoc │ ├── Contracts │ │ └── Api │ │ │ ├── Contracts.http │ │ │ └── http-client.env.json │ ├── Passes │ │ └── Api │ │ │ ├── Passes.http │ │ │ └── http-client.env.json │ └── Reports │ │ └── Api │ │ ├── Reports.http │ │ └── http-client.env.json ├── Fitnet.Common │ ├── .dockerignore │ ├── .editorconfig │ ├── Directory.Build.props │ ├── Fitnet.Common.Api.UnitTests │ │ ├── ExceptionMiddlewareTests.cs │ │ ├── Fitnet.Common.Api.UnitTests.csproj │ │ └── GlobalUsings.cs │ ├── Fitnet.Common.Api │ │ ├── ApiPaths.cs │ │ ├── ErrorHandling │ │ │ ├── ErrorHandlingExtensions.cs │ │ │ ├── ExceptionMiddleware.cs │ │ │ └── ResourceNotFoundException.cs │ │ ├── Fitnet.Common.Api.csproj │ │ └── Validations │ │ │ ├── EndpointBuilderExtensions.cs │ │ │ └── RequestValidationApiFilter.cs │ ├── Fitnet.Common.Core.UnitTests │ │ ├── BusinessRulesEngine │ │ │ ├── BusinessRuleValidatorTests.cs │ │ │ └── FakeBusinessRule.cs │ │ ├── Fitnet.Common.Core.UnitTests.csproj │ │ └── GlobalUsings.cs │ ├── Fitnet.Common.Core │ │ ├── BusinessRules │ │ │ ├── BusinessRuleValidationException.cs │ │ │ ├── BusinessRuleValidator.cs │ │ │ └── IBusinessRule.cs │ │ ├── Fitnet.Common.Core.csproj │ │ └── SystemClock │ │ │ ├── ISystemClock.cs │ │ │ ├── SystemClock.cs │ │ │ └── SystemClockModule.cs │ ├── Fitnet.Common.Infrastructure │ │ ├── Clock │ │ │ └── ClockModule.cs │ │ ├── Events │ │ │ ├── IIntegrationEvent.cs │ │ │ └── IIntegrationEventHandler.cs │ │ ├── Fitnet.Common.Infrastructure.csproj │ │ ├── Mediator │ │ │ └── MediatorModule.cs │ │ └── Modules │ │ │ └── ModuleAvailabilityChecker.cs │ ├── Fitnet.Common.IntegrationTestsToolbox │ │ ├── Fitnet.Common.IntegrationTestsToolbox.csproj │ │ ├── GlobalUsings.cs │ │ └── TestEngine │ │ │ ├── Configuration │ │ │ └── ConfigurationExtensions.cs │ │ │ ├── Database │ │ │ ├── DatabaseContainer.cs │ │ │ └── IDatabaseConfiguration.cs │ │ │ ├── EventBus │ │ │ ├── EventBusAssertions.cs │ │ │ └── EventBusExtensions.cs │ │ │ ├── FitnetWebApplicationFactory.cs │ │ │ └── Time │ │ │ ├── FakeTimeProvider.cs │ │ │ └── TimeExtensions.cs │ └── Fitnet.Common.slnx ├── Fitnet.Contracts │ ├── .dockerignore │ └── Src │ │ ├── .dockerignore │ │ ├── .editorconfig │ │ ├── Directory.Build.props │ │ ├── Dockerfile │ │ ├── Fitnet.Contracts.Api.UnitTests │ │ ├── Fitnet.Contracts.Api.UnitTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── Prepare │ │ │ └── RequestValidator │ │ │ │ ├── InvalidPrepareContractRequestTestCases.cs │ │ │ │ ├── PrepareContractRequestParameters.cs │ │ │ │ └── PrepareContractRequestValidatorTests.cs │ │ └── Sign │ │ │ └── SignContract │ │ │ └── RequestValidator │ │ │ └── SignContractRequestValidatorTests.cs │ │ ├── Fitnet.Contracts.Api │ │ ├── ContractsApiPaths.cs │ │ ├── ContractsEndpoints.cs │ │ ├── ContractsModule.cs │ │ ├── Fitnet.Contracts.Api.csproj │ │ ├── Prepare │ │ │ ├── PrepareContractEndpoint.cs │ │ │ ├── PrepareContractRequest.cs │ │ │ └── PrepareContractRequestValidator.cs │ │ └── Sign │ │ │ ├── SignContractEndpoint.cs │ │ │ ├── SignContractRequest.cs │ │ │ └── SignContractRequestValidator.cs │ │ ├── Fitnet.Contracts.Application │ │ ├── Fitnet.Contracts.Application.csproj │ │ ├── GlobalUsings.cs │ │ ├── ICommand.cs │ │ ├── IContractsModule.cs │ │ ├── IContractsRepository.cs │ │ ├── Prepare │ │ │ ├── PrepareContractCommand.cs │ │ │ └── PrepareContractCommandHandler.cs │ │ └── Sign │ │ │ ├── SignContractCommand.cs │ │ │ └── SignContractCommandHandler.cs │ │ ├── Fitnet.Contracts.Core.UnitTests │ │ ├── Fitnet.Contracts.Core.UnitTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRuleTests.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs │ │ │ │ └── PreviousContractHasToBeSignedRuleTests.cs │ │ │ └── PrepareContractParameters.cs │ │ └── SignContract │ │ │ ├── BusinessRules │ │ │ └── ContractCanOnlyBeSignedWithin30DaysFromPreparationTests.cs │ │ │ ├── SignContractTestData.cs │ │ │ └── SignContractTests.cs │ │ ├── Fitnet.Contracts.Core │ │ ├── Contract.cs │ │ ├── Fitnet.Contracts.Core.csproj │ │ ├── PrepareContract │ │ │ └── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRule.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRule.cs │ │ │ │ └── PreviousContractHasToBeSignedRule.cs │ │ └── SignContract │ │ │ └── BusinessRules │ │ │ └── ContractCanOnlyBeSignedWithin30DaysFromPreparation.cs │ │ ├── Fitnet.Contracts.Infrastructure │ │ ├── ContractsModule.cs │ │ ├── Database │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ ├── ContractEntityConfiguration.cs │ │ │ ├── ContractsPersistence.cs │ │ │ ├── DatabaseModule.cs │ │ │ ├── Migrations │ │ │ │ ├── 20230503180334_CreateContractsTable.Designer.cs │ │ │ │ ├── 20230503180334_CreateContractsTable.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230601184153_MakeSignedAtColumnNullable.Designer.cs │ │ │ │ ├── 20230601184153_MakeSignedAtColumnNullable.cs │ │ │ │ ├── 20230601184328_AddCustomerIdColumn.Designer.cs │ │ │ │ ├── 20230601184328_AddCustomerIdColumn.cs │ │ │ │ ├── 20230624171216_ContractsAddColumnsToSupportContractExpiration.Designer.cs │ │ │ │ ├── 20230624171216_ContractsAddColumnsToSupportContractExpiration.cs │ │ │ │ └── ContractsPersistenceModelSnapshot.cs │ │ │ └── Repositories │ │ │ │ ├── ContractsRepository.cs │ │ │ │ └── RepositoriesModule.cs │ │ ├── EventBus │ │ │ ├── EventBusModule.cs │ │ │ └── EventBusOptions.cs │ │ ├── Fitnet.Contracts.Infrastructure.csproj │ │ ├── GlobalUsings.cs │ │ ├── InfrastructureModule.cs │ │ └── Mediation │ │ │ └── MediationModule.cs │ │ ├── Fitnet.Contracts.IntegrationEvents │ │ ├── ContractSignedEvent.cs │ │ └── Fitnet.Contracts.IntegrationEvents.csproj │ │ ├── Fitnet.Contracts.IntegrationTests │ │ ├── ContractsDatabaseConfiguration.cs │ │ ├── Fitnet.Contracts.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── PrepareContractRequestFaker.cs │ │ │ ├── PrepareContractRequestParameters.cs │ │ │ └── PrepareContractTests.cs │ │ ├── SignContract │ │ │ ├── SignContractRequestParameters.cs │ │ │ └── SignContractTests.cs │ │ └── appsettings.IntegrationTests.json │ │ ├── Fitnet.Contracts.slnx │ │ ├── Fitnet.Contracts │ │ ├── .dockerignore │ │ ├── Fitnet.Contracts.csproj │ │ ├── GlobalUsings.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ └── nuget.config ├── Fitnet │ ├── .dockerignore │ └── Src │ │ ├── .editorconfig │ │ ├── Directory.Build.props │ │ ├── Dockerfile │ │ ├── Fitnet.sln.DotSettings │ │ ├── Fitnet.slnx │ │ ├── Fitnet │ │ ├── .dockerignore │ │ ├── Fitnet.csproj │ │ ├── GlobalUsings.cs │ │ ├── Module.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ ├── Offers │ │ ├── Fitnet.Offers.Api │ │ │ ├── Fitnet.Offers.Api.csproj │ │ │ ├── OffersModule.cs │ │ │ └── Prepare │ │ │ │ ├── OfferPrepareEvent.cs │ │ │ │ └── PassExpiredEventConsumer.cs │ │ ├── Fitnet.Offers.DataAccess │ │ │ ├── DataAccessModule.cs │ │ │ ├── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ ├── Migrations │ │ │ │ │ ├── 20230503180337_CreateOffersTable.Designer.cs │ │ │ │ │ ├── 20230503180337_CreateOffersTable.cs │ │ │ │ │ └── OffersPersistenceModelSnapshot.cs │ │ │ │ ├── OfferEntityConfiguration.cs │ │ │ │ └── OffersPersistence.cs │ │ │ ├── Fitnet.Offers.DataAccess.csproj │ │ │ ├── GlobalUsings.cs │ │ │ └── Offer.cs │ │ └── Tests │ │ │ └── Fitnet.Offers.IntegrationTests │ │ │ ├── Fitnet.Offers.IntegrationTests.csproj │ │ │ ├── GlobalUsings.cs │ │ │ ├── OffersDatabaseConfiguration.cs │ │ │ ├── Prepare │ │ │ ├── PassExpiredEventFaker.cs │ │ │ └── PrepareOfferTests.cs │ │ │ └── appsettings.IntegrationTests.json │ │ ├── Passes │ │ ├── Fitnet.Passes.Api │ │ │ ├── Common │ │ │ │ └── EventBus │ │ │ │ │ ├── EventBusModule.cs │ │ │ │ │ ├── EventBusOptions.cs │ │ │ │ │ └── Outbox │ │ │ │ │ └── OutboxExtensions.cs │ │ │ ├── Fitnet.Passes.Api.csproj │ │ │ ├── GetAllPasses │ │ │ │ ├── GetAllPassesEndpoint.cs │ │ │ │ └── GetAllPassesResponse.cs │ │ │ ├── MarkPassAsExpired │ │ │ │ └── MarkPassAsExpiredEndpoint.cs │ │ │ ├── PassesApiPaths.cs │ │ │ ├── PassesEndpoints.cs │ │ │ ├── PassesModule.cs │ │ │ └── RegisterPass │ │ │ │ ├── ContractSignedEventConsumer.cs │ │ │ │ ├── ContractSignedEventConsumerDefinition.cs │ │ │ │ └── PassRegisteredEvent.cs │ │ ├── Fitnet.Passes.DataAccess │ │ │ ├── DataAccessModule.cs │ │ │ ├── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ ├── Migrations │ │ │ │ │ ├── 20230503180338_CreatePassesTable.Designer.cs │ │ │ │ │ ├── 20230503180338_CreatePassesTable.cs │ │ │ │ │ ├── 20231107192159_AddOutbox.Designer.cs │ │ │ │ │ ├── 20231107192159_AddOutbox.cs │ │ │ │ │ └── PassesPersistenceModelSnapshot.cs │ │ │ │ ├── PassEntityConfiguration.cs │ │ │ │ └── PassesPersistence.cs │ │ │ ├── Fitnet.Passes.DataAccess.csproj │ │ │ ├── GlobalUsings.cs │ │ │ └── Pass.cs │ │ ├── Fitnet.Passes.IntegrationEvents │ │ │ ├── Fitnet.Passes.IntegrationEvents.csproj │ │ │ └── PassExpiredEvent.cs │ │ └── Tests │ │ │ └── Fitnet.Passes.IntegrationTests │ │ │ ├── Fitnet.Passes.IntegrationTests.csproj │ │ │ ├── GlobalUsings.cs │ │ │ ├── MarkPassAsExpired │ │ │ └── MarkPassAsExpiredTests.cs │ │ │ ├── PassesDatabaseConfiguration.cs │ │ │ ├── RegisterPass │ │ │ ├── ContractSignedEventFaker.cs │ │ │ └── RegisterPassTests.cs │ │ │ └── appsettings.IntegrationTests.json │ │ ├── Reports │ │ ├── Fitnet.Reports │ │ │ ├── DataAccess │ │ │ │ ├── DatabaseAccessModule.cs │ │ │ │ ├── DatabaseConnectionFactory.cs │ │ │ │ └── IDatabaseConnectionFactory.cs │ │ │ ├── Fitnet.Reports.csproj │ │ │ ├── GenerateNewPassesRegistrationsPerMonthReport │ │ │ │ ├── DataRetriever │ │ │ │ │ ├── INewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ │ │ └── NewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ │ ├── Dtos │ │ │ │ │ ├── NewPassesRegistrationsPerMonthDto.cs │ │ │ │ │ └── NewPassesRegistrationsPerMonthResponse.cs │ │ │ │ ├── GenerateNewPassesPerMonthReportEndpoint.cs │ │ │ │ └── GenerateNewPassesPerMonthReportModule.cs │ │ │ ├── ReportsApiPaths.cs │ │ │ ├── ReportsEndpoints.cs │ │ │ └── ReportsModule.cs │ │ └── Tests │ │ │ └── Fitnet.Reports.IntegrationTests │ │ │ ├── Fitnet.Reports.IntegrationTests.csproj │ │ │ ├── GenerateNewPassesPerMonthReport │ │ │ ├── ContractSignedEventFaker.cs │ │ │ ├── GenerateNewPassesPerMonthReportTests.Given_valid_generate_new_report_request_Then_should_return_correct_data.verified.txt │ │ │ ├── GenerateNewPassesPerMonthReportTests.cs │ │ │ └── TestData │ │ │ │ ├── PassRegistrationDateRange.cs │ │ │ │ └── ReportTestCases.cs │ │ │ ├── GlobalUsings.cs │ │ │ ├── ReportsDatabaseConfiguration.cs │ │ │ └── appsettings.IntegrationTests.json │ │ ├── nuget.config │ │ └── qodana.yaml ├── README.adoc └── docker-compose.yml ├── Chapter-4-applying-tactical-domain-driven-design ├── Assets │ ├── aggregate_design_canvas.jpg │ ├── aggregate_root_internals.png │ ├── ddd-starter-modeling-circular.svg │ ├── design-level-event-storming.png │ ├── flow.png │ ├── persistance_ignorance.png │ └── value_object_sets.png ├── Docs │ ├── ArchitectureDecisionLog │ │ ├── 0001-record-architecture-decisions.adoc │ │ ├── 0002-use-one-project.adoc │ │ ├── 0003-use-modules.adoc │ │ ├── 0004-use-separate-database-schemas.adoc │ │ ├── 0005-use-vertical-slices.adoc │ │ ├── 0006-use-docker.adoc │ │ ├── 0007-use-in-memory-event-bus.adoc │ │ ├── 0008-use-internal-sealed-as-default.adoc │ │ ├── 0009-select-testing-strategy.adoc │ │ ├── 0010-select-integration-style-between-passes-and-offers-modules.adoc │ │ ├── 0011-turn-on-static-code-analysis.adoc │ │ ├── 0012-applying-clean-architecture-approach-to-contracts-module.adoc │ │ ├── 0013-applying-layered-approach-to-passes-module.adoc │ │ ├── 0014-use-shared-integration-events.adoc │ │ ├── 0015-applying-command-query-separation-to-contracts-module.adoc │ │ ├── 0016-leveraging-disabling-configuration-to-isolate-integration-tests.adoc │ │ ├── 0017-selection-of-microsoft-feature-management-tool.adoc │ │ ├── 0018-extraction-contracts-module.adoc │ │ ├── 0019-adopt-out-of-process-message-queue.adoc │ │ ├── 0020-implementing-rabbitmq-as-the-inter-service-communication-solution-for-microservices.adoc │ │ ├── 0021-transitioning-fully-from-inmemory -to-outoprocess -event-bus.adoc │ │ ├── 0022-introducing-inbox-outbox-pattern-for-reliability-contracts-and-passes.adoc │ │ ├── 0023-implementation-of-Inbox-Outbox-Pattern-with-MassTransit.adoc │ │ ├── 0024-contracts-error-handling.adoc │ │ ├── 0025-applying-domain-driven-design-patterns-for-contracts.adoc │ │ └── 0026-select-assertion-framework.adoc │ ├── Contracts │ │ └── Api │ │ │ ├── BindingContracts.http │ │ │ ├── Contracts.http │ │ │ └── http-client.env.json │ ├── Passes │ │ └── Api │ │ │ ├── Passes.http │ │ │ └── http-client.env.json │ └── Reports │ │ └── Api │ │ ├── Reports.http │ │ └── http-client.env.json ├── Fitnet.Common │ ├── .dockerignore │ ├── .editorconfig │ ├── Directory.Build.props │ ├── Fitnet.Common.Api.UnitTests │ │ ├── ExceptionMiddlewareTests.cs │ │ ├── Fitnet.Common.Api.UnitTests.csproj │ │ └── GlobalUsings.cs │ ├── Fitnet.Common.Api │ │ ├── ApiPaths.cs │ │ ├── ErrorHandling │ │ │ ├── ErrorHandlingExtensions.cs │ │ │ ├── ExceptionMiddleware.cs │ │ │ ├── Problems │ │ │ │ └── ProblemResults.cs │ │ │ └── ResourceNotFoundException.cs │ │ ├── Fitnet.Common.Api.csproj │ │ └── Validations │ │ │ ├── EndpointBuilderExtensions.cs │ │ │ └── RequestValidationApiFilter.cs │ ├── Fitnet.Common.Core.UnitTests │ │ ├── BusinessRulesEngine │ │ │ ├── BusinessRuleValidatorTests.cs │ │ │ └── FakeBusinessRule.cs │ │ ├── Fitnet.Common.Core.UnitTests.csproj │ │ ├── GlobalUsings.cs │ │ └── ValueObjectTests.cs │ ├── Fitnet.Common.Core │ │ ├── BussinessRules │ │ │ ├── BusinessErrors.cs │ │ │ ├── BusinessRuleValidator.cs │ │ │ └── IBusinessRule.cs │ │ ├── Entity.cs │ │ ├── Fitnet.Common.Core.csproj │ │ ├── GlobalUsings.cs │ │ ├── IDomainEvent.cs │ │ └── ValueObject.cs │ ├── Fitnet.Common.Infrastructure │ │ ├── Clock │ │ │ └── ClockModule.cs │ │ ├── Events │ │ │ ├── IIntegrationEvent.cs │ │ │ └── IIntegrationEventHandler.cs │ │ ├── Fitnet.Common.Infrastructure.csproj │ │ ├── Mediator │ │ │ └── MediatorModule.cs │ │ └── Modules │ │ │ └── ModuleAvailabilityChecker.cs │ ├── Fitnet.Common.IntegrationTestsToolbox │ │ ├── Fitnet.Common.IntegrationTestsToolbox.csproj │ │ ├── GlobalUsings.cs │ │ └── TestEngine │ │ │ ├── Configuration │ │ │ └── ConfigurationExtensions.cs │ │ │ ├── Database │ │ │ ├── DatabaseContainer.cs │ │ │ └── IDatabaseConfiguration.cs │ │ │ ├── EventBus │ │ │ ├── EventBusAssertions.cs │ │ │ └── EventBusExtensions.cs │ │ │ ├── FitnetWebApplicationFactory.cs │ │ │ └── Time │ │ │ ├── FakeTimeProvider.cs │ │ │ └── TimeExtensions.cs │ ├── Fitnet.Common.UnitTesting │ │ ├── Assertions │ │ │ └── ErrorOr │ │ │ │ ├── ErrorOrAssertions.cs │ │ │ │ └── ErrorOrExtensions.cs │ │ ├── Fitnet.Common.UnitTesting.csproj │ │ └── GlobalUsings.cs │ └── Fitnet.Common.sln ├── Fitnet.Contracts │ ├── .dockerignore │ └── Src │ │ ├── .dockerignore │ │ ├── .editorconfig │ │ ├── Directory.Build.props │ │ ├── Dockerfile │ │ ├── Fitnet.Contracts.Api.UnitTests │ │ ├── AttachAnnexToBindingContract │ │ │ └── AttachAnnexToBindingContractRequestValidatorTests.cs │ │ ├── Fitnet.Contracts.Api.UnitTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ └── RequestValidator │ │ │ │ ├── InvalidPrepareContractRequestTestCases.cs │ │ │ │ ├── PrepareContractRequestParameters.cs │ │ │ │ └── PrepareContractRequestValidatorTests.cs │ │ └── SignContract │ │ │ ├── SignContractRequestValidatorTests.cs │ │ │ └── Signatures │ │ │ └── SignatureTests.cs │ │ ├── Fitnet.Contracts.Api │ │ ├── AttachAnnexToBindingContract │ │ │ ├── AttachAnnexToBindingContractEndpoint.cs │ │ │ ├── AttachAnnexToBindingContractRequest.cs │ │ │ └── AttachAnnexToBindingContractRequestValidator.cs │ │ ├── Common │ │ │ └── Errors │ │ │ │ └── ProblemResults.cs │ │ ├── ContractsApiPaths.cs │ │ ├── ContractsEndpoints.cs │ │ ├── ContractsModule.cs │ │ ├── Fitnet.Contracts.Api.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── PrepareContractEndpoint.cs │ │ │ ├── PrepareContractRequest.cs │ │ │ └── PrepareContractRequestValidator.cs │ │ ├── SignContract │ │ │ ├── SignContractEndpoint.cs │ │ │ ├── SignContractRequest.cs │ │ │ └── SignContractRequestValidator.cs │ │ └── TerminateBindingContract │ │ │ ├── TerminateBindingContractEndpoint.cs │ │ │ └── TerminateContractEndpoint.cs │ │ ├── Fitnet.Contracts.Application │ │ ├── AttachAnnexToBindingContract │ │ │ ├── AttachAnnexToBindingContractCommand.cs │ │ │ └── AttachAnnexToBindingContractCommandHandler.cs │ │ ├── Fitnet.Contracts.Application.csproj │ │ ├── GlobalUsings.cs │ │ ├── IBindingContractsRepository.cs │ │ ├── ICommand.cs │ │ ├── IContractsModule.cs │ │ ├── IContractsRepository.cs │ │ ├── PrepareContract │ │ │ ├── PrepareContractCommand.cs │ │ │ └── PrepareContractCommandHandler.cs │ │ ├── SignContract │ │ │ ├── SignContractCommand.cs │ │ │ └── SignContractCommandHandler.cs │ │ └── TerminateBindingContract │ │ │ ├── TerminateBindingContractCommand.cs │ │ │ └── TerminateBindingContractCommandHandler.cs │ │ ├── Fitnet.Contracts.Core.UnitTests │ │ ├── AttachAnnexToBindingContract │ │ │ └── BusinessRules │ │ │ │ ├── AnnexCanOnlyBeAttachedToActiveBindingContractRuleTests.cs │ │ │ │ └── AnnexCanOnlyStartDuringBindingContractPeriodRuleTests.cs │ │ ├── Common │ │ │ ├── Assertions │ │ │ │ └── ErrorOr │ │ │ │ │ └── ErrorOrAssertions.cs │ │ │ ├── Builders │ │ │ │ ├── ContractBuilder.cs │ │ │ │ └── SignContractBuilder.cs │ │ │ ├── EntityExtensions.cs │ │ │ └── FakeContractDates.cs │ │ ├── Fitnet.Contracts.Core.UnitTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRuleTests.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRuleTests.cs │ │ │ │ └── PreviousContractHasToBeSignedRuleTests.cs │ │ │ ├── PrepareContractParameters.cs │ │ │ └── PrepareContractTests.cs │ │ ├── SignContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanOnlyBeSignedWithin30DaysFromPreparationRuleTests.cs │ │ │ │ └── ContractMustNotBeAlreadySignedRuleTests.cs │ │ │ ├── SignContractTestData.cs │ │ │ ├── SignContractTests.cs │ │ │ └── SignedContractBuilder.cs │ │ └── TerminateBindingContract │ │ │ ├── BusinessRules │ │ │ ├── TerminationIsPossibleOnlyAfterThreeMonthsHavePassed │ │ │ │ └── TestData │ │ │ │ │ ├── BindingContractThreeMonthsHaveElapsedTestData.cs │ │ │ │ │ └── BindingContractThreeMonthsHaveNotElapsedTestData.cs │ │ │ └── TerminationIsPossibleOnlyAfterThreeMonthsHavePassedTests.cs │ │ │ └── TerminateBindingContractTests.cs │ │ ├── Fitnet.Contracts.Core │ │ ├── Annex.cs │ │ ├── AttachAnnexToBindingContract │ │ │ ├── AnnexAttachedToBindingContractEvent.cs │ │ │ └── BusinessRules │ │ │ │ ├── AnnexCanOnlyBeAttachedToActiveBindingContractRule.cs │ │ │ │ └── AnnexCanOnlyStartDuringBindingContractPeriodRule.cs │ │ ├── BindingContract.cs │ │ ├── Contract.cs │ │ ├── Fitnet.Contracts.Core.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanBePreparedOnlyForAdultRule.cs │ │ │ │ ├── CustomerMustBeSmallerThanMaximumHeightLimitRule.cs │ │ │ │ └── PreviousContractHasToBeSignedRule.cs │ │ │ └── ContractPreparedEvent.cs │ │ ├── SignContract │ │ │ ├── BindingContractStartedEvent.cs │ │ │ ├── BusinessRules │ │ │ │ ├── ContractCanOnlyBeSignedWithin30DaysFromPreparationRule.cs │ │ │ │ └── ContractMustNotBeAlreadySignedRule.cs │ │ │ └── Signatures │ │ │ │ ├── Exceptions │ │ │ │ └── SignatureNotValidException.cs │ │ │ │ └── Signature.cs │ │ └── TerminateBindingContract │ │ │ ├── BindingContractTerminatedEvent.cs │ │ │ └── BusinessRules │ │ │ └── TerminationIsPossibleOnlyAfterThreeMonthsHavePassedRule.cs │ │ ├── Fitnet.Contracts.Infrastructure │ │ ├── ContractsModule.cs │ │ ├── Database │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ ├── Configurations │ │ │ │ ├── BindingContractEntityConfiguration.cs │ │ │ │ ├── BindingContractExtensions.cs │ │ │ │ └── ContractEntityConfiguration.cs │ │ │ ├── ContractsPersistence.cs │ │ │ ├── DatabaseModule.cs │ │ │ ├── Migrations │ │ │ │ ├── 20230503180334_CreateContractsTable.Designer.cs │ │ │ │ ├── 20230503180334_CreateContractsTable.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180335_AddSignedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.Designer.cs │ │ │ │ ├── 20230503180336_AddPreparedAtDateColumnToContractsTable.cs │ │ │ │ ├── 20230601184153_MakeSignedAtColumnNullable.Designer.cs │ │ │ │ ├── 20230601184153_MakeSignedAtColumnNullable.cs │ │ │ │ ├── 20230601184328_AddCustomerIdColumn.Designer.cs │ │ │ │ ├── 20230601184328_AddCustomerIdColumn.cs │ │ │ │ ├── 20230624171216_ContractsAddColumnsToSupportContractExpiration.Designer.cs │ │ │ │ ├── 20230624171216_ContractsAddColumnsToSupportContractExpiration.cs │ │ │ │ ├── 20240314062526_AddBindingContractTable.Designer.cs │ │ │ │ ├── 20240314062526_AddBindingContractTable.cs │ │ │ │ ├── 20240412114229_AddAnnexes.Designer.cs │ │ │ │ ├── 20240412114229_AddAnnexes.cs │ │ │ │ ├── 20240507073145_ChangeBindingContractTerminatedAtToNullable.Designer.cs │ │ │ │ ├── 20240507073145_ChangeBindingContractTerminatedAtToNullable.cs │ │ │ │ ├── 20241026165318_AddSignature.Designer.cs │ │ │ │ ├── 20241026165318_AddSignature.cs │ │ │ │ └── ContractsPersistenceModelSnapshot.cs │ │ │ └── Repositories │ │ │ │ ├── BindingContractsRepository.cs │ │ │ │ ├── ContractsRepository.cs │ │ │ │ └── RepositoriesModule.cs │ │ ├── EventBus │ │ │ ├── EventBusModule.cs │ │ │ └── EventBusOptions.cs │ │ ├── Fitnet.Contracts.Infrastructure.csproj │ │ ├── GlobalUsings.cs │ │ ├── InfrastructureModule.cs │ │ └── Mediation │ │ │ └── MediationModule.cs │ │ ├── Fitnet.Contracts.IntegrationEvents │ │ ├── ContractSignedEvent.cs │ │ └── Fitnet.Contracts.IntegrationEvents.csproj │ │ ├── Fitnet.Contracts.IntegrationTests │ │ ├── AttachAnnexToBindingContract │ │ │ ├── AttachAnnexToBindingContractRequestFaker.cs │ │ │ └── AttachAnnexToBindingContractTests.cs │ │ ├── ContractsDatabaseConfiguration.cs │ │ ├── Fitnet.Contracts.IntegrationTests.csproj │ │ ├── GlobalUsings.cs │ │ ├── PrepareContract │ │ │ ├── PrepareContractRequestFaker.cs │ │ │ ├── PrepareContractRequestParameters.cs │ │ │ ├── PrepareContractTestExtensions.cs │ │ │ └── PrepareContractTests.cs │ │ ├── SignContract │ │ │ ├── SignContractRequestParameters.cs │ │ │ ├── SignContractTestExtensions.cs │ │ │ └── SignContractTests.cs │ │ ├── TerminateBindingContract │ │ │ ├── TerminateBindingContractRequestParameters.cs │ │ │ ├── TerminateBindingContractTestExtensions.cs │ │ │ └── TerminateBindingContractTests.cs │ │ └── appsettings.IntegrationTests.json │ │ ├── Fitnet.Contracts.sln │ │ ├── Fitnet.Contracts │ │ ├── .dockerignore │ │ ├── Fitnet.Contracts.csproj │ │ ├── GlobalUsings.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ └── nuget.config ├── Fitnet │ ├── .dockerignore │ └── Src │ │ ├── .editorconfig │ │ ├── Directory.Build.props │ │ ├── Dockerfile │ │ ├── Fitnet.sln │ │ ├── Fitnet.sln.DotSettings │ │ ├── Fitnet │ │ ├── .dockerignore │ │ ├── Fitnet.csproj │ │ ├── GlobalUsings.cs │ │ ├── Module.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ ├── Offers │ │ ├── Fitnet.Offers.Api │ │ │ ├── Fitnet.Offers.Api.csproj │ │ │ ├── OffersModule.cs │ │ │ └── Prepare │ │ │ │ ├── OfferPrepareEvent.cs │ │ │ │ └── PassExpiredEventConsumer.cs │ │ ├── Fitnet.Offers.DataAccess │ │ │ ├── DataAccessModule.cs │ │ │ ├── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ ├── Migrations │ │ │ │ │ ├── 20230503180337_CreateOffersTable.Designer.cs │ │ │ │ │ ├── 20230503180337_CreateOffersTable.cs │ │ │ │ │ └── OffersPersistenceModelSnapshot.cs │ │ │ │ ├── OfferEntityConfiguration.cs │ │ │ │ └── OffersPersistence.cs │ │ │ ├── Fitnet.Offers.DataAccess.csproj │ │ │ ├── GlobalUsings.cs │ │ │ └── Offer.cs │ │ └── Tests │ │ │ └── Fitnet.Offers.IntegrationTests │ │ │ ├── Fitnet.Offers.IntegrationTests.csproj │ │ │ ├── GlobalUsings.cs │ │ │ ├── OffersDatabaseConfiguration.cs │ │ │ ├── Prepare │ │ │ ├── PassExpiredEventFaker.cs │ │ │ └── PrepareOfferTests.cs │ │ │ └── appsettings.IntegrationTests.json │ │ ├── Passes │ │ ├── Fitnet.Passes.Api │ │ │ ├── Common │ │ │ │ └── EventBus │ │ │ │ │ ├── EventBusModule.cs │ │ │ │ │ ├── EventBusOptions.cs │ │ │ │ │ └── Outbox │ │ │ │ │ └── OutboxExtensions.cs │ │ │ ├── Fitnet.Passes.Api.csproj │ │ │ ├── GetAllPasses │ │ │ │ ├── GetAllPassesEndpoint.cs │ │ │ │ └── GetAllPassesResponse.cs │ │ │ ├── MarkPassAsExpired │ │ │ │ └── MarkPassAsExpiredEndpoint.cs │ │ │ ├── PassesApiPaths.cs │ │ │ ├── PassesEndpoints.cs │ │ │ ├── PassesModule.cs │ │ │ └── RegisterPass │ │ │ │ ├── ContractSignedEventConsumer.cs │ │ │ │ ├── ContractSignedEventConsumerDefinition.cs │ │ │ │ └── PassRegisteredEvent.cs │ │ ├── Fitnet.Passes.DataAccess │ │ │ ├── DataAccessModule.cs │ │ │ ├── Database │ │ │ │ ├── AutomaticMigrationsExtensions.cs │ │ │ │ ├── DatabaseModule.cs │ │ │ │ ├── Migrations │ │ │ │ │ ├── 20230503180338_CreatePassesTable.Designer.cs │ │ │ │ │ ├── 20230503180338_CreatePassesTable.cs │ │ │ │ │ ├── 20231107192159_AddOutbox.Designer.cs │ │ │ │ │ ├── 20231107192159_AddOutbox.cs │ │ │ │ │ └── PassesPersistenceModelSnapshot.cs │ │ │ │ ├── PassEntityConfiguration.cs │ │ │ │ └── PassesPersistence.cs │ │ │ ├── Fitnet.Passes.DataAccess.csproj │ │ │ ├── GlobalUsings.cs │ │ │ └── Pass.cs │ │ ├── Fitnet.Passes.IntegrationEvents │ │ │ ├── Fitnet.Passes.IntegrationEvents.csproj │ │ │ └── PassExpiredEvent.cs │ │ └── Tests │ │ │ └── Fitnet.Passes.IntegrationTests │ │ │ ├── Fitnet.Passes.IntegrationTests.csproj │ │ │ ├── GlobalUsings.cs │ │ │ ├── MarkPassAsExpired │ │ │ └── MarkPassAsExpiredTests.cs │ │ │ ├── PassesDatabaseConfiguration.cs │ │ │ ├── RegisterPass │ │ │ ├── ContractSignedEventFaker.cs │ │ │ └── RegisterPassTests.cs │ │ │ └── appsettings.IntegrationTests.json │ │ ├── Reports │ │ ├── Fitnet.Reports │ │ │ ├── DataAccess │ │ │ │ ├── DatabaseAccessModule.cs │ │ │ │ ├── DatabaseConnectionFactory.cs │ │ │ │ └── IDatabaseConnectionFactory.cs │ │ │ ├── Fitnet.Reports.csproj │ │ │ ├── GenerateNewPassesRegistrationsPerMonthReport │ │ │ │ ├── DataRetriever │ │ │ │ │ ├── INewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ │ │ └── NewPassesRegistrationPerMonthReportDataRetriever.cs │ │ │ │ ├── Dtos │ │ │ │ │ ├── NewPassesRegistrationsPerMonthDto.cs │ │ │ │ │ └── NewPassesRegistrationsPerMonthResponse.cs │ │ │ │ ├── GenerateNewPassesPerMonthReportEndpoint.cs │ │ │ │ └── GenerateNewPassesPerMonthReportModule.cs │ │ │ ├── ReportsApiPaths.cs │ │ │ ├── ReportsEndpoints.cs │ │ │ └── ReportsModule.cs │ │ └── Tests │ │ │ └── Fitnet.Reports.IntegrationTests │ │ │ ├── Fitnet.Reports.IntegrationTests.csproj │ │ │ ├── GenerateNewPassesPerMonthReport │ │ │ ├── ContractSignedEventFaker.cs │ │ │ ├── GenerateNewPassesPerMonthReportTests.Given_valid_generate_new_report_request_Then_should_return_correct_data.verified.txt │ │ │ ├── GenerateNewPassesPerMonthReportTests.cs │ │ │ └── TestData │ │ │ │ ├── PassRegistrationDateRange.cs │ │ │ │ └── ReportTestCases.cs │ │ │ ├── GlobalUsings.cs │ │ │ ├── ReportsDatabaseConfiguration.cs │ │ │ └── appsettings.IntegrationTests.json │ │ ├── nuget.config │ │ └── qodana.yaml ├── README.adoc └── docker-compose.yml ├── LICENSE └── README.adoc /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📋 Description 2 | 3 | Please include a summary of the changes and the related issue. 4 | 5 | ## 📦 PR Includes 6 | 7 | - [ ] Feature added 🆕 8 | - [ ] Bug fix 🐛 9 | - [ ] Code refactor 🔄 10 | - [ ] Documentation update 📚 11 | - [ ] Tests added/updated 🧪 12 | - [ ] Other: (describe) 13 | 14 | ## 🚫 PR Does Not Include 15 | 16 | - [ ] Breaking changes ❌ 17 | - [ ] Major architectural changes 🏗️ 18 | - [ ] Unrelated features/tasks 🚫 19 | 20 | ## 💡 Additional Notes 21 | 22 | Please add any additional information or context that might be useful during the review of this PR. 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Rider 12 | .idea 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Oo]ut/ 32 | msbuild.log 33 | msbuild.err 34 | msbuild.wrn 35 | 36 | # Visual Studio 2015 37 | .vs/ 38 | 39 | node_modules/ 40 | dist/ -------------------------------------------------------------------------------- /Assets/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/discord.png -------------------------------------------------------------------------------- /Assets/ea_banner_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/ea_banner_dark.png -------------------------------------------------------------------------------- /Assets/ea_banner_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/ea_banner_light.png -------------------------------------------------------------------------------- /Assets/ice_panel_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/ice_panel_black.png -------------------------------------------------------------------------------- /Assets/nuget-feed-credentials-vs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/nuget-feed-credentials-vs.png -------------------------------------------------------------------------------- /Assets/nuget_feed_credentials_rider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/nuget_feed_credentials_rider.png -------------------------------------------------------------------------------- /Assets/project_paradox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/project_paradox.png -------------------------------------------------------------------------------- /Assets/subdomain_contracts_logic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomain_contracts_logic.jpg -------------------------------------------------------------------------------- /Assets/subdomain_offers_logic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomain_offers_logic.jpg -------------------------------------------------------------------------------- /Assets/subdomain_passes_logic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomain_passes_logic.jpg -------------------------------------------------------------------------------- /Assets/subdomain_reports.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomain_reports.jpg -------------------------------------------------------------------------------- /Assets/subdomains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomains.png -------------------------------------------------------------------------------- /Assets/subdomains_communication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomains_communication.png -------------------------------------------------------------------------------- /Assets/subdomains_processes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomains_processes.png -------------------------------------------------------------------------------- /Assets/subdomains_theory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Assets/subdomains_theory.png -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Assets/multiple_subdomains_single_bounded_context.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-1-initial-architecture/Assets/multiple_subdomains_single_bounded_context.jpg -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Assets/projects_division.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-1-initial-architecture/Assets/projects_division.png -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Docs/Contracts/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Docs/Passes/Api/Passes.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Register a new pass 3 | # @name registerPass 4 | POST {{baseUrl}}/api/passes 5 | Content-Type: application/json 6 | 7 | { 8 | "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", 9 | "from": "2023-03-26T08:52:27.747Z", 10 | "to": "2023-03-26T08:52:27.747Z" 11 | } 12 | 13 | ### 14 | ### Mark a pass as expired 15 | # @name markPassAsExpired 16 | PATCH {{baseUrl}}/api/passes/{{passId}} -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Docs/Passes/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Docs/Reports/Api/Reports.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Generate new report 3 | # @name generateNewReport 4 | GET {{baseUrl}}/api/reports/generate -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Docs/Reports/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | EXPOSE 443 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build 7 | WORKDIR /src 8 | COPY Directory.Build.props ./ 9 | COPY ["Fitnet/Fitnet.csproj", "Fitnet/"] 10 | RUN dotnet restore "Fitnet/Fitnet.csproj" 11 | COPY . . 12 | WORKDIR "/src/Fitnet" 13 | RUN dotnet build "Fitnet.csproj" -c Release -o /app/build 14 | 15 | FROM build AS publish 16 | RUN dotnet publish "Fitnet.csproj" -c Release -o /app/publish 17 | 18 | FROM base AS final 19 | WORKDIR /app 20 | COPY --from=publish /app/publish . 21 | ENTRYPOINT ["dotnet", "EvolutionaryArchitecture.Fitnet.dll"] -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.ArchitectureTests/Common/Modules.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.ArchitectureTests.Common; 2 | 3 | internal static class Modules 4 | { 5 | internal const string Contracts = "EvolutionaryArchitecture.Fitnet.Contracts"; 6 | internal const string Reports = "EvolutionaryArchitecture.Fitnet.Reports"; 7 | internal const string Offers = "EvolutionaryArchitecture.Fitnet.Offers"; 8 | internal const string Passes = "EvolutionaryArchitecture.Fitnet.Passes"; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.ArchitectureTests/Common/Predicates/PredicatesExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.ArchitectureTests.Common.Predicates; 2 | 3 | internal static class PredicatesExtensions 4 | { 5 | internal static string[] GetModuleTypes(this PredicateList predicates) => 6 | predicates 7 | .GetTypes() 8 | .Select(type => type.FullName) 9 | .Distinct() 10 | .ToArray()!; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.ArchitectureTests/Common/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.ArchitectureTests.Common; 2 | 3 | internal static class Solution 4 | { 5 | private static readonly Assembly Program = typeof(Program).Assembly; 6 | internal static Types Types => Types.InAssembly(Program); 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.ArchitectureTests/Conventions/InterfacesConventionsTests.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.ArchitectureTests.Conventions; 2 | 3 | using Common; 4 | 5 | public sealed class InterfacesConventionsTests 6 | { 7 | [Fact] 8 | internal void Should_start_with_I() 9 | { 10 | // Arrange 11 | var rules = Solution.Types.That() 12 | .AreInterfaces() 13 | .Should() 14 | .HaveNameStartingWith("I"); 15 | 16 | // Act 17 | var result = rules.GetResult(); 18 | 19 | // Assert 20 | result.IsSuccessful.ShouldBeTrue(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.ArchitectureTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Reflection; 2 | global using EvolutionaryArchitecture.Fitnet.ArchitectureTests.Common.Predicates; 3 | global using NetArchTest.Rules; 4 | global using Shouldly; 5 | global using Xunit; 6 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/Events/EventBus/InMemory/FakeEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.Events.EventBus.InMemory; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.Events; 4 | 5 | internal record FakeEvent(Guid Id, DateTimeOffset OccurredDateTime, bool Consumed) : IIntegrationEvent 6 | { 7 | private FakeEvent() : this(Guid.NewGuid(), DateTimeOffset.UtcNow, false) 8 | { 9 | } 10 | 11 | internal bool Consumed { get; private set; } = Consumed; 12 | 13 | public void MarkAsConsumed() => Consumed = true; 14 | 15 | public static FakeEvent Create() => new(); 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/Events/EventBus/InMemory/FakeEventConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.Events.EventBus.InMemory; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.Events; 4 | 5 | internal sealed class TestEventConsumer : IIntegrationEventHandler 6 | { 7 | public Task Handle(FakeEvent @event, CancellationToken cancellationToken) 8 | { 9 | @event.MarkAsConsumed(); 10 | return Task.CompletedTask; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Configuration/ConfigurationKeys.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.Configuration; 2 | 3 | internal static class ConfigurationKeys 4 | { 5 | private const string ConnectionStringsSection = "ConnectionStrings"; 6 | internal const string PassesConnectionString = $"{ConnectionStringsSection}:Passes"; 7 | internal const string OffersConnectionString = $"{ConnectionStringsSection}:Offers"; 8 | internal const string ContractsConnectionString = $"{ConnectionStringsSection}:Contracts"; 9 | internal const string ReportsConnectionString = $"{ConnectionStringsSection}:Reports"; 10 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Time/FakeTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.Time; 2 | 3 | [UsedImplicitly] 4 | internal sealed class FakeTimeProvider(DateTimeOffset? now = null) : TimeProvider 5 | { 6 | private DateTimeOffset TimeNowOffset { get; set; } = now ?? new Faker().Date.RecentOffset().UtcDateTime; 7 | 8 | public override DateTimeOffset GetUtcNow() => TimeNowOffset; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Time/TimeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.Time; 2 | 3 | using Microsoft.AspNetCore.TestHost; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class TimeExtensions 7 | { 8 | internal static WebApplicationFactory WithTime( 9 | this WebApplicationFactory webApplicationFactory, FakeTimeProvider fakeSystemTimeProvider) 10 | where T : class => webApplicationFactory 11 | .WithWebHostBuilder(builder => builder.ConfigureTestServices(services => services.AddSingleton(fakeSystemTimeProvider))); 12 | } 13 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using System.Collections; 4 | global using System.Net; 5 | global using Xunit; 6 | global using Microsoft.AspNetCore.Mvc.Testing; 7 | global using Bogus; 8 | global using JetBrains.Annotations; 9 | global using NSubstitute; 10 | global using EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine; 11 | global using Shouldly; 12 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Offers/Prepare/PassExpiredEventFaker.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Offers.Prepare; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Passes.MarkPassAsExpired.Events; 4 | 5 | internal sealed class PassExpiredEventFaker : Faker 6 | { 7 | private PassExpiredEventFaker() => CustomInstantiator(faker => 8 | new PassExpiredEvent( 9 | Guid.NewGuid(), 10 | Guid.NewGuid(), 11 | Guid.NewGuid(), 12 | faker.Date.RecentOffset() 13 | ) 14 | ); 15 | 16 | internal static PassExpiredEvent CreateValid() => new PassExpiredEventFaker(); 17 | } 18 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Reports/GenerateNewPassesPerMonthReport/TestData/PassRegistrationDateRange.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Reports.GenerateNewPassesPerMonthReport.TestData; 2 | 3 | internal sealed record PassRegistrationDateRange(DateTimeOffset From, DateTimeOffset To); -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.UnitTests/BusinessRulesEngine/FakeBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.UnitTests.BusinessRulesEngine; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.BusinessRulesEngine; 4 | 5 | internal sealed class FakeBusinessRule : IBusinessRule 6 | { 7 | private readonly int _someNumber; 8 | 9 | internal FakeBusinessRule(int someNumber) => 10 | _someNumber = someNumber; 11 | 12 | public bool IsMet() => _someNumber > 10; 13 | 14 | public string Error => "Fake business rule was not met"; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.UnitTests/Contracts/PrepareContract/PrepareContractParameters.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.UnitTests.Contracts.PrepareContract; 2 | 3 | internal sealed record PrepareContractParameters(int MinAge, int MaxAge, int MinHeight, int MaxHeight) 4 | { 5 | private const int MinimumAge = 18; 6 | private const int MaximumAge = 100; 7 | private const int MinimumHeight = 0; 8 | private const int MaximumHeight = 210; 9 | 10 | internal static PrepareContractParameters GetValid() => new(MinimumAge, MaximumAge, MinimumHeight, MaximumHeight); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using System.Collections; 4 | global using Bogus; 5 | global using Microsoft.AspNetCore.Http; 6 | global using NSubstitute; 7 | global using Shouldly; 8 | global using Xunit; 9 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet.slnx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/ApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet; 2 | 3 | internal static class ApiPaths 4 | { 5 | internal const string Root = "api"; 6 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/BusinessRulesEngine/BusinessRuleValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.BusinessRulesEngine; 2 | 3 | internal class BusinessRuleValidationException : InvalidOperationException 4 | { 5 | internal BusinessRuleValidationException(string message) : base(message) 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/BusinessRulesEngine/BusinessRuleValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.BusinessRulesEngine; 2 | 3 | internal static class BusinessRuleValidator 4 | { 5 | internal static void Validate(IBusinessRule rule) 6 | { 7 | if (!rule.IsMet()) 8 | { 9 | throw new BusinessRuleValidationException(rule.Error); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/BusinessRulesEngine/IBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.BusinessRulesEngine; 2 | 3 | internal interface IBusinessRule 4 | { 5 | bool IsMet(); 6 | string Error { get; } 7 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Clock/ClockModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Clock; 2 | 3 | internal static class ClockModule 4 | { 5 | internal static IServiceCollection AddClock(this IServiceCollection services) => 6 | services.AddSingleton(TimeProvider.System); 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Events/EventBus/EventBusModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; 2 | 3 | using System.Reflection; 4 | using InMemory; 5 | 6 | internal static class EventBusModule 7 | { 8 | internal static IServiceCollection AddEventBus(this IServiceCollection services) => 9 | services.AddInMemoryEventBus(Assembly.GetExecutingAssembly()); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Events/EventBus/IEventBus.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; 2 | 3 | internal interface IEventBus 4 | { 5 | Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) 6 | where TEvent : IIntegrationEvent; 7 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Events/EventBus/InMemory/InMemoryEventBus.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Events.EventBus.InMemory; 2 | 3 | using MediatR; 4 | 5 | internal sealed class InMemoryEventBus(IMediator mediator) : IEventBus 6 | { 7 | public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) where TEvent : IIntegrationEvent => 8 | await mediator.Publish(@event, cancellationToken); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Events/EventBus/InMemory/InMemoryEventBusModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Events.EventBus.InMemory; 2 | 3 | using System.Reflection; 4 | 5 | internal static class InMemoryEventBusModule 6 | { 7 | internal static IServiceCollection AddInMemoryEventBus(this IServiceCollection services, Assembly assembly) 8 | { 9 | services.AddScoped(); 10 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssembly(assembly)); 11 | 12 | return services; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Events/IIntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Events; 2 | 3 | using MediatR; 4 | 5 | internal interface IIntegrationEvent : INotification 6 | { 7 | Guid Id { get; } 8 | DateTimeOffset OccurredDateTime { get; } 9 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Events/IIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Events; 2 | 3 | using MediatR; 4 | 5 | internal interface IIntegrationEventHandler : INotificationHandler where TEvent : IIntegrationEvent; 6 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Validation/Requests/EndpointBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Validation.Requests; 2 | 3 | internal static class EndpointBuilderExtensions 4 | { 5 | internal static RouteHandlerBuilder ValidateRequest(this RouteHandlerBuilder builder) where TRequest : class => 6 | builder.AddEndpointFilter>(); 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Common/Validation/Requests/RequestValidationsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Validation.Requests; 2 | 3 | using FluentValidation; 4 | 5 | internal static class RequestValidationsExtensions 6 | { 7 | internal static IServiceCollection AddRequestsValidations(this IServiceCollection services) => 8 | services.AddValidatorsFromAssemblyContaining(includeInternalTypes: true); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/ContractsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts; 2 | 3 | internal static class ContractsApiPaths 4 | { 5 | private const string ContractsRootApi = $"{ApiPaths.Root}/contracts"; 6 | 7 | internal const string Prepare = ContractsRootApi; 8 | internal const string Sign = $"{ContractsRootApi}/{{id}}"; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/ContractsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts; 2 | 3 | using SignContract; 4 | using PrepareContract; 5 | 6 | internal static class ContractsEndpoints 7 | { 8 | internal static void MapContracts(this IEndpointRouteBuilder app) 9 | { 10 | app.MapPrepareContract(); 11 | app.MapSignContract(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/ContractsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts; 2 | 3 | using Data.Database; 4 | 5 | internal static class ContractsModule 6 | { 7 | internal static IServiceCollection AddContracts(this IServiceCollection services, IConfiguration configuration) 8 | { 9 | services.AddDatabase(configuration); 10 | 11 | return services; 12 | } 13 | 14 | internal static IApplicationBuilder UseContracts(this IApplicationBuilder applicationBuilder) 15 | { 16 | applicationBuilder.UseDatabase(); 17 | 18 | return applicationBuilder; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/Data/Database/AutomaticMigrationsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Data.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | internal static class AutomaticMigrationsExtensions 6 | { 7 | internal static IApplicationBuilder UseAutomaticMigrations(this IApplicationBuilder applicationBuilder) 8 | { 9 | using var scope = applicationBuilder.ApplicationServices.CreateScope(); 10 | var context = scope.ServiceProvider.GetRequiredService(); 11 | context.Database.Migrate(); 12 | 13 | return applicationBuilder; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/Data/Database/ContractsPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Data.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | internal sealed class ContractsPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Contracts"; 8 | 9 | public DbSet Contracts => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new ContractEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.PrepareContract.BusinessRules; 2 | 3 | using Common.BusinessRulesEngine; 4 | 5 | internal sealed class ContractCanBePreparedOnlyForAdultRule : IBusinessRule 6 | { 7 | private readonly int _age; 8 | 9 | internal ContractCanBePreparedOnlyForAdultRule(int age) => _age = age; 10 | 11 | public bool IsMet() => _age >= 18; 12 | 13 | public string Error => "Contract can not be prepared for a person who is not adult"; 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.PrepareContract.BusinessRules; 2 | 3 | using Common.BusinessRulesEngine; 4 | 5 | internal sealed class PreviousContractHasToBeSignedRule : IBusinessRule 6 | { 7 | private readonly bool? _signed; 8 | 9 | internal PreviousContractHasToBeSignedRule(bool? signed) => _signed = signed; 10 | public bool IsMet() => _signed is true or null; 11 | public string Error => "Previous contract must be signed by the customer"; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/PrepareContract/PrepareContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.PrepareContract; 2 | 3 | public sealed record PrepareContractRequest(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt); -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/PrepareContract/PrepareContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.PrepareContract; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class PrepareContractRequestValidator : AbstractValidator 6 | { 7 | public PrepareContractRequestValidator() 8 | { 9 | RuleFor(request => request.CustomerId).NotEmpty(); 10 | RuleFor(request => request.CustomerAge).GreaterThan(0); 11 | RuleFor(request => request.CustomerHeight).GreaterThan(0); 12 | RuleFor(request => request.PreparedAt).NotEmpty(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/SignContract/SignContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.SignContract; 2 | 3 | public sealed record SignContractRequest(DateTimeOffset SignedAt); -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Contracts/SignContract/SignContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.SignContract; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class SignContractRequestValidator : AbstractValidator 6 | { 7 | public SignContractRequestValidator() => RuleFor(signContractRequest => signContractRequest.SignedAt) 8 | .NotEmpty(); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using System.Diagnostics.CodeAnalysis; 4 | global using JetBrains.Annotations; -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Offers/Data/Database/AutomaticMigrationsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.Data.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | internal static class AutomaticMigrationsExtensions 6 | { 7 | internal static IApplicationBuilder UseAutomaticMigrations(this IApplicationBuilder applicationBuilder) 8 | { 9 | using var scope = applicationBuilder.ApplicationServices.CreateScope(); 10 | var context = scope.ServiceProvider.GetRequiredService(); 11 | context.Database.Migrate(); 12 | 13 | return applicationBuilder; 14 | } 15 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Offers/Data/Database/OffersPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.Data.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | internal sealed class OffersPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Offers"; 8 | 9 | public DbSet Offers => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new OfferEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Offers/OffersModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers; 2 | 3 | using Data.Database; 4 | 5 | internal static class OffersModule 6 | { 7 | internal static IServiceCollection AddOffers(this IServiceCollection services, IConfiguration configuration) 8 | { 9 | services.AddDatabase(configuration); 10 | 11 | return services; 12 | } 13 | 14 | internal static IApplicationBuilder UseOffers(this IApplicationBuilder applicationBuilder) 15 | { 16 | applicationBuilder.UseDatabase(); 17 | 18 | return applicationBuilder; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Offers/Prepare/OfferPrepareEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.Prepare; 2 | 3 | using Common.Events; 4 | 5 | internal sealed record OfferPrepareEvent(Guid Id, Guid OfferId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static OfferPrepareEvent Create(Guid offerId, Guid customerId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), offerId, customerId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/Data/Database/AutomaticMigrationsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Data.Database; 2 | 3 | internal static class AutomaticMigrationsExtensions 4 | { 5 | internal static IApplicationBuilder UseAutomaticMigrations(this IApplicationBuilder applicationBuilder) 6 | { 7 | using var scope = applicationBuilder.ApplicationServices.CreateScope(); 8 | var context = scope.ServiceProvider.GetRequiredService(); 9 | context.Database.EnsureCreated(); 10 | 11 | return applicationBuilder; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/Data/Database/PassEntityConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Data.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 5 | 6 | internal sealed class PassEntityConfiguration : IEntityTypeConfiguration 7 | { 8 | public void Configure(EntityTypeBuilder builder) 9 | { 10 | builder.ToTable("Passes"); 11 | builder.HasKey(pass => pass.Id); 12 | builder.Property(pass => pass.CustomerId).IsRequired(); 13 | builder.Property(pass => pass.From).IsRequired(); 14 | builder.Property(pass => pass.To).IsRequired(); 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/Data/Database/PassesPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Data.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | internal sealed class PassesPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Passes"; 8 | 9 | public DbSet Passes => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new PassEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/GetAllPasses/GetAllPassesResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.GetAllPasses; 2 | 3 | using Data; 4 | 5 | internal record GetAllPassesResponse(IReadOnlyCollection Passes) 6 | { 7 | internal static GetAllPassesResponse Create(IReadOnlyCollection passes) => new(passes); 8 | } 9 | 10 | internal record PassDto(Guid Id, Guid CustomerId) 11 | { 12 | internal static PassDto From(Pass contract) => new(contract.Id, contract.CustomerId); 13 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/MarkPassAsExpired/Events/PassExpiredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.MarkPassAsExpired.Events; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.Events; 4 | 5 | internal record PassExpiredEvent(Guid Id, Guid PassId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static PassExpiredEvent Create(Guid passId, Guid customerId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), passId, customerId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/PassesApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes; 2 | 3 | internal static class PassesApiPaths 4 | { 5 | internal const string GetAll = $"{ApiPaths.Root}/passes"; 6 | internal const string MarkPassAsExpired = $"{ApiPaths.Root}/passes/{{id}}"; 7 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/PassesEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes; 2 | 3 | using GetAllPasses; 4 | using MarkPassAsExpired; 5 | 6 | internal static class PassesEndpoints 7 | { 8 | internal static void MapPasses(this IEndpointRouteBuilder app) 9 | { 10 | app.MapGetAllPasses(); 11 | app.MapMarkPassAsExpired(); 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/PassesModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes; 2 | 3 | using Data.Database; 4 | 5 | internal static class PassesModule 6 | { 7 | internal static IServiceCollection AddPasses(this IServiceCollection services, IConfiguration configuration) 8 | { 9 | services.AddDatabase(configuration); 10 | 11 | return services; 12 | } 13 | 14 | internal static IApplicationBuilder UsePasses(this IApplicationBuilder applicationBuilder) 15 | { 16 | applicationBuilder.UseDatabase(); 17 | 18 | return applicationBuilder; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/RegisterPass/Events/PassRegisteredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.RegisterPass.Events; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.Events; 4 | 5 | internal record PassRegisteredEvent(Guid Id, Guid PassId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static PassRegisteredEvent Create(Guid passId) => 8 | new(Guid.NewGuid(), passId, DateTimeOffset.UtcNow); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Passes/RegisterPass/RegisterPassRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.RegisterPass; 2 | 3 | public record RegisterPassRequest(Guid CustomerId, DateTimeOffset From, DateTimeOffset To); -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/DataAccess/DatabaseAccessModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | internal static class DatabaseAccessModule 4 | { 5 | internal static IServiceCollection AddDataAccess(this IServiceCollection services) 6 | { 7 | services.AddSingleton(); 8 | 9 | return services; 10 | } 11 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/DataAccess/IDatabaseConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using System.Data; 4 | 5 | internal interface IDatabaseConnectionFactory : IDisposable 6 | { 7 | IDbConnection Create(); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/GenerateNewPassesRegistrationsPerMonthReport/DataRetriever/INewPassesRegistrationPerMonthReportDataRetriever.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.DataRetriever; 2 | 3 | using Dtos; 4 | 5 | internal interface INewPassesRegistrationPerMonthReportDataRetriever 6 | { 7 | Task> GetReportDataAsync(CancellationToken cancellationToken = default); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthDto.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthDto(int MonthOrder, string MonthName, long RegisteredPasses); -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthResponse(IReadOnlyCollection PassesRegistrationsPerMonth) 4 | { 5 | internal static NewPassesRegistrationsPerMonthResponse Create(IReadOnlyCollection passesRegistrationsPerMonth) => new(passesRegistrationsPerMonth); 6 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/ReportsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | internal static class ReportsApiPaths 4 | { 5 | private const string Reports = $"{ApiPaths.Root}/reports"; 6 | internal const string GenerateNewReport = $"{Reports}/generate"; 7 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/ReportsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using GenerateNewPassesRegistrationsPerMonthReport; 4 | 5 | internal static class ReportsEndpoints 6 | { 7 | internal static void MapReports(this IEndpointRouteBuilder app) => 8 | app.MapGenerateNewPassesRegistrationsPerMonthReport(); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/Reports/ReportsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using DataAccess; 4 | using GenerateNewPassesRegistrationsPerMonthReport; 5 | 6 | internal static class ReportsModule 7 | { 8 | internal static IServiceCollection AddReports(this IServiceCollection services) 9 | { 10 | services.AddDataAccess(); 11 | services.AddNewPassesRegistrationsPerMonthReport(); 12 | 13 | return services; 14 | } 15 | 16 | internal static IApplicationBuilder UseReports(this IApplicationBuilder applicationBuilder) => 17 | applicationBuilder; 18 | } 19 | -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "ConnectionStrings": { 9 | "Passes": "Host=localhost:5432;Database=fitnet;Username=postgres;Password=mysecretpassword", 10 | "Contracts": "Host=localhost:5432;Database=fitnet;Username=postgres;Password=mysecretpassword", 11 | "Reports": "Host=localhost:5432;Database=fitnet;Username=postgres;Password=mysecretpassword", 12 | "Offers": "Host=localhost:5432;Database=fitnet;Username=postgres;Password=mysecretpassword" 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-1-initial-architecture/Src/Fitnet/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "Passes": "", 11 | "Contracts": "", 12 | "Reports": "", 13 | "Offers": "" 14 | } 15 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Assets/architectural_patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-2-modules-separation/Assets/architectural_patterns.png -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Assets/subdomain_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-2-modules-separation/Assets/subdomain_types.png -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Docs/Contracts/Api/Contracts.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Prepare a contract 3 | # @name prepareContract 4 | POST {{baseUrl}}/api/contracts 5 | Content-Type: application/json 6 | 7 | { 8 | "customerAge": 30, 9 | "customerHeight": 180, 10 | "preparedAt": "2023-05-04T12:00:00.000Z" 11 | } 12 | 13 | ### 14 | ### Sign a contract 15 | # @name signContract 16 | PATCH {{baseUrl}}/api/contracts/{{contractId}} 17 | Content-Type: application/json 18 | 19 | { 20 | "signedAt": "2023-05-04T14:00:00.000Z" 21 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Docs/Contracts/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Docs/Passes/Api/Passes.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Register a new pass 3 | # @name registerPass 4 | POST {{baseUrl}}/api/passes 5 | Content-Type: application/json 6 | 7 | { 8 | "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", 9 | "from": "2023-03-26T08:52:27.747Z", 10 | "to": "2023-03-26T08:52:27.747Z" 11 | } 12 | 13 | ### 14 | ### Mark a pass as expired 15 | # @name markPassAsExpired 16 | PATCH {{baseUrl}}/api/passes/{{passId}} -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Docs/Passes/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Docs/Reports/Api/Reports.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Generate new report 3 | # @name generateNewReport 4 | GET {{baseUrl}}/api/reports/generate -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Docs/Reports/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Api/ApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api; 2 | 3 | public static class ApiPaths 4 | { 5 | public const string Root = "api"; 6 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Api/Documentation/SwaggerDocumentationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.Documentation; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Routing; 6 | 7 | public static class ApiDocumentationExtensions 8 | { 9 | public static void UseApiDocumentation(this IEndpointRouteBuilder app) => 10 | app.MapGet("/", () => Results.Redirect("/swagger")) 11 | .Produces(StatusCodes.Status200OK) 12 | .WithTags("Documentation"); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Api/ErrorHandling/ResourceNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.ErrorHandling; 2 | 3 | public sealed class ResourceNotFoundException(Guid id) : InvalidOperationException($"Resource with '{id}' not found ") 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Api/Validation/Requests/EndpointBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.Validation.Requests; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | public static class EndpointBuilderExtensions 7 | { 8 | public static RouteHandlerBuilder ValidateRequest(this RouteHandlerBuilder builder) where TRequest : class => 9 | builder.AddEndpointFilter>(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Api/Validation/Requests/RequestValidationsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.Validation.Requests; 2 | 3 | using System.Reflection; 4 | using FluentValidation; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | public static class RequestValidationsExtensions 8 | { 9 | public static IServiceCollection AddRequestsValidations(this IServiceCollection services, Assembly assembly) => 10 | services.AddValidatorsFromAssembly(assembly, includeInternalTypes: true); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Core/BusinessRules/BusinessRuleValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; 2 | 3 | public class BusinessRuleValidationException(string message) : InvalidOperationException(message) 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Core/BusinessRules/BusinessRuleValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; 2 | 3 | public static class BusinessRuleValidator 4 | { 5 | public static void Validate(IBusinessRule rule) 6 | { 7 | if (!rule.IsMet()) 8 | { 9 | throw new BusinessRuleValidationException(rule.Error); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Core/BusinessRules/IBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; 2 | 3 | public interface IBusinessRule 4 | { 5 | bool IsMet(); 6 | string Error { get; } 7 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Core/Fitnet.Common.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Clock/ClockModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Clock; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | internal static class ClockModule 6 | { 7 | internal static IServiceCollection AddClock(this IServiceCollection services) => 8 | services.AddSingleton(TimeProvider.System); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/CommonInfrastructureModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure; 2 | 3 | using Clock; 4 | using Events.EventBus; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | public static class CommonInfrastructureModule 8 | { 9 | public static IServiceCollection AddCommonInfrastructure(this IServiceCollection services) 10 | { 11 | services.AddEventBus(); 12 | services.AddClock(); 13 | 14 | return services; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Events/EventBus/EventBusModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events.EventBus; 2 | 3 | using InMemory; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class EventBusModule 7 | { 8 | internal static IServiceCollection AddEventBus(this IServiceCollection services) => 9 | services.AddInMemoryEventBus(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Events/EventBus/IEventBus.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events.EventBus; 2 | 3 | public interface IEventBus 4 | { 5 | Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) 6 | where TEvent : IIntegrationEvent; 7 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Events/EventBus/InMemory/InMemoryEventBus.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events.EventBus.InMemory; 2 | 3 | using MediatR; 4 | 5 | internal sealed class InMemoryEventBus(IPublisher mediator) : IEventBus 6 | { 7 | public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) where TEvent : IIntegrationEvent => 8 | await mediator.Publish(@event, cancellationToken); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Events/EventBus/InMemory/InMemoryEventBusModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events.EventBus.InMemory; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | public static class InMemoryEventBusModule 6 | { 7 | public static IServiceCollection AddInMemoryEventBus(this IServiceCollection services) 8 | { 9 | services.AddScoped(); 10 | 11 | return services; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Events/IIntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 2 | 3 | using MediatR; 4 | 5 | public interface IIntegrationEvent : INotification 6 | { 7 | Guid Id { get; } 8 | DateTimeOffset OccurredDateTime { get; } 9 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Events/IIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 2 | 3 | using MediatR; 4 | 5 | public interface IIntegrationEventHandler : INotificationHandler where TEvent : IIntegrationEvent 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.Infrastructure/Mediator/MediatorModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Mediator; 2 | 3 | using System.Reflection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | public static class MediatorModule 7 | { 8 | public static IServiceCollection AddMediator(this IServiceCollection services, Assembly assembly) 9 | { 10 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssembly(assembly)); 11 | 12 | return services; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Reflection; 2 | global using JetBrains.Annotations; 3 | global using Microsoft.AspNetCore.Mvc.Testing; 4 | global using Microsoft.AspNetCore.Hosting; 5 | global using Microsoft.Extensions.Configuration; 6 | global using Microsoft.AspNetCore.TestHost; 7 | global using Microsoft.Extensions.DependencyInjection; 8 | global using Bogus; 9 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.IntegrationTests/TestEngine/Database/IDatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTests.TestEngine.Database; 2 | 3 | public interface IDatabaseConfiguration 4 | { 5 | Dictionary Get(); 6 | } 7 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.IntegrationTests/TestEngine/Time/FakeTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTests.TestEngine.Time; 2 | 3 | [UsedImplicitly] 4 | public sealed class FakeTimeProvider(DateTimeOffset? now = null) : TimeProvider 5 | { 6 | private DateTimeOffset TimeNowOffset { get; set; } = now ?? new Faker().Date.RecentOffset().UtcDateTime; 7 | 8 | public override DateTimeOffset GetUtcNow() => TimeNowOffset; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Fitnet.Common.IntegrationTests/TestEngine/Time/TimeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTests.TestEngine.Time; 2 | 3 | public static class TimeExtensions 4 | { 5 | public static WebApplicationFactory WithTime( 6 | this WebApplicationFactory webApplicationFactory, FakeTimeProvider fakeSystemTimeProvider) 7 | where T : class => webApplicationFactory 8 | .WithWebHostBuilder(builder => builder.ConfigureTestServices(services => services.AddSingleton(fakeSystemTimeProvider))); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Tests/Fitnet.Common.Api.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using Xunit; 3 | global using Microsoft.AspNetCore.Http; 4 | global using Newtonsoft.Json; 5 | global using NSubstitute; 6 | global using Shouldly; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Tests/Fitnet.Common.Core.UnitTests/BusinessRulesEngine/FakeBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.UnitTests.BusinessRulesEngine; 2 | 3 | using BusinessRules; 4 | 5 | internal sealed class FakeBusinessRule : IBusinessRule 6 | { 7 | private readonly int _someNumber; 8 | 9 | internal FakeBusinessRule(int someNumber) => 10 | _someNumber = someNumber; 11 | 12 | public bool IsMet() => _someNumber > 10; 13 | 14 | public string Error => "Fake business rule was not met"; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Tests/Fitnet.Common.Core.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Shouldly; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Tests/Fitnet.Common.Infrastructure.IntegrationTests/Events/EventBus/InMemory/FakeEventConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.IntegrationTests.Events.EventBus.InMemory; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 4 | 5 | internal sealed class TestEventConsumer : IIntegrationEventHandler 6 | { 7 | public Task Handle(FakeEvent @event, CancellationToken cancellationToken) 8 | { 9 | @event.MarkAsConsumed(); 10 | return Task.CompletedTask; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Tests/Fitnet.Common.Infrastructure.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Reflection; 2 | global using Microsoft.Extensions.DependencyInjection; 3 | global using Microsoft.AspNetCore.Mvc.Testing; 4 | global using Shouldly; 5 | global using Xunit; 6 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Common/Tests/Fitnet.Common.Infrastructure.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Contracts": { 7 | "Enabled": false 8 | }, 9 | "Reports": { 10 | "Enabled": false 11 | }, 12 | "Offers": { 13 | "Enabled": false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Api/ContractsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api; 2 | 3 | using Common.Api; 4 | 5 | internal static class ContractsApiPaths 6 | { 7 | private const string ContractsRootApi = $"{ApiPaths.Root}/contracts"; 8 | 9 | internal const string Prepare = ContractsRootApi; 10 | internal const string Sign = $"{ContractsRootApi}/{{id}}"; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Api/ContractsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api; 2 | 3 | using Microsoft.AspNetCore.Routing; 4 | using Prepare; 5 | using Sign; 6 | 7 | internal static class ContractsEndpoints 8 | { 9 | internal static void MapContracts(this IEndpointRouteBuilder app) 10 | { 11 | app.MapPrepareContract(); 12 | app.MapSignContract(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Api/Prepare/PrepareContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Prepare; 2 | 3 | using Application.Prepare; 4 | 5 | internal sealed record PrepareContractRequest(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt) 6 | { 7 | internal PrepareContractCommand ToCommand() => new(CustomerId, CustomerAge, CustomerHeight, PreparedAt); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Api/Prepare/PrepareContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Prepare; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class PrepareContractRequestValidator : AbstractValidator 6 | { 7 | public PrepareContractRequestValidator() 8 | { 9 | RuleFor(request => request.CustomerId).NotEmpty(); 10 | RuleFor(request => request.CustomerAge).GreaterThan(0); 11 | RuleFor(request => request.CustomerHeight).GreaterThan(0); 12 | RuleFor(request => request.PreparedAt).NotEmpty(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Api/Sign/SignContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Sign; 2 | 3 | using Application.Sign; 4 | 5 | internal sealed record SignContractRequest(DateTimeOffset SignedAt) 6 | { 7 | internal SignContractCommand ToCommand(Guid id) => 8 | new(id, SignedAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Api/Sign/SignContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Sign; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class SignContractRequestValidator : AbstractValidator 6 | { 7 | public SignContractRequestValidator() => RuleFor(signContractRequest => signContractRequest.SignedAt) 8 | .NotEmpty(); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Application/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Threading.Tasks; 2 | global using JetBrains.Annotations; 3 | global using MediatR; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Application/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | public interface ICommand : IRequest 4 | { } 5 | 6 | public interface ICommand : IRequest 7 | { } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Application/IContractsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | public interface IContractsModule 4 | { 5 | Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default); 6 | 7 | Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default); 8 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Application/Prepare/PrepareContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.Prepare; 2 | 3 | public sealed record PrepareContractCommand(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt) : ICommand; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Application/Sign/SignContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.Sign; 2 | 3 | public sealed record SignContractCommand(Guid Id, DateTimeOffset SignedAt) : ICommand; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Core/Fitnet.Contracts.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Core/IContractsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core; 2 | 3 | public interface IContractsRepository 4 | { 5 | Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); 6 | Task GetPreviousForCustomerAsync(Guid customerId, CancellationToken cancellationToken = default); 7 | Task AddAsync(Contract contract, CancellationToken cancellationToken = default); 8 | Task CommitAsync(CancellationToken cancellationToken = default); 9 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Core/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.PrepareContract.BusinessRules; 2 | 3 | using Common.Core.BusinessRules; 4 | 5 | internal sealed class ContractCanBePreparedOnlyForAdultRule : IBusinessRule 6 | { 7 | private readonly int _age; 8 | 9 | internal ContractCanBePreparedOnlyForAdultRule(int age) => _age = age; 10 | 11 | public bool IsMet() => _age >= 18; 12 | 13 | public string Error => "Contract can not be prepared for a person who is not adult"; 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Core/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.PrepareContract.BusinessRules; 2 | 3 | using Common.Core.BusinessRules; 4 | 5 | internal sealed class PreviousContractHasToBeSignedRule : IBusinessRule 6 | { 7 | private readonly bool? _signed; 8 | 9 | internal PreviousContractHasToBeSignedRule(bool? signed) => _signed = signed; 10 | 11 | public bool IsMet() => _signed is true or null; 12 | 13 | public string Error => "Previous contract must be signed by the customer"; 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Infrastructure/ContractsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure; 2 | 3 | using Application; 4 | using MediatR; 5 | 6 | internal sealed class ContractsModule(ISender mediator) : IContractsModule 7 | { 8 | public async Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default) => 9 | await mediator.Send(command, cancellationToken); 10 | 11 | public async Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default) => 12 | await mediator.Send(command, cancellationToken); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; 2 | 3 | using Core; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class RepositoriesModule 7 | { 8 | internal static IServiceCollection AddRepositories(this IServiceCollection services) 9 | { 10 | services.AddScoped(); 11 | 12 | return services; 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Infrastructure/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Diagnostics.CodeAnalysis; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.Infrastructure/Mediation/MediationModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Mediation; 2 | 3 | using Application; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class MediationModule 7 | { 8 | internal static IServiceCollection AddMediationModule(this IServiceCollection services) 9 | { 10 | var commandsHandlersAssembly = typeof(IContractsModule).Assembly; 11 | 12 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssemblies(commandsHandlersAssembly)); 13 | 14 | return services; 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Fitnet.Contracts.IntegrationEvents/Fitnet.Contracts.IntegrationEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Tests/Fitnet.Contracts.Core.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Collections; 2 | global using Shouldly; 3 | global using Xunit; 4 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Tests/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractParameters.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract; 2 | 3 | internal sealed record PrepareContractParameters(int MinAge, int MaxAge, int MinHeight, int MaxHeight) 4 | { 5 | private const int MinimumAge = 18; 6 | private const int MaximumAge = 100; 7 | private const int MinimumHeight = 0; 8 | private const int MaximumHeight = 210; 9 | 10 | internal static PrepareContractParameters GetValid() => new(MinimumAge, MaximumAge, MinimumHeight, MaximumHeight); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Tests/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using System.Net.Http.Json; 3 | global using Xunit; 4 | global using Bogus; 5 | global using NSubstitute; 6 | global using Shouldly; 7 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Contracts/Tests/Fitnet.Contracts.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Contracts": { 7 | "Enabled": true 8 | }, 9 | "Reports": { 10 | "Enabled": false 11 | }, 12 | "Offers": { 13 | "Enabled": false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | EXPOSE 443 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build 7 | WORKDIR /src 8 | COPY Directory.Build.props ./ 9 | COPY ["Fitnet/Fitnet.csproj", "Fitnet/"] 10 | RUN dotnet restore "Fitnet/Fitnet.csproj" 11 | COPY . . 12 | WORKDIR "/src/Fitnet" 13 | RUN dotnet build "Fitnet.csproj" -c Release -o /app/build 14 | 15 | FROM build AS publish 16 | RUN dotnet publish "Fitnet.csproj" -c Release -o /app/publish 17 | 18 | FROM base AS final 19 | WORKDIR /app 20 | COPY --from=publish /app/publish . 21 | ENTRYPOINT ["dotnet", "EvolutionaryArchitecture.Fitnet.dll"] -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Fitnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Fitnet/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using JetBrains.Annotations; 4 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Fitnet/Modules/Module.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Modules; 2 | 3 | internal record Module(string Value) 4 | { 5 | internal static readonly Module Contracts = new("Contracts"); 6 | internal static readonly Module Offers = new("Offers"); 7 | internal static readonly Module Passes = new("Passes"); 8 | internal static readonly Module Reports = new("Reports"); 9 | 10 | public static implicit operator string(Module module) => module.Value; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Fitnet.Offers.Api/Prepare/OfferPrepareEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.Api.Prepare; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | internal sealed record OfferPrepareEvent(Guid Id, Guid OfferId, Guid CustomerId, DateTimeOffset OccurredDateTime) 6 | : IIntegrationEvent 7 | { 8 | internal static OfferPrepareEvent Create(Guid offerId, Guid customerId, DateTimeOffset occurredAt) => 9 | new(Guid.NewGuid(), offerId, customerId, occurredAt); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Fitnet.Offers.DataAccess/Database/OffersPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.DataAccess.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | public sealed class OffersPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Offers"; 8 | 9 | public DbSet Offers => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new OfferEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Fitnet.Offers.DataAccess/Fitnet.Offers.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Fitnet.Offers.DataAccess/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Microsoft.AspNetCore.Mvc.Testing; 3 | global using Bogus; 4 | global using NSubstitute; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/Prepare/PassExpiredEventFaker.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.IntegrationTests.Prepare; 2 | 3 | using Passes.IntegrationEvents; 4 | 5 | internal sealed class PassExpiredEventFaker : Faker 6 | { 7 | private PassExpiredEventFaker() => CustomInstantiator(faker => 8 | new PassExpiredEvent( 9 | Guid.NewGuid(), 10 | Guid.NewGuid(), 11 | Guid.NewGuid(), 12 | faker.Date.RecentOffset() 13 | ) 14 | ); 15 | 16 | internal static PassExpiredEvent CreateValid() => new PassExpiredEventFaker(); 17 | } 18 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Contracts": { 7 | "Enabled": false 8 | }, 9 | "Reports": { 10 | "Enabled": false 11 | }, 12 | "Offers": { 13 | "Enabled": true 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.Api/GetAllPasses/GetAllPassesResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.GetAllPasses; 2 | 3 | using DataAccess; 4 | 5 | internal record GetAllPassesResponse(IReadOnlyCollection Passes) 6 | { 7 | internal static GetAllPassesResponse Create(IReadOnlyCollection passes) => new(passes); 8 | } 9 | 10 | internal record PassDto(Guid Id, Guid CustomerId) 11 | { 12 | internal static PassDto From(Pass contract) => new(contract.Id, contract.CustomerId); 13 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.Api/PassesApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.Api; 4 | 5 | internal static class PassesApiPaths 6 | { 7 | internal const string GetAll = $"{ApiPaths.Root}/passes"; 8 | internal const string MarkPassAsExpired = $"{ApiPaths.Root}/passes/{{id}}"; 9 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.Api/PassesEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api; 2 | 3 | using GetAllPasses; 4 | using MarkPassAsExpired; 5 | using Microsoft.AspNetCore.Routing; 6 | 7 | internal static class PassesEndpoints 8 | { 9 | internal static void MapPasses(this IEndpointRouteBuilder app) 10 | { 11 | app.MapMarkPassAsExpired(); 12 | app.MapGetAllPasses(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.Api/RegisterPass/PassRegisteredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.RegisterPass; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | internal record PassRegisteredEvent(Guid Id, Guid PassId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static PassRegisteredEvent Create(Guid passId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), passId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.DataAccess/Database/PassEntityConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.DataAccess.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 5 | 6 | internal sealed class PassEntityConfiguration : IEntityTypeConfiguration 7 | { 8 | public void Configure(EntityTypeBuilder builder) 9 | { 10 | builder.ToTable("Passes"); 11 | builder.HasKey(pass => pass.Id); 12 | builder.Property(pass => pass.CustomerId).IsRequired(); 13 | builder.Property(pass => pass.To).IsRequired(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.DataAccess/Database/PassesPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.DataAccess.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | public sealed class PassesPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Passes"; 8 | 9 | public DbSet Passes => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new PassEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.DataAccess/Fitnet.Passes.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.DataAccess/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Diagnostics.CodeAnalysis; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.IntegrationEvents/Fitnet.Passes.IntegrationEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Fitnet.Passes.IntegrationEvents/PassExpiredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.IntegrationEvents; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | public record PassExpiredEvent 6 | (Guid Id, Guid PassId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 7 | { 8 | public static PassExpiredEvent Create(Guid passId, Guid customerId, DateTimeOffset occurredAt) => 9 | new(Guid.NewGuid(), passId, customerId, occurredAt); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using System.Net.Http.Json; 3 | global using Xunit; 4 | global using Microsoft.AspNetCore.Mvc.Testing; 5 | global using Bogus; 6 | global using NSubstitute; 7 | global using Shouldly; -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/PassesDatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.IntegrationTests; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.IntegrationTests.TestEngine.Database; 4 | 5 | internal sealed class PassesDatabaseConfiguration : IDatabaseConfiguration 6 | { 7 | private readonly string _connectionString; 8 | 9 | internal PassesDatabaseConfiguration(string connectionString) => _connectionString = connectionString; 10 | 11 | public Dictionary Get() => new() 12 | { 13 | { "Modules:Passes:ConnectionStrings:Primary", _connectionString } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": true 5 | }, 6 | "Contracts": { 7 | "Enabled": false 8 | }, 9 | "Reports": { 10 | "Enabled": false 11 | }, 12 | "Offers": { 13 | "Enabled": false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/DataAccess/DatabaseAccessModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | internal static class DatabaseAccessModule 6 | { 7 | internal static IServiceCollection AddDataAccess(this IServiceCollection services) 8 | { 9 | services.AddSingleton(); 10 | 11 | return services; 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/DataAccess/IDatabaseConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using System.Data; 4 | 5 | internal interface IDatabaseConnectionFactory : IDisposable 6 | { 7 | IDbConnection Create(); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/DataRetriever/INewPassesRegistrationPerMonthReportDataRetriever.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.DataRetriever; 2 | 3 | using Dtos; 4 | 5 | internal interface INewPassesRegistrationPerMonthReportDataRetriever 6 | { 7 | Task> GetReportDataAsync(CancellationToken cancellationToken = default); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthDto.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthDto(int MonthOrder, string MonthName, long RegisteredPasses); -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthResponse(IReadOnlyCollection PassesRegistrationsPerMonth) 4 | { 5 | internal static NewPassesRegistrationsPerMonthResponse Create(IReadOnlyCollection passesRegistrationsPerMonth) => new(passesRegistrationsPerMonth); 6 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/GenerateNewPassesPerMonthReportModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | using DataRetriever; 5 | 6 | internal static class GenerateNewPassesPerMonthReportModule 7 | { 8 | internal static IServiceCollection AddNewPassesRegistrationsPerMonthReport(this IServiceCollection services) 9 | { 10 | services.AddSingleton(); 11 | 12 | return services; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/ReportsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using Common.Api; 4 | 5 | internal static class ReportsApiPaths 6 | { 7 | private const string Reports = $"{ApiPaths.Root}/reports"; 8 | internal const string GenerateNewReport = $"{Reports}/generate"; 9 | } -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Fitnet.Reports/ReportsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using Microsoft.AspNetCore.Routing; 4 | using GenerateNewPassesRegistrationsPerMonthReport; 5 | 6 | internal static class ReportsEndpoints 7 | { 8 | internal static void MapReports(this IEndpointRouteBuilder app) => 9 | app.MapGenerateNewPassesRegistrationsPerMonthReport(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/GenerateNewPassesPerMonthReport/TestData/PassRegistrationDateRange.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.IntegrationTests.GenerateNewPassesPerMonthReport.TestData; 2 | 3 | internal sealed record PassRegistrationDateRange(DateTimeOffset From, DateTimeOffset To); -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Collections; 2 | global using System.Net; 3 | global using System.Net.Http.Json; 4 | global using Xunit; 5 | global using Microsoft.AspNetCore.Mvc.Testing; 6 | global using Bogus; 7 | global using Shouldly; 8 | -------------------------------------------------------------------------------- /Chapter-2-modules-separation/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": true 5 | }, 6 | "Contracts": { 7 | "Enabled": false 8 | }, 9 | "Reports": { 10 | "Enabled": true 11 | }, 12 | "Offers": { 13 | "Enabled": false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Assets/communication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-3-microservice-extraction/Assets/communication.png -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Assets/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-3-microservice-extraction/Assets/components.png -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Docs/Contracts/Api/Contracts.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Prepare a contract 3 | # @name prepareContract 4 | POST {{baseUrl}}/api/contracts 5 | Content-Type: application/json 6 | 7 | { 8 | "customerAge": 30, 9 | "customerHeight": 180, 10 | "preparedAt": "2023-05-04T12:00:00.000Z" 11 | } 12 | 13 | ### 14 | ### Sign a contract 15 | # @name signContract 16 | PATCH {{baseUrl}}/api/contracts/{{contractId}} 17 | Content-Type: application/json 18 | 19 | { 20 | "signedAt": "2023-05-04T14:00:00.000Z" 21 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Docs/Contracts/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5010" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Docs/Passes/Api/Passes.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Register a new pass 3 | # @name registerPass 4 | POST {{baseUrl}}/api/passes 5 | Content-Type: application/json 6 | 7 | { 8 | "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", 9 | "from": "2023-03-26T08:52:27.747Z", 10 | "to": "2023-03-26T08:52:27.747Z" 11 | } 12 | 13 | ### 14 | ### Mark a pass as expired 15 | # @name markPassAsExpired 16 | PATCH {{baseUrl}}/api/passes/{{passId}} -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Docs/Passes/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Docs/Reports/Api/Reports.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Generate new report 3 | # @name generateNewReport 4 | GET {{baseUrl}}/api/reports/generate -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Docs/Reports/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Api.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using Xunit; 3 | global using Microsoft.AspNetCore.Http; 4 | global using Newtonsoft.Json; 5 | global using Shouldly; 6 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Api/ApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api; 2 | 3 | public static class ApiPaths 4 | { 5 | public const string Root = "api"; 6 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Api/ErrorHandling/ErrorHandlingExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.ErrorHandling; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | 5 | public static class ErrorHandlingExtensions 6 | { 7 | public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder applicationBuilder) 8 | { 9 | applicationBuilder.UseMiddleware(); 10 | 11 | return applicationBuilder; 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Api/ErrorHandling/ResourceNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.ErrorHandling; 2 | 3 | public sealed class ResourceNotFoundException(Guid id) : InvalidOperationException($"Resource with '{id}' not found "); 4 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Api/Validations/EndpointBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.Validations; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | public static class EndpointBuilderExtensions 7 | { 8 | public static RouteHandlerBuilder ValidateRequest(this RouteHandlerBuilder builder) where TRequest : class => 9 | builder.AddEndpointFilter>(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core.UnitTests/BusinessRulesEngine/FakeBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.UnitTests.BusinessRulesEngine; 2 | 3 | using BusinessRules; 4 | 5 | internal sealed class FakeBusinessRule : IBusinessRule 6 | { 7 | private readonly int _someNumber; 8 | 9 | internal FakeBusinessRule(int someNumber) => 10 | _someNumber = someNumber; 11 | 12 | public bool IsMet() => _someNumber > 10; 13 | 14 | public string Error => "Fake business rule was not met"; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Shouldly; 2 | global using Xunit; 3 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/BusinessRules/BusinessRuleValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; 2 | 3 | public class BusinessRuleValidationException(string message) : InvalidOperationException(message); 4 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/BusinessRules/BusinessRuleValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; 2 | 3 | public static class BusinessRuleValidator 4 | { 5 | public static void Validate(IBusinessRule rule) 6 | { 7 | if (!rule.IsMet()) 8 | { 9 | throw new BusinessRuleValidationException(rule.Error); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/BusinessRules/IBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BusinessRules; 2 | 3 | public interface IBusinessRule 4 | { 5 | bool IsMet(); 6 | string Error { get; } 7 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/Fitnet.Common.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/SystemClock/ISystemClock.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.SystemClock; 2 | 3 | public interface ISystemClock 4 | { 5 | DateTimeOffset Now { get; } 6 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/SystemClock/SystemClock.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.SystemClock; 2 | 3 | internal sealed class SystemClock : ISystemClock 4 | { 5 | public DateTimeOffset Now => DateTimeOffset.UtcNow; 6 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Core/SystemClock/SystemClockModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.SystemClock; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | public static class SystemClockModule 6 | { 7 | public static IServiceCollection AddSystemClock(this IServiceCollection services) 8 | { 9 | services.AddSingleton(); 10 | 11 | return services; 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Infrastructure/Clock/ClockModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Clock; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | public static class ClockModule 6 | { 7 | public static IServiceCollection AddClock(this IServiceCollection services) => 8 | services.AddSingleton(TimeProvider.System); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Infrastructure/Events/IIntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 2 | 3 | using MediatR; 4 | 5 | public interface IIntegrationEvent : INotification 6 | { 7 | Guid Id { get; } 8 | DateTimeOffset OccurredDateTime { get; } 9 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Infrastructure/Events/IIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 2 | 3 | using MediatR; 4 | 5 | public interface IIntegrationEventHandler : INotificationHandler where TEvent : IIntegrationEvent 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.Infrastructure/Mediator/MediatorModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Mediator; 2 | 3 | using System.Reflection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | public static class MediatorModule 7 | { 8 | public static IServiceCollection AddMediator(this IServiceCollection services, Assembly assembly) 9 | { 10 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssembly(assembly)); 11 | 12 | return services; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Reflection; 2 | global using JetBrains.Annotations; 3 | global using Xunit; 4 | global using Microsoft.AspNetCore.Mvc.Testing; 5 | global using Microsoft.AspNetCore.Hosting; 6 | global using Microsoft.Extensions.Configuration; 7 | global using Bogus; 8 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/TestEngine/Database/IDatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; 2 | 3 | public interface IDatabaseConfiguration 4 | { 5 | Dictionary Get(); 6 | } 7 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/TestEngine/EventBus/EventBusAssertions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.EventBus; 2 | 3 | using FluentAssertions; 4 | using MassTransit.Testing; 5 | 6 | public static class EventBusAssertions 7 | { 8 | public static void EnsureConsumed(this ITestHarness harness) where TEvent : class => 9 | harness.Consumed.Select().Any().Should().BeTrue(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/TestEngine/Time/FakeTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Time; 2 | 3 | [UsedImplicitly] 4 | public sealed class FakeTimeProvider(DateTimeOffset? now = null) : TimeProvider 5 | { 6 | private DateTimeOffset TimeNowOffset { get; set; } = now ?? new Faker().Date.RecentOffset().UtcDateTime; 7 | 8 | public override DateTimeOffset GetUtcNow() => TimeNowOffset; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Common/Fitnet.Common.slnx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Bogus; 2 | global using Xunit; 3 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/Prepare/RequestValidator/PrepareContractRequestParameters.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.UnitTests.Prepare.RequestValidator; 2 | 3 | internal sealed record PrepareContractRequestParameters(int MinAge, int MaxAge, int MinHeight, int MaxHeight) 4 | { 5 | private const int MinimumAge = 18; 6 | private const int MaximumAge = 100; 7 | private const int MinimumHeight = 0; 8 | private const int MaximumHeight = 210; 9 | 10 | internal static PrepareContractRequestParameters GetValid() => 11 | new(MinimumAge, MaximumAge, MinimumHeight, MaximumHeight); 12 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api/ContractsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api; 2 | 3 | using Common.Api; 4 | 5 | internal static class ContractsApiPaths 6 | { 7 | private const string ContractsRootApi = $"{ApiPaths.Root}/contracts"; 8 | 9 | internal const string Prepare = ContractsRootApi; 10 | internal const string Sign = $"{ContractsRootApi}/{{id}}"; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api/ContractsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api; 2 | 3 | using Microsoft.AspNetCore.Routing; 4 | using Prepare; 5 | using Sign; 6 | 7 | internal static class ContractsEndpoints 8 | { 9 | internal static void MapContracts(this IEndpointRouteBuilder app) 10 | { 11 | app.MapPrepareContract(); 12 | app.MapSignContract(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api/Prepare/PrepareContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Prepare; 2 | 3 | using Application.Prepare; 4 | 5 | internal sealed record PrepareContractRequest(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt) 6 | { 7 | internal PrepareContractCommand ToCommand() => new(CustomerId, CustomerAge, CustomerHeight, PreparedAt); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api/Prepare/PrepareContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Prepare; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class PrepareContractRequestValidator : AbstractValidator 6 | { 7 | public PrepareContractRequestValidator() 8 | { 9 | RuleFor(request => request.CustomerId).NotEmpty(); 10 | RuleFor(request => request.CustomerAge).GreaterThan(0); 11 | RuleFor(request => request.CustomerHeight).GreaterThan(0); 12 | RuleFor(request => request.PreparedAt).NotEmpty(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api/Sign/SignContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Sign; 2 | 3 | using Application.Sign; 4 | 5 | internal sealed record SignContractRequest(DateTimeOffset SignedAt) 6 | { 7 | internal SignContractCommand ToCommand(Guid id) => 8 | new(id, SignedAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Api/Sign/SignContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.Sign; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class SignContractRequestValidator : AbstractValidator 6 | { 7 | public SignContractRequestValidator() => RuleFor(signContractRequest => signContractRequest.SignedAt) 8 | .NotEmpty(); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Threading.Tasks; 2 | global using JetBrains.Annotations; 3 | global using MediatR; -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | public interface ICommand : IRequest 4 | { } 5 | 6 | public interface ICommand : IRequest 7 | { } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | public interface IContractsModule 4 | { 5 | Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default); 6 | 7 | Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default); 8 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | using Core; 4 | 5 | public interface IContractsRepository 6 | { 7 | Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); 8 | Task GetPreviousForCustomerAsync(Guid customerId, CancellationToken cancellationToken = default); 9 | Task AddAsync(Contract contract, CancellationToken cancellationToken = default); 10 | Task CommitAsync(CancellationToken cancellationToken = default); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/Prepare/PrepareContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.Prepare; 2 | 3 | public sealed record PrepareContractCommand(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt) : ICommand; -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Application/Sign/SignContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.Sign; 2 | 3 | public sealed record SignContractCommand(Guid Id, DateTimeOffset SignedAt) : ICommand; -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using Shouldly; 3 | global using Xunit; 4 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractParameters.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract; 2 | 3 | internal sealed record PrepareContractParameters(int MinAge, int MaxAge, int MinHeight, int MaxHeight) 4 | { 5 | private const int MinimumAge = 18; 6 | private const int MaximumAge = 100; 7 | private const int MinimumHeight = 0; 8 | private const int MaximumHeight = 210; 9 | 10 | internal static PrepareContractParameters GetValid() => new(MinimumAge, MaximumAge, MinimumHeight, MaximumHeight); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Fitnet.Contracts.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core/PrepareContract/BusinessRules/ContractCanBePreparedOnlyForAdultRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.PrepareContract.BusinessRules; 2 | 3 | using Common.Core.BusinessRules; 4 | 5 | internal sealed class ContractCanBePreparedOnlyForAdultRule : IBusinessRule 6 | { 7 | private readonly int _age; 8 | 9 | internal ContractCanBePreparedOnlyForAdultRule(int age) => _age = age; 10 | 11 | public bool IsMet() => _age >= 18; 12 | 13 | public string Error => "Contract can not be prepared for a person who is not adult"; 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Core/PrepareContract/BusinessRules/PreviousContractHasToBeSignedRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.PrepareContract.BusinessRules; 2 | 3 | using Common.Core.BusinessRules; 4 | 5 | internal sealed class PreviousContractHasToBeSignedRule : IBusinessRule 6 | { 7 | private readonly bool? _signed; 8 | 9 | internal PreviousContractHasToBeSignedRule(bool? signed) => _signed = signed; 10 | 11 | public bool IsMet() => _signed is true or null; 12 | 13 | public string Error => "Previous contract must be signed by the customer"; 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/ContractsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure; 2 | 3 | using Application; 4 | using MediatR; 5 | 6 | internal sealed class ContractsModule(IMediator mediator) : IContractsModule 7 | { 8 | public async Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default) => 9 | await mediator.Send(command, cancellationToken); 10 | 11 | public async Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default) => 12 | await mediator.Send(command, cancellationToken); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; 2 | 3 | using Application; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class RepositoriesModule 7 | { 8 | internal static IServiceCollection AddRepositories(this IServiceCollection services) 9 | { 10 | services.AddScoped(); 11 | 12 | return services; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/EventBus/EventBusOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.EventBus; 2 | 3 | internal sealed class EventBusOptions 4 | { 5 | public string? Uri { get; init; } 6 | public string? Username { get; init; } 7 | public string? Password { get; init; } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Diagnostics.CodeAnalysis; -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Mediation/MediationModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Mediation; 2 | 3 | using Application; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class MediationModule 7 | { 8 | internal static IServiceCollection AddMediationModule(this IServiceCollection services) 9 | { 10 | var commandsHandlersAssembly = typeof(IContractsModule).Assembly; 11 | 12 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssemblies(commandsHandlersAssembly)); 13 | 14 | return services; 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationEvents/Fitnet.Contracts.IntegrationEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://github.com/evolutionary-architecture/evolutionary-architecture-by-example 4 | 1.0.7 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using System.Net.Http.Json; 3 | global using Xunit; 4 | global using Bogus; 5 | global using Shouldly; 6 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Contracts": { 7 | "Enabled": true 8 | }, 9 | "Reports": { 10 | "Enabled": false 11 | }, 12 | "Offers": { 13 | "Enabled": false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using JetBrains.Annotations; 4 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "FeatureManagement": { 9 | "Contracts": true, 10 | }, 11 | "ConnectionStrings": { 12 | "Contracts": "Host=postgres:5432;Database=fitnet;Username=postgres;Password=mysecretpassword" 13 | }, 14 | "EventBus": { 15 | "Uri": "rabbitmq", 16 | "Username": "guest", 17 | "Password": "guest" 18 | } 19 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/Fitnet.Contracts/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "FeatureManagement": { 10 | "Contracts": true, 11 | }, 12 | "ConnectionStrings": { 13 | "Contracts": "" 14 | }, 15 | "ExternalEventBus": { 16 | "Uri": "localhost", 17 | "Username": "guest", 18 | "Password": "guest" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet.Contracts/Src/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Fitnet.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Fitnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Fitnet/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using JetBrains.Annotations; 4 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Fitnet/Module.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet; 2 | 3 | internal record Module(string Value) 4 | { 5 | internal static readonly Module Offers = new("Offers"); 6 | internal static readonly Module Passes = new("Passes"); 7 | internal static readonly Module Reports = new("Reports"); 8 | 9 | public static implicit operator string(Module module) => module.Value; 10 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Fitnet/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Modules": { 10 | "Passes": { 11 | "Enabled": true 12 | }, 13 | "Reports": { 14 | "Enabled": true 15 | }, 16 | "Offers": { 17 | "Enabled": true 18 | } 19 | }, 20 | "ConnectionStrings": { 21 | "Passes": "", 22 | "Reports": "", 23 | "Offers": "" 24 | }, 25 | "EventBus": { 26 | "Uri": "localhost", 27 | "Username": "guest", 28 | "Password": "guest" 29 | } 30 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Fitnet.Offers.Api/Prepare/OfferPrepareEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.Api.Prepare; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | internal sealed record OfferPrepareEvent(Guid Id, Guid OfferId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static OfferPrepareEvent Create(Guid offerId, Guid customerId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), offerId, customerId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/Database/OffersPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.DataAccess.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | public sealed class OffersPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Offers"; 8 | 9 | public DbSet Offers => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new OfferEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/Fitnet.Offers.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Bogus; 3 | global using MassTransit.Testing; 4 | global using Shouldly; 5 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/OffersDatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.IntegrationTests; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; 4 | 5 | internal sealed class OffersDatabaseConfiguration : IDatabaseConfiguration 6 | { 7 | private readonly string _connectionString; 8 | 9 | internal OffersDatabaseConfiguration(string connectionString) => _connectionString = connectionString; 10 | 11 | public Dictionary Get() => new() 12 | { 13 | { "ConnectionStrings:Offers", _connectionString } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/Prepare/PassExpiredEventFaker.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.IntegrationTests.Prepare; 2 | 3 | using Passes.IntegrationEvents; 4 | 5 | internal sealed class PassExpiredEventFaker : Faker 6 | { 7 | private PassExpiredEventFaker() => CustomInstantiator(faker => 8 | new PassExpiredEvent( 9 | Guid.NewGuid(), 10 | Guid.NewGuid(), 11 | Guid.NewGuid(), 12 | faker.Date.RecentOffset() 13 | ) 14 | ); 15 | 16 | internal static PassExpiredEvent CreateValid() => new PassExpiredEventFaker(); 17 | } 18 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Reports": { 7 | "Enabled": false 8 | }, 9 | "Offers": { 10 | "Enabled": true 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.Api/Common/EventBus/EventBusOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.Common.EventBus; 2 | 3 | internal sealed class EventBusOptions 4 | { 5 | public string? Uri { get; init; } 6 | public string? Username { get; init; } 7 | public string? Password { get; init; } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.Api/GetAllPasses/GetAllPassesResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.GetAllPasses; 2 | 3 | using DataAccess; 4 | 5 | internal record GetAllPassesResponse(IReadOnlyCollection Passes) 6 | { 7 | internal static GetAllPassesResponse Create(IReadOnlyCollection passes) => new(passes); 8 | } 9 | 10 | internal record PassDto(Guid Id, Guid CustomerId) 11 | { 12 | internal static PassDto From(Pass contract) => new(contract.Id, contract.CustomerId); 13 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.Api/PassesApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api; 2 | 3 | using Fitnet.Common.Api; 4 | 5 | internal static class PassesApiPaths 6 | { 7 | internal const string GetAll = $"{ApiPaths.Root}/passes"; 8 | internal const string MarkPassAsExpired = $"{ApiPaths.Root}/passes/{{id}}"; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.Api/PassesEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api; 2 | 3 | using GetAllPasses; 4 | using MarkPassAsExpired; 5 | using Microsoft.AspNetCore.Routing; 6 | 7 | internal static class PassesEndpoints 8 | { 9 | internal static void MapPasses(this IEndpointRouteBuilder app) 10 | { 11 | app.MapMarkPassAsExpired(); 12 | app.MapGetAllPasses(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.Api/RegisterPass/PassRegisteredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.RegisterPass; 2 | 3 | using Fitnet.Common.Infrastructure.Events; 4 | 5 | internal record PassRegisteredEvent(Guid Id, Guid PassId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static PassRegisteredEvent Create(Guid passId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), passId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.DataAccess/Fitnet.Passes.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.DataAccess/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Diagnostics.CodeAnalysis; -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Fitnet.Passes.IntegrationEvents/PassExpiredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.IntegrationEvents; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | public record PassExpiredEvent 6 | (Guid Id, Guid PassId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 7 | { 8 | public static PassExpiredEvent Create(Guid passId, Guid customerId, DateTimeOffset occurredAt) => 9 | new(Guid.NewGuid(), passId, customerId, occurredAt); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Net; 3 | global using System.Net.Http; 4 | global using System.Net.Http.Json; 5 | global using System.Threading.Tasks; 6 | global using Xunit; 7 | global using Bogus; 8 | global using MassTransit.Testing; 9 | global using Shouldly; 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/PassesDatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.IntegrationTests; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; 4 | 5 | internal sealed class PassesDatabaseConfiguration : IDatabaseConfiguration 6 | { 7 | private readonly string _connectionString; 8 | 9 | internal PassesDatabaseConfiguration(string connectionString) => _connectionString = connectionString; 10 | 11 | public Dictionary Get() => new() 12 | { 13 | { "ConnectionStrings:Passes", _connectionString } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": true 5 | }, 6 | "Reports": { 7 | "Enabled": false 8 | }, 9 | "Offers": { 10 | "Enabled": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/DataAccess/DatabaseAccessModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | internal static class DatabaseAccessModule 6 | { 7 | internal static IServiceCollection AddDataAccess(this IServiceCollection services) 8 | { 9 | services.AddSingleton(); 10 | 11 | return services; 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/DataAccess/IDatabaseConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using System.Data; 4 | 5 | internal interface IDatabaseConnectionFactory : IDisposable 6 | { 7 | IDbConnection Create(); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/DataRetriever/INewPassesRegistrationPerMonthReportDataRetriever.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.DataRetriever; 2 | 3 | using Dtos; 4 | 5 | internal interface INewPassesRegistrationPerMonthReportDataRetriever 6 | { 7 | Task> GetReportDataAsync( 8 | CancellationToken cancellationToken = default); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthDto.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthDto(int MonthOrder, string MonthName, long RegisteredPasses); -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthResponse(IReadOnlyCollection PassesRegistrationsPerMonth) 4 | { 5 | internal static NewPassesRegistrationsPerMonthResponse Create(IReadOnlyCollection passesRegistrationsPerMonth) => new(passesRegistrationsPerMonth); 6 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/ReportsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using Common.Api; 4 | 5 | internal static class ReportsApiPaths 6 | { 7 | private const string Reports = $"{ApiPaths.Root}/reports"; 8 | internal const string GenerateNewReport = $"{Reports}/generate"; 9 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Fitnet.Reports/ReportsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using Microsoft.AspNetCore.Routing; 4 | using GenerateNewPassesRegistrationsPerMonthReport; 5 | 6 | internal static class ReportsEndpoints 7 | { 8 | internal static void MapReports(this IEndpointRouteBuilder app) => 9 | app.MapGenerateNewPassesRegistrationsPerMonthReport(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/GenerateNewPassesPerMonthReport/TestData/PassRegistrationDateRange.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.IntegrationTests.GenerateNewPassesPerMonthReport.TestData; 2 | 3 | internal sealed record PassRegistrationDateRange(DateTimeOffset From, DateTimeOffset To); -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using System.Net.Http.Json; 3 | global using Xunit; 4 | global using Bogus; 5 | global using FluentAssertions; 6 | global using MassTransit.Testing; 7 | -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": true 5 | }, 6 | "Reports": { 7 | "Enabled": true 8 | }, 9 | "Offers": { 10 | "Enabled": false 11 | } 12 | }, 13 | "RabbitMq": 14 | { 15 | "Uri": "", 16 | "Username": "", 17 | "Password": "" 18 | } 19 | } -------------------------------------------------------------------------------- /Chapter-3-microservice-extraction/Fitnet/Src/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Assets/aggregate_design_canvas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Assets/aggregate_design_canvas.jpg -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Assets/aggregate_root_internals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Assets/aggregate_root_internals.png -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Assets/design-level-event-storming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Assets/design-level-event-storming.png -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Assets/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Assets/flow.png -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Assets/persistance_ignorance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Assets/persistance_ignorance.png -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Assets/value_object_sets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Assets/value_object_sets.png -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Contracts/Api/BindingContracts.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Terminate a binding contract 3 | # @name terminateBindingContract 4 | PATCH {{baseUrl}}/api/binding-contracts/{{bindingContractId}} 5 | 6 | ### 7 | ### Attach an annex 8 | # @name attachAnnex 9 | POST {{baseUrl}}/api/binding-contracts/{{bindingContractId}}/annexes 10 | 11 | Content-Type: application/json 12 | 13 | { 14 | "validFrom": "2023-05-04T12:00:00.000Z" 15 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Contracts/Api/Contracts.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Prepare a contract 3 | # @name prepareContract 4 | POST {{baseUrl}}/api/contracts 5 | Content-Type: application/json 6 | 7 | { 8 | "customerAge": 30, 9 | "customerHeight": 180, 10 | "preparedAt": "2023-05-04T12:00:00.000Z" 11 | } 12 | 13 | ### 14 | ### Sign a contract 15 | # @name signContract 16 | PATCH {{baseUrl}}/api/contracts/{{contractId}} 17 | Content-Type: application/json 18 | 19 | { 20 | "signedAt": "2023-05-04T14:00:00.000Z" 21 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Contracts/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5010" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Passes/Api/Passes.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Register a new pass 3 | # @name registerPass 4 | POST {{baseUrl}}/api/passes 5 | Content-Type: application/json 6 | 7 | { 8 | "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", 9 | "from": "2023-03-26T08:52:27.747Z", 10 | "to": "2023-03-26T08:52:27.747Z" 11 | } 12 | 13 | ### 14 | ### Mark a pass as expired 15 | # @name markPassAsExpired 16 | PATCH {{baseUrl}}/api/passes/{{passId}} -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Passes/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Reports/Api/Reports.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Generate new report 3 | # @name generateNewReport 4 | GET {{baseUrl}}/api/reports/generate -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Docs/Reports/Api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "baseUrl": "http://localhost:5013" 4 | } 5 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Api.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Shouldly; 2 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Api/ApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api; 2 | 3 | public static class ApiPaths 4 | { 5 | public const string Root = "api"; 6 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Api/ErrorHandling/ErrorHandlingExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.ErrorHandling; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | 5 | public static class ErrorHandlingExtensions 6 | { 7 | public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder applicationBuilder) 8 | { 9 | applicationBuilder.UseMiddleware(); 10 | 11 | return applicationBuilder; 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Api/ErrorHandling/ResourceNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.ErrorHandling; 2 | 3 | public sealed class ResourceNotFoundException(Guid id) : InvalidOperationException($"Resource with '{id}' not found "); 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Api/Validations/EndpointBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Api.Validations; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | public static class EndpointBuilderExtensions 7 | { 8 | public static RouteHandlerBuilder ValidateRequest(this RouteHandlerBuilder builder) where TRequest : class => 9 | builder.AddEndpointFilter>(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core.UnitTests/BusinessRulesEngine/FakeBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.UnitTests.BusinessRulesEngine; 2 | 3 | using BussinessRules; 4 | using ErrorOr; 5 | 6 | internal sealed class FakeBusinessRule : IBusinessRule 7 | { 8 | private readonly int _someNumber; 9 | 10 | internal FakeBusinessRule(int someNumber) => 11 | _someNumber = someNumber; 12 | 13 | public bool IsMet() => _someNumber > 10; 14 | public Error Error => Error.Custom(1, "test code", "Fake business rule was not met"); 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Shouldly; 3 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core/BussinessRules/BusinessErrors.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BussinessRules; 2 | 3 | public static class BusinessRuleError 4 | { 5 | public const int Type = 100; 6 | public static Error Create(string code, string description) => Error.Custom(Type, code, description); 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core/BussinessRules/BusinessRuleValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BussinessRules; 2 | 3 | public static class BusinessRuleValidator 4 | { 5 | public static ErrorOr Validate(params IBusinessRule[] rules) 6 | { 7 | var errors = rules 8 | .Where(rule => !rule.IsMet()) 9 | .Select(rule => rule.Error) 10 | .ToList(); 11 | 12 | return errors.Count != 0 ? (ErrorOr)errors : new Success(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core/BussinessRules/IBusinessRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core.BussinessRules; 2 | 3 | public interface IBusinessRule 4 | { 5 | bool IsMet(); 6 | 7 | Error Error { get; } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core/Fitnet.Common.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using ErrorOr; 2 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Core/IDomainEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Core; 2 | 3 | /// 4 | /// Represents a domain event, which encapsulates a significant occurrence or state change within the domain model. 5 | /// 6 | public interface IDomainEvent 7 | { 8 | /// 9 | /// Gets the unique identifier of the domain event. 10 | /// 11 | Guid Id { get; } 12 | 13 | /// 14 | /// Gets the timestamp when the domain event occurred. 15 | /// 16 | DateTime OccuredAt { get; } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Infrastructure/Clock/ClockModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Clock; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | public static class ClockModule 6 | { 7 | public static IServiceCollection AddClock(this IServiceCollection services) => 8 | services.AddSingleton(TimeProvider.System); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Infrastructure/Events/IIntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 2 | 3 | using MediatR; 4 | 5 | public interface IIntegrationEvent : INotification 6 | { 7 | Guid Id { get; } 8 | DateTimeOffset OccurredDateTime { get; } 9 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Infrastructure/Events/IIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Events; 2 | 3 | using MediatR; 4 | 5 | public interface IIntegrationEventHandler : INotificationHandler where TEvent : IIntegrationEvent 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.Infrastructure/Mediator/MediatorModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.Infrastructure.Mediator; 2 | 3 | using System.Reflection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | public static class MediatorModule 7 | { 8 | public static IServiceCollection AddMediator(this IServiceCollection services, Assembly assembly) 9 | { 10 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssembly(assembly)); 11 | 12 | return services; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Reflection; 2 | global using JetBrains.Annotations; 3 | global using Microsoft.AspNetCore.Mvc.Testing; 4 | global using Microsoft.AspNetCore.Hosting; 5 | global using Microsoft.Extensions.Configuration; 6 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/TestEngine/Database/IDatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Database; 2 | 3 | public interface IDatabaseConfiguration 4 | { 5 | Dictionary Get(); 6 | } 7 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/TestEngine/EventBus/EventBusAssertions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.EventBus; 2 | 3 | using FluentAssertions; 4 | using MassTransit.Testing; 5 | 6 | public static class EventBusAssertions 7 | { 8 | public static void EnsureConsumed(this ITestHarness harness) where TEvent : class => 9 | harness.Consumed.Select().Any().Should().BeTrue(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.IntegrationTestsToolbox/TestEngine/Time/FakeTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.IntegrationTestsToolbox.TestEngine.Time; 2 | 3 | using Bogus; 4 | 5 | [UsedImplicitly] 6 | public sealed class FakeTimeProvider(DateTimeOffset? now = null) : TimeProvider 7 | { 8 | private DateTimeOffset TimeNowOffset { get; set; } = now ?? new Faker().Date.RecentOffset().UtcDateTime; 9 | 10 | public override DateTimeOffset GetUtcNow() => TimeNowOffset; 11 | 12 | public void SimulateTimeSkip(int passedDays = 30) => 13 | TimeNowOffset = TimeNowOffset.AddDays(passedDays); 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.UnitTesting/Assertions/ErrorOr/ErrorOrExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Common.UnitTesting.Assertions.ErrorOr; 2 | 3 | public static class ErrorOrExtensions 4 | { 5 | public static ErrorOrSuccessAssertions Should(this ErrorOr instance) => new(instance); 6 | } 7 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.UnitTesting/Fitnet.Common.UnitTesting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Common/Fitnet.Common.UnitTesting/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using ErrorOr; 2 | global using FluentAssertions; 3 | global using FluentAssertions.Execution; 4 | global using FluentAssertions.Primitives; 5 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using Bogus; 3 | global using Shouldly; 4 | global using Xunit; 5 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/AttachAnnexToBindingContract/AttachAnnexToBindingContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.AttachAnnexToBindingContract; 2 | 3 | using Application.AttachAnnexToBindingContract; 4 | 5 | internal sealed record AttachAnnexToBindingContractRequest(DateTimeOffset ValidFrom) 6 | { 7 | internal AttachAnnexToBindingContractCommand ToCommand(Guid bindingContractId) => 8 | new(bindingContractId, ValidFrom); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/AttachAnnexToBindingContract/AttachAnnexToBindingContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.AttachAnnexToBindingContract; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class AttachAnnexToBindingContractRequestValidator : AbstractValidator 6 | { 7 | public AttachAnnexToBindingContractRequestValidator() => RuleFor(request => request.ValidFrom).NotEmpty(); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/ContractsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api; 2 | 3 | using AttachAnnexToBindingContract; 4 | using Microsoft.AspNetCore.Routing; 5 | using PrepareContract; 6 | using SignContract; 7 | using TerminateBindingContract; 8 | 9 | internal static class ContractsEndpoints 10 | { 11 | internal static void MapContracts(this IEndpointRouteBuilder app) 12 | { 13 | app.MapPrepareContract(); 14 | app.MapSignContract(); 15 | app.MapTerminateBindingContract(); 16 | app.MapAttachAnnexToBindingContract(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using ErrorOr; 2 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/PrepareContract/PrepareContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.PrepareContract; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Contracts.Application.PrepareContract; 4 | 5 | internal sealed record PrepareContractRequest(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt) 6 | { 7 | internal PrepareContractCommand ToCommand() => new(CustomerId, CustomerAge, CustomerHeight, PreparedAt); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/PrepareContract/PrepareContractRequestValidator.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.PrepareContract; 2 | 3 | using FluentValidation; 4 | 5 | internal sealed class PrepareContractRequestValidator : AbstractValidator 6 | { 7 | public PrepareContractRequestValidator() 8 | { 9 | RuleFor(request => request.CustomerId).NotEmpty(); 10 | RuleFor(request => request.CustomerAge).GreaterThan(0); 11 | RuleFor(request => request.CustomerHeight).GreaterThan(0); 12 | RuleFor(request => request.PreparedAt).NotEmpty(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Api/SignContract/SignContractRequest.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Api.SignContract; 2 | 3 | using EvolutionaryArchitecture.Fitnet.Contracts.Application.SignContract; 4 | 5 | internal sealed record SignContractRequest(DateTimeOffset SignedAt, string Signature) 6 | { 7 | internal SignContractCommand ToCommand(Guid id) => 8 | new(id, Signature, SignedAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/AttachAnnexToBindingContract/AttachAnnexToBindingContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.AttachAnnexToBindingContract; 2 | 3 | public sealed record AttachAnnexToBindingContractCommand(Guid BindingContractId, DateTimeOffset ValidFrom) 4 | : ICommand>; 5 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using ErrorOr; 2 | global using JetBrains.Annotations; 3 | global using MediatR; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IBindingContractsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | using Core; 4 | 5 | public interface IBindingContractsRepository 6 | { 7 | Task> GetByIdAsync(Guid bindingContract, CancellationToken cancellationToken = default); 8 | Task AddAsync(BindingContract bindingContract, CancellationToken cancellationToken = default); 9 | Task CommitAsync(CancellationToken cancellationToken = default); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | public interface ICommand : IRequest 4 | { } 5 | 6 | public interface ICommand : IRequest> 7 | { } 8 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | public interface IContractsModule 4 | { 5 | Task> ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default); 6 | 7 | Task ExecuteCommandAsync(ICommand command, CancellationToken cancellationToken = default); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/IContractsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application; 2 | 3 | using Core; 4 | 5 | public interface IContractsRepository 6 | { 7 | Task> GetByIdAsync(Guid id, CancellationToken cancellationToken = default); 8 | Task GetPreviousForCustomerAsync(Guid customerId, CancellationToken cancellationToken = default); 9 | Task AddAsync(Contract contract, CancellationToken cancellationToken = default); 10 | Task CommitAsync(CancellationToken cancellationToken = default); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/PrepareContract/PrepareContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.PrepareContract; 2 | 3 | public sealed record PrepareContractCommand(Guid CustomerId, int CustomerAge, int CustomerHeight, DateTimeOffset PreparedAt) : ICommand>; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/SignContract/SignContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.SignContract; 2 | 3 | public sealed record SignContractCommand(Guid Id, string Signature, DateTimeOffset SignedAt) : ICommand>; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Application/TerminateBindingContract/TerminateBindingContractCommand.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Application.TerminateBindingContract; 2 | 3 | public sealed record TerminateBindingContractCommand(Guid BindingContractId) : ICommand; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/EntityExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.Common; 2 | 3 | using DomainDrivenDesign.BuildingBlocks; 4 | 5 | internal static class EntityExtensions 6 | { 7 | public static TEvent? GetPublishedEvent(this Entity entity) where TEvent : class, IDomainEvent => entity.Events.OfType() 8 | .MinBy(domainEvent => domainEvent.OccuredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/Common/FakeContractDates.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.Common; 2 | 3 | internal static class FakeContractDates 4 | { 5 | public static DateTimeOffset PreparedAt => new(2022, 2, 3, 1, 1, 1, TimeSpan.Zero); 6 | public static DateTimeOffset SignDay => new(2022, 1, 3, 1, 1, 1, TimeSpan.Zero); 7 | } 8 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using ErrorOr; 2 | global using EvolutionaryArchitecture.Fitnet.Common.UnitTesting.Assertions.ErrorOr; 3 | global using Shouldly; 4 | global using Xunit; 5 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core.UnitTests/PrepareContract/PrepareContractParameters.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.UnitTests.PrepareContract; 2 | 3 | internal sealed record PrepareContractParameters(int MinAge, int MaxAge, int MinHeight, int MaxHeight) 4 | { 5 | private const int MinimumAge = 18; 6 | private const int MaximumAge = 100; 7 | private const int MinimumHeight = 0; 8 | private const int MaximumHeight = 210; 9 | 10 | internal static PrepareContractParameters GetValid() => new(MinimumAge, MaximumAge, MinimumHeight, MaximumHeight); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/Fitnet.Contracts.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Text.RegularExpressions; 2 | global using ErrorOr; 3 | global using EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures.Exceptions; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/BindingContractStartedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract; 2 | 3 | using DomainDrivenDesign.BuildingBlocks; 4 | 5 | public sealed record BindingContractStartedEvent( 6 | Guid Id, 7 | DateTimeOffset BindingFrom, 8 | DateTimeOffset? ExpiringAt, 9 | DateTime OccuredAt) : IDomainEvent 10 | { 11 | internal static BindingContractStartedEvent Raise( 12 | DateTimeOffset bindingFrom, 13 | DateTimeOffset? expiringAt) 14 | => new(Guid.NewGuid(), bindingFrom, expiringAt, DateTime.UtcNow); 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/BusinessRules/ContractMustNotBeAlreadySignedRule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.BusinessRules; 2 | 3 | using Common.Core.BussinessRules; 4 | 5 | internal sealed class ContractMustNotBeAlreadySignedRule(bool signed) : IBusinessRule 6 | { 7 | public bool IsMet() => !signed; 8 | public Error Error => BusinessRuleError.Create(nameof(ContractMustNotBeAlreadySignedRule), "Contract must not be already signed"); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/SignContract/Signatures/Exceptions/SignatureNotValidException.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.SignContract.Signatures.Exceptions; 2 | 3 | public sealed class SignatureNotValidException(string signature) : InvalidOperationException($"Signature: '{signature}' contains invalid characters."); 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Core/TerminateBindingContract/BindingContractTerminatedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Core.TerminateBindingContract; 2 | 3 | using DomainDrivenDesign.BuildingBlocks; 4 | 5 | public sealed record BindingContractTerminatedEvent( 6 | Guid Id, 7 | DateTimeOffset TerminatedAt, 8 | DateTime OccuredAt) : IDomainEvent 9 | { 10 | internal static BindingContractTerminatedEvent Raise(DateTimeOffset terminatedAt) 11 | => new(Guid.NewGuid(), terminatedAt, DateTime.UtcNow); 12 | } 13 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/Repositories/RepositoriesModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Database.Repositories; 2 | 3 | using Application; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class RepositoriesModule 7 | { 8 | internal static IServiceCollection AddRepositories(this IServiceCollection services) 9 | { 10 | services.AddScoped(); 11 | services.AddScoped(); 12 | 13 | return services; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/EventBus/EventBusOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.EventBus; 2 | 3 | internal sealed class EventBusOptions 4 | { 5 | public string? Uri { get; init; } 6 | public string? Username { get; init; } 7 | public string? Password { get; init; } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Diagnostics.CodeAnalysis; 2 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Mediation/MediationModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.Infrastructure.Mediation; 2 | 3 | using Application; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | internal static class MediationModule 7 | { 8 | internal static IServiceCollection AddMediationModule(this IServiceCollection services) 9 | { 10 | var commandsHandlersAssembly = typeof(IContractsModule).Assembly; 11 | 12 | services.AddMediatR(configuration => configuration.RegisterServicesFromAssemblies(commandsHandlersAssembly)); 13 | 14 | return services; 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationEvents/ContractSignedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationEvents; 2 | 3 | public sealed record ContractSignedEvent( 4 | Guid Id, 5 | Guid ContractId, 6 | Guid ContractCustomerId, 7 | DateTimeOffset SignedAt, 8 | DateTimeOffset ExpireAt, 9 | DateTimeOffset OccurredDateTime) 10 | { 11 | public static ContractSignedEvent Create(Guid contractId, Guid contractCustomerId, DateTimeOffset signedAt, 12 | DateTimeOffset expireAt) => 13 | new(Guid.NewGuid(), contractId, contractCustomerId, signedAt, expireAt, DateTimeOffset.UtcNow); 14 | } 15 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationEvents/Fitnet.Contracts.IntegrationEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://github.com/evolutionary-architecture/evolutionary-architecture-by-example 4 | 1.0.7 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using System.Net.Http.Json; 3 | global using Bogus; 4 | global using Microsoft.AspNetCore.Mvc; 5 | global using Shouldly; 6 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/TerminateBindingContract/TerminateBindingContractRequestParameters.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationTests.TerminateBindingContract; 2 | 3 | using Api; 4 | 5 | internal record TerminateBindingContractRequestParameters(string Url) 6 | { 7 | internal static TerminateBindingContractRequestParameters GetValid(Guid bindingContractId) => 8 | new(BuildUrl(bindingContractId)); 9 | 10 | private static string BuildUrl(Guid bindingContractId) => 11 | ContractsApiPaths.Terminate.Replace("{id}", bindingContractId.ToString()); 12 | } 13 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/TerminateBindingContract/TerminateBindingContractTestExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Contracts.IntegrationTests.TerminateBindingContract; 2 | 3 | internal static class TerminateBindingContractTestExtensions 4 | { 5 | internal static async Task TerminateBindingContractAsync(this HttpClient httpClient, Guid bindingContractId) 6 | { 7 | var request = TerminateBindingContractRequestParameters.GetValid(bindingContractId); 8 | var response = await httpClient.PatchAsync(request.Url, null); 9 | response.EnsureSuccessStatusCode(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Contracts": { 7 | "Enabled": true 8 | }, 9 | "Reports": { 10 | "Enabled": false 11 | }, 12 | "Offers": { 13 | "Enabled": false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using JetBrains.Annotations; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "FeatureManagement": { 9 | "Contracts": true, 10 | }, 11 | "ConnectionStrings": { 12 | "Contracts": "Host=postgres:5432;Database=fitnet;Username=postgres;Password=mysecretpassword" 13 | }, 14 | "EventBus": { 15 | "Uri": "rabbitmq", 16 | "Username": "guest", 17 | "Password": "guest" 18 | } 19 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/Fitnet.Contracts/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "Contracts": "" 11 | }, 12 | "ExternalEventBus": { 13 | "Uri": "localhost", 14 | "Username": "guest", 15 | "Password": "guest" 16 | } 17 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet.Contracts/Src/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Fitnet.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Fitnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Fitnet/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using JetBrains.Annotations; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Fitnet/Module.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet; 2 | 3 | internal record Module(string Value) 4 | { 5 | internal static readonly Module Offers = new("Offers"); 6 | internal static readonly Module Passes = new("Passes"); 7 | internal static readonly Module Reports = new("Reports"); 8 | 9 | public static implicit operator string(Module module) => module.Value; 10 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Fitnet/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Modules": { 10 | "Passes": { 11 | "Enabled": true 12 | }, 13 | "Reports": { 14 | "Enabled": true 15 | }, 16 | "Offers": { 17 | "Enabled": true 18 | } 19 | }, 20 | "ConnectionStrings": { 21 | "Passes": "", 22 | "Reports": "", 23 | "Offers": "" 24 | }, 25 | "EventBus": { 26 | "Uri": "localhost", 27 | "Username": "guest", 28 | "Password": "guest" 29 | } 30 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Fitnet.Offers.Api/Prepare/OfferPrepareEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.Api.Prepare; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | internal sealed record OfferPrepareEvent(Guid Id, Guid OfferId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static OfferPrepareEvent Create(Guid offerId, Guid customerId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), offerId, customerId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/Database/OffersPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.DataAccess.Database; 2 | 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | public sealed class OffersPersistence(DbContextOptions options) : DbContext(options) 6 | { 7 | private const string Schema = "Offers"; 8 | 9 | public DbSet Offers => Set(); 10 | 11 | protected override void OnModelCreating(ModelBuilder modelBuilder) 12 | { 13 | modelBuilder.HasDefaultSchema(Schema); 14 | modelBuilder.ApplyConfiguration(new OfferEntityConfiguration()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/Fitnet.Offers.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/GlobalUsings.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evolutionary-architecture/evolutionary-architecture-by-example/172fe07e6ae87f1bfd5c3ff9f99b5361f9965437/Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Fitnet.Offers.DataAccess/GlobalUsings.cs -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Bogus; 2 | global using MassTransit.Testing; 3 | global using Shouldly; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/Prepare/PassExpiredEventFaker.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Offers.IntegrationTests.Prepare; 2 | 3 | using Passes.IntegrationEvents; 4 | 5 | internal sealed class PassExpiredEventFaker : Faker 6 | { 7 | private PassExpiredEventFaker() => CustomInstantiator(faker => 8 | new PassExpiredEvent( 9 | Guid.NewGuid(), 10 | Guid.NewGuid(), 11 | Guid.NewGuid(), 12 | faker.Date.RecentOffset() 13 | ) 14 | ); 15 | 16 | internal static PassExpiredEvent CreateValid() => new PassExpiredEventFaker(); 17 | } 18 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Offers/Tests/Fitnet.Offers.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": false 5 | }, 6 | "Reports": { 7 | "Enabled": false 8 | }, 9 | "Offers": { 10 | "Enabled": true 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.Api/Common/EventBus/EventBusOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.Common.EventBus; 2 | 3 | internal sealed class EventBusOptions 4 | { 5 | public string? Uri { get; init; } 6 | public string? Username { get; init; } 7 | public string? Password { get; init; } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.Api/GetAllPasses/GetAllPassesResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.GetAllPasses; 2 | 3 | using DataAccess; 4 | 5 | internal record GetAllPassesResponse(IReadOnlyCollection Passes) 6 | { 7 | internal static GetAllPassesResponse Create(IReadOnlyCollection passes) => new(passes); 8 | } 9 | 10 | internal record PassDto(Guid Id, Guid CustomerId) 11 | { 12 | internal static PassDto From(Pass contract) => new(contract.Id, contract.CustomerId); 13 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.Api/PassesApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api; 2 | 3 | using Fitnet.Common.Api; 4 | 5 | internal static class PassesApiPaths 6 | { 7 | internal const string GetAll = $"{ApiPaths.Root}/passes"; 8 | internal const string MarkPassAsExpired = $"{ApiPaths.Root}/passes/{{id}}"; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.Api/PassesEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api; 2 | 3 | using GetAllPasses; 4 | using MarkPassAsExpired; 5 | using Microsoft.AspNetCore.Routing; 6 | 7 | internal static class PassesEndpoints 8 | { 9 | internal static void MapPasses(this IEndpointRouteBuilder app) 10 | { 11 | app.MapMarkPassAsExpired(); 12 | app.MapGetAllPasses(); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.Api/RegisterPass/PassRegisteredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.Api.RegisterPass; 2 | 3 | using Fitnet.Common.Infrastructure.Events; 4 | 5 | internal record PassRegisteredEvent(Guid Id, Guid PassId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 6 | { 7 | internal static PassRegisteredEvent Create(Guid passId, DateTimeOffset occurredAt) => 8 | new(Guid.NewGuid(), passId, occurredAt); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.DataAccess/Fitnet.Passes.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.DataAccess/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Diagnostics.CodeAnalysis; 2 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.IntegrationEvents/Fitnet.Passes.IntegrationEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Fitnet.Passes.IntegrationEvents/PassExpiredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Passes.IntegrationEvents; 2 | 3 | using Common.Infrastructure.Events; 4 | 5 | public record PassExpiredEvent 6 | (Guid Id, Guid PassId, Guid CustomerId, DateTimeOffset OccurredDateTime) : IIntegrationEvent 7 | { 8 | public static PassExpiredEvent Create(Guid passId, Guid customerId, DateTimeOffset occuredAt) => 9 | new(Guid.NewGuid(), passId, customerId, occuredAt); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Bogus; 2 | global using MassTransit.Testing; 3 | global using Shouldly; 4 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Passes/Tests/Fitnet.Passes.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": true 5 | }, 6 | "Reports": { 7 | "Enabled": false 8 | }, 9 | "Offers": { 10 | "Enabled": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/DataAccess/DatabaseAccessModule.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | internal static class DatabaseAccessModule 6 | { 7 | internal static IServiceCollection AddDataAccess(this IServiceCollection services) 8 | { 9 | services.AddSingleton(); 10 | 11 | return services; 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/DataAccess/IDatabaseConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.DataAccess; 2 | 3 | using System.Data; 4 | 5 | internal interface IDatabaseConnectionFactory : IDisposable 6 | { 7 | IDbConnection Create(); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/DataRetriever/INewPassesRegistrationPerMonthReportDataRetriever.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.DataRetriever; 2 | 3 | using Dtos; 4 | 5 | internal interface INewPassesRegistrationPerMonthReportDataRetriever 6 | { 7 | Task> GetReportDataAsync( 8 | CancellationToken cancellationToken = default); 9 | } 10 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthDto.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthDto(int MonthOrder, string MonthName, long RegisteredPasses); -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/GenerateNewPassesRegistrationsPerMonthReport/Dtos/NewPassesRegistrationsPerMonthResponse.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; 2 | 3 | public sealed record NewPassesRegistrationsPerMonthResponse(IReadOnlyCollection PassesRegistrationsPerMonth) 4 | { 5 | internal static NewPassesRegistrationsPerMonthResponse Create(IReadOnlyCollection passesRegistrationsPerMonth) => new(passesRegistrationsPerMonth); 6 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/ReportsApiPaths.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using Common.Api; 4 | 5 | internal static class ReportsApiPaths 6 | { 7 | private const string Reports = $"{ApiPaths.Root}/reports"; 8 | internal const string GenerateNewReport = $"{Reports}/generate"; 9 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Fitnet.Reports/ReportsEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports; 2 | 3 | using Microsoft.AspNetCore.Routing; 4 | using GenerateNewPassesRegistrationsPerMonthReport; 5 | 6 | internal static class ReportsEndpoints 7 | { 8 | internal static void MapReports(this IEndpointRouteBuilder app) => 9 | app.MapGenerateNewPassesRegistrationsPerMonthReport(); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/GenerateNewPassesPerMonthReport/TestData/PassRegistrationDateRange.cs: -------------------------------------------------------------------------------- 1 | namespace EvolutionaryArchitecture.Fitnet.Reports.IntegrationTests.GenerateNewPassesPerMonthReport.TestData; 2 | 3 | internal sealed record PassRegistrationDateRange(DateTimeOffset From, DateTimeOffset To); -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Net; 2 | global using System.Net.Http.Json; 3 | global using Bogus; 4 | global using MassTransit.Testing; 5 | global using Shouldly; 6 | -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/Reports/Tests/Fitnet.Reports.IntegrationTests/appsettings.IntegrationTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "Modules": { 3 | "Passes": { 4 | "Enabled": true 5 | }, 6 | "Reports": { 7 | "Enabled": true 8 | }, 9 | "Offers": { 10 | "Enabled": false 11 | } 12 | }, 13 | "RabbitMq": 14 | { 15 | "Uri": "", 16 | "Username": "", 17 | "Password": "" 18 | } 19 | } -------------------------------------------------------------------------------- /Chapter-4-applying-tactical-domain-driven-design/Fitnet/Src/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------