- add flight read models

- add aircraft read models
- add airport read models
This commit is contained in:
meysamhadeli 2022-07-15 18:36:41 +04:30
parent 58e93a0761
commit a6f7bd46d5
45 changed files with 566 additions and 104 deletions

View File

@ -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

View File

@ -3,6 +3,7 @@ namespace BuildingBlocks.Core.Event;
[Flags]
public enum EventType
{
IntegrationEvent = 1,
DomainEvent = 2
DomainEvent = 1,
IntegrationEvent = 2,
InternalCommand = 4
}

View File

@ -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; }
}

View File

@ -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());
}

View File

@ -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();

View File

@ -31,20 +31,22 @@ public sealed class EventDispatcher : IEventDispatcher
}
public async Task SendAsync<T>(IReadOnlyList<T> events, CancellationToken cancellationToken = default)
public async Task SendAsync<T>(IReadOnlyList<T> events, EventType eventType = default,
CancellationToken cancellationToken = default)
where T : IEvent
{
async Task PublishIntegrationEvent(IReadOnlyList<IIntegrationEvent> integrationEvents)
{
foreach (var integrationEvent in integrationEvents)
{
await _persistMessageProcessor.PublishMessageAsync(new MessageEnvelope(integrationEvent, SetHeaders()),
cancellationToken);
}
}
if (events.Count > 0)
{
async Task PublishIntegrationEvent(IReadOnlyList<IIntegrationEvent> integrationEvents)
{
foreach (var integrationEvent in integrationEvents)
{
await _persistMessageProcessor.PublishMessageAsync(
new MessageEnvelope(integrationEvent, SetHeaders()),
cancellationToken);
}
}
switch (events)
{
case IReadOnlyList<IDomainEvent> 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<IDomainEvent>)
.ConfigureAwait(false);
foreach (var internalMessage in internalMessages)
{
await _persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken);
}
}
}
}
public async Task SendAsync<T>(T @event, CancellationToken cancellationToken = default)
public async Task SendAsync<T>(T @event, EventType eventType = default,
CancellationToken cancellationToken = default)
where T : IEvent =>
await SendAsync(new[] {@event}, cancellationToken);
await SendAsync(new[] {@event}, eventType, cancellationToken);
private Task<IReadOnlyList<IIntegrationEvent>> 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<IReadOnlyList<IIntegrationEvent>>(integrationEvents);
}
private Task<IReadOnlyList<InternalCommand>> MapDomainEventToInternalCommandAsync(
IReadOnlyList<IDomainEvent> events)
{
_logger.LogTrace("Processing internal message start...");
var internalCommands = new List<InternalCommand>();
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<IReadOnlyList<InternalCommand>>(internalCommands);
}
private IEnumerable<IIntegrationEvent> GetWrappedIntegrationEvents(IReadOnlyList<IDomainEvent> domainEvents)
{
foreach (var domainEvent in domainEvents.Where(x =>

View File

@ -4,8 +4,8 @@ namespace BuildingBlocks.Core;
public interface IEventDispatcher
{
public Task SendAsync<T>(IReadOnlyList<T> events, CancellationToken cancellationToken = default)
public Task SendAsync<T>(IReadOnlyList<T> events, EventType eventType = default, CancellationToken cancellationToken = default)
where T : IEvent;
public Task SendAsync<T>(T @event, CancellationToken cancellationToken = default)
public Task SendAsync<T>(T @event, EventType eventType = default, CancellationToken cancellationToken = default)
where T : IEvent;
}

View File

@ -4,6 +4,6 @@ namespace BuildingBlocks.Core;
public interface IEventMapper
{
IIntegrationEvent Map(IDomainEvent @event);
IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events);
IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event);
InternalCommand MapToInternalCommand(IDomainEvent @event);
}

View File

@ -61,7 +61,9 @@ public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TRe
var domainEvents = _dbContextBase.GetDomainEvents();
await _eventDispatcher.SendAsync(domainEvents.ToArray(), cancellationToken);
var eventType = typeof(TRequest).IsAssignableTo(typeof(IInternalCommand)) ? EventType.InternalCommand : EventType.DomainEvent;
await _eventDispatcher.SendAsync(domainEvents.ToArray(), eventType, cancellationToken);
return response;
}

