mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-05-02 19:02:55 +08:00
Merge pull request #51 from meysamhadeli/develop
change grpc service from magic-onion to grpc-dotnet
This commit is contained in:
commit
dacd6cbeed
@ -73,7 +73,7 @@ High-level plan is represented in the table
|
|||||||
- ✔️ **[`IdGen`](https://github.com/RobThree/IdGen)** - Twitter Snowflake-alike ID generator for .Net
|
- ✔️ **[`IdGen`](https://github.com/RobThree/IdGen)** - Twitter Snowflake-alike ID generator for .Net
|
||||||
- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET
|
- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET
|
||||||
- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier
|
- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier
|
||||||
- ✔️ **[`MagicOnion`](https://github.com/Cysharp/MagicOnion)** - gRPC based HTTP/2 RPC Streaming Framework for .NET, .NET Core and Unity.
|
- ✔️ **[`gRPC-dotnet`](https://github.com/grpc/grpc-dotnet)** - gRPC functionality for .NET.
|
||||||
- ✔️ **[`EventStore`](https://github.com/EventStore/EventStore)** - The open-source, functional database with Complex Event Processing.
|
- ✔️ **[`EventStore`](https://github.com/EventStore/EventStore)** - The open-source, functional database with Complex Event Processing.
|
||||||
- ✔️ **[`MongoDB.Driver`](https://github.com/mongodb/mongo-csharp-driver)** - .NET Driver for MongoDB.
|
- ✔️ **[`MongoDB.Driver`](https://github.com/mongodb/mongo-csharp-driver)** - .NET Driver for MongoDB.
|
||||||
- ✔️ **[`xUnit.net`](https://github.com/xunit/xunit)** - A free, open source, community-focused unit testing tool for the .NET Framework.
|
- ✔️ **[`xUnit.net`](https://github.com/xunit/xunit)** - A free, open source, community-focused unit testing tool for the .NET Framework.
|
||||||
|
|||||||
@ -251,7 +251,7 @@ Content-Type: application/json
|
|||||||
authorization: bearer {{Authenticate.response.body.access_token}}
|
authorization: bearer {{Authenticate.response.body.access_token}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"passengerId": 4776722699124736,
|
"passengerId": 4779536520052736,
|
||||||
"flightId": 1,
|
"flightId": 1,
|
||||||
"description": "I want to fly to iran"
|
"description": "I want to fly to iran"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,10 +21,7 @@
|
|||||||
<PackageReference Include="Figgle" Version="0.4.0" />
|
<PackageReference Include="Figgle" Version="0.4.0" />
|
||||||
<PackageReference Include="FluentValidation" Version="10.3.6" />
|
<PackageReference Include="FluentValidation" Version="10.3.6" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6" />
|
||||||
<PackageReference Include="MagicOnion" Version="4.4.0" />
|
<PackageReference Include="Grpc.Core.Testing" Version="2.46.5" />
|
||||||
<PackageReference Include="MagicOnion.Abstractions" Version="4.4.0" />
|
|
||||||
<PackageReference Include="MagicOnion.Client" Version="4.4.0" />
|
|
||||||
<PackageReference Include="MagicOnion.Server" Version="4.4.0" />
|
|
||||||
<PackageReference Include="Mongo2Go" Version="3.1.3" />
|
<PackageReference Include="Mongo2Go" Version="3.1.3" />
|
||||||
<PackageReference Include="NSubstitute" Version="4.3.0" />
|
<PackageReference Include="NSubstitute" Version="4.3.0" />
|
||||||
<PackageReference Include="Polly" Version="7.2.3" />
|
<PackageReference Include="Polly" Version="7.2.3" />
|
||||||
@ -124,6 +121,10 @@
|
|||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||||
|
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.21.7" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
|
||||||
|
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.49.0" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
using MagicOnion;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.Contracts.Grpc;
|
|
||||||
|
|
||||||
|
|
||||||
public interface IFlightGrpcService : IService<IFlightGrpcService>
|
|
||||||
{
|
|
||||||
UnaryResult<FlightResponseDto> GetById(long id);
|
|
||||||
UnaryResult<IEnumerable<SeatResponseDto>> GetAvailableSeats(long flightId);
|
|
||||||
UnaryResult<SeatResponseDto> ReserveSeat(ReserveSeatRequestDto request);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public class ReserveSeatRequestDto
|
|
||||||
{
|
|
||||||
[Key(0)]
|
|
||||||
public long FlightId { get; set; }
|
|
||||||
[Key(1)]
|
|
||||||
public string SeatNumber { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public record SeatResponseDto
|
|
||||||
{
|
|
||||||
[Key(0)]
|
|
||||||
public long Id { get; set; }
|
|
||||||
[Key(1)]
|
|
||||||
public string SeatNumber { get; init; }
|
|
||||||
[Key(2)]
|
|
||||||
public SeatType Type { get; init; }
|
|
||||||
[Key(3)]
|
|
||||||
public SeatClass Class { get; init; }
|
|
||||||
[Key(4)]
|
|
||||||
public long FlightId { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public record FlightResponseDto
|
|
||||||
{
|
|
||||||
[Key(0)]
|
|
||||||
public long Id { get; init; }
|
|
||||||
[Key(1)]
|
|
||||||
public string FlightNumber { get; init; }
|
|
||||||
[Key(2)]
|
|
||||||
public long AircraftId { get; init; }
|
|
||||||
[Key(3)]
|
|
||||||
public long DepartureAirportId { get; init; }
|
|
||||||
[Key(4)]
|
|
||||||
public DateTime DepartureDate { get; init; }
|
|
||||||
[Key(5)]
|
|
||||||
public DateTime ArriveDate { get; init; }
|
|
||||||
[Key(6)]
|
|
||||||
public long ArriveAirportId { get; init; }
|
|
||||||
[Key(7)]
|
|
||||||
public decimal DurationMinutes { get; init; }
|
|
||||||
[Key(8)]
|
|
||||||
public DateTime FlightDate { get; init; }
|
|
||||||
[Key(9)]
|
|
||||||
public FlightStatus Status { get; init; }
|
|
||||||
[Key(10)]
|
|
||||||
public decimal Price { get; init; }
|
|
||||||
[Key(11)]
|
|
||||||
public long FlightId { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FlightStatus
|
|
||||||
{
|
|
||||||
Flying = 1,
|
|
||||||
Delay = 2,
|
|
||||||
Canceled = 3,
|
|
||||||
Completed = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SeatType
|
|
||||||
{
|
|
||||||
Window,
|
|
||||||
Middle,
|
|
||||||
Aisle
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SeatClass
|
|
||||||
{
|
|
||||||
FirstClass,
|
|
||||||
Business,
|
|
||||||
Economy
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
using MagicOnion;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.Contracts.Grpc;
|
|
||||||
|
|
||||||
public interface IPassengerGrpcService : IService<IPassengerGrpcService>
|
|
||||||
{
|
|
||||||
UnaryResult<PassengerResponseDto> GetById(long id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public class PassengerResponseDto
|
|
||||||
{
|
|
||||||
[Key(0)]
|
|
||||||
public long Id { get; init; }
|
|
||||||
[Key(1)]
|
|
||||||
public string Name { get; init; }
|
|
||||||
[Key(2)]
|
|
||||||
public string PassportNumber { get; init; }
|
|
||||||
[Key(3)]
|
|
||||||
public PassengerType PassengerType { get; init; }
|
|
||||||
[Key(4)]
|
|
||||||
public int Age { get; init; }
|
|
||||||
[Key(5)]
|
|
||||||
public string Email { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PassengerType
|
|
||||||
{
|
|
||||||
Male,
|
|
||||||
Female,
|
|
||||||
Baby,
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
@ -1,6 +1,4 @@
|
|||||||
using BuildingBlocks.Contracts.Grpc;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Moq;
|
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
|
||||||
namespace BuildingBlocks.Web;
|
namespace BuildingBlocks.Web;
|
||||||
|
|||||||
@ -15,9 +15,11 @@ using BuildingBlocks.PersistMessageProcessor;
|
|||||||
using BuildingBlocks.Swagger;
|
using BuildingBlocks.Swagger;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
|
using Flight;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
using Hellang.Middleware.ProblemDetails;
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
|
using Passenger;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -26,7 +28,6 @@ var configuration = builder.Configuration;
|
|||||||
var env = builder.Environment;
|
var env = builder.Environment;
|
||||||
|
|
||||||
var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
|
var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
|
||||||
builder.Services.Configure<GrpcOptions>(options => configuration.GetSection("Grpc").Bind(options));
|
|
||||||
|
|
||||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||||
|
|
||||||
@ -49,14 +50,14 @@ builder.Services.AddCustomMassTransit(typeof(BookingRoot).Assembly, env);
|
|||||||
builder.Services.AddCustomOpenTelemetry();
|
builder.Services.AddCustomOpenTelemetry();
|
||||||
builder.Services.AddTransient<AuthHeaderHandler>();
|
builder.Services.AddTransient<AuthHeaderHandler>();
|
||||||
|
|
||||||
builder.Services.AddMagicOnionClients();
|
|
||||||
|
|
||||||
SnowFlakIdGenerator.Configure(3);
|
SnowFlakIdGenerator.Configure(3);
|
||||||
|
|
||||||
// ref: https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/ECommerce
|
// ref: https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/ECommerce
|
||||||
builder.Services.AddEventStore(configuration, typeof(BookingRoot).Assembly)
|
builder.Services.AddEventStore(configuration, typeof(BookingRoot).Assembly)
|
||||||
.AddEventStoreDBSubscriptionToAll();
|
.AddEventStoreDBSubscriptionToAll();
|
||||||
|
|
||||||
|
builder.Services.AddGrpcClients();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
|
|||||||
@ -6,15 +6,30 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.49.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="GrpcClient\Protos\flight.proto" GrpcServices="Both" />
|
||||||
|
<Protobuf Include="GrpcClient\Protos\passenger.proto" GrpcServices="Both" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="GrpcClient\Protos" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,49 +1,54 @@
|
|||||||
using Ardalis.GuardClauses;
|
using Ardalis.GuardClauses;
|
||||||
using Booking.Booking.Events.Domain;
|
|
||||||
using Booking.Booking.Exceptions;
|
using Booking.Booking.Exceptions;
|
||||||
using Booking.Booking.Models.ValueObjects;
|
using Booking.Booking.Models.ValueObjects;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
using Booking.Configuration;
|
||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using BuildingBlocks.Core.CQRS;
|
using BuildingBlocks.Core.CQRS;
|
||||||
using BuildingBlocks.EventStoreDB.Repository;
|
using BuildingBlocks.EventStoreDB.Repository;
|
||||||
using BuildingBlocks.Utils;
|
using BuildingBlocks.Utils;
|
||||||
|
using Flight;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Passenger;
|
||||||
|
|
||||||
namespace Booking.Booking.Features.CreateBooking;
|
namespace Booking.Booking.Features.CreateBooking;
|
||||||
|
|
||||||
public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand, ulong>
|
public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand, ulong>
|
||||||
{
|
{
|
||||||
private readonly IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
|
private readonly IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
|
||||||
private readonly IFlightGrpcService _flightGrpcService;
|
|
||||||
private readonly ICurrentUserProvider _currentUserProvider;
|
private readonly ICurrentUserProvider _currentUserProvider;
|
||||||
private readonly IEventDispatcher _eventDispatcher;
|
private readonly IEventDispatcher _eventDispatcher;
|
||||||
private readonly IPassengerGrpcService _passengerGrpcService;
|
private readonly FlightGrpcService.FlightGrpcServiceClient _flightGrpcServiceClient;
|
||||||
|
private readonly PassengerGrpcService.PassengerGrpcServiceClient _passengerGrpcServiceClient;
|
||||||
|
|
||||||
public CreateBookingCommandHandler(IEventStoreDBRepository<Models.Booking> eventStoreDbRepository,
|
public CreateBookingCommandHandler(IEventStoreDBRepository<Models.Booking> eventStoreDbRepository,
|
||||||
IPassengerGrpcService passengerGrpcService,
|
|
||||||
IFlightGrpcService flightGrpcService,
|
|
||||||
ICurrentUserProvider currentUserProvider,
|
ICurrentUserProvider currentUserProvider,
|
||||||
IEventDispatcher eventDispatcher)
|
IEventDispatcher eventDispatcher,
|
||||||
|
FlightGrpcService.FlightGrpcServiceClient flightGrpcServiceClient,
|
||||||
|
PassengerGrpcService.PassengerGrpcServiceClient passengerGrpcServiceClient)
|
||||||
{
|
{
|
||||||
_eventStoreDbRepository = eventStoreDbRepository;
|
_eventStoreDbRepository = eventStoreDbRepository;
|
||||||
_passengerGrpcService = passengerGrpcService;
|
|
||||||
_flightGrpcService = flightGrpcService;
|
|
||||||
_currentUserProvider = currentUserProvider;
|
_currentUserProvider = currentUserProvider;
|
||||||
_eventDispatcher = eventDispatcher;
|
_eventDispatcher = eventDispatcher;
|
||||||
|
_flightGrpcServiceClient = flightGrpcServiceClient;
|
||||||
|
_passengerGrpcServiceClient = passengerGrpcServiceClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ulong> Handle(CreateBookingCommand command,
|
public async Task<ulong> Handle(CreateBookingCommand command, CancellationToken cancellationToken)
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
Guard.Against.Null(command, nameof(command));
|
Guard.Against.Null(command, nameof(command));
|
||||||
|
|
||||||
var flight = await _flightGrpcService.GetById(command.FlightId);
|
var flight = await _flightGrpcServiceClient.GetByIdAsync(new Flight.GetByIdRequest {Id = command.FlightId});
|
||||||
|
|
||||||
if (flight is null)
|
if (flight is null)
|
||||||
throw new FlightNotFoundException();
|
throw new FlightNotFoundException();
|
||||||
|
|
||||||
var passenger = await _passengerGrpcService.GetById(command.PassengerId);
|
var passenger =
|
||||||
|
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest {Id = command.PassengerId});
|
||||||
|
|
||||||
var emptySeat = (await _flightGrpcService.GetAvailableSeats(command.FlightId))?.First();
|
var emptySeat = (await _flightGrpcServiceClient
|
||||||
|
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest {FlightId = command.FlightId}).ResponseAsync)
|
||||||
|
?.Items?.FirstOrDefault();
|
||||||
|
|
||||||
var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
|
var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
|
||||||
|
|
||||||
@ -52,12 +57,13 @@ public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand,
|
|||||||
|
|
||||||
var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
|
var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
|
||||||
flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,
|
flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,
|
||||||
flight.ArriveAirportId, flight.FlightDate, flight.Price, command.Description, emptySeat?.SeatNumber),
|
flight.ArriveAirportId, flight.FlightDate.ToDateTime(), (decimal)flight.Price, command.Description,
|
||||||
|
emptySeat?.SeatNumber),
|
||||||
false, _currentUserProvider.GetCurrentUserId());
|
false, _currentUserProvider.GetCurrentUserId());
|
||||||
|
|
||||||
await _eventDispatcher.SendAsync(aggrigate.DomainEvents, cancellationToken: cancellationToken);
|
await _eventDispatcher.SendAsync(aggrigate.DomainEvents, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
await _flightGrpcService.ReserveSeat(new ReserveSeatRequestDto
|
await _flightGrpcServiceClient.ReserveSeatAsync(new ReserveSeatRequest
|
||||||
{
|
{
|
||||||
FlightId = flight.FlightId, SeatNumber = emptySeat?.SeatNumber
|
FlightId = flight.FlightId, SeatNumber = emptySeat?.SeatNumber
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
using Booking.Configuration;
|
||||||
|
using BuildingBlocks.Web;
|
||||||
|
using Flight;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Passenger;
|
||||||
|
|
||||||
|
namespace Booking.Extensions;
|
||||||
|
|
||||||
|
public static class GrpcClientExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddGrpcClients(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var grpcOptions = services.GetOptions<GrpcOptions>("Grpc");
|
||||||
|
|
||||||
|
services.AddGrpcClient<FlightGrpcService.FlightGrpcServiceClient>(o =>
|
||||||
|
{
|
||||||
|
o.Address = new Uri(grpcOptions.FlightAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddGrpcClient<PassengerGrpcService.PassengerGrpcServiceClient>(o =>
|
||||||
|
{
|
||||||
|
o.Address = new Uri(grpcOptions.PassengerAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using Booking.Configuration;
|
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.Web;
|
|
||||||
using Grpc.Net.Client;
|
|
||||||
using MagicOnion.Client;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Booking.Extensions;
|
|
||||||
|
|
||||||
public static class MagicOnionClientExtensions
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddMagicOnionClients(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
var grpcOptions = services.GetOptions<GrpcOptions>("Grpc");
|
|
||||||
|
|
||||||
services.AddSingleton(x => MagicOnionClient.Create<IPassengerGrpcService>(GrpcChannel.ForAddress(grpcOptions.PassengerAddress)));
|
|
||||||
services.AddSingleton(x => MagicOnionClient.Create<IFlightGrpcService>(GrpcChannel.ForAddress(grpcOptions.FlightAddress)));
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package flight;
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
service FlightGrpcService {
|
||||||
|
|
||||||
|
rpc GetById (GetByIdRequest) returns (FlightResponse);
|
||||||
|
rpc GetAvailableSeats (GetAvailableSeatsRequest) returns (ListSeatsResponse);
|
||||||
|
rpc ReserveSeat (ReserveSeatRequest) returns (SeatsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetByIdRequest {
|
||||||
|
int64 Id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FlightResponse {
|
||||||
|
int64 Id = 1;
|
||||||
|
string FlightNumber = 2;
|
||||||
|
int64 AircraftId = 3;
|
||||||
|
int64 DepartureAirportId = 4;
|
||||||
|
google.protobuf.Timestamp DepartureDate = 5;
|
||||||
|
google.protobuf.Timestamp ArriveDate = 6;
|
||||||
|
int64 ArriveAirportId = 7;
|
||||||
|
double DurationMinutes = 8;
|
||||||
|
google.protobuf.Timestamp FlightDate = 9;
|
||||||
|
FlightStatus Status = 10;
|
||||||
|
double Price = 11;
|
||||||
|
int64 FlightId = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAvailableSeatsRequest {
|
||||||
|
int64 FlightId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SeatsResponse {
|
||||||
|
int64 Id = 1;
|
||||||
|
string SeatNumber = 2;
|
||||||
|
SeatType Type = 3;
|
||||||
|
SeatClass Class = 4;
|
||||||
|
int64 FlightId = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ReserveSeatRequest {
|
||||||
|
int64 FlightId = 1;
|
||||||
|
string SeatNumber = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListSeatsResponse {
|
||||||
|
repeated SeatsResponse items = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlightStatus {
|
||||||
|
FLIGHT_STATUS_UNKNOWN = 0;
|
||||||
|
FLIGHT_STATUS_FLYING = 1;
|
||||||
|
FLIGHT_STATUS_DELAY = 2;
|
||||||
|
FLIGHT_STATUS_CANCELED = 3;
|
||||||
|
FLIGHT_STATUS_COMPLETED = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum SeatType {
|
||||||
|
SEAT_TYPE_UNKNOWN = 0;
|
||||||
|
SEAT_TYPE_WINDOW = 1;
|
||||||
|
SEAT_TYPE_MIDDLE = 2;
|
||||||
|
SEAT_TYPE_AISLE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum SeatClass {
|
||||||
|
SEAT_CLASS_UNKNOWN = 0;
|
||||||
|
SEAT_CLASS_FIRST_CLASS = 1;
|
||||||
|
SEAT_CLASS_BUSINESS = 2;
|
||||||
|
SEAT_CLASS_ECONOMY = 3;
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package passenger;
|
||||||
|
|
||||||
|
service PassengerGrpcService {
|
||||||
|
|
||||||
|
rpc GetById (GetByIdRequest) returns (PassengerResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetByIdRequest {
|
||||||
|
int64 Id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PassengerResponse {
|
||||||
|
int64 Id = 1;
|
||||||
|
string Name = 2;
|
||||||
|
string PassportNumber = 3;
|
||||||
|
PassengerType PassengerType = 4;
|
||||||
|
int32 Age = 5;
|
||||||
|
string Email = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum PassengerType {
|
||||||
|
PASSENGER_TYPE_UNKNOWN = 0;
|
||||||
|
PASSENGER_TYPE_MALE = 1;
|
||||||
|
PASSENGER_TYPE_FEMALE = 2;
|
||||||
|
PASSENGER_TYPE_BABY = 3;
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,27 +1,30 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Booking.Booking.Models.Reads;
|
|
||||||
using Booking.Data;
|
using Booking.Data;
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.PersistMessageProcessor.Data;
|
using BuildingBlocks.PersistMessageProcessor.Data;
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
|
using Flight;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Core.Testing;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using MagicOnion;
|
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
using Passenger;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using GetByIdRequest = Flight.GetByIdRequest;
|
||||||
|
|
||||||
namespace Integration.Test.Booking.Features;
|
namespace Integration.Test.Booking.Features;
|
||||||
|
|
||||||
public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbContext, BookingReadDbContext>
|
public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbContext, BookingReadDbContext>
|
||||||
{
|
{
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public CreateBookingTests(
|
public CreateBookingTests(
|
||||||
IntegrationTestFixture<Program, PersistMessageDbContext, BookingReadDbContext> integrationTestFixture) : base(
|
IntegrationTestFixture<Program, PersistMessageDbContext, BookingReadDbContext> integrationTestFixture) : base(
|
||||||
integrationTestFixture)
|
integrationTestFixture)
|
||||||
@ -56,11 +59,13 @@ public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbC
|
|||||||
{
|
{
|
||||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||||
{
|
{
|
||||||
var mock = Substitute.For<IPassengerGrpcService>();
|
var mockPassenger = Substitute.For<PassengerGrpcService.PassengerGrpcServiceClient>();
|
||||||
mock.GetById(Arg.Any<long>())
|
|
||||||
.Returns(new UnaryResult<PassengerResponseDto>(new FakePassengerResponseDto().Generate()));
|
|
||||||
|
|
||||||
return mock;
|
mockPassenger.GetByIdAsync(Arg.Any<Passenger.GetByIdRequest>())
|
||||||
|
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(new FakePassengerResponse().Generate()),
|
||||||
|
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||||
|
|
||||||
|
return mockPassenger;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,19 +73,21 @@ public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbC
|
|||||||
{
|
{
|
||||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||||
{
|
{
|
||||||
var mock = Substitute.For<IFlightGrpcService>();
|
var mockFlight = Substitute.For<FlightGrpcService.FlightGrpcServiceClient>();
|
||||||
|
|
||||||
mock.GetById(Arg.Any<long>())
|
mockFlight.GetByIdAsync(Arg.Any<GetByIdRequest>())
|
||||||
.Returns(new UnaryResult<FlightResponseDto>(Task.FromResult(new FakeFlightResponseDto().Generate())));
|
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(new FakeFlightResponse().Generate()),
|
||||||
|
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||||
|
|
||||||
mock.GetAvailableSeats(Arg.Any<long>())
|
mockFlight.GetAvailableSeatsAsync(Arg.Any<GetAvailableSeatsRequest>())
|
||||||
.Returns(
|
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeSeatsResponse.Generate()),
|
||||||
new UnaryResult<IEnumerable<SeatResponseDto>>(Task.FromResult(FakeSeatsResponseDto.Generate())));
|
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||||
|
|
||||||
mock.ReserveSeat(new FakeReserveSeatRequestDto().Generate())
|
mockFlight.ReserveSeatAsync(Arg.Any<ReserveSeatRequest>())
|
||||||
.Returns(new UnaryResult<SeatResponseDto>(Task.FromResult(FakeSeatsResponseDto.Generate().First())));
|
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeSeatsResponse.Generate()?.Items?.First()),
|
||||||
|
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||||
|
|
||||||
return mock;
|
return mockFlight;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using AutoBogus;
|
||||||
|
using Flight;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
|
||||||
|
namespace Integration.Test.Fakes;
|
||||||
|
|
||||||
|
public class FakeFlightResponse : AutoFaker<FlightResponse>
|
||||||
|
{
|
||||||
|
public FakeFlightResponse()
|
||||||
|
{
|
||||||
|
RuleFor(r => r.Id, _ => 1);
|
||||||
|
RuleFor(r => r.Price, _ => 100);
|
||||||
|
RuleFor(r => r.Status, _ => FlightStatus.Completed);
|
||||||
|
RuleFor(r => r.AircraftId, _ => 1);
|
||||||
|
RuleFor(r => r.ArriveAirportId, _ => 1);
|
||||||
|
RuleFor(r => r.ArriveDate, _ => DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc).ToTimestamp());
|
||||||
|
RuleFor(r => r.DepartureDate, _ => DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc).ToTimestamp());
|
||||||
|
RuleFor(r => r.FlightDate, _ => DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc).ToTimestamp());
|
||||||
|
RuleFor(r => r.FlightNumber, _ => "121LP");
|
||||||
|
RuleFor(r => r.DepartureAirportId, _ => 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using AutoBogus;
|
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
|
|
||||||
namespace Integration.Test.Fakes;
|
|
||||||
|
|
||||||
public class FakeFlightResponseDto : AutoFaker<FlightResponseDto>
|
|
||||||
{
|
|
||||||
public FakeFlightResponseDto()
|
|
||||||
{
|
|
||||||
RuleFor(r => r.Id, _ => 1);
|
|
||||||
RuleFor(r => r.Price, _ => 100);
|
|
||||||
RuleFor(r => r.Status, _ => FlightStatus.Completed);
|
|
||||||
RuleFor(r => r.AircraftId, _ => 1);
|
|
||||||
RuleFor(r => r.ArriveAirportId, _ => 1);
|
|
||||||
RuleFor(r => r.ArriveDate, _ => DateTime.Now);
|
|
||||||
RuleFor(r => r.DepartureDate, _ => DateTime.Now);
|
|
||||||
RuleFor(r => r.FlightDate, _ => DateTime.Now);
|
|
||||||
RuleFor(r => r.FlightNumber, _ => "121LP");
|
|
||||||
RuleFor(r => r.DepartureAirportId, _ => 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
using AutoBogus;
|
using AutoBogus;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.IdsGenerator;
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using Passenger;
|
||||||
|
|
||||||
namespace Integration.Test.Fakes;
|
namespace Integration.Test.Fakes;
|
||||||
|
|
||||||
public class FakePassengerResponseDto : AutoFaker<PassengerResponseDto>
|
public class FakePassengerResponse : AutoFaker<PassengerResponse>
|
||||||
{
|
{
|
||||||
public FakePassengerResponseDto()
|
public FakePassengerResponse()
|
||||||
{
|
{
|
||||||
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
||||||
}
|
}
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using AutoBogus;
|
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
|
|
||||||
namespace Integration.Test.Fakes;
|
|
||||||
|
|
||||||
public class FakeReserveSeatRequestDto : AutoFaker<ReserveSeatRequestDto>
|
|
||||||
{
|
|
||||||
public FakeReserveSeatRequestDto()
|
|
||||||
{
|
|
||||||
RuleFor(r => r.FlightId, _ => 1);
|
|
||||||
RuleFor(r => r.SeatNumber, _ => "33F");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
using Flight;
|
||||||
|
|
||||||
namespace Integration.Test.Fakes;
|
namespace Integration.Test.Fakes;
|
||||||
|
|
||||||
public static class FakeSeatsResponseDto
|
public static class FakeSeatsResponse
|
||||||
{
|
{
|
||||||
public static IEnumerable<SeatResponseDto> Generate()
|
public static ListSeatsResponse Generate()
|
||||||
{
|
{
|
||||||
return new List<SeatResponseDto>()
|
var result = new ListSeatsResponse();
|
||||||
|
result.Items.AddRange(new List<SeatsResponse>
|
||||||
{
|
{
|
||||||
new SeatResponseDto()
|
new SeatsResponse()
|
||||||
{
|
{
|
||||||
FlightId = 1,
|
FlightId = 1,
|
||||||
Class = SeatClass.Economy,
|
Class = SeatClass.Economy,
|
||||||
@ -17,7 +18,7 @@ public static class FakeSeatsResponseDto
|
|||||||
SeatNumber = "33F",
|
SeatNumber = "33F",
|
||||||
Id = 1
|
Id = 1
|
||||||
},
|
},
|
||||||
new SeatResponseDto()
|
new SeatsResponse()
|
||||||
{
|
{
|
||||||
FlightId = 1,
|
FlightId = 1,
|
||||||
Class = SeatClass.Economy,
|
Class = SeatClass.Economy,
|
||||||
@ -25,6 +26,8 @@ public static class FakeSeatsResponseDto
|
|||||||
SeatNumber = "22D",
|
SeatNumber = "22D",
|
||||||
Id = 2
|
Id = 2
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using BuildingBlocks.Caching;
|
using BuildingBlocks.Caching;
|
||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
@ -19,9 +20,11 @@ using Flight;
|
|||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
using Flight.Data.Seed;
|
using Flight.Data.Seed;
|
||||||
using Flight.Extensions;
|
using Flight.Extensions;
|
||||||
|
using Flight.GrpcServer.Services;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
using Hellang.Middleware.ProblemDetails;
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -60,8 +63,6 @@ builder.Services.AddGrpc(options =>
|
|||||||
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddMagicOnion();
|
|
||||||
|
|
||||||
SnowFlakIdGenerator.Configure(1);
|
SnowFlakIdGenerator.Configure(1);
|
||||||
|
|
||||||
builder.Services.AddCachingRequest(new List<Assembly> {typeof(FlightRoot).Assembly});
|
builder.Services.AddCachingRequest(new List<Assembly> {typeof(FlightRoot).Assembly});
|
||||||
@ -91,7 +92,7 @@ app.UseEndpoints(endpoints =>
|
|||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
endpoints.MapMetrics();
|
endpoints.MapMetrics();
|
||||||
endpoints.MapMagicOnionService();
|
endpoints.MapGrpcService<FlightGrpcServices>();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||||
|
|||||||
@ -79,12 +79,12 @@ public class FlightDataSeeder : IDataSeeder
|
|||||||
{
|
{
|
||||||
var seats = new List<Seat>
|
var seats = new List<Seat>
|
||||||
{
|
{
|
||||||
Seat.Create(1 ,"12A", SeatType.Window, SeatClass.Economy, 1),
|
Seat.Create(1 ,"12A", Seats.Enums.SeatType.Window, Seats.Enums.SeatClass.Economy, 1),
|
||||||
Seat.Create(2, "12B", SeatType.Window, SeatClass.Economy, 1),
|
Seat.Create(2, "12B", Seats.Enums.SeatType.Window, Seats.Enums.SeatClass.Economy, 1),
|
||||||
Seat.Create(3, "12C", SeatType.Middle, SeatClass.Economy, 1),
|
Seat.Create(3, "12C", Seats.Enums.SeatType.Middle, Seats.Enums.SeatClass.Economy, 1),
|
||||||
Seat.Create(4, "12D", SeatType.Middle, SeatClass.Economy, 1),
|
Seat.Create(4, "12D", Seats.Enums.SeatType.Middle, Seats.Enums.SeatClass.Economy, 1),
|
||||||
Seat.Create(5, "12E", SeatType.Aisle, SeatClass.Economy, 1),
|
Seat.Create(5, "12E", Seats.Enums.SeatType.Aisle, Seats.Enums.SeatClass.Economy, 1),
|
||||||
Seat.Create(6, "12F", SeatType.Aisle, SeatClass.Economy, 1)
|
Seat.Create(6, "12F", Seats.Enums.SeatType.Aisle, Seats.Enums.SeatClass.Economy, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
await _flightDbContext.Seats.AddRangeAsync(seats);
|
await _flightDbContext.Seats.AddRangeAsync(seats);
|
||||||
@ -102,7 +102,7 @@ public class FlightDataSeeder : IDataSeeder
|
|||||||
Flights.Models.Flight.Create(1, "BD467", 1, 1, new DateTime(2022, 1, 31, 12, 0, 0),
|
Flights.Models.Flight.Create(1, "BD467", 1, 1, new DateTime(2022, 1, 31, 12, 0, 0),
|
||||||
new DateTime(2022, 1, 31, 14, 0, 0),
|
new DateTime(2022, 1, 31, 14, 0, 0),
|
||||||
2, 120m,
|
2, 120m,
|
||||||
new DateTime(2022, 1, 31), FlightStatus.Completed,
|
new DateTime(2022, 1, 31), Flights.Enums.FlightStatus.Completed,
|
||||||
8000)
|
8000)
|
||||||
};
|
};
|
||||||
await _flightDbContext.Flights.AddRangeAsync(flights);
|
await _flightDbContext.Flights.AddRangeAsync(flights);
|
||||||
|
|||||||
@ -5,6 +5,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.49.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@ -15,7 +21,10 @@
|
|||||||
<Folder Include="Aircrafts\Exceptions" />
|
<Folder Include="Aircrafts\Exceptions" />
|
||||||
<Folder Include="Airports\Exceptions" />
|
<Folder Include="Airports\Exceptions" />
|
||||||
<Folder Include="Data\Migrations" />
|
<Folder Include="Data\Migrations" />
|
||||||
<Folder Include="Enum" />
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="GrpcServer\Protos\flight.proto" GrpcServices="Both" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
namespace Flight.Flights.Models;
|
namespace Flight.Flights.Enums;
|
||||||
|
|
||||||
public enum FlightStatus
|
public enum FlightStatus
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
Flying = 1,
|
Flying = 1,
|
||||||
Delay = 2,
|
Delay = 2,
|
||||||
Canceled = 3,
|
Canceled = 3,
|
||||||
@ -6,4 +6,4 @@ namespace Flight.Flights.Events.Domain;
|
|||||||
|
|
||||||
public record FlightCreatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
public record FlightCreatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
||||||
DateTime FlightDate, FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -6,4 +6,4 @@ namespace Flight.Flights.Events.Domain;
|
|||||||
|
|
||||||
public record FlightDeletedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
public record FlightDeletedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
||||||
DateTime FlightDate, FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -5,4 +5,4 @@ using Flight.Flights.Models;
|
|||||||
namespace Flight.Flights.Events.Domain;
|
namespace Flight.Flights.Events.Domain;
|
||||||
public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
||||||
DateTime FlightDate, FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace Flight.Flights.Features.CreateFlight;
|
|||||||
|
|
||||||
public record CreateFlightCommand(string FlightNumber, long AircraftId, long DepartureAirportId,
|
public record CreateFlightCommand(string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
||||||
decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand<FlightResponseDto>, IInternalCommand
|
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price) : ICommand<FlightResponseDto>, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,10 @@ public class CreateFlightCommandValidator : AbstractValidator<CreateFlightComman
|
|||||||
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price must be greater than 0");
|
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price must be greater than 0");
|
||||||
|
|
||||||
RuleFor(x => x.Status).Must(p => (p.GetType().IsEnum &&
|
RuleFor(x => x.Status).Must(p => (p.GetType().IsEnum &&
|
||||||
p == FlightStatus.Flying) ||
|
p == Enums.FlightStatus.Flying) ||
|
||||||
p == FlightStatus.Canceled ||
|
p == Enums.FlightStatus.Canceled ||
|
||||||
p == FlightStatus.Delay ||
|
p == Enums.FlightStatus.Delay ||
|
||||||
p == FlightStatus.Completed)
|
p == Enums.FlightStatus.Completed)
|
||||||
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
||||||
|
|
||||||
RuleFor(x => x.AircraftId).NotEmpty().WithMessage("AircraftId must be not empty");
|
RuleFor(x => x.AircraftId).NotEmpty().WithMessage("AircraftId must be not empty");
|
||||||
|
|||||||
@ -8,7 +8,7 @@ public class CreateFlightMongoCommand : InternalCommand
|
|||||||
{
|
{
|
||||||
public CreateFlightMongoCommand(long id, string flightNumber, long aircraftId, DateTime departureDate,
|
public CreateFlightMongoCommand(long id, string flightNumber, long aircraftId, DateTime departureDate,
|
||||||
long departureAirportId,
|
long departureAirportId,
|
||||||
DateTime arriveDate, long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status,
|
DateTime arriveDate, long arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
|
||||||
decimal price, bool isDeleted)
|
decimal price, bool isDeleted)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
@ -33,7 +33,7 @@ public class CreateFlightMongoCommand : InternalCommand
|
|||||||
public long ArriveAirportId { get; }
|
public long ArriveAirportId { get; }
|
||||||
public decimal DurationMinutes { get; }
|
public decimal DurationMinutes { get; }
|
||||||
public DateTime FlightDate { get; }
|
public DateTime FlightDate { get; }
|
||||||
public FlightStatus Status { get; }
|
public Enums.FlightStatus Status { get; }
|
||||||
public decimal Price { get; }
|
public decimal Price { get; }
|
||||||
public bool IsDeleted { get; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ public class DeleteFlightMongoCommand : InternalCommand
|
|||||||
{
|
{
|
||||||
public DeleteFlightMongoCommand(long id, string flightNumber, long aircraftId, DateTime departureDate,
|
public DeleteFlightMongoCommand(long id, string flightNumber, long aircraftId, DateTime departureDate,
|
||||||
long departureAirportId,
|
long departureAirportId,
|
||||||
DateTime arriveDate, long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status,
|
DateTime arriveDate, long arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
|
||||||
decimal price, bool isDeleted)
|
decimal price, bool isDeleted)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
@ -33,7 +33,7 @@ public class DeleteFlightMongoCommand : InternalCommand
|
|||||||
public long ArriveAirportId { get; }
|
public long ArriveAirportId { get; }
|
||||||
public decimal DurationMinutes { get; }
|
public decimal DurationMinutes { get; }
|
||||||
public DateTime FlightDate { get; }
|
public DateTime FlightDate { get; }
|
||||||
public FlightStatus Status { get; }
|
public Enums.FlightStatus Status { get; }
|
||||||
public decimal Price { get; }
|
public decimal Price { get; }
|
||||||
public bool IsDeleted { get; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ public class UpdateFlightMongoCommand : InternalCommand
|
|||||||
{
|
{
|
||||||
public UpdateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
public UpdateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
long DepartureAirportId,
|
long DepartureAirportId,
|
||||||
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status,
|
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status,
|
||||||
decimal Price, bool IsDeleted)
|
decimal Price, bool IsDeleted)
|
||||||
{
|
{
|
||||||
this.Id = Id;
|
this.Id = Id;
|
||||||
@ -33,7 +33,7 @@ public class UpdateFlightMongoCommand : InternalCommand
|
|||||||
public long ArriveAirportId { get; }
|
public long ArriveAirportId { get; }
|
||||||
public decimal DurationMinutes { get; }
|
public decimal DurationMinutes { get; }
|
||||||
public DateTime FlightDate { get; }
|
public DateTime FlightDate { get; }
|
||||||
public FlightStatus Status { get; }
|
public Enums.FlightStatus Status { get; }
|
||||||
public decimal Price { get; }
|
public decimal Price { get; }
|
||||||
public bool IsDeleted { get; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ public record UpdateFlightCommand : ICommand<FlightResponseDto>, IInvalidateCach
|
|||||||
public decimal DurationMinutes { get; init; }
|
public decimal DurationMinutes { get; init; }
|
||||||
public DateTime FlightDate { get; init; }
|
public DateTime FlightDate { get; init; }
|
||||||
|
|
||||||
public FlightStatus Status { get; init; }
|
public Enums.FlightStatus Status { get; init; }
|
||||||
|
|
||||||
public bool IsDeleted { get; init; } = false;
|
public bool IsDeleted { get; init; } = false;
|
||||||
public decimal Price { get; init; }
|
public decimal Price { get; init; }
|
||||||
|
|||||||
@ -13,10 +13,10 @@ public class UpdateFlightCommandValidator : AbstractValidator<CreateFlightComman
|
|||||||
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price must be greater than 0");
|
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price must be greater than 0");
|
||||||
|
|
||||||
RuleFor(x => x.Status).Must(p => (p.GetType().IsEnum &&
|
RuleFor(x => x.Status).Must(p => (p.GetType().IsEnum &&
|
||||||
p == FlightStatus.Flying) ||
|
p == Enums.FlightStatus.Flying) ||
|
||||||
p == FlightStatus.Canceled ||
|
p == Enums.FlightStatus.Canceled ||
|
||||||
p == FlightStatus.Delay ||
|
p == Enums.FlightStatus.Delay ||
|
||||||
p == FlightStatus.Completed)
|
p == Enums.FlightStatus.Completed)
|
||||||
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
||||||
|
|
||||||
RuleFor(x => x.AircraftId).NotEmpty().WithMessage("AircraftId must be not empty");
|
RuleFor(x => x.AircraftId).NotEmpty().WithMessage("AircraftId must be not empty");
|
||||||
|
|||||||
@ -14,12 +14,12 @@ public record Flight : Aggregate<long>
|
|||||||
public long ArriveAirportId { get; private set; }
|
public long ArriveAirportId { get; private set; }
|
||||||
public decimal DurationMinutes { get; private set; }
|
public decimal DurationMinutes { get; private set; }
|
||||||
public DateTime FlightDate { get; private set; }
|
public DateTime FlightDate { get; private set; }
|
||||||
public FlightStatus Status { get; private set; }
|
public Enums.FlightStatus Status { get; private set; }
|
||||||
public decimal Price { get; private set; }
|
public decimal Price { get; private set; }
|
||||||
|
|
||||||
public static Flight Create(long id, string flightNumber, long aircraftId,
|
public static Flight Create(long id, string flightNumber, long aircraftId,
|
||||||
long departureAirportId, DateTime departureDate, DateTime arriveDate,
|
long departureAirportId, DateTime departureDate, DateTime arriveDate,
|
||||||
long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status,
|
long arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
|
||||||
decimal price, bool isDeleted = false)
|
decimal price, bool isDeleted = false)
|
||||||
{
|
{
|
||||||
var flight = new Flight
|
var flight = new Flight
|
||||||
@ -51,7 +51,7 @@ public record Flight : Aggregate<long>
|
|||||||
|
|
||||||
public void Update(long id, string flightNumber, long aircraftId,
|
public void Update(long id, string flightNumber, long aircraftId,
|
||||||
long departureAirportId, DateTime departureDate, DateTime arriveDate,
|
long departureAirportId, DateTime departureDate, DateTime arriveDate,
|
||||||
long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status,
|
long arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
|
||||||
decimal price, bool isDeleted = false)
|
decimal price, bool isDeleted = false)
|
||||||
{
|
{
|
||||||
FlightNumber = flightNumber;
|
FlightNumber = flightNumber;
|
||||||
@ -74,7 +74,7 @@ public record Flight : Aggregate<long>
|
|||||||
|
|
||||||
public void Delete(long id, string flightNumber, long aircraftId,
|
public void Delete(long id, string flightNumber, long aircraftId,
|
||||||
long departureAirportId, DateTime departureDate, DateTime arriveDate,
|
long departureAirportId, DateTime departureDate, DateTime arriveDate,
|
||||||
long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status,
|
long arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
|
||||||
decimal price, bool isDeleted = true)
|
decimal price, bool isDeleted = true)
|
||||||
{
|
{
|
||||||
FlightNumber = flightNumber;
|
FlightNumber = flightNumber;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public class FlightReadModel
|
|||||||
public long ArriveAirportId { get; init; }
|
public long ArriveAirportId { get; init; }
|
||||||
public decimal DurationMinutes { get; init; }
|
public decimal DurationMinutes { get; init; }
|
||||||
public DateTime FlightDate { get; init; }
|
public DateTime FlightDate { get; init; }
|
||||||
public FlightStatus Status { get; init; }
|
public Enums.FlightStatus Status { get; init; }
|
||||||
public decimal Price { get; init; }
|
public decimal Price { get; init; }
|
||||||
public bool IsDeleted { get; init; }
|
public bool IsDeleted { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using Flight.Flights.Features.GetFlightById;
|
|
||||||
using Flight.Seats.Features.GetAvailableSeats;
|
|
||||||
using Flight.Seats.Features.ReserveSeat;
|
|
||||||
using MagicOnion;
|
|
||||||
using MagicOnion.Server;
|
|
||||||
using Mapster;
|
|
||||||
using MediatR;
|
|
||||||
using SeatResponseDto = BuildingBlocks.Contracts.Grpc.SeatResponseDto;
|
|
||||||
|
|
||||||
namespace Flight.GrpcServer;
|
|
||||||
|
|
||||||
public class FlightGrpcService : ServiceBase<IFlightGrpcService>, IFlightGrpcService
|
|
||||||
{
|
|
||||||
private readonly IMediator _mediator;
|
|
||||||
|
|
||||||
public FlightGrpcService(IMediator mediator)
|
|
||||||
{
|
|
||||||
_mediator = mediator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UnaryResult<FlightResponseDto> GetById(long id)
|
|
||||||
{
|
|
||||||
var result = await _mediator.Send(new GetFlightByIdQuery(id));
|
|
||||||
return result.Adapt<FlightResponseDto>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UnaryResult<IEnumerable<SeatResponseDto>> GetAvailableSeats(long flightId)
|
|
||||||
{
|
|
||||||
var result = await _mediator.Send(new GetAvailableSeatsQuery(flightId));
|
|
||||||
return result.Adapt<IEnumerable<SeatResponseDto>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UnaryResult<SeatResponseDto> ReserveSeat(ReserveSeatRequestDto request)
|
|
||||||
{
|
|
||||||
var result = await _mediator.Send(new ReserveSeatCommand(request.FlightId, request.SeatNumber));
|
|
||||||
return result.Adapt<SeatResponseDto>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package flight;
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
service FlightGrpcService {
|
||||||
|
|
||||||
|
rpc GetById (GetByIdRequest) returns (FlightResponse);
|
||||||
|
rpc GetAvailableSeats (GetAvailableSeatsRequest) returns (ListSeatsResponse);
|
||||||
|
rpc ReserveSeat (ReserveSeatRequest) returns (SeatsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetByIdRequest {
|
||||||
|
int64 Id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FlightResponse {
|
||||||
|
int64 Id = 1;
|
||||||
|
string FlightNumber = 2;
|
||||||
|
int64 AircraftId = 3;
|
||||||
|
int64 DepartureAirportId = 4;
|
||||||
|
google.protobuf.Timestamp DepartureDate = 5;
|
||||||
|
google.protobuf.Timestamp ArriveDate = 6;
|
||||||
|
int64 ArriveAirportId = 7;
|
||||||
|
double DurationMinutes = 8;
|
||||||
|
google.protobuf.Timestamp FlightDate = 9;
|
||||||
|
FlightStatus Status = 10;
|
||||||
|
double Price = 11;
|
||||||
|
int64 FlightId = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAvailableSeatsRequest {
|
||||||
|
int64 FlightId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SeatsResponse {
|
||||||
|
int64 Id = 1;
|
||||||
|
string SeatNumber = 2;
|
||||||
|
SeatType Type = 3;
|
||||||
|
SeatClass Class = 4;
|
||||||
|
int64 FlightId = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ReserveSeatRequest {
|
||||||
|
int64 FlightId = 1;
|
||||||
|
string SeatNumber = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListSeatsResponse {
|
||||||
|
repeated SeatsResponse items = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlightStatus {
|
||||||
|
FLIGHT_STATUS_UNKNOWN = 0;
|
||||||
|
FLIGHT_STATUS_FLYING = 1;
|
||||||
|
FLIGHT_STATUS_DELAY = 2;
|
||||||
|
FLIGHT_STATUS_CANCELED = 3;
|
||||||
|
FLIGHT_STATUS_COMPLETED = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum SeatType {
|
||||||
|
SEAT_TYPE_UNKNOWN = 0;
|
||||||
|
SEAT_TYPE_WINDOW = 1;
|
||||||
|
SEAT_TYPE_MIDDLE = 2;
|
||||||
|
SEAT_TYPE_AISLE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum SeatClass {
|
||||||
|
SEAT_CLASS_UNKNOWN = 0;
|
||||||
|
SEAT_CLASS_FIRST_CLASS = 1;
|
||||||
|
SEAT_CLASS_BUSINESS = 2;
|
||||||
|
SEAT_CLASS_ECONOMY = 3;
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Flight.Flights.Features.GetFlightById;
|
||||||
|
using Flight.Seats.Features.GetAvailableSeats;
|
||||||
|
using Flight.Seats.Features.ReserveSeat;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Mapster;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Flight.GrpcServer.Services;
|
||||||
|
|
||||||
|
public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
|
||||||
|
{
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public FlightGrpcServices(IMediator mediator)
|
||||||
|
{
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<FlightResponse> GetById(GetByIdRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = await _mediator.Send(new GetFlightByIdQuery(request.Id));
|
||||||
|
return result.Adapt<FlightResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<SeatsResponse> ReserveSeat(ReserveSeatRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = await _mediator.Send(new ReserveSeatCommand(request.FlightId, request.SeatNumber));
|
||||||
|
return result.Adapt<SeatsResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ListSeatsResponse> GetAvailableSeats(GetAvailableSeatsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = new ListSeatsResponse();
|
||||||
|
|
||||||
|
var availableSeats = await _mediator.Send(new GetAvailableSeatsQuery(request.FlightId));
|
||||||
|
|
||||||
|
foreach (var availableSeat in availableSeats)
|
||||||
|
{
|
||||||
|
result.Items.Add(availableSeat.Adapt<SeatsResponse>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
namespace Flight.Seats.Models;
|
namespace Flight.Seats.Enums;
|
||||||
|
|
||||||
public enum SeatClass
|
public enum SeatClass
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
FirstClass,
|
FirstClass,
|
||||||
Business,
|
Business,
|
||||||
Economy
|
Economy
|
||||||
@ -1,7 +1,8 @@
|
|||||||
namespace Flight.Seats.Models;
|
namespace Flight.Seats.Enums;
|
||||||
|
|
||||||
public enum SeatType
|
public enum SeatType
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
Window,
|
Window,
|
||||||
Middle,
|
Middle,
|
||||||
Aisle
|
Aisle
|
||||||
@ -3,4 +3,4 @@ using Flight.Seats.Models;
|
|||||||
|
|
||||||
namespace Flight.Seats.Events;
|
namespace Flight.Seats.Events;
|
||||||
|
|
||||||
public record SeatCreatedDomainEvent(long Id, string SeatNumber, SeatType Type, SeatClass Class, long FlightId, bool IsDeleted) : IDomainEvent;
|
public record SeatCreatedDomainEvent(long Id, string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -3,4 +3,4 @@ using Flight.Seats.Models;
|
|||||||
|
|
||||||
namespace Flight.Seats.Events;
|
namespace Flight.Seats.Events;
|
||||||
|
|
||||||
public record SeatReservedDomainEvent(long Id, string SeatNumber, SeatType Type, SeatClass Class, long FlightId, bool IsDeleted) : IDomainEvent;
|
public record SeatReservedDomainEvent(long Id, string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ using Flight.Seats.Models;
|
|||||||
|
|
||||||
namespace Flight.Seats.Features.CreateSeat;
|
namespace Flight.Seats.Features.CreateSeat;
|
||||||
|
|
||||||
public record CreateSeatCommand(string SeatNumber, SeatType Type, SeatClass Class, long FlightId) : ICommand<SeatResponseDto>, IInternalCommand
|
public record CreateSeatCommand(string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId) : ICommand<SeatResponseDto>, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,9 +11,9 @@ public class CreateSeatCommandValidator : AbstractValidator<CreateSeatCommand>
|
|||||||
RuleFor(x => x.SeatNumber).NotEmpty().WithMessage("SeatNumber is required");
|
RuleFor(x => x.SeatNumber).NotEmpty().WithMessage("SeatNumber is required");
|
||||||
RuleFor(x => x.FlightId).NotEmpty().WithMessage("FlightId is required");
|
RuleFor(x => x.FlightId).NotEmpty().WithMessage("FlightId is required");
|
||||||
RuleFor(x => x.Class).Must(p => (p.GetType().IsEnum &&
|
RuleFor(x => x.Class).Must(p => (p.GetType().IsEnum &&
|
||||||
p == SeatClass.FirstClass) ||
|
p == Enums.SeatClass.FirstClass) ||
|
||||||
p == SeatClass.Business ||
|
p == Enums.SeatClass.Business ||
|
||||||
p == SeatClass.Economy)
|
p == Enums.SeatClass.Economy)
|
||||||
.WithMessage("Status must be FirstClass, Business or Economy");
|
.WithMessage("Status must be FirstClass, Business or Economy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ namespace Flight.Seats.Features.CreateSeat.Reads;
|
|||||||
|
|
||||||
public class CreateSeatMongoCommand : InternalCommand
|
public class CreateSeatMongoCommand : InternalCommand
|
||||||
{
|
{
|
||||||
public CreateSeatMongoCommand(long id, string seatNumber, SeatType type, SeatClass @class,
|
public CreateSeatMongoCommand(long id, string seatNumber, Enums.SeatType type, Enums.SeatClass @class,
|
||||||
long flightId, bool isDeleted)
|
long flightId, bool isDeleted)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
@ -18,8 +18,8 @@ public class CreateSeatMongoCommand : InternalCommand
|
|||||||
|
|
||||||
public long Id { get; }
|
public long Id { get; }
|
||||||
public string SeatNumber { get; }
|
public string SeatNumber { get; }
|
||||||
public SeatType Type { get; }
|
public Enums.SeatType Type { get; }
|
||||||
public SeatClass Class { get; }
|
public Enums.SeatClass Class { get; }
|
||||||
public long FlightId { get; }
|
public long FlightId { get; }
|
||||||
public bool IsDeleted { get; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ namespace Flight.Seats.Features.ReserveSeat.Reads;
|
|||||||
|
|
||||||
public class ReserveSeatMongoCommand : InternalCommand
|
public class ReserveSeatMongoCommand : InternalCommand
|
||||||
{
|
{
|
||||||
public ReserveSeatMongoCommand(long id, string seatNumber, SeatType type, SeatClass @class, long flightId,
|
public ReserveSeatMongoCommand(long id, string seatNumber, Enums.SeatType type, Enums.SeatClass @class, long flightId,
|
||||||
bool isDeleted)
|
bool isDeleted)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
@ -17,8 +17,8 @@ public class ReserveSeatMongoCommand : InternalCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string SeatNumber { get; }
|
public string SeatNumber { get; }
|
||||||
public SeatType Type { get; }
|
public Enums.SeatType Type { get; }
|
||||||
public SeatClass Class { get; }
|
public Enums.SeatClass Class { get; }
|
||||||
public long FlightId { get; }
|
public long FlightId { get; }
|
||||||
public bool IsDeleted { get; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ public class SeatReadModel
|
|||||||
public long Id { get; init; }
|
public long Id { get; init; }
|
||||||
public long SeatId { get; init; }
|
public long SeatId { get; init; }
|
||||||
public string SeatNumber { get; init; }
|
public string SeatNumber { get; init; }
|
||||||
public SeatType Type { get; init; }
|
public Enums.SeatType Type { get; init; }
|
||||||
public SeatClass Class { get; init; }
|
public Enums.SeatClass Class { get; init; }
|
||||||
public long FlightId { get; init; }
|
public long FlightId { get; init; }
|
||||||
public bool IsDeleted { get; init; }
|
public bool IsDeleted { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace Flight.Seats.Models;
|
|||||||
|
|
||||||
public record Seat : Aggregate<long>
|
public record Seat : Aggregate<long>
|
||||||
{
|
{
|
||||||
public static Seat Create(long id, string seatNumber, SeatType type, SeatClass @class, long flightId,
|
public static Seat Create(long id, string seatNumber, Enums.SeatType type, Enums.SeatClass @class, long flightId,
|
||||||
bool isDeleted = false)
|
bool isDeleted = false)
|
||||||
{
|
{
|
||||||
var seat = new Seat()
|
var seat = new Seat()
|
||||||
@ -52,7 +52,7 @@ public record Seat : Aggregate<long>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string SeatNumber { get; private set; }
|
public string SeatNumber { get; private set; }
|
||||||
public SeatType Type { get; private set; }
|
public Enums.SeatType Type { get; private set; }
|
||||||
public SeatClass Class { get; private set; }
|
public Enums.SeatClass Class { get; private set; }
|
||||||
public long FlightId { get; private set; }
|
public long FlightId { get; private set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
|
using Flight;
|
||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
using Flight.Flights.Features.CreateFlight.Reads;
|
using Flight.Flights.Features.CreateFlight.Reads;
|
||||||
using Flight.Flights.Features.GetFlightById;
|
using Flight.Flights.Features.GetFlightById;
|
||||||
|
using Flight.GrpcServer.Services;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using MagicOnion.Client;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
@ -16,7 +16,9 @@ public class GetFlightByIdTests : IntegrationTestBase<Program, FlightDbContext,
|
|||||||
{
|
{
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
|
||||||
public GetFlightByIdTests(IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(integrationTestFixture)
|
public GetFlightByIdTests(
|
||||||
|
IntegrationTestFixture<Program, FlightDbContext, FlightReadDbContext> integrationTestFixture) : base(
|
||||||
|
integrationTestFixture)
|
||||||
{
|
{
|
||||||
_channel = Fixture.Channel;
|
_channel = Fixture.Channel;
|
||||||
}
|
}
|
||||||
@ -49,10 +51,10 @@ public class GetFlightByIdTests : IntegrationTestBase<Program, FlightDbContext,
|
|||||||
|
|
||||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateFlightMongoCommand>();
|
await Fixture.ShouldProcessedPersistInternalCommand<CreateFlightMongoCommand>();
|
||||||
|
|
||||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
|
var flightGrpcClient = new FlightGrpcService.FlightGrpcServiceClient(_channel);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await flightGrpcClient.GetById(command.Id);
|
var response = await flightGrpcClient.GetByIdAsync(new GetByIdRequest {Id = command.Id});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response?.Should().NotBeNull();
|
response?.Should().NotBeNull();
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
using System.Linq;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
|
using Flight;
|
||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
using Flight.Flights.Features.CreateFlight.Reads;
|
using Flight.Flights.Features.CreateFlight.Reads;
|
||||||
using Flight.Seats.Features.CreateSeat.Reads;
|
using Flight.Seats.Features.CreateSeat.Reads;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using MagicOnion.Client;
|
|
||||||
using MassTransit.Testing;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Seat.Features;
|
namespace Integration.Test.Seat.Features;
|
||||||
@ -39,13 +36,13 @@ public class GetAvailableSeatsTests : IntegrationTestBase<Program, FlightDbConte
|
|||||||
|
|
||||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
||||||
|
|
||||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
|
var flightGrpcClient = new FlightGrpcService.FlightGrpcServiceClient(_channel);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await flightGrpcClient.GetAvailableSeats(flightCommand.Id);
|
var response = await flightGrpcClient.GetAvailableSeatsAsync(new GetAvailableSeatsRequest{FlightId = flightCommand.Id});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response?.Should().NotBeNull();
|
response?.Should().NotBeNull();
|
||||||
response?.Count().Should().BeGreaterOrEqualTo(1);
|
response?.Items?.Count.Should().BeGreaterOrEqualTo(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
|
using Flight;
|
||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
using Flight.Flights.Features.CreateFlight.Reads;
|
using Flight.Flights.Features.CreateFlight.Reads;
|
||||||
using Flight.Seats.Features.CreateSeat.Reads;
|
using Flight.Seats.Features.CreateSeat.Reads;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using MagicOnion.Client;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Seat.Features;
|
namespace Integration.Test.Seat.Features;
|
||||||
@ -39,10 +38,10 @@ public class ReserveSeatTests : IntegrationTestBase<Program, FlightDbContext, Fl
|
|||||||
|
|
||||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
||||||
|
|
||||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
|
var flightGrpcClient = new FlightGrpcService.FlightGrpcServiceClient(_channel);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await flightGrpcClient.ReserveSeat(new ReserveSeatRequestDto
|
var response = await flightGrpcClient.ReserveSeatAsync(new ReserveSeatRequest()
|
||||||
{
|
{
|
||||||
FlightId = seatCommand.FlightId, SeatNumber = seatCommand.SeatNumber
|
FlightId = seatCommand.FlightId, SeatNumber = seatCommand.SeatNumber
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
|||||||
using Flight.Aircrafts.Models;
|
using Flight.Aircrafts.Models;
|
||||||
using Flight.Airports.Models;
|
using Flight.Airports.Models;
|
||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
|
using Flight.Flights.Enums;
|
||||||
using Flight.Flights.Models;
|
using Flight.Flights.Models;
|
||||||
|
using Flight.Seats.Enums;
|
||||||
using Flight.Seats.Models;
|
using Flight.Seats.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using AutoBogus;
|
using AutoBogus;
|
||||||
using BuildingBlocks.IdsGenerator;
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using Flight.Seats.Enums;
|
||||||
using Flight.Seats.Features.CreateSeat;
|
using Flight.Seats.Features.CreateSeat;
|
||||||
using Flight.Seats.Models;
|
using Flight.Seats.Models;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using AutoBogus;
|
using AutoBogus;
|
||||||
|
using Flight.Flights.Enums;
|
||||||
using Flight.Flights.Features.CreateFlight;
|
using Flight.Flights.Features.CreateFlight;
|
||||||
using Flight.Flights.Models;
|
using Flight.Flights.Models;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using AutoBogus;
|
using AutoBogus;
|
||||||
|
using Flight.Seats.Enums;
|
||||||
using Flight.Seats.Features.CreateSeat;
|
using Flight.Seats.Features.CreateSeat;
|
||||||
using Flight.Seats.Models;
|
using Flight.Seats.Models;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
{"Version":1,"Id":"A7DD53B5F56AA3937910E9A73600964A","Created":"2022-10-13T20:19:56.3638146Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8GjYME9sg_tHn5C8_j2EtO1vvYKDE1hzq5Kp8GHesS2tNpBMrM_9i8n6u2MDK4eczb8P8fRryy3dD8HS7GCrRB4dY-qvHq7eRrDdJz8C-HHEZTRt-H843A7O0aOLgXJoR6P0Yk3EbBBJcmFsEGHy9G9PfmVU6LyCpRcuv9be6YLfBI-ahU3XvkSLTLEV0YIs9HRY24Ue71_vWp7G_IawDuUMjQZgTT-ATwvu97iE1ubfIwh-HQWF34Rfk7xvuyWgWftqWxfzCDqP1VIE088c8zCvGTIDb6P7TCC-PFOtOlb6xNy7TlyHNSnvDLCu8WJvc9r5S0P_6Byl9zHVwxmXtGira_tu-7ZkqaVHHrLVwoV0-f4bTRnXn7nvXvvLdOHCTZcl9zh9pyNYVDlLy6arQcfLY09B6pqdaPxw8lt6hrs9D5vwVzknt9PANW7wod9-hN8RBX0oAf0qOyF2Kn6Bt4wWDTO9-JbNebHmGfq0rkaFHepugz4XS40-NfCYwo9Wn3H0uw5Wq0r4SWxhdU9Vbrgb0SCl4am46O_smKVsM5VVSA_3IqvYSWLq1OpqIasJthonDq8ZAxX9w6i3nXjea-T3kD-eoZxUnB5hSnMN62HoyvBSTIlYjUtw_bRHNmnb7oqneL2zCNGZBPIdOf9QByYOCifJvK27AFeSm6xMEZPMK1MRMr4K64_9Jj61wAZTtYp6tIMig8plHwnxpqjrMBYGvjH_L5YHcnDl784IujzwjYfuTqrD8BzlaTTcbCYl4Jxk5DKP6Jlhg7srv6MwO-Y4jvBfOq9PSLR8nN0KdNMHN-OV46b_aRIyefpAfkJIuAWKc2Qoh3jd6yPObVXwP9qYJ21DnaMuBigxt7c6m71z_3NKiSV1d-O9XNPwbwQ8mlkKwc236KnlSRmLli1HMQIJKJhiC5XI1gBXP6XbSi3bvVXgeEj9cVpBnXFkvkRxfXxZ3jZZG97VNuKuH0LSaeHatZIR8IcdeRYRieyAKFyQlXAhWqqTibaZKBx6SCs-9vSi7zojEkng4Aodgjpf79zH_FRQRWg9xufftZpJlAclE1T5119uCMcwDkOngL3xVbTY7S9Ak9lB7lDyuDKm2kIilbuhtrsLx3XJqLEsH0ub8PUuaBmnOZRz8fGikUoy2ZU9F0fIm3_Advymdohu8jjMQ6x4Cp3YWNusn-FblEC8HcdOgdQOw63ZRwJuij1xdLQlTgx3qsRf8xl2zOMnF3nUWodULk2ynTpkYjCp4lbQGGboZ1lxbrKeMeybvr2hmjANtYQz2ahRdMGoVDB2HSruEOrYHx7lFIk5EVaOBhqLigBkLFw5RtDMMo3x8SdLBkNzP2C3oYTy_iGaTwpSRktabgDTmCySLPXGXWiRy0AVTGwozvLhPl1xz-14vlidGvfxcW-n2ahfpN9kqM3MyB5GT85wq-aYxyYi6m3eF2Gek6xSmtVRhCwWnXlUysfawsCQDK_Bio9nEJiYF9WVHjKXvB7ZKq8dQy0IBWrXFz2p04JdBl6s9rzXUvTWtfJUePRh0gQqV24iFCRkg86_WpLXpgPMg91AXwurgrhgJjfUKhxeyl0Riej_hVndAF16H-lz0-N4mDhyy2liRTX56dlC4cWTv2DU4iPtLOarfCna_m7QTdzyeidkcNzTjdtCkTAciZaA_8HZifDbjyIJeCTHBHoIZqfO1VaRiXytx5gEdtt-DQSanj4EA5cvlRLF1xRD7VGI5FTYR-mua1xdw0xpPj28GQ9Tjh8fi2a1WbwRzmjKnOAPwJyG5YelMRHgYnpLqqmESNuLjgfvlAlFmXqPjaJ8FSiPKZEFpwoy2lH_cSx4lYprq2Z9evdY6yaP6lI0lDi2WF3ddCzEeWj2MMg1Rz7fqYzjxum4RINbugJNeck584dTJnESySSZCqQ67m_vd6QE4u6ujB6yzu7TpXc_sOG2AS3r39XtuiPn570r8Z6LWUfdv8WnyRHJ_P5Sf4YAZ8LrGRuz6UHyKesL3bQnHvj0tv9PlOUv9hxemQcfkDi0nrN_3E3HGXbFHQLJ-9RHuVgE0SvL2JIA0aOfpIl69eN8CkBRHVvFnmfKh6OzFDMToBXkhczBWAmCW9nwGVARd_7yjWpu9y8oeAL3yuPXUvJQTDvjLxFHvGntNISf8KM5g9SW_QIxzUFClw2qlqhRLo29TFiSsVQM-hxSu05Js_fGocSek4nkLAotNpCG1AAoWP8TeI1NpLZVRwwzngaBijL_AbIoeFyTY31XdUztEQsi0dvBpu0iLd68GBTKOdeFeZ2vo9AFa4OwTFZ7SATUYRMgixzLlcuWqn60JKdWSQgSPCTLBHHZfgkmfxXLLwT65GEHlwuI8pb20xBeorUhbs3cc9LqXGnuwno5K5v0LaeFailxaBMl4fcgoBDj8TpdKqFKrcsIPol6suVKZRW_drrkc18p5Nt_MR5Xnd6D9SrkXYOUlc8c7gRrnAxE","DataProtected":true}
|
||||||
@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
|||||||
using Passenger;
|
using Passenger;
|
||||||
using Passenger.Data;
|
using Passenger.Data;
|
||||||
using Passenger.Extensions;
|
using Passenger.Extensions;
|
||||||
|
using Passenger.GrpcServer.Services;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -51,7 +52,6 @@ builder.Services.AddGrpc(options =>
|
|||||||
{
|
{
|
||||||
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
||||||
});
|
});
|
||||||
builder.Services.AddMagicOnion();
|
|
||||||
|
|
||||||
SnowFlakIdGenerator.Configure(2);
|
SnowFlakIdGenerator.Configure(2);
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ app.UseEndpoints(endpoints =>
|
|||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
endpoints.MapMetrics();
|
endpoints.MapMetrics();
|
||||||
endpoints.MapMagicOnionService();
|
endpoints.MapGrpcService<PassengerGrpcServices>();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||||
|
|||||||
@ -3,8 +3,6 @@ using BuildingBlocks.Core;
|
|||||||
using BuildingBlocks.Core.Event;
|
using BuildingBlocks.Core.Event;
|
||||||
using Passenger.Passengers.Events.Domain;
|
using Passenger.Passengers.Events.Domain;
|
||||||
using Passenger.Passengers.Features.CompleteRegisterPassenger.Reads;
|
using Passenger.Passengers.Features.CompleteRegisterPassenger.Reads;
|
||||||
using Passenger.Passengers.Models;
|
|
||||||
using PassengerType = BuildingBlocks.Contracts.Grpc.PassengerType;
|
|
||||||
|
|
||||||
namespace Passenger;
|
namespace Passenger;
|
||||||
|
|
||||||
@ -26,7 +24,7 @@ public sealed class EventMapper : IEventMapper
|
|||||||
{
|
{
|
||||||
PassengerRegistrationCompletedDomainEvent e => new CompleteRegisterPassengerMongoCommand(e.Id, e.PassportNumber, e.Name, e.PassengerType,
|
PassengerRegistrationCompletedDomainEvent e => new CompleteRegisterPassengerMongoCommand(e.Id, e.PassportNumber, e.Name, e.PassengerType,
|
||||||
e.Age, e.IsDeleted),
|
e.Age, e.IsDeleted),
|
||||||
PassengerCreatedDomainEvent e => new CompleteRegisterPassengerMongoCommand(e.Id, e.PassportNumber, e.Name, Passengers.Models.PassengerType.Unknown,
|
PassengerCreatedDomainEvent e => new CompleteRegisterPassengerMongoCommand(e.Id, e.PassportNumber, e.Name, Passengers.Enums.PassengerType.Unknown,
|
||||||
0, e.IsDeleted),
|
0, e.IsDeleted),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using MagicOnion;
|
|
||||||
using MagicOnion.Server;
|
|
||||||
using Mapster;
|
|
||||||
using MediatR;
|
|
||||||
using Passenger.Passengers.Features.GetPassengerById;
|
|
||||||
|
|
||||||
namespace Passenger.GrpcServer;
|
|
||||||
|
|
||||||
public class PassengerGrpcService : ServiceBase<IPassengerGrpcService>, IPassengerGrpcService
|
|
||||||
{
|
|
||||||
private readonly IMediator _mediator;
|
|
||||||
|
|
||||||
public PassengerGrpcService(IMediator mediator)
|
|
||||||
{
|
|
||||||
_mediator = mediator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UnaryResult<PassengerResponseDto> GetById(long id)
|
|
||||||
{
|
|
||||||
var result = await _mediator.Send(new GetPassengerQueryById(id));
|
|
||||||
return result.Adapt<PassengerResponseDto>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package passenger;
|
||||||
|
|
||||||
|
service PassengerGrpcService {
|
||||||
|
|
||||||
|
rpc GetById (GetByIdRequest) returns (PassengerResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetByIdRequest {
|
||||||
|
int64 Id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PassengerResponse {
|
||||||
|
int64 Id = 1;
|
||||||
|
string Name = 2;
|
||||||
|
string PassportNumber = 3;
|
||||||
|
PassengerType PassengerType = 4;
|
||||||
|
int32 Age = 5;
|
||||||
|
string Email = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum PassengerType {
|
||||||
|
PASSENGER_TYPE_UNKNOWN = 0;
|
||||||
|
PASSENGER_TYPE_MALE = 1;
|
||||||
|
PASSENGER_TYPE_FEMALE = 2;
|
||||||
|
PASSENGER_TYPE_BABY = 3;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using Mapster;
|
||||||
|
using MediatR;
|
||||||
|
using Passenger.Passengers.Features.GetPassengerById;
|
||||||
|
|
||||||
|
namespace Passenger.GrpcServer.Services;
|
||||||
|
|
||||||
|
public class PassengerGrpcServices : PassengerGrpcService.PassengerGrpcServiceBase
|
||||||
|
{
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public PassengerGrpcServices(IMediator mediator)
|
||||||
|
{
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<PassengerResponse> GetById(GetByIdRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = await _mediator.Send(new GetPassengerQueryById(request.Id));
|
||||||
|
return result.Adapt<PassengerResponse>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@ -21,4 +22,8 @@
|
|||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="GrpcServer\Protos\passenger.proto" GrpcServices="Both" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
namespace Passenger.Passengers.Enums;
|
||||||
|
|
||||||
|
public enum PassengerType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Male,
|
||||||
|
Female,
|
||||||
|
Baby
|
||||||
|
}
|
||||||
@ -4,4 +4,4 @@ using Passenger.Passengers.Models;
|
|||||||
namespace Passenger.Passengers.Events.Domain;
|
namespace Passenger.Passengers.Events.Domain;
|
||||||
|
|
||||||
public record PassengerRegistrationCompletedDomainEvent(long Id, string Name, string PassportNumber,
|
public record PassengerRegistrationCompletedDomainEvent(long Id, string Name, string PassportNumber,
|
||||||
PassengerType PassengerType, int Age, bool IsDeleted = false) : IDomainEvent;
|
Enums.PassengerType PassengerType, int Age, bool IsDeleted = false) : IDomainEvent;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ using Passenger.Passengers.Models;
|
|||||||
|
|
||||||
namespace Passenger.Passengers.Features.CompleteRegisterPassenger;
|
namespace Passenger.Passengers.Features.CompleteRegisterPassenger;
|
||||||
|
|
||||||
public record CompleteRegisterPassengerCommand(string PassportNumber, PassengerType PassengerType, int Age) : ICommand<PassengerResponseDto>, IInternalCommand
|
public record CompleteRegisterPassengerCommand(string PassportNumber, Enums.PassengerType PassengerType, int Age) : ICommand<PassengerResponseDto>, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,10 +12,10 @@ public class CompleteRegisterPassengerCommandValidator : AbstractValidator<Compl
|
|||||||
RuleFor(x => x.PassportNumber).NotNull().WithMessage("The PassportNumber is required!");
|
RuleFor(x => x.PassportNumber).NotNull().WithMessage("The PassportNumber is required!");
|
||||||
RuleFor(x => x.Age).GreaterThan(0).WithMessage("The Age must be greater than 0!");
|
RuleFor(x => x.Age).GreaterThan(0).WithMessage("The Age must be greater than 0!");
|
||||||
RuleFor(x => x.PassengerType).Must(p => p.GetType().IsEnum &&
|
RuleFor(x => x.PassengerType).Must(p => p.GetType().IsEnum &&
|
||||||
p == PassengerType.Baby ||
|
p == Enums.PassengerType.Baby ||
|
||||||
p == PassengerType.Female ||
|
p == Enums.PassengerType.Female ||
|
||||||
p == PassengerType.Male ||
|
p == Enums.PassengerType.Male ||
|
||||||
p == PassengerType.Unknown)
|
p == Enums.PassengerType.Unknown)
|
||||||
.WithMessage("PassengerType must be Male, Female, Baby or Unknown");
|
.WithMessage("PassengerType must be Male, Female, Baby or Unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ namespace Passenger.Passengers.Features.CompleteRegisterPassenger.Reads;
|
|||||||
public class CompleteRegisterPassengerMongoCommand : InternalCommand
|
public class CompleteRegisterPassengerMongoCommand : InternalCommand
|
||||||
{
|
{
|
||||||
public CompleteRegisterPassengerMongoCommand(long id, string passportNumber, string name,
|
public CompleteRegisterPassengerMongoCommand(long id, string passportNumber, string name,
|
||||||
PassengerType passengerType, int age, bool isDeleted)
|
Enums.PassengerType passengerType, int age, bool isDeleted)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
PassportNumber = passportNumber;
|
PassportNumber = passportNumber;
|
||||||
@ -18,7 +18,7 @@ public class CompleteRegisterPassengerMongoCommand : InternalCommand
|
|||||||
|
|
||||||
public string PassportNumber { get; }
|
public string PassportNumber { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public PassengerType PassengerType { get; }
|
public Enums.PassengerType PassengerType { get; }
|
||||||
public int Age { get; }
|
public int Age { get; }
|
||||||
public bool IsDeleted { get; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ namespace Passenger.Passengers.Models;
|
|||||||
|
|
||||||
public record Passenger : Aggregate<long>
|
public record Passenger : Aggregate<long>
|
||||||
{
|
{
|
||||||
public Passenger CompleteRegistrationPassenger(long id, string name, string passportNumber, PassengerType passengerType, int age, bool isDeleted = false)
|
public Passenger CompleteRegistrationPassenger(long id, string name, string passportNumber, Enums.PassengerType passengerType, int age, bool isDeleted = false)
|
||||||
{
|
{
|
||||||
var passenger = new Passenger
|
var passenger = new Passenger
|
||||||
{
|
{
|
||||||
@ -40,6 +40,6 @@ public record Passenger : Aggregate<long>
|
|||||||
|
|
||||||
public string PassportNumber { get; private set; }
|
public string PassportNumber { get; private set; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public PassengerType PassengerType { get; private set; }
|
public Enums.PassengerType PassengerType { get; private set; }
|
||||||
public int Age { get; private set; }
|
public int Age { get; private set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
namespace Passenger.Passengers.Models;
|
|
||||||
|
|
||||||
public enum PassengerType
|
|
||||||
{
|
|
||||||
Male,
|
|
||||||
Female,
|
|
||||||
Baby,
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
@ -6,7 +6,7 @@ public class PassengerReadModel
|
|||||||
public long PassengerId { get; init; }
|
public long PassengerId { get; init; }
|
||||||
public string PassportNumber { get; private set; }
|
public string PassportNumber { get; private set; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public PassengerType PassengerType { get; private set; }
|
public Enums.PassengerType PassengerType { get; private set; }
|
||||||
public int Age { get; private set; }
|
public int Age { get; private set; }
|
||||||
public bool IsDeleted { get; init; }
|
public bool IsDeleted { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using AutoBogus;
|
using AutoBogus;
|
||||||
using BuildingBlocks.IdsGenerator;
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using Passenger.Passengers.Enums;
|
||||||
using Passenger.Passengers.Features.CompleteRegisterPassenger;
|
using Passenger.Passengers.Features.CompleteRegisterPassenger;
|
||||||
using Passenger.Passengers.Models;
|
using Passenger.Passengers.Models;
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
using AutoBogus;
|
using AutoBogus;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.IdsGenerator;
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using Passenger;
|
||||||
|
|
||||||
public class FakePassengerResponseDto : AutoFaker<PassengerResponseDto>
|
public class FakePassengerResponse : AutoFaker<PassengerResponse>
|
||||||
{
|
{
|
||||||
public FakePassengerResponseDto()
|
public FakePassengerResponse()
|
||||||
{
|
{
|
||||||
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,8 @@ public class CompleteRegisterPassengerTests : IntegrationTestBase<Program, Passe
|
|||||||
{
|
{
|
||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
|
|
||||||
public CompleteRegisterPassengerTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(integrationTestFixture)
|
public CompleteRegisterPassengerTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) :
|
||||||
|
base(integrationTestFixture)
|
||||||
{
|
{
|
||||||
_testHarness = Fixture.TestHarness;
|
_testHarness = Fixture.TestHarness;
|
||||||
}
|
}
|
||||||
@ -36,7 +37,7 @@ public class CompleteRegisterPassengerTests : IntegrationTestBase<Program, Passe
|
|||||||
response.Should().NotBeNull();
|
response.Should().NotBeNull();
|
||||||
response?.Name.Should().Be(userCreated.Name);
|
response?.Name.Should().Be(userCreated.Name);
|
||||||
response?.PassportNumber.Should().Be(command.PassportNumber);
|
response?.PassportNumber.Should().Be(command.PassportNumber);
|
||||||
response?.PassengerType.Should().Be(command.PassengerType);
|
response?.PassengerType.ToString().Should().Be(command.PassengerType.ToString());
|
||||||
response?.Age.Should().Be(command.Age);
|
response?.Age.Should().Be(command.Age);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using BuildingBlocks.Contracts.Grpc;
|
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using MagicOnion;
|
|
||||||
using MagicOnion.Client;
|
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Passenger;
|
||||||
using NSubstitute;
|
|
||||||
using Passenger.Data;
|
using Passenger.Data;
|
||||||
using Passenger.Passengers.Features.GetPassengerById;
|
using Passenger.Passengers.Features.GetPassengerById;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -22,7 +18,8 @@ public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbCon
|
|||||||
private readonly ITestHarness _testHarness;
|
private readonly ITestHarness _testHarness;
|
||||||
private readonly GrpcChannel _channel;
|
private readonly GrpcChannel _channel;
|
||||||
|
|
||||||
public GetPassengerByIdTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(integrationTestFixture)
|
public GetPassengerByIdTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(
|
||||||
|
integrationTestFixture)
|
||||||
{
|
{
|
||||||
_channel = Fixture.Channel;
|
_channel = Fixture.Channel;
|
||||||
_testHarness = Fixture.TestHarness;
|
_testHarness = Fixture.TestHarness;
|
||||||
@ -30,7 +27,6 @@ public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbCon
|
|||||||
|
|
||||||
protected override void RegisterTestsServices(IServiceCollection services)
|
protected override void RegisterTestsServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
MockPassengerGrpcServices(services);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,25 +60,13 @@ public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbCon
|
|||||||
var passengerEntity = FakePassengerCreated.Generate(userCreated);
|
var passengerEntity = FakePassengerCreated.Generate(userCreated);
|
||||||
await Fixture.InsertAsync(passengerEntity);
|
await Fixture.InsertAsync(passengerEntity);
|
||||||
|
|
||||||
var passengerGrpcClient = MagicOnionClient.Create<IPassengerGrpcService>(_channel);
|
var passengerGrpcClient = new PassengerGrpcService.PassengerGrpcServiceClient(_channel);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await passengerGrpcClient.GetById(passengerEntity.Id);
|
var response = await passengerGrpcClient.GetByIdAsync(new GetByIdRequest {Id = passengerEntity.Id});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response?.Should().NotBeNull();
|
response?.Should().NotBeNull();
|
||||||
response?.Id.Should().Be(passengerEntity.Id);
|
response?.Id.Should().Be(passengerEntity.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MockPassengerGrpcServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
|
||||||
{
|
|
||||||
var mock = Substitute.For<IPassengerGrpcService>();
|
|
||||||
mock.GetById(Arg.Any<long>())
|
|
||||||
.Returns(new UnaryResult<PassengerResponseDto>(new FakePassengerResponseDto().Generate()));
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user