mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-14 12:48:34 +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
|
||||
- ✔️ **[`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
|
||||
- ✔️ **[`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.
|
||||
- ✔️ **[`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.
|
||||
|
||||
@ -251,7 +251,7 @@ Content-Type: application/json
|
||||
authorization: bearer {{Authenticate.response.body.access_token}}
|
||||
|
||||
{
|
||||
"passengerId": 4776722699124736,
|
||||
"passengerId": 4779536520052736,
|
||||
"flightId": 1,
|
||||
"description": "I want to fly to iran"
|
||||
}
|
||||
|
||||
@ -21,10 +21,7 @@
|
||||
<PackageReference Include="Figgle" Version="0.4.0" />
|
||||
<PackageReference Include="FluentValidation" Version="10.3.6" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6" />
|
||||
<PackageReference Include="MagicOnion" Version="4.4.0" />
|
||||
<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="Grpc.Core.Testing" Version="2.46.5" />
|
||||
<PackageReference Include="Mongo2Go" Version="3.1.3" />
|
||||
<PackageReference Include="NSubstitute" Version="4.3.0" />
|
||||
<PackageReference Include="Polly" Version="7.2.3" />
|
||||
@ -124,6 +121,10 @@
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
@ -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 Moq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
|
||||
namespace BuildingBlocks.Web;
|
||||
|
||||
@ -15,9 +15,11 @@ using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.Swagger;
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using Flight;
|
||||
using FluentValidation;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Passenger;
|
||||
using Prometheus;
|
||||
using Serilog;
|
||||
|
||||
@ -26,7 +28,6 @@ var configuration = builder.Configuration;
|
||||
var env = builder.Environment;
|
||||
|
||||
var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
|
||||
builder.Services.Configure<GrpcOptions>(options => configuration.GetSection("Grpc").Bind(options));
|
||||
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
@ -49,14 +50,14 @@ builder.Services.AddCustomMassTransit(typeof(BookingRoot).Assembly, env);
|
||||
builder.Services.AddCustomOpenTelemetry();
|
||||
builder.Services.AddTransient<AuthHeaderHandler>();
|
||||
|
||||
builder.Services.AddMagicOnionClients();
|
||||
|
||||
SnowFlakIdGenerator.Configure(3);
|
||||
|
||||
// ref: https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/ECommerce
|
||||
builder.Services.AddEventStore(configuration, typeof(BookingRoot).Assembly)
|
||||
.AddEventStoreDBSubscriptionToAll();
|
||||
|
||||
builder.Services.AddGrpcClients();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
|
||||
@ -6,15 +6,30 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</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>
|
||||
|
||||
@ -1,49 +1,54 @@
|
||||
using Ardalis.GuardClauses;
|
||||
using Booking.Booking.Events.Domain;
|
||||
using Booking.Booking.Exceptions;
|
||||
using Booking.Booking.Models.ValueObjects;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
using Booking.Configuration;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.EventStoreDB.Repository;
|
||||
using BuildingBlocks.Utils;
|
||||
using Flight;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Passenger;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking;
|
||||
|
||||
public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand, ulong>
|
||||
{
|
||||
private readonly IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
|
||||
private readonly IFlightGrpcService _flightGrpcService;
|
||||
private readonly ICurrentUserProvider _currentUserProvider;
|
||||
private readonly IEventDispatcher _eventDispatcher;
|
||||
private readonly IPassengerGrpcService _passengerGrpcService;
|
||||
private readonly FlightGrpcService.FlightGrpcServiceClient _flightGrpcServiceClient;
|
||||
private readonly PassengerGrpcService.PassengerGrpcServiceClient _passengerGrpcServiceClient;
|
||||
|
||||
public CreateBookingCommandHandler(IEventStoreDBRepository<Models.Booking> eventStoreDbRepository,
|
||||
IPassengerGrpcService passengerGrpcService,
|
||||
IFlightGrpcService flightGrpcService,
|
||||
ICurrentUserProvider currentUserProvider,
|
||||
IEventDispatcher eventDispatcher)
|
||||
IEventDispatcher eventDispatcher,
|
||||
FlightGrpcService.FlightGrpcServiceClient flightGrpcServiceClient,
|
||||
PassengerGrpcService.PassengerGrpcServiceClient passengerGrpcServiceClient)
|
||||
{
|
||||
_eventStoreDbRepository = eventStoreDbRepository;
|
||||
_passengerGrpcService = passengerGrpcService;
|
||||
_flightGrpcService = flightGrpcService;
|
||||
_currentUserProvider = currentUserProvider;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_flightGrpcServiceClient = flightGrpcServiceClient;
|
||||
_passengerGrpcServiceClient = passengerGrpcServiceClient;
|
||||
}
|
||||
|
||||
public async Task<ulong> Handle(CreateBookingCommand command,
|
||||
CancellationToken cancellationToken)
|
||||
public async Task<ulong> Handle(CreateBookingCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
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)
|
||||
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);
|
||||
|
||||
@ -52,12 +57,13 @@ public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand,
|
||||
|
||||
var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
|
||||
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());
|
||||
|
||||
await _eventDispatcher.SendAsync(aggrigate.DomainEvents, cancellationToken: cancellationToken);
|
||||
|
||||
await _flightGrpcService.ReserveSeat(new ReserveSeatRequestDto
|
||||
await _flightGrpcServiceClient.ReserveSeatAsync(new ReserveSeatRequest
|
||||
{
|
||||
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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Booking.Booking.Models.Reads;
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
using BuildingBlocks.PersistMessageProcessor.Data;
|
||||
using BuildingBlocks.TestBase;
|
||||
using Flight;
|
||||
using FluentAssertions;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Testing;
|
||||
using Integration.Test.Fakes;
|
||||
using MagicOnion;
|
||||
using MassTransit;
|
||||
using MassTransit.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using NSubstitute;
|
||||
using Passenger;
|
||||
using Xunit;
|
||||
using GetByIdRequest = Flight.GetByIdRequest;
|
||||
|
||||
namespace Integration.Test.Booking.Features;
|
||||
|
||||
public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbContext, BookingReadDbContext>
|
||||
{
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public CreateBookingTests(
|
||||
IntegrationTestFixture<Program, PersistMessageDbContext, BookingReadDbContext> integrationTestFixture) : base(
|
||||
integrationTestFixture)
|
||||
@ -56,11 +59,13 @@ public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbC
|
||||
{
|
||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mock = Substitute.For<IPassengerGrpcService>();
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<PassengerResponseDto>(new FakePassengerResponseDto().Generate()));
|
||||
var mockPassenger = Substitute.For<PassengerGrpcService.PassengerGrpcServiceClient>();
|
||||
|
||||
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 =>
|
||||
{
|
||||
var mock = Substitute.For<IFlightGrpcService>();
|
||||
var mockFlight = Substitute.For<FlightGrpcService.FlightGrpcServiceClient>();
|
||||
|
||||
mock.GetById(Arg.Any<long>())
|
||||
.Returns(new UnaryResult<FlightResponseDto>(Task.FromResult(new FakeFlightResponseDto().Generate())));
|
||||
mockFlight.GetByIdAsync(Arg.Any<GetByIdRequest>())
|
||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(new FakeFlightResponse().Generate()),
|
||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||
|
||||
mock.GetAvailableSeats(Arg.Any<long>())
|
||||
.Returns(
|
||||
new UnaryResult<IEnumerable<SeatResponseDto>>(Task.FromResult(FakeSeatsResponseDto.Generate())));
|
||||
mockFlight.GetAvailableSeatsAsync(Arg.Any<GetAvailableSeatsRequest>())
|
||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeSeatsResponse.Generate()),
|
||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||
|
||||
mock.ReserveSeat(new FakeReserveSeatRequestDto().Generate())
|
||||
.Returns(new UnaryResult<SeatResponseDto>(Task.FromResult(FakeSeatsResponseDto.Generate().First())));
|
||||
mockFlight.ReserveSeatAsync(Arg.Any<ReserveSeatRequest>())
|
||||
.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 BuildingBlocks.Contracts.Grpc;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Passenger;
|
||||
|
||||
namespace Integration.Test.Fakes;
|
||||
|
||||
public class FakePassengerResponseDto : AutoFaker<PassengerResponseDto>
|
||||
public class FakePassengerResponse : AutoFaker<PassengerResponse>
|
||||
{
|
||||
public FakePassengerResponseDto()
|
||||
public FakePassengerResponse()
|
||||
{
|
||||
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 BuildingBlocks.Contracts.Grpc;
|
||||
using Flight;
|
||||
|
||||
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,
|
||||
Class = SeatClass.Economy,
|
||||
@ -17,7 +18,7 @@ public static class FakeSeatsResponseDto
|
||||
SeatNumber = "33F",
|
||||
Id = 1
|
||||
},
|
||||
new SeatResponseDto()
|
||||
new SeatsResponse()
|
||||
{
|
||||
FlightId = 1,
|
||||
Class = SeatClass.Economy,
|
||||
@ -25,6 +26,8 @@ public static class FakeSeatsResponseDto
|
||||
SeatNumber = "22D",
|
||||
Id = 2
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Core;
|
||||
@ -19,9 +20,11 @@ using Flight;
|
||||
using Flight.Data;
|
||||
using Flight.Data.Seed;
|
||||
using Flight.Extensions;
|
||||
using Flight.GrpcServer.Services;
|
||||
using FluentValidation;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Prometheus;
|
||||
using Serilog;
|
||||
|
||||
@ -60,8 +63,6 @@ builder.Services.AddGrpc(options =>
|
||||
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
||||
});
|
||||
|
||||
builder.Services.AddMagicOnion();
|
||||
|
||||
SnowFlakIdGenerator.Configure(1);
|
||||
|
||||
builder.Services.AddCachingRequest(new List<Assembly> {typeof(FlightRoot).Assembly});
|
||||
@ -91,7 +92,7 @@ app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapMetrics();
|
||||
endpoints.MapMagicOnionService();
|
||||
endpoints.MapGrpcService<FlightGrpcServices>();
|
||||
});
|
||||
|
||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||
|
||||
@ -79,12 +79,12 @@ public class FlightDataSeeder : IDataSeeder
|
||||
{
|
||||
var seats = new List<Seat>
|
||||
{
|
||||
Seat.Create(1 ,"12A", SeatType.Window, SeatClass.Economy, 1),
|
||||
Seat.Create(2, "12B", SeatType.Window, SeatClass.Economy, 1),
|
||||
Seat.Create(3, "12C", SeatType.Middle, SeatClass.Economy, 1),
|
||||
Seat.Create(4, "12D", SeatType.Middle, SeatClass.Economy, 1),
|
||||
Seat.Create(5, "12E", SeatType.Aisle, SeatClass.Economy, 1),
|
||||
Seat.Create(6, "12F", SeatType.Aisle, SeatClass.Economy, 1)
|
||||
Seat.Create(1 ,"12A", Seats.Enums.SeatType.Window, Seats.Enums.SeatClass.Economy, 1),
|
||||
Seat.Create(2, "12B", Seats.Enums.SeatType.Window, Seats.Enums.SeatClass.Economy, 1),
|
||||
Seat.Create(3, "12C", Seats.Enums.SeatType.Middle, Seats.Enums.SeatClass.Economy, 1),
|
||||
Seat.Create(4, "12D", Seats.Enums.SeatType.Middle, Seats.Enums.SeatClass.Economy, 1),
|
||||
Seat.Create(5, "12E", Seats.Enums.SeatType.Aisle, Seats.Enums.SeatClass.Economy, 1),
|
||||
Seat.Create(6, "12F", Seats.Enums.SeatType.Aisle, Seats.Enums.SeatClass.Economy, 1)
|
||||
};
|
||||
|
||||
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),
|
||||
new DateTime(2022, 1, 31, 14, 0, 0),
|
||||
2, 120m,
|
||||
new DateTime(2022, 1, 31), FlightStatus.Completed,
|
||||
new DateTime(2022, 1, 31), Flights.Enums.FlightStatus.Completed,
|
||||
8000)
|
||||
};
|
||||
await _flightDbContext.Flights.AddRangeAsync(flights);
|
||||
|
||||
@ -5,6 +5,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@ -15,7 +21,10 @@
|
||||
<Folder Include="Aircrafts\Exceptions" />
|
||||
<Folder Include="Airports\Exceptions" />
|
||||
<Folder Include="Data\Migrations" />
|
||||
<Folder Include="Enum" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="GrpcServer\Protos\flight.proto" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
namespace Flight.Flights.Models;
|
||||
namespace Flight.Flights.Enums;
|
||||
|
||||
public enum FlightStatus
|
||||
{
|
||||
Unknown = 0,
|
||||
Flying = 1,
|
||||
Delay = 2,
|
||||
Canceled = 3,
|
||||
@ -6,4 +6,4 @@ namespace Flight.Flights.Events.Domain;
|
||||
|
||||
public record FlightCreatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
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,
|
||||
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;
|
||||
public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
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,
|
||||
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();
|
||||
}
|
||||
|
||||
@ -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.Status).Must(p => (p.GetType().IsEnum &&
|
||||
p == FlightStatus.Flying) ||
|
||||
p == FlightStatus.Canceled ||
|
||||
p == FlightStatus.Delay ||
|
||||
p == FlightStatus.Completed)
|
||||
p == Enums.FlightStatus.Flying) ||
|
||||
p == Enums.FlightStatus.Canceled ||
|
||||
p == Enums.FlightStatus.Delay ||
|
||||
p == Enums.FlightStatus.Completed)
|
||||
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
||||
|
||||
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,
|
||||
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)
|
||||
{
|
||||
Id = id;
|
||||
@ -33,7 +33,7 @@ public class CreateFlightMongoCommand : InternalCommand
|
||||
public long ArriveAirportId { get; }
|
||||
public decimal DurationMinutes { get; }
|
||||
public DateTime FlightDate { get; }
|
||||
public FlightStatus Status { get; }
|
||||
public Enums.FlightStatus Status { get; }
|
||||
public decimal Price { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ public class DeleteFlightMongoCommand : InternalCommand
|
||||
{
|
||||
public DeleteFlightMongoCommand(long id, string flightNumber, long aircraftId, DateTime departureDate,
|
||||
long departureAirportId,
|
||||
DateTime arriveDate, long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status,
|
||||
DateTime arriveDate, long arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
|
||||
decimal price, bool isDeleted)
|
||||
{
|
||||
Id = id;
|
||||
@ -33,7 +33,7 @@ public class DeleteFlightMongoCommand : InternalCommand
|
||||
public long ArriveAirportId { get; }
|
||||
public decimal DurationMinutes { get; }
|
||||
public DateTime FlightDate { get; }
|
||||
public FlightStatus Status { get; }
|
||||
public Enums.FlightStatus Status { get; }
|
||||
public decimal Price { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ public class UpdateFlightMongoCommand : InternalCommand
|
||||
{
|
||||
public UpdateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId,
|
||||
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status,
|
||||
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status,
|
||||
decimal Price, bool IsDeleted)
|
||||
{
|
||||
this.Id = Id;
|
||||
@ -33,7 +33,7 @@ public class UpdateFlightMongoCommand : InternalCommand
|
||||
public long ArriveAirportId { get; }
|
||||
public decimal DurationMinutes { get; }
|
||||
public DateTime FlightDate { get; }
|
||||
public FlightStatus Status { get; }
|
||||
public Enums.FlightStatus Status { get; }
|
||||
public decimal Price { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public record UpdateFlightCommand : ICommand<FlightResponseDto>, IInvalidateCach
|
||||
public decimal DurationMinutes { 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 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.Status).Must(p => (p.GetType().IsEnum &&
|
||||
p == FlightStatus.Flying) ||
|
||||
p == FlightStatus.Canceled ||
|
||||
p == FlightStatus.Delay ||
|
||||
p == FlightStatus.Completed)
|
||||
p == Enums.FlightStatus.Flying) ||
|
||||
p == Enums.FlightStatus.Canceled ||
|
||||
p == Enums.FlightStatus.Delay ||
|
||||
p == Enums.FlightStatus.Completed)
|
||||
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
||||
|
||||
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 decimal DurationMinutes { 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 static Flight Create(long id, string flightNumber, long aircraftId,
|
||||
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)
|
||||
{
|
||||
var flight = new Flight
|
||||
@ -51,7 +51,7 @@ public record Flight : Aggregate<long>
|
||||
|
||||
public void Update(long id, string flightNumber, long aircraftId,
|
||||
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)
|
||||
{
|
||||
FlightNumber = flightNumber;
|
||||
@ -74,7 +74,7 @@ public record Flight : Aggregate<long>
|
||||
|
||||
public void Delete(long id, string flightNumber, long aircraftId,
|
||||
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)
|
||||
{
|
||||
FlightNumber = flightNumber;
|
||||
|
||||
@ -16,7 +16,7 @@ public class FlightReadModel
|
||||
public long ArriveAirportId { get; init; }
|
||||
public decimal DurationMinutes { get; init; }
|
||||
public DateTime FlightDate { get; init; }
|
||||
public FlightStatus Status { get; init; }
|
||||
public Enums.FlightStatus Status { get; init; }
|
||||
public decimal Price { 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
|
||||
{
|
||||
Unknown = 0,
|
||||
FirstClass,
|
||||
Business,
|
||||
Economy
|
||||
@ -1,7 +1,8 @@
|
||||
namespace Flight.Seats.Models;
|
||||
namespace Flight.Seats.Enums;
|
||||
|
||||
public enum SeatType
|
||||
{
|
||||
Unknown = 0,
|
||||
Window,
|
||||
Middle,
|
||||
Aisle
|
||||
@ -3,4 +3,4 @@ using Flight.Seats.Models;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -11,9 +11,9 @@ public class CreateSeatCommandValidator : AbstractValidator<CreateSeatCommand>
|
||||
RuleFor(x => x.SeatNumber).NotEmpty().WithMessage("SeatNumber is required");
|
||||
RuleFor(x => x.FlightId).NotEmpty().WithMessage("FlightId is required");
|
||||
RuleFor(x => x.Class).Must(p => (p.GetType().IsEnum &&
|
||||
p == SeatClass.FirstClass) ||
|
||||
p == SeatClass.Business ||
|
||||
p == SeatClass.Economy)
|
||||
p == Enums.SeatClass.FirstClass) ||
|
||||
p == Enums.SeatClass.Business ||
|
||||
p == Enums.SeatClass.Economy)
|
||||
.WithMessage("Status must be FirstClass, Business or Economy");
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ namespace Flight.Seats.Features.CreateSeat.Reads;
|
||||
|
||||
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)
|
||||
{
|
||||
Id = id;
|
||||
@ -18,8 +18,8 @@ public class CreateSeatMongoCommand : InternalCommand
|
||||
|
||||
public long Id { get; }
|
||||
public string SeatNumber { get; }
|
||||
public SeatType Type { get; }
|
||||
public SeatClass Class { get; }
|
||||
public Enums.SeatType Type { get; }
|
||||
public Enums.SeatClass Class { get; }
|
||||
public long FlightId { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ namespace Flight.Seats.Features.ReserveSeat.Reads;
|
||||
|
||||
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)
|
||||
{
|
||||
Id = id;
|
||||
@ -17,8 +17,8 @@ public class ReserveSeatMongoCommand : InternalCommand
|
||||
}
|
||||
|
||||
public string SeatNumber { get; }
|
||||
public SeatType Type { get; }
|
||||
public SeatClass Class { get; }
|
||||
public Enums.SeatType Type { get; }
|
||||
public Enums.SeatClass Class { get; }
|
||||
public long FlightId { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ public class SeatReadModel
|
||||
public long Id { get; init; }
|
||||
public long SeatId { get; init; }
|
||||
public string SeatNumber { get; init; }
|
||||
public SeatType Type { get; init; }
|
||||
public SeatClass Class { get; init; }
|
||||
public Enums.SeatType Type { get; init; }
|
||||
public Enums.SeatClass Class { get; init; }
|
||||
public long FlightId { get; init; }
|
||||
public bool IsDeleted { get; init; }
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ namespace Flight.Seats.Models;
|
||||
|
||||
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)
|
||||
{
|
||||
var seat = new Seat()
|
||||
@ -52,7 +52,7 @@ public record Seat : Aggregate<long>
|
||||
}
|
||||
|
||||
public string SeatNumber { get; private set; }
|
||||
public SeatType Type { get; private set; }
|
||||
public SeatClass Class { get; private set; }
|
||||
public Enums.SeatType Type { get; private set; }
|
||||
public Enums.SeatClass Class { get; private set; }
|
||||
public long FlightId { get; private set; }
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
using BuildingBlocks.TestBase;
|
||||
using Flight;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.CreateFlight.Reads;
|
||||
using Flight.Flights.Features.GetFlightById;
|
||||
using Flight.GrpcServer.Services;
|
||||
using FluentAssertions;
|
||||
using Grpc.Net.Client;
|
||||
using Integration.Test.Fakes;
|
||||
using MagicOnion.Client;
|
||||
using Xunit;
|
||||
|
||||
namespace Integration.Test.Flight.Features;
|
||||
@ -16,7 +16,9 @@ public class GetFlightByIdTests : IntegrationTestBase<Program, FlightDbContext,
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -49,10 +51,10 @@ public class GetFlightByIdTests : IntegrationTestBase<Program, FlightDbContext,
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateFlightMongoCommand>();
|
||||
|
||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
|
||||
var flightGrpcClient = new FlightGrpcService.FlightGrpcServiceClient(_channel);
|
||||
|
||||
// Act
|
||||
var response = await flightGrpcClient.GetById(command.Id);
|
||||
var response = await flightGrpcClient.GetByIdAsync(new GetByIdRequest {Id = command.Id});
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.TestBase;
|
||||
using Flight;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.CreateFlight.Reads;
|
||||
using Flight.Seats.Features.CreateSeat.Reads;
|
||||
using FluentAssertions;
|
||||
using Grpc.Net.Client;
|
||||
using Integration.Test.Fakes;
|
||||
using MagicOnion.Client;
|
||||
using MassTransit.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Integration.Test.Seat.Features;
|
||||
@ -39,13 +36,13 @@ public class GetAvailableSeatsTests : IntegrationTestBase<Program, FlightDbConte
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
||||
|
||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
|
||||
var flightGrpcClient = new FlightGrpcService.FlightGrpcServiceClient(_channel);
|
||||
|
||||
// Act
|
||||
var response = await flightGrpcClient.GetAvailableSeats(flightCommand.Id);
|
||||
var response = await flightGrpcClient.GetAvailableSeatsAsync(new GetAvailableSeatsRequest{FlightId = flightCommand.Id});
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
response?.Count().Should().BeGreaterOrEqualTo(1);
|
||||
response?.Items?.Count.Should().BeGreaterOrEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
using BuildingBlocks.TestBase;
|
||||
using Flight;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Features.CreateFlight.Reads;
|
||||
using Flight.Seats.Features.CreateSeat.Reads;
|
||||
using FluentAssertions;
|
||||
using Grpc.Net.Client;
|
||||
using Integration.Test.Fakes;
|
||||
using MagicOnion.Client;
|
||||
using Xunit;
|
||||
|
||||
namespace Integration.Test.Seat.Features;
|
||||
@ -39,10 +38,10 @@ public class ReserveSeatTests : IntegrationTestBase<Program, FlightDbContext, Fl
|
||||
|
||||
await Fixture.ShouldProcessedPersistInternalCommand<CreateSeatMongoCommand>();
|
||||
|
||||
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
|
||||
var flightGrpcClient = new FlightGrpcService.FlightGrpcServiceClient(_channel);
|
||||
|
||||
// Act
|
||||
var response = await flightGrpcClient.ReserveSeat(new ReserveSeatRequestDto
|
||||
var response = await flightGrpcClient.ReserveSeatAsync(new ReserveSeatRequest()
|
||||
{
|
||||
FlightId = seatCommand.FlightId, SeatNumber = seatCommand.SeatNumber
|
||||
});
|
||||
|
||||
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Airports.Models;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Enums;
|
||||
using Flight.Flights.Models;
|
||||
using Flight.Seats.Enums;
|
||||
using Flight.Seats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using AutoBogus;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Seats.Enums;
|
||||
using Flight.Seats.Features.CreateSeat;
|
||||
using Flight.Seats.Models;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using AutoBogus;
|
||||
using Flight.Flights.Enums;
|
||||
using Flight.Flights.Features.CreateFlight;
|
||||
using Flight.Flights.Models;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using AutoBogus;
|
||||
using Flight.Seats.Enums;
|
||||
using Flight.Seats.Features.CreateSeat;
|
||||
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.Data;
|
||||
using Passenger.Extensions;
|
||||
using Passenger.GrpcServer.Services;
|
||||
using Prometheus;
|
||||
using Serilog;
|
||||
|
||||
@ -51,7 +52,6 @@ builder.Services.AddGrpc(options =>
|
||||
{
|
||||
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
||||
});
|
||||
builder.Services.AddMagicOnion();
|
||||
|
||||
SnowFlakIdGenerator.Configure(2);
|
||||
|
||||
@ -78,7 +78,7 @@ app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapMetrics();
|
||||
endpoints.MapMagicOnionService();
|
||||
endpoints.MapGrpcService<PassengerGrpcServices>();
|
||||
});
|
||||
|
||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||
|
||||
@ -3,8 +3,6 @@ using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Passenger.Passengers.Events.Domain;
|
||||
using Passenger.Passengers.Features.CompleteRegisterPassenger.Reads;
|
||||
using Passenger.Passengers.Models;
|
||||
using PassengerType = BuildingBlocks.Contracts.Grpc.PassengerType;
|
||||
|
||||
namespace Passenger;
|
||||
|
||||
@ -26,7 +24,7 @@ public sealed class EventMapper : IEventMapper
|
||||
{
|
||||
PassengerRegistrationCompletedDomainEvent e => new CompleteRegisterPassengerMongoCommand(e.Id, e.PassportNumber, e.Name, e.PassengerType,
|
||||
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),
|
||||
_ => 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>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@ -21,4 +22,8 @@
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="GrpcServer\Protos\passenger.proto" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
</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;
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -12,10 +12,10 @@ public class CompleteRegisterPassengerCommandValidator : AbstractValidator<Compl
|
||||
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.PassengerType).Must(p => p.GetType().IsEnum &&
|
||||
p == PassengerType.Baby ||
|
||||
p == PassengerType.Female ||
|
||||
p == PassengerType.Male ||
|
||||
p == PassengerType.Unknown)
|
||||
p == Enums.PassengerType.Baby ||
|
||||
p == Enums.PassengerType.Female ||
|
||||
p == Enums.PassengerType.Male ||
|
||||
p == Enums.PassengerType.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 CompleteRegisterPassengerMongoCommand(long id, string passportNumber, string name,
|
||||
PassengerType passengerType, int age, bool isDeleted)
|
||||
Enums.PassengerType passengerType, int age, bool isDeleted)
|
||||
{
|
||||
Id = id;
|
||||
PassportNumber = passportNumber;
|
||||
@ -18,7 +18,7 @@ public class CompleteRegisterPassengerMongoCommand : InternalCommand
|
||||
|
||||
public string PassportNumber { get; }
|
||||
public string Name { get; }
|
||||
public PassengerType PassengerType { get; }
|
||||
public Enums.PassengerType PassengerType { get; }
|
||||
public int Age { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ namespace Passenger.Passengers.Models;
|
||||
|
||||
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
|
||||
{
|
||||
@ -40,6 +40,6 @@ public record Passenger : Aggregate<long>
|
||||
|
||||
public string PassportNumber { 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; }
|
||||
}
|
||||
|
||||
@ -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 string PassportNumber { 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 bool IsDeleted { get; init; }
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using AutoBogus;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Passenger.Passengers.Enums;
|
||||
using Passenger.Passengers.Features.CompleteRegisterPassenger;
|
||||
using Passenger.Passengers.Models;
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
using AutoBogus;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
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());
|
||||
}
|
||||
|
||||
@ -13,7 +13,8 @@ public class CompleteRegisterPassengerTests : IntegrationTestBase<Program, Passe
|
||||
{
|
||||
private readonly ITestHarness _testHarness;
|
||||
|
||||
public CompleteRegisterPassengerTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
public CompleteRegisterPassengerTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) :
|
||||
base(integrationTestFixture)
|
||||
{
|
||||
_testHarness = Fixture.TestHarness;
|
||||
}
|
||||
@ -36,7 +37,7 @@ public class CompleteRegisterPassengerTests : IntegrationTestBase<Program, Passe
|
||||
response.Should().NotBeNull();
|
||||
response?.Name.Should().Be(userCreated.Name);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.Contracts.Grpc;
|
||||
using BuildingBlocks.TestBase;
|
||||
using FluentAssertions;
|
||||
using Grpc.Net.Client;
|
||||
using Integration.Test.Fakes;
|
||||
using MagicOnion;
|
||||
using MagicOnion.Client;
|
||||
using MassTransit.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using NSubstitute;
|
||||
using Passenger;
|
||||
using Passenger.Data;
|
||||
using Passenger.Passengers.Features.GetPassengerById;
|
||||
using Xunit;
|
||||
@ -22,7 +18,8 @@ public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbCon
|
||||
private readonly ITestHarness _testHarness;
|
||||
private readonly GrpcChannel _channel;
|
||||
|
||||
public GetPassengerByIdTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
public GetPassengerByIdTests(IntegrationTestFixture<Program, PassengerDbContext> integrationTestFixture) : base(
|
||||
integrationTestFixture)
|
||||
{
|
||||
_channel = Fixture.Channel;
|
||||
_testHarness = Fixture.TestHarness;
|
||||
@ -30,7 +27,6 @@ public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbCon
|
||||
|
||||
protected override void RegisterTestsServices(IServiceCollection services)
|
||||
{
|
||||
MockPassengerGrpcServices(services);
|
||||
}
|
||||
|
||||
|
||||
@ -64,25 +60,13 @@ public class GetPassengerByIdTests : IntegrationTestBase<Program, PassengerDbCon
|
||||
var passengerEntity = FakePassengerCreated.Generate(userCreated);
|
||||
await Fixture.InsertAsync(passengerEntity);
|
||||
|
||||
var passengerGrpcClient = MagicOnionClient.Create<IPassengerGrpcService>(_channel);
|
||||
var passengerGrpcClient = new PassengerGrpcService.PassengerGrpcServiceClient(_channel);
|
||||
|
||||
// Act
|
||||
var response = await passengerGrpcClient.GetById(passengerEntity.Id);
|
||||
var response = await passengerGrpcClient.GetByIdAsync(new GetByIdRequest {Id = passengerEntity.Id});
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
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