View File

@ -23,12 +23,15 @@ public static class Extensions
var mongoOptions = services.GetOptions<MongoOptions>("MongoOptions");
var logOptions = services.GetOptions<LogOptions>("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

View File

@ -7,12 +7,7 @@ namespace Booking;
public sealed class EventMapper : IEventMapper
{
public IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> 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
};
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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<CreateAircraftMongoCommand, AircraftReadModel>()
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.AircraftId, s => s.Id);
}
}

View File

@ -4,7 +4,7 @@ using MediatR;
namespace Flight.Aircrafts.Features.CreateAircraft;
public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : IRequest<AircraftResponseDto>
public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : IRequest<AircraftResponseDto>, IInternalCommand
{
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
}

View File

@ -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; }
}

View File

@ -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<CreateAircraftMongoCommand>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public CreateAircraftMongoCommandHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
_flightReadDbContext = flightReadDbContext;
_mapper = mapper;
}
public async Task<Unit> Handle(CreateAircraftMongoCommand command, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
var aircraftReadModel = _mapper.Map<AircraftReadModel>(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;
}
}

View File

@ -14,7 +14,7 @@ public class Aircraft : Aggregate<long>
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<long>
aircraft.Id,
aircraft.Name,
aircraft.Model,
aircraft.ManufacturingYear);
aircraft.ManufacturingYear,
isDeleted);
aircraft.AddDomainEvent(@event);

View File

@ -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; }
}

View File

@ -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<CreateAirportMongoCommand, AirportReadModel>()
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.AirportId, s => s.Id);
}
}

View File

@ -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;

View File

@ -4,7 +4,7 @@ using MediatR;
namespace Flight.Airports.Features.CreateAirport;
public record CreateAirportCommand(string Name, string Address, string Code) : IRequest<AirportResponseDto>
public record CreateAirportCommand(string Name, string Address, string Code) : IRequest<AirportResponseDto>, IInternalCommand
{
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
}

View File

@ -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; }
}

View File

@ -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<CreateAirportMongoCommand>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public CreateAirportMongoCommandHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
_flightReadDbContext = flightReadDbContext;
_mapper = mapper;
}
public async Task<Unit> Handle(CreateAirportMongoCommand command, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
var airportReadModel = _mapper.Map<AirportReadModel>(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;
}
}

View File

@ -14,7 +14,7 @@ public class Airport : Aggregate<long>
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<long>
airport.Id,
airport.Name,
airport.Address,
airport.Code);
airport.Code,
isDeleted);
airport.AddDomainEvent(@event);

View File

@ -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; }
}

View File

@ -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<MongoOptions> options) : base(options)
{
Flight = GetCollection<FlightReadModel>(nameof(Flight).Underscore());
Aircraft = GetCollection<AircraftReadModel>(nameof(Aircraft).Underscore());
Airport = GetCollection<AirportReadModel>(nameof(Airport).Underscore());
}
public IMongoCollection<FlightReadModel> Flight { get; }
public IMongoCollection<AircraftReadModel> Aircraft { get; }
public IMongoCollection<AirportReadModel> Airport { get; }
}

View File

@ -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<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> 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
};
}
}

View File

@ -16,7 +16,6 @@
<Folder Include="Airports\Exceptions" />
<Folder Include="Data\Migrations" />
<Folder Include="Enum" />
<Folder Include="Flights\Features\UpdateFlight" />
<Folder Include="Seats\Features\CreateSeat" />
</ItemGroup>

View File

@ -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<FlightResponseDto>
decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand<FlightResponseDto>, IInternalCommand
{
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
}

View File

@ -44,10 +44,6 @@ public class CreateFlightCommandHandler : ICommandHandler<CreateFlightCommand, F
var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken);
var createFlightMongoCommand = _mapper.Map<CreateFlightMongoCommand>(newFlight.Entity);
await _persistMessageProcessor.AddInternalMessageAsync(createFlightMongoCommand, cancellationToken);
return _mapper.Map<FlightResponseDto>(newFlight.Entity);
}
}

