diff --git a/booking.rest b/booking.rest index 046ec07..390ef61 100644 --- a/booking.rest +++ b/booking.rest @@ -143,6 +143,20 @@ authorization: bearer {{Authenticate.response.body.access_token}} } ### + +### +# @name Delete_Flights +DELETE {{api-gateway}}/api/v1/flight +accept: application/json +Content-Type: application/json +authorization: bearer {{Authenticate.response.body.access_token}} + +{ + "id": 1 +} +### + + ### # @name Create_Airport POST {{api-gateway}}/api/v1/flight/airport diff --git a/src/BuildingBlocks/Core/Event/EventType.cs b/src/BuildingBlocks/Core/Event/EventType.cs index 9c0de49..818a456 100644 --- a/src/BuildingBlocks/Core/Event/EventType.cs +++ b/src/BuildingBlocks/Core/Event/EventType.cs @@ -3,6 +3,7 @@ namespace BuildingBlocks.Core.Event; [Flags] public enum EventType { - IntegrationEvent = 1, - DomainEvent = 2 + DomainEvent = 1, + IntegrationEvent = 2, + InternalCommand = 4 } diff --git a/src/BuildingBlocks/Core/Event/IInternalCommand.cs b/src/BuildingBlocks/Core/Event/IInternalCommand.cs deleted file mode 100644 index 683db88..0000000 --- a/src/BuildingBlocks/Core/Event/IInternalCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using BuildingBlocks.Core.CQRS; - -namespace BuildingBlocks.Core.Event; - -public interface IInternalCommand : ICommand -{ - long Id { get; } - DateTime OccurredOn { get; } - string Type { get; } -} diff --git a/src/BuildingBlocks/Core/Event/IInternalCommnad.cs b/src/BuildingBlocks/Core/Event/IInternalCommnad.cs new file mode 100644 index 0000000..185c0f7 --- /dev/null +++ b/src/BuildingBlocks/Core/Event/IInternalCommnad.cs @@ -0,0 +1,11 @@ +using BuildingBlocks.IdsGenerator; +using BuildingBlocks.Utils; + +public interface IInternalCommand +{ + public long Id => SnowFlakIdGenerator.NewId(); + + public DateTime OccurredOn => DateTime.Now; + + public string Type => TypeProvider.GetTypeName(GetType()); +} diff --git a/src/BuildingBlocks/Core/Event/InternalCommand.cs b/src/BuildingBlocks/Core/Event/InternalCommand.cs index 0ddcfc0..38eb014 100644 --- a/src/BuildingBlocks/Core/Event/InternalCommand.cs +++ b/src/BuildingBlocks/Core/Event/InternalCommand.cs @@ -1,9 +1,10 @@ using BuildingBlocks.IdsGenerator; using BuildingBlocks.Utils; +using ICommand = BuildingBlocks.Core.CQRS.ICommand; namespace BuildingBlocks.Core.Event; -public class InternalCommand : IInternalCommand +public class InternalCommand : IInternalCommand, ICommand { public long Id { get; set; } = SnowFlakIdGenerator.NewId(); diff --git a/src/BuildingBlocks/Core/EventDispatcher.cs b/src/BuildingBlocks/Core/EventDispatcher.cs index b41f3cb..035bec4 100644 --- a/src/BuildingBlocks/Core/EventDispatcher.cs +++ b/src/BuildingBlocks/Core/EventDispatcher.cs @@ -31,20 +31,22 @@ public sealed class EventDispatcher : IEventDispatcher } - public async Task SendAsync(IReadOnlyList events, CancellationToken cancellationToken = default) + public async Task SendAsync(IReadOnlyList events, EventType eventType = default, + CancellationToken cancellationToken = default) where T : IEvent { - async Task PublishIntegrationEvent(IReadOnlyList integrationEvents) - { - foreach (var integrationEvent in integrationEvents) - { - await _persistMessageProcessor.PublishMessageAsync(new MessageEnvelope(integrationEvent, SetHeaders()), - cancellationToken); - } - } - if (events.Count > 0) { + async Task PublishIntegrationEvent(IReadOnlyList integrationEvents) + { + foreach (var integrationEvent in integrationEvents) + { + await _persistMessageProcessor.PublishMessageAsync( + new MessageEnvelope(integrationEvent, SetHeaders()), + cancellationToken); + } + } + switch (events) { case IReadOnlyList domainEvents: @@ -60,12 +62,24 @@ public sealed class EventDispatcher : IEventDispatcher await PublishIntegrationEvent(integrationEvents); break; } + + if (eventType == EventType.InternalCommand) + { + var internalMessages = await MapDomainEventToInternalCommandAsync(events as IReadOnlyList) + .ConfigureAwait(false); + + foreach (var internalMessage in internalMessages) + { + await _persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken); + } + } } } - public async Task SendAsync(T @event, CancellationToken cancellationToken = default) + public async Task SendAsync(T @event, EventType eventType = default, + CancellationToken cancellationToken = default) where T : IEvent => - await SendAsync(new[] {@event}, cancellationToken); + await SendAsync(new[] {@event}, eventType, cancellationToken); private Task> MapDomainEventToIntegrationEventAsync( @@ -84,7 +98,7 @@ public sealed class EventDispatcher : IEventDispatcher var eventType = @event.GetType(); _logger.LogTrace($"Handling domain event: {eventType.Name}"); - var integrationEvent = _eventMapper.Map(@event); + var integrationEvent = _eventMapper.MapToIntegrationEvent(@event); if (integrationEvent is null) continue; @@ -96,6 +110,31 @@ public sealed class EventDispatcher : IEventDispatcher return Task.FromResult>(integrationEvents); } + + private Task> MapDomainEventToInternalCommandAsync( + IReadOnlyList events) + { + _logger.LogTrace("Processing internal message start..."); + + var internalCommands = new List(); + using var scope = _serviceScopeFactory.CreateScope(); + foreach (var @event in events) + { + var eventType = @event.GetType(); + _logger.LogTrace($"Handling domain event: {eventType.Name}"); + + var integrationEvent = _eventMapper.MapToInternalCommand(@event); + + if (integrationEvent is null) continue; + + internalCommands.Add(integrationEvent); + } + + _logger.LogTrace("Processing internal message done..."); + + return Task.FromResult>(internalCommands); + } + private IEnumerable GetWrappedIntegrationEvents(IReadOnlyList domainEvents) { foreach (var domainEvent in domainEvents.Where(x => diff --git a/src/BuildingBlocks/Core/IEventDispatcher.cs b/src/BuildingBlocks/Core/IEventDispatcher.cs index 44a4f94..4e7a795 100644 --- a/src/BuildingBlocks/Core/IEventDispatcher.cs +++ b/src/BuildingBlocks/Core/IEventDispatcher.cs @@ -4,8 +4,8 @@ namespace BuildingBlocks.Core; public interface IEventDispatcher { - public Task SendAsync(IReadOnlyList events, CancellationToken cancellationToken = default) + public Task SendAsync(IReadOnlyList events, EventType eventType = default, CancellationToken cancellationToken = default) where T : IEvent; - public Task SendAsync(T @event, CancellationToken cancellationToken = default) + public Task SendAsync(T @event, EventType eventType = default, CancellationToken cancellationToken = default) where T : IEvent; } diff --git a/src/BuildingBlocks/Core/IEventMapper.cs b/src/BuildingBlocks/Core/IEventMapper.cs index aa5d359..26f552c 100644 --- a/src/BuildingBlocks/Core/IEventMapper.cs +++ b/src/BuildingBlocks/Core/IEventMapper.cs @@ -4,6 +4,6 @@ namespace BuildingBlocks.Core; public interface IEventMapper { - IIntegrationEvent Map(IDomainEvent @event); - IEnumerable MapAll(IEnumerable events); + IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event); + InternalCommand MapToInternalCommand(IDomainEvent @event); } diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index 6916c57..060c954 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -61,7 +61,9 @@ public class EfTxBehavior : IPipelineBehavior("MongoOptions"); var logOptions = services.GetOptions("LogOptions"); - services.AddHealthChecks() + var healthChecksBuilder = services.AddHealthChecks() .AddSqlServer(sqlOptions.DefaultConnection) - .AddMongoDb(mongoOptions.ConnectionString) .AddRabbitMQ(rabbitConnectionString: $"amqp://{rabbitMqOptions.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}") .AddElasticsearch(logOptions.ElasticUri); + if (mongoOptions.ConnectionString is not null) + healthChecksBuilder.AddMongoDb(mongoOptions.ConnectionString); + + services.AddHealthChecksUI(setup => { setup.SetEvaluationTimeInSeconds(60); // time in seconds between check diff --git a/src/Services/Booking/src/Booking/EventMapper.cs b/src/Services/Booking/src/Booking/EventMapper.cs index 2de0fee..2435c35 100644 --- a/src/Services/Booking/src/Booking/EventMapper.cs +++ b/src/Services/Booking/src/Booking/EventMapper.cs @@ -7,12 +7,7 @@ namespace Booking; public sealed class EventMapper : IEventMapper { - public IEnumerable MapAll(IEnumerable events) - { - return events.Select(Map); - } - - public IIntegrationEvent Map(IDomainEvent @event) + public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event) { return @event switch { @@ -20,4 +15,12 @@ public sealed class EventMapper : IEventMapper _ => null }; } + + public InternalCommand MapToInternalCommand(IDomainEvent @event) + { + return @event switch + { + _ => null + }; + } } diff --git a/src/Services/Flight/src/Flight.Api/Program.cs b/src/Services/Flight/src/Flight.Api/Program.cs index 1e29209..540ed5d 100644 --- a/src/Services/Flight/src/Flight.Api/Program.cs +++ b/src/Services/Flight/src/Flight.Api/Program.cs @@ -20,9 +20,7 @@ using Flight.Data; using Flight.Data.Seed; using Flight.Extensions; using FluentValidation; -using HealthChecks.UI.Client; using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Prometheus; using Serilog; diff --git a/src/Services/Flight/src/Flight/Aircrafts/Events/AircraftCreatedDomainEvent.cs b/src/Services/Flight/src/Flight/Aircrafts/Events/AircraftCreatedDomainEvent.cs index e3dbdb7..0a90be0 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Events/AircraftCreatedDomainEvent.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Events/AircraftCreatedDomainEvent.cs @@ -2,4 +2,4 @@ using BuildingBlocks.Core.Event; namespace Flight.Aircrafts.Events; -public record AircraftCreatedDomainEvent(long Id, string Name, string Model, int ManufacturingYear) : IDomainEvent; +public record AircraftCreatedDomainEvent(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent; diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs new file mode 100644 index 0000000..288ffad --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs @@ -0,0 +1,16 @@ +using BuildingBlocks.IdsGenerator; +using Flight.Aircrafts.Features.CreateAircraft.Reads; +using Flight.Aircrafts.Models.Reads; +using Mapster; + +namespace Flight.Aircrafts.Features; + +public class AircraftMappings : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.AircraftId, s => s.Id); + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs index 260ab03..2777974 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/CreateAircraftCommand.cs @@ -4,7 +4,7 @@ using MediatR; namespace Flight.Aircrafts.Features.CreateAircraft; -public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : IRequest +public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : IRequest, IInternalCommand { public long Id { get; set; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/Reads/CreateAircraftMongoCommand.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/Reads/CreateAircraftMongoCommand.cs new file mode 100644 index 0000000..25acf47 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/Reads/CreateAircraftMongoCommand.cs @@ -0,0 +1,22 @@ +using BuildingBlocks.Core.Event; + +namespace Flight.Aircrafts.Features.CreateAircraft.Reads; + +public class CreateAircraftMongoCommand : InternalCommand +{ + public CreateAircraftMongoCommand(long id, string name, string model, int manufacturingYear, bool isDeleted) + { + Id = id; + Name = name; + Model = model; + ManufacturingYear = manufacturingYear; + IsDeleted = isDeleted; + } + + + public long Id { get; } + public string Name { get; } + public string Model { get; } + public int ManufacturingYear { get; } + public bool IsDeleted { get; } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/Reads/CreateAircraftMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/Reads/CreateAircraftMongoCommandHandler.cs new file mode 100644 index 0000000..93e805c --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreateAircraft/Reads/CreateAircraftMongoCommandHandler.cs @@ -0,0 +1,44 @@ +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using BuildingBlocks.Core.CQRS; +using Flight.Aircrafts.Exceptions; +using Flight.Aircrafts.Models.Reads; +using Flight.Data; +using MapsterMapper; +using MediatR; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace Flight.Aircrafts.Features.CreateAircraft.Reads; + +public class CreateAircraftMongoCommandHandler : ICommandHandler +{ + private readonly FlightReadDbContext _flightReadDbContext; + private readonly IMapper _mapper; + + public CreateAircraftMongoCommandHandler( + FlightReadDbContext flightReadDbContext, + IMapper mapper) + { + _flightReadDbContext = flightReadDbContext; + _mapper = mapper; + } + + public async Task Handle(CreateAircraftMongoCommand command, CancellationToken cancellationToken) + { + Guard.Against.Null(command, nameof(command)); + + var aircraftReadModel = _mapper.Map(command); + + var aircraft = await _flightReadDbContext.Aircraft.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == aircraftReadModel.Id, cancellationToken); + + if (aircraft is not null) + throw new AircraftAlreadyExistException(); + + await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs index 2d04d75..91cb225 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs @@ -14,7 +14,7 @@ public class Aircraft : Aggregate public string Model { get; private set; } public int ManufacturingYear { get; private set; } - public static Aircraft Create(long id, string name, string model, int manufacturingYear) + public static Aircraft Create(long id, string name, string model, int manufacturingYear, bool isDeleted = false) { var aircraft = new Aircraft { @@ -28,7 +28,8 @@ public class Aircraft : Aggregate aircraft.Id, aircraft.Name, aircraft.Model, - aircraft.ManufacturingYear); + aircraft.ManufacturingYear, + isDeleted); aircraft.AddDomainEvent(@event); diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/Reads/AircraftReadModel.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/Reads/AircraftReadModel.cs new file mode 100644 index 0000000..c444229 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/Reads/AircraftReadModel.cs @@ -0,0 +1,11 @@ +namespace Flight.Aircrafts.Models.Reads; + +public class AircraftReadModel +{ + public long Id { get; init; } + public long AircraftId { get; init; } + public string Name { get; init; } + public string Model { get; init; } + public int ManufacturingYear { get; init; } + public bool IsDeleted { get; init; } +} diff --git a/src/Services/Flight/src/Flight/Airports/AirportMappings.cs b/src/Services/Flight/src/Flight/Airports/AirportMappings.cs new file mode 100644 index 0000000..daf0e3c --- /dev/null +++ b/src/Services/Flight/src/Flight/Airports/AirportMappings.cs @@ -0,0 +1,16 @@ +using BuildingBlocks.IdsGenerator; +using Flight.Airports.Features.CreateAirport.Reads; +using Flight.Airports.Models.Reads; +using Mapster; + +namespace Flight.Airports; + +public class AirportMappings : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.AirportId, s => s.Id); + } +} diff --git a/src/Services/Flight/src/Flight/Airports/Events/AirportCreatedDomainEvent.cs b/src/Services/Flight/src/Flight/Airports/Events/AirportCreatedDomainEvent.cs index e04c281..d6b9285 100644 --- a/src/Services/Flight/src/Flight/Airports/Events/AirportCreatedDomainEvent.cs +++ b/src/Services/Flight/src/Flight/Airports/Events/AirportCreatedDomainEvent.cs @@ -2,4 +2,4 @@ using BuildingBlocks.Core.Event; namespace Flight.Airports.Events; -public record AirportCreatedDomainEvent(long Id, string Name, string Address, string Code) : IDomainEvent; +public record AirportCreatedDomainEvent(long Id, string Name, string Address, string Code, bool IsDeleted) : IDomainEvent; diff --git a/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs index 2f80cfa..b86d5d4 100644 --- a/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs +++ b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/CreateAirportCommand.cs @@ -4,7 +4,7 @@ using MediatR; namespace Flight.Airports.Features.CreateAirport; -public record CreateAirportCommand(string Name, string Address, string Code) : IRequest +public record CreateAirportCommand(string Name, string Address, string Code) : IRequest, IInternalCommand { public long Id { get; set; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/Reads/CreateAirportMongoCommand.cs b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/Reads/CreateAirportMongoCommand.cs new file mode 100644 index 0000000..f67988a --- /dev/null +++ b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/Reads/CreateAirportMongoCommand.cs @@ -0,0 +1,21 @@ +using BuildingBlocks.Core.Event; + +namespace Flight.Airports.Features.CreateAirport.Reads; + +public class CreateAirportMongoCommand : InternalCommand +{ + public CreateAirportMongoCommand(long id, string name, string address, string code, bool isDeleted) + { + Id = id; + Name = name; + Address = address; + Code = code; + IsDeleted = isDeleted; + } + + public long Id { get; } + public string Name { get; } + public string Address { get; } + public string Code { get; } + public bool IsDeleted { get; } +} diff --git a/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/Reads/CreateAirportMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/Reads/CreateAirportMongoCommandHandler.cs new file mode 100644 index 0000000..3ceab97 --- /dev/null +++ b/src/Services/Flight/src/Flight/Airports/Features/CreateAirport/Reads/CreateAirportMongoCommandHandler.cs @@ -0,0 +1,44 @@ +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using BuildingBlocks.Core.CQRS; +using Flight.Airports.Exceptions; +using Flight.Airports.Models.Reads; +using Flight.Data; +using MapsterMapper; +using MediatR; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace Flight.Airports.Features.CreateAirport.Reads; + +public class CreateAirportMongoCommandHandler : ICommandHandler +{ + private readonly FlightReadDbContext _flightReadDbContext; + private readonly IMapper _mapper; + + public CreateAirportMongoCommandHandler( + FlightReadDbContext flightReadDbContext, + IMapper mapper) + { + _flightReadDbContext = flightReadDbContext; + _mapper = mapper; + } + + public async Task Handle(CreateAirportMongoCommand command, CancellationToken cancellationToken) + { + Guard.Against.Null(command, nameof(command)); + + var airportReadModel = _mapper.Map(command); + + var aircraft = await _flightReadDbContext.Airport.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == airportReadModel.Id, cancellationToken); + + if (aircraft is not null) + throw new AirportAlreadyExistException(); + + await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Airports/Models/Airport.cs b/src/Services/Flight/src/Flight/Airports/Models/Airport.cs index ca33b8f..2dcc4b0 100644 --- a/src/Services/Flight/src/Flight/Airports/Models/Airport.cs +++ b/src/Services/Flight/src/Flight/Airports/Models/Airport.cs @@ -14,7 +14,7 @@ public class Airport : Aggregate public string Address { get; private set; } public string Code { get; private set; } - public static Airport Create(long id, string name, string address, string code) + public static Airport Create(long id, string name, string address, string code, bool isDeleted = false) { var airport = new Airport { @@ -28,7 +28,8 @@ public class Airport : Aggregate airport.Id, airport.Name, airport.Address, - airport.Code); + airport.Code, + isDeleted); airport.AddDomainEvent(@event); diff --git a/src/Services/Flight/src/Flight/Airports/Models/Reads/AirportReadModel.cs b/src/Services/Flight/src/Flight/Airports/Models/Reads/AirportReadModel.cs new file mode 100644 index 0000000..e94d320 --- /dev/null +++ b/src/Services/Flight/src/Flight/Airports/Models/Reads/AirportReadModel.cs @@ -0,0 +1,11 @@ +namespace Flight.Airports.Models.Reads; + +public class AirportReadModel +{ + public long Id { get; init; } + public long AirportId { get; init; } + public string Name { get; init; } + public string Address { get; init; } + public string Code { get; init; } + public bool IsDeleted { get; set; } +} diff --git a/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs b/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs index 1b18e7b..7202e2a 100644 --- a/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs +++ b/src/Services/Flight/src/Flight/Data/FlightReadDbContext.cs @@ -1,4 +1,7 @@ using BuildingBlocks.Mongo; +using Flight.Aircrafts.Models.Reads; +using Flight.Airports.Models; +using Flight.Airports.Models.Reads; using Flight.Flights.Models.Reads; using Humanizer; using Microsoft.Extensions.Options; @@ -11,7 +14,11 @@ public class FlightReadDbContext : MongoDbContext public FlightReadDbContext(IOptions options) : base(options) { Flight = GetCollection(nameof(Flight).Underscore()); + Aircraft = GetCollection(nameof(Aircraft).Underscore()); + Airport = GetCollection(nameof(Airport).Underscore()); } public IMongoCollection Flight { get; } + public IMongoCollection Aircraft { get; } + public IMongoCollection Airport { get; } } diff --git a/src/Services/Flight/src/Flight/EventMapper.cs b/src/Services/Flight/src/Flight/EventMapper.cs index ad0229b..8b1fbc9 100644 --- a/src/Services/Flight/src/Flight/EventMapper.cs +++ b/src/Services/Flight/src/Flight/EventMapper.cs @@ -1,20 +1,21 @@ -using System.Collections.Generic; -using System.Linq; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Core; using BuildingBlocks.Core.Event; using Flight.Aircrafts.Events; +using Flight.Aircrafts.Features.CreateAircraft.Reads; using Flight.Airports.Events; +using Flight.Airports.Features.CreateAirport.Reads; using Flight.Flights.Events.Domain; +using Flight.Flights.Features.CreateFlight.Reads; +using Flight.Flights.Features.DeleteFlight.Reads; +using Flight.Flights.Features.UpdateFlight.Reads; namespace Flight; // ref: https://www.ledjonbehluli.com/posts/domain_to_integration_event/ public sealed class EventMapper : IEventMapper { - public IEnumerable MapAll(IEnumerable events) => events.Select(Map); - - public IIntegrationEvent Map(IDomainEvent @event) + public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event) { return @event switch { @@ -26,4 +27,20 @@ public sealed class EventMapper : IEventMapper _ => null }; } + + public InternalCommand MapToInternalCommand(IDomainEvent @event) + { + return @event switch + { + FlightCreatedDomainEvent e => new CreateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId, + e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted), + FlightUpdatedDomainEvent e => new UpdateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId, + e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted), + FlightDeletedDomainEvent e => new DeleteFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId, + e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted), + AircraftCreatedDomainEvent e => new CreateAircraftMongoCommand(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted), + AirportCreatedDomainEvent e => new CreateAirportMongoCommand(e.Id, e.Name, e.Address, e.Code, e.IsDeleted), + _ => null + }; + } } diff --git a/src/Services/Flight/src/Flight/Flight.csproj b/src/Services/Flight/src/Flight/Flight.csproj index b252a70..46314ef 100644 --- a/src/Services/Flight/src/Flight/Flight.csproj +++ b/src/Services/Flight/src/Flight/Flight.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs index 7698298..dff8e1f 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommand.cs @@ -8,7 +8,7 @@ namespace Flight.Flights.Features.CreateFlight; public record CreateFlightCommand(string FlightNumber, long AircraftId, long DepartureAirportId, DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, - decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand + decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand, IInternalCommand { public long Id { get; set; } = SnowFlakIdGenerator.NewId(); } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs index 4f942ab..a655213 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs @@ -44,10 +44,6 @@ public class CreateFlightCommandHandler : ICommandHandler(newFlight.Entity); - - await _persistMessageProcessor.AddInternalMessageAsync(createFlightMongoCommand, cancellationToken); - return _mapper.Map(newFlight.Entity); } } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommand.cs index 5108853..060e833 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommand.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommand.cs @@ -6,12 +6,8 @@ namespace Flight.Flights.Features.CreateFlight.Reads; public class CreateFlightMongoCommand : InternalCommand { - public CreateFlightMongoCommand() - { - - } - - public CreateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, long DepartureAirportId, + public CreateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, + long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price, bool IsDeleted) { @@ -29,15 +25,15 @@ public class CreateFlightMongoCommand : InternalCommand this.IsDeleted = IsDeleted; } - public string FlightNumber { get; init; } - public long AircraftId { get; init; } - public DateTime DepartureDate { get; init; } - public long DepartureAirportId { get; init; } - public DateTime ArriveDate { get; init; } - public long ArriveAirportId { get; init; } - public decimal DurationMinutes { get; init; } - public DateTime FlightDate { get; init; } - public FlightStatus Status { get; init; } - public decimal Price { get; init; } - public bool IsDeleted { get; init; } + public string FlightNumber { get; } + public long AircraftId { get; } + public DateTime DepartureDate { get; } + public long DepartureAirportId { get; } + public DateTime ArriveDate { get; } + public long ArriveAirportId { get; } + public decimal DurationMinutes { get; } + public DateTime FlightDate { get; } + public FlightStatus Status { get; } + public decimal Price { get; } + public bool IsDeleted { get; } } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs index e48549f..071b542 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/Reads/CreateFlightMongoCommandHandler.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; @@ -33,7 +32,7 @@ public class CreateFlightMongoCommandHandler : ICommandHandler(command); var flight = await _flightReadDbContext.Flight.AsQueryable() - .FirstOrDefaultAsync(x => x.Id == command.Id, cancellationToken); + .FirstOrDefaultAsync(x => x.Id == flightReadModel.Id, cancellationToken); if (flight is not null) throw new FlightAlreadyExistException(); diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommand.cs index d5fc3b1..efa9aaf 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommand.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommand.cs @@ -3,4 +3,4 @@ using Flight.Flights.Dtos; namespace Flight.Flights.Features.DeleteFlight; -public record DeleteFlightCommand(long Id) : ICommand; +public record DeleteFlightCommand(long Id) : ICommand, IInternalCommand; diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/Reads/DeleteFlightMongoCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/Reads/DeleteFlightMongoCommand.cs new file mode 100644 index 0000000..00312a5 --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/Reads/DeleteFlightMongoCommand.cs @@ -0,0 +1,39 @@ +using System; +using BuildingBlocks.Core.Event; +using Flight.Flights.Models; + +namespace Flight.Flights.Features.DeleteFlight.Reads; + +public class DeleteFlightMongoCommand : InternalCommand +{ + public DeleteFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, + long DepartureAirportId, + DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, + decimal Price, bool IsDeleted) + { + this.Id = Id; + this.FlightNumber = FlightNumber; + this.AircraftId = AircraftId; + this.DepartureDate = DepartureDate; + this.DepartureAirportId = DepartureAirportId; + this.ArriveDate = ArriveDate; + this.ArriveAirportId = ArriveAirportId; + this.DurationMinutes = DurationMinutes; + this.FlightDate = FlightDate; + this.Status = Status; + this.Price = Price; + this.IsDeleted = IsDeleted; + } + + public string FlightNumber { get; } + public long AircraftId { get; } + public DateTime DepartureDate { get; } + public long DepartureAirportId { get; } + public DateTime ArriveDate { get; } + public long ArriveAirportId { get; } + public decimal DurationMinutes { get; } + public DateTime FlightDate { get; } + public FlightStatus Status { get; } + public decimal Price { get; } + public bool IsDeleted { get; } +} diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/Reads/DeleteFlightMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/Reads/DeleteFlightMongoCommandHandler.cs new file mode 100644 index 0000000..b38a98b --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/Reads/DeleteFlightMongoCommandHandler.cs @@ -0,0 +1,48 @@ +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using BuildingBlocks.Core.CQRS; +using Flight.Data; +using Flight.Flights.Exceptions; +using Flight.Flights.Models.Reads; +using MapsterMapper; +using MediatR; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace Flight.Flights.Features.DeleteFlight.Reads; + +public class DeleteFlightMongoCommandHandler : ICommandHandler +{ + private readonly FlightReadDbContext _flightReadDbContext; + private readonly IMapper _mapper; + + public DeleteFlightMongoCommandHandler( + FlightReadDbContext flightReadDbContext, + IMapper mapper) + { + _flightReadDbContext = flightReadDbContext; + _mapper = mapper; + } + + public async Task Handle(DeleteFlightMongoCommand command, CancellationToken cancellationToken) + { + Guard.Against.Null(command, nameof(command)); + + var flightReadModel = _mapper.Map(command); + + var flight = await _flightReadDbContext.Flight.AsQueryable() + .FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId, cancellationToken); + + if (flight is null) + throw new FlightNotFountException(); + + await _flightReadDbContext.Flight.UpdateOneAsync( + x => x.FlightId == flightReadModel.FlightId, + Builders.Update + .Set(x => x.IsDeleted, flightReadModel.IsDeleted), + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs b/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs index adc1005..7a9ec98 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/FlightMappings.cs @@ -1,17 +1,25 @@ using AutoMapper; +using BuildingBlocks.IdsGenerator; using Flight.Flights.Dtos; using Flight.Flights.Features.CreateFlight.Reads; +using Flight.Flights.Features.DeleteFlight.Reads; +using Flight.Flights.Features.UpdateFlight.Reads; using Flight.Flights.Models.Reads; using Mapster; namespace Flight.Flights.Features; -public class FlightMappings : Profile +public class FlightMappings : IRegister { public void Register(TypeAdapterConfig config) { - config.NewConfig(); - config.NewConfig(); - config.NewConfig(); + config.NewConfig(); + config.NewConfig() + .Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) + .Map(d => d.FlightId, s => s.Id); + config.NewConfig() + .Map(d => d.FlightId, s => s.Id); + config.NewConfig() + .Map(d => d.FlightId, s => s.Id); } } diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommand.cs new file mode 100644 index 0000000..12ff399 --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommand.cs @@ -0,0 +1,39 @@ +using System; +using BuildingBlocks.Core.Event; +using Flight.Flights.Models; + +namespace Flight.Flights.Features.UpdateFlight.Reads; + +public class UpdateFlightMongoCommand : InternalCommand +{ + public UpdateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, + long DepartureAirportId, + DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, + decimal Price, bool IsDeleted) + { + this.Id = Id; + this.FlightNumber = FlightNumber; + this.AircraftId = AircraftId; + this.DepartureDate = DepartureDate; + this.DepartureAirportId = DepartureAirportId; + this.ArriveDate = ArriveDate; + this.ArriveAirportId = ArriveAirportId; + this.DurationMinutes = DurationMinutes; + this.FlightDate = FlightDate; + this.Status = Status; + this.Price = Price; + this.IsDeleted = IsDeleted; + } + + public string FlightNumber { get; } + public long AircraftId { get; } + public DateTime DepartureDate { get; } + public long DepartureAirportId { get; } + public DateTime ArriveDate { get; } + public long ArriveAirportId { get; } + public decimal DurationMinutes { get; } + public DateTime FlightDate { get; } + public FlightStatus Status { get; } + public decimal Price { get; } + public bool IsDeleted { get; } +} diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs new file mode 100644 index 0000000..c1e606d --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/Reads/UpdateFlightMongoCommandHandler.cs @@ -0,0 +1,60 @@ +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using BuildingBlocks.Core.CQRS; +using Flight.Data; +using Flight.Flights.Exceptions; +using Flight.Flights.Models.Reads; +using MapsterMapper; +using MediatR; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace Flight.Flights.Features.UpdateFlight.Reads; + +public class UpdateFlightMongoCommandHandler : ICommandHandler +{ + private readonly FlightReadDbContext _flightReadDbContext; + private readonly IMapper _mapper; + + public UpdateFlightMongoCommandHandler( + FlightReadDbContext flightReadDbContext, + IMapper mapper) + { + _flightReadDbContext = flightReadDbContext; + _mapper = mapper; + } + + public async Task Handle(UpdateFlightMongoCommand command, CancellationToken cancellationToken) + { + Guard.Against.Null(command, nameof(command)); + + var flightReadModel = _mapper.Map(command); + + var flight = await _flightReadDbContext.Flight.AsQueryable() + .FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId, cancellationToken); + + if (flight is null) + throw new FlightNotFountException(); + + await _flightReadDbContext.Flight.UpdateOneAsync( + x => x.FlightId == flightReadModel.FlightId, + Builders.Update + .Set(x => x.Id, flightReadModel.Id) + .Set(x => x.Price, flightReadModel.Price) + .Set(x => x.ArriveDate, flightReadModel.ArriveDate) + .Set(x => x.AircraftId, flightReadModel.AircraftId) + .Set(x => x.DurationMinutes, flightReadModel.DurationMinutes) + .Set(x => x.DepartureDate, flightReadModel.DepartureDate) + .Set(x => x.FlightDate, flightReadModel.FlightDate) + .Set(x => x.FlightId, flightReadModel.FlightId) + .Set(x => x.FlightNumber, flightReadModel.FlightNumber) + .Set(x => x.IsDeleted, flightReadModel.IsDeleted) + .Set(x => x.Status, flightReadModel.Status) + .Set(x => x.ArriveAirportId, flightReadModel.ArriveAirportId) + .Set(x => x.DepartureAirportId, flightReadModel.DepartureAirportId), + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommand.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommand.cs index c51c0cb..3ad01b1 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommand.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommand.cs @@ -7,7 +7,7 @@ using MediatR; namespace Flight.Flights.Features.UpdateFlight; -public record UpdateFlightCommand : ICommand, IInvalidateCacheRequest +public record UpdateFlightCommand : ICommand, IInvalidateCacheRequest, IInternalCommand { public long Id { get; init; } public string FlightNumber { get; init; } diff --git a/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs b/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs index 60a062a..2f14ef9 100644 --- a/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs +++ b/src/Services/Flight/src/Flight/Flights/Models/Reads/FlightReadModel.cs @@ -7,6 +7,7 @@ namespace Flight.Flights.Models.Reads; public class FlightReadModel { public long Id { get; init; } + public long FlightId { get; set; } public string FlightNumber { get; init; } public long AircraftId { get; init; } public DateTime DepartureDate { get; init; } diff --git a/src/Services/Identity/src/Identity.Api/Program.cs b/src/Services/Identity/src/Identity.Api/Program.cs index 0b8a0ee..e0e3e45 100644 --- a/src/Services/Identity/src/Identity.Api/Program.cs +++ b/src/Services/Identity/src/Identity.Api/Program.cs @@ -63,10 +63,10 @@ app.UseRouting(); app.UseHttpMetrics(); app.UseProblemDetails(); app.UseHttpsRedirection(); +app.UseCustomHealthCheck(); app.UseAuthentication(); app.UseAuthorization(); app.UseIdentityServer(); -app.UseCustomHealthCheck(); app.UseEndpoints(endpoints => { diff --git a/src/Services/Identity/src/Identity/EventMapper.cs b/src/Services/Identity/src/Identity/EventMapper.cs index f8295c8..f4d593a 100644 --- a/src/Services/Identity/src/Identity/EventMapper.cs +++ b/src/Services/Identity/src/Identity/EventMapper.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using BuildingBlocks.Core; using BuildingBlocks.Core.Event; @@ -7,16 +5,19 @@ namespace Identity; public sealed class EventMapper : IEventMapper { - public IEnumerable MapAll(IEnumerable events) - { - return events.Select(Map); - } - - public IIntegrationEvent Map(IDomainEvent @event) + public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event) { return @event switch { _ => null }; } -} \ No newline at end of file + + public InternalCommand MapToInternalCommand(IDomainEvent @event) + { + return @event switch + { + _ => null + }; + } +} diff --git a/src/Services/Identity/src/Identity/Identity/Features/RegisterNewUser/RegisterNewUserCommandHandler.cs b/src/Services/Identity/src/Identity/Identity/Features/RegisterNewUser/RegisterNewUserCommandHandler.cs index 5088d5c..2a35c2b 100644 --- a/src/Services/Identity/src/Identity/Identity/Features/RegisterNewUser/RegisterNewUserCommandHandler.cs +++ b/src/Services/Identity/src/Identity/Identity/Features/RegisterNewUser/RegisterNewUserCommandHandler.cs @@ -50,7 +50,7 @@ public class RegisterNewUserCommandHandler : ICommandHandler e.Description))); await _eventDispatcher.SendAsync(new UserCreated(applicationUser.Id, applicationUser.FirstName + " " + applicationUser.LastName, - applicationUser.PassPortNumber), cancellationToken); + applicationUser.PassPortNumber), cancellationToken: cancellationToken); return new RegisterNewUserResponseDto { diff --git a/src/Services/Passenger/src/Passenger/EventMapper.cs b/src/Services/Passenger/src/Passenger/EventMapper.cs index a11f997..13a9f30 100644 --- a/src/Services/Passenger/src/Passenger/EventMapper.cs +++ b/src/Services/Passenger/src/Passenger/EventMapper.cs @@ -5,16 +5,19 @@ namespace Passenger; public sealed class EventMapper : IEventMapper { - public IEnumerable MapAll(IEnumerable events) - { - return events.Select(Map); - } - - public IIntegrationEvent Map(IDomainEvent @event) + public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event) { return @event switch { _ => null }; } -} \ No newline at end of file + + public InternalCommand MapToInternalCommand(IDomainEvent @event) + { + return @event switch + { + _ => null + }; + } +}