View File

@ -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; }
}

View File

@ -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<CreateFlightMongo
var flightReadModel = _mapper.Map<FlightReadModel>(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();

View File

@ -3,4 +3,4 @@ using Flight.Flights.Dtos;
namespace Flight.Flights.Features.DeleteFlight;
public record DeleteFlightCommand(long Id) : ICommand<FlightResponseDto>;
public record DeleteFlightCommand(long Id) : ICommand<FlightResponseDto>, IInternalCommand;

View File

@ -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; }
}

View File

@ -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<DeleteFlightMongoCommand>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public DeleteFlightMongoCommandHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
_flightReadDbContext = flightReadDbContext;
_mapper = mapper;
}
public async Task<Unit> Handle(DeleteFlightMongoCommand command, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
var flightReadModel = _mapper.Map<FlightReadModel>(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<FlightReadModel>.Update
.Set(x => x.IsDeleted, flightReadModel.IsDeleted),
cancellationToken: cancellationToken);
return Unit.Value;
}
}

View File

@ -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<Models.Flight, FlightResponseDto>();
config.NewConfig<Models.Flight, CreateFlightMongoCommand>();
config.NewConfig<CreateFlightMongoCommand, FlightReadModel>();
config.NewConfig<Models.Flight, FlightResponseDto>();
config.NewConfig<CreateFlightMongoCommand, FlightReadModel>()
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.FlightId, s => s.Id);
config.NewConfig<UpdateFlightMongoCommand, FlightReadModel>()
.Map(d => d.FlightId, s => s.Id);
config.NewConfig<DeleteFlightMongoCommand, FlightReadModel>()
.Map(d => d.FlightId, s => s.Id);
}
}

View File

@ -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; }
}

View File

@ -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<UpdateFlightMongoCommand>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public UpdateFlightMongoCommandHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
_flightReadDbContext = flightReadDbContext;
_mapper = mapper;
}
public async Task<Unit> Handle(UpdateFlightMongoCommand command, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
var flightReadModel = _mapper.Map<FlightReadModel>(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<FlightReadModel>.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;
}
}

View File

@ -7,7 +7,7 @@ using MediatR;
namespace Flight.Flights.Features.UpdateFlight;
public record UpdateFlightCommand : ICommand<FlightResponseDto>, IInvalidateCacheRequest
public record UpdateFlightCommand : ICommand<FlightResponseDto>, IInvalidateCacheRequest, IInternalCommand
{
public long Id { get; init; }
public string FlightNumber { get; init; }

View File

@ -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; }

View File

@ -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 =>
{

View File

@ -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<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events)
{
return events.Select(Map);
}
public IIntegrationEvent Map(IDomainEvent @event)
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
{
return @event switch
{
_ => null
};
}
}
public InternalCommand MapToInternalCommand(IDomainEvent @event)
{
return @event switch
{
_ => null
};
}
}

View File

@ -50,7 +50,7 @@ public class RegisterNewUserCommandHandler : ICommandHandler<RegisterNewUserComm
throw new RegisterIdentityUserException(string.Join(',', roleResult.Errors.Select(e => e.Description)));
await _eventDispatcher.SendAsync(new UserCreated(applicationUser.Id, applicationUser.FirstName + " " + applicationUser.LastName,
applicationUser.PassPortNumber), cancellationToken);
applicationUser.PassPortNumber), cancellationToken: cancellationToken);
return new RegisterNewUserResponseDto
{

View File

@ -5,16 +5,19 @@ namespace Passenger;
public sealed class EventMapper : IEventMapper
{
public IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events)
{
return events.Select(Map);
}
public IIntegrationEvent Map(IDomainEvent @event)
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
{
return @event switch
{
_ => null
};
}
}
public InternalCommand MapToInternalCommand(IDomainEvent @event)
{
return @event switch
{
_ => null
};
}
}