Merge pull request #262 from amirhossein18121380/RefactorModel

Add Value Objects to Aircraft and Passenger
This commit is contained in:
Meysam Hadeli 2023-06-02 17:44:47 +03:30 committed by GitHub
commit f928dc2506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1623 additions and 131 deletions

3
.gitignore vendored
View File

@ -435,4 +435,5 @@ fabric.properties
*.jwk
**/keys
**/keys
/src/ApiGateway/back.txt

View File

@ -0,0 +1,12 @@
namespace Flight.Aircrafts.Exceptions;
using System;
using BuildingBlocks.Exception;
public class InvalidAircraftIdExceptions : BadRequestException
{
public InvalidAircraftIdExceptions(Guid aircraftId)
: base($"AircraftId: '{aircraftId}' is invalid.")
{
}
}

View File

@ -0,0 +1,10 @@
namespace Flight.Aircrafts.Exceptions;
using BuildingBlocks.Exception;
public class InvalidManufacturingYearException : BadRequestException
{
public InvalidManufacturingYearException() : base("ManufacturingYear must be greater than 1900")
{
}
}

View File

@ -0,0 +1,10 @@
namespace Flight.Aircrafts.Exceptions;
using BuildingBlocks.Exception;
public class InvalidModelException : BadRequestException
{
public InvalidModelException() : base("Model cannot be empty or whitespace.")
{
}
}

View File

@ -0,0 +1,10 @@
namespace Flight.Aircrafts.Exceptions;
using BuildingBlocks.Exception;
public class InvalidNameException : BadRequestException
{
public InvalidNameException() : base("Name cannot be empty or whitespace.")
{
}
}

View File

@ -1,9 +1,10 @@
using Flight.Aircrafts.Models;
using Flight.Aircrafts.Models;
using Mapster;
namespace Flight.Aircrafts.Features;
using CreatingAircraft.V1;
using Flight.Aircrafts.Models.ValueObjects;
using MassTransit;
public class AircraftMappings : IRegister
@ -11,12 +12,12 @@ public class AircraftMappings : IRegister
public void Register(TypeAdapterConfig config)
{
config.NewConfig<CreateAircraftMongo, AircraftReadModel>()
.Map(d => d.Id, s => NewId.NextGuid())
.Map(d => d.AircraftId, s => s.Id);
.Map(d => d.Id, s => NewId.NextGuid())
.Map(d => d.AircraftId, s => AircraftId.Of(s.Id));
config.NewConfig<Aircraft, AircraftReadModel>()
.Map(d => d.Id, s => NewId.NextGuid())
.Map(d => d.AircraftId, s => s.Id);
.Map(d => d.AircraftId, s => AircraftId.Of(s.Id));
config.NewConfig<CreateAircraftRequestDto, CreatingAircraft.V1.CreateAircraft>()
.ConstructUsing(x => new CreatingAircraft.V1.CreateAircraft(x.Name, x.Model, x.ManufacturingYear));

View File

@ -1,16 +1,17 @@
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using BuildingBlocks.Web;
using Exceptions;
using Models;
using Data;
using Duende.IdentityServer.EntityFramework.Entities;
using Exceptions;
using Flight.Aircrafts.ValueObjects;
using FluentValidation;
using MapsterMapper;
using MassTransit;
@ -19,6 +20,8 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Models;
using Models.ValueObjects;
public record CreateAircraft(string Name, string Model, int ManufacturingYear) : ICommand<CreateAircraftResult>,
IInternalCommand
@ -26,7 +29,7 @@ public record CreateAircraft(string Name, string Model, int ManufacturingYear) :
public Guid Id { get; init; } = NewId.NextGuid();
}
public record CreateAircraftResult(Guid Id);
public record CreateAircraftResult(AircraftId Id);
public record AircraftCreatedDomainEvent
(Guid Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent;
@ -88,15 +91,16 @@ internal class CreateAircraftHandler : IRequestHandler<CreateAircraft, CreateAir
{
Guard.Against.Null(request, nameof(request));
var aircraft =
await _flightDbContext.Aircraft.SingleOrDefaultAsync(x => x.Model == request.Model, cancellationToken);
var aircraft = await _flightDbContext.Aircraft.AsNoTracking().SingleOrDefaultAsync(
a => a.Model.Value.Equals(Model.Of(request.Model)), cancellationToken);
if (aircraft is not null)
{
throw new AircraftAlreadyExistException();
}
var aircraftEntity = Aircraft.Create(request.Id, request.Name, request.Model, request.ManufacturingYear);
var aircraftEntity = Aircraft.Create(AircraftId.Of(request.Id), Name.Of(request.Name), Model.Of(request.Model), ManufacturingYear.Of(request.ManufacturingYear));
var newAircraft = (await _flightDbContext.Aircraft.AddAsync(aircraftEntity, cancellationToken))?.Entity;

View File

@ -1,4 +1,4 @@
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
using System;
using System.Threading;
@ -6,11 +6,12 @@ using System.Threading.Tasks;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using Exceptions;
using Models;
using Data;
using Exceptions;
using MapsterMapper;
using MediatR;
using Models;
using Models.ValueObjects;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
@ -36,7 +37,7 @@ public class CreateAircraftMongoHandler : ICommandHandler<CreateAircraftMongo>
var aircraftReadModel = _mapper.Map<AircraftReadModel>(request);
var aircraft = await _flightReadDbContext.Aircraft.AsQueryable()
.FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId, cancellationToken);
.FirstOrDefaultAsync(x => x.AircraftId == AircraftId.Of(aircraftReadModel.AircraftId), cancellationToken);
if (aircraft is not null)
{

View File

@ -2,16 +2,17 @@ using BuildingBlocks.Core.Model;
namespace Flight.Aircrafts.Models;
using System;
using Features.CreatingAircraft.V1;
using Flight.Aircrafts.ValueObjects;
using ValueObjects;
public record Aircraft : Aggregate<Guid>
public record Aircraft : Aggregate<AircraftId>
{
public string Name { get; private set; }
public string Model { get; private set; }
public int ManufacturingYear { get; private set; }
public Name Name { get; private set; } = default!;
public Model Model { get; private set; } = default!;
public ManufacturingYear ManufacturingYear { get; private set; } = default!;
public static Aircraft Create(Guid id, string name, string model, int manufacturingYear, bool isDeleted = false)
public static Aircraft Create(AircraftId id, Name name, Model model, ManufacturingYear manufacturingYear, bool isDeleted = false)
{
var aircraft = new Aircraft
{

View File

@ -1,4 +1,4 @@
namespace Flight.Aircrafts.Models;
namespace Flight.Aircrafts.Models;
using System;

View File

@ -0,0 +1,28 @@
namespace Flight.Aircrafts.Models.ValueObjects;
using System;
using Exceptions;
public record AircraftId
{
public Guid Value { get; }
private AircraftId(Guid value)
{
if (value == Guid.Empty)
{
throw new InvalidAircraftIdExceptions(value);
}
Value = value;
}
public static AircraftId Of(Guid value)
{
return new AircraftId(value);
}
public static implicit operator Guid(AircraftId aircraftId)
{
return aircraftId.Value;
}
}

View File

@ -0,0 +1,26 @@
namespace Flight.Aircrafts.ValueObjects;
using Flight.Aircrafts.Exceptions;
public record ManufacturingYear
{
public int Value { get; }
public ManufacturingYear(int value)
{
if (value < 1900)
{
throw new InvalidManufacturingYearException();
}
Value = value;
}
public static ManufacturingYear Of(int value)
{
return new ManufacturingYear(value);
}
public static implicit operator int(ManufacturingYear manufacturingYear)
{
return manufacturingYear.Value;
}
}

View File

@ -0,0 +1,24 @@
namespace Flight.Aircrafts.Models.ValueObjects;
using Flight.Aircrafts.Exceptions;
public record Model
{
public string Value { get; }
public Model(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidModelException();
}
Value = value;
}
public static Model Of(string value)
{
return new Model(value);
}
public static implicit operator string(Model model)
{
return model.Value;
}
}

View File

@ -0,0 +1,24 @@
namespace Flight.Aircrafts.Models.ValueObjects;
using Exceptions;
public record Name
{
public string Value { get; }
public Name(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidNameException();
}
Value = value;
}
public static Name Of(string value)
{
return new Name(value);
}
public static implicit operator string(Name name)
{
return name.Value;
}
}

View File

@ -4,17 +4,53 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Flight.Data.Configurations;
using System;
using Flight.Aircrafts.Models.ValueObjects;
public class AircraftConfiguration : IEntityTypeConfiguration<Aircraft>
{
public void Configure(EntityTypeBuilder<Aircraft> builder)
{
builder.ToTable(nameof(Aircraft));
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).ValueGeneratedNever();
builder.Property(r => r.Id).ValueGeneratedNever()
.HasConversion<Guid>(aircraftId => aircraftId.Value, dbId => AircraftId.Of(dbId));
// // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api
builder.Property(r => r.Version).IsConcurrencyToken();
builder.OwnsOne(
x => x.Name,
a =>
{
a.Property(p => p.Value)
.HasColumnName(nameof(Aircraft.Name))
.HasMaxLength(50)
.IsRequired();
}
);
builder.OwnsOne(
x => x.Model,
a =>
{
a.Property(p => p.Value)
.HasColumnName(nameof(Aircraft.Model))
.HasMaxLength(50)
.IsRequired();
}
);
builder.OwnsOne(
x => x.ManufacturingYear,
a =>
{
a.Property(p => p.Value)
.HasColumnName(nameof(Aircraft.ManufacturingYear))
.HasMaxLength(5)
.IsRequired();
}
);
}
}

View File

@ -0,0 +1,363 @@
// <auto-generated />
using System;
using Flight.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Flight.Data.Migrations
{
[DbContext(typeof(FlightDbContext))]
[Migration("20230525075149_aircraftValueObjects")]
partial class aircraftValueObjects
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasColumnName("created_by");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnName("is_deleted");
b.Property<DateTime?>("LastModified")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_modified");
b.Property<long?>("LastModifiedBy")
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
.HasColumnName("version");
b.HasKey("Id")
.HasName("pk_aircraft");
b.ToTable("aircraft", (string)null);
});
modelBuilder.Entity("Flight.Airports.Models.Airport", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<string>("Address")
.HasColumnType("text")
.HasColumnName("address");
b.Property<string>("Code")
.HasColumnType("text")
.HasColumnName("code");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasColumnName("created_by");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnName("is_deleted");
b.Property<DateTime?>("LastModified")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_modified");
b.Property<long?>("LastModifiedBy")
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
.HasColumnName("version");
b.HasKey("Id")
.HasName("pk_airport");
b.ToTable("airport", (string)null);
});
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<Guid?>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("aircraft_id");
b.Property<Guid>("ArriveAirportId")
.HasColumnType("uuid")
.HasColumnName("arrive_airport_id");
b.Property<DateTime>("ArriveDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("arrive_date");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasColumnName("created_by");
b.Property<Guid>("DepartureAirportId")
.HasColumnType("uuid")
.HasColumnName("departure_airport_id");
b.Property<DateTime>("DepartureDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("departure_date");
b.Property<decimal>("DurationMinutes")
.HasColumnType("numeric")
.HasColumnName("duration_minutes");
b.Property<DateTime>("FlightDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("flight_date");
b.Property<string>("FlightNumber")
.HasColumnType("text")
.HasColumnName("flight_number");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnName("is_deleted");
b.Property<DateTime?>("LastModified")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_modified");
b.Property<long?>("LastModifiedBy")
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<decimal>("Price")
.HasColumnType("numeric")
.HasColumnName("price");
b.Property<string>("Status")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("Unknown")
.HasColumnName("status");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
.HasColumnName("version");
b.HasKey("Id")
.HasName("pk_flight");
b.HasIndex("AircraftId")
.HasDatabaseName("ix_flight_aircraft_id");
b.HasIndex("ArriveAirportId")
.HasDatabaseName("ix_flight_arrive_airport_id");
b.ToTable("flight", (string)null);
});
modelBuilder.Entity("Flight.Seats.Models.Seat", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<string>("Class")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("Unknown")
.HasColumnName("class");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasColumnName("created_by");
b.Property<Guid>("FlightId")
.HasColumnType("uuid")
.HasColumnName("flight_id");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnName("is_deleted");
b.Property<DateTime?>("LastModified")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_modified");
b.Property<long?>("LastModifiedBy")
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<string>("SeatNumber")
.HasColumnType("text")
.HasColumnName("seat_number");
b.Property<string>("Type")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("Unknown")
.HasColumnName("type");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
.HasColumnName("version");
b.HasKey("Id")
.HasName("pk_seat");
b.HasIndex("FlightId")
.HasDatabaseName("ix_seat_flight_id");
b.ToTable("seat", (string)null);
});
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
{
b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ManufacturingYearValue", "ManufacturingYear", b1 =>
{
b1.Property<Guid>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<int>("Value")
.HasMaxLength(5)
.HasColumnType("integer")
.HasColumnName("manufacturing_year");
b1.HasKey("AircraftId")
.HasName("pk_aircraft");
b1.ToTable("aircraft");
b1.WithOwner()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_aircraft_aircraft_id");
});
b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ModelValue", "Model", b1 =>
{
b1.Property<Guid>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("model");
b1.HasKey("AircraftId")
.HasName("pk_aircraft");
b1.ToTable("aircraft");
b1.WithOwner()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_aircraft_aircraft_id");
});
b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.NameValue", "Name", b1 =>
{
b1.Property<Guid>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b1.HasKey("AircraftId")
.HasName("pk_aircraft");
b1.ToTable("aircraft");
b1.WithOwner()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_aircraft_aircraft_id");
});
b.Navigation("ManufacturingYear");
b.Navigation("Model");
b.Navigation("Name");
});
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
{
b.HasOne("Flight.Aircrafts.Models.Aircraft", null)
.WithMany()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_flight_aircraft_aircraft_id");
b.HasOne("Flight.Airports.Models.Airport", null)
.WithMany()
.HasForeignKey("ArriveAirportId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_flight_airport_arrive_airport_id");
});
modelBuilder.Entity("Flight.Seats.Models.Seat", b =>
{
b.HasOne("Flight.Flights.Models.Flight", null)
.WithMany()
.HasForeignKey("FlightId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_seat_flight_flight_id");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,120 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Flight.Data.Migrations
{
/// <inheritdoc />
public partial class aircraftValueObjects : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_flight_aircraft_aircraft_id",
table: "flight");
migrationBuilder.AlterColumn<Guid>(
name: "aircraft_id",
table: "flight",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<string>(
name: "name",
table: "aircraft",
type: "character varying(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "model",
table: "aircraft",
type: "character varying(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "manufacturing_year",
table: "aircraft",
type: "integer",
maxLength: 5,
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AddForeignKey(
name: "fk_flight_aircraft_aircraft_id",
table: "flight",
column: "aircraft_id",
principalTable: "aircraft",
principalColumn: "id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_flight_aircraft_aircraft_id",
table: "flight");
migrationBuilder.AlterColumn<Guid>(
name: "aircraft_id",
table: "flight",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "name",
table: "aircraft",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(50)",
oldMaxLength: 50,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "model",
table: "aircraft",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(50)",
oldMaxLength: 50,
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "manufacturing_year",
table: "aircraft",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldMaxLength: 5,
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "fk_flight_aircraft_aircraft_id",
table: "flight",
column: "aircraft_id",
principalTable: "aircraft",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -48,18 +48,6 @@ namespace Flight.Data.Migrations
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<int>("ManufacturingYear")
.HasColumnType("integer")
.HasColumnName("manufacturing_year");
b.Property<string>("Model")
.HasColumnType("text")
.HasColumnName("model");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
@ -126,7 +114,7 @@ namespace Flight.Data.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<Guid>("AircraftId")
b.Property<Guid?>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("aircraft_id");
@ -268,13 +256,85 @@ namespace Flight.Data.Migrations
b.ToTable("seat", (string)null);
});
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
{
b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ManufacturingYearValue", "ManufacturingYear", b1 =>
{
b1.Property<Guid>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<int>("Value")
.HasMaxLength(5)
.HasColumnType("integer")
.HasColumnName("manufacturing_year");
b1.HasKey("AircraftId")
.HasName("pk_aircraft");
b1.ToTable("aircraft");
b1.WithOwner()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_aircraft_aircraft_id");
});
b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ModelValue", "Model", b1 =>
{
b1.Property<Guid>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("model");
b1.HasKey("AircraftId")
.HasName("pk_aircraft");
b1.ToTable("aircraft");
b1.WithOwner()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_aircraft_aircraft_id");
});
b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.NameValue", "Name", b1 =>
{
b1.Property<Guid>("AircraftId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b1.HasKey("AircraftId")
.HasName("pk_aircraft");
b1.ToTable("aircraft");
b1.WithOwner()
.HasForeignKey("AircraftId")
.HasConstraintName("fk_aircraft_aircraft_id");
});
b.Navigation("ManufacturingYear");
b.Navigation("Model");
b.Navigation("Name");
});
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
{
b.HasOne("Flight.Aircrafts.Models.Aircraft", null)
.WithMany()
.HasForeignKey("AircraftId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_flight_aircraft_aircraft_id");
b.HasOne("Flight.Airports.Models.Airport", null)

View File

@ -1,19 +1,21 @@
namespace Flight.Data.Seed;
namespace Flight.Data.Seed;
using System;
using System.Collections.Generic;
using System.Linq;
using Aircrafts.Models;
using Aircrafts.Models.ValueObjects;
using Airports.Models;
using Flight.Aircrafts.ValueObjects;
using Flights.Models;
using MassTransit;
using Seats.Models;
public static class InitialData
{
public static List<Airport> Airports { get;}
public static List<Aircraft> Aircrafts { get;}
public static List<Seat> Seats { get;}
public static List<Airport> Airports { get; }
public static List<Aircraft> Aircrafts { get; }
public static List<Seat> Seats { get; }
public static List<Flight> Flights { get; }
@ -27,9 +29,9 @@ public static class InitialData
Aircrafts = new List<Aircraft>
{
Aircraft.Create(new Guid("3c5c0000-97c6-fc34-fcd3-08db322230c8"), "Boeing 737", "B737", 2005),
Aircraft.Create(new Guid("3c5c0000-97c6-fc34-2e04-08db322230c9"), "Airbus 300", "A300", 2000),
Aircraft.Create(new Guid("3c5c0000-97c6-fc34-2e11-08db322230c9"), "Airbus 320", "A320", 2003)
Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-fcd3-08db322230c8")), Name.Of("Boeing 737"), Model.Of("B737"), ManufacturingYear.Of(2005)),
Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-2e04-08db322230c9")), Name.Of("Airbus 300"), Model.Of("A300"), ManufacturingYear.Of(2000)),
Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-2e11-08db322230c9")), Name.Of("Airbus 320"), Model.Of("A320"), ManufacturingYear.Of(2003))
};

View File

@ -5,6 +5,7 @@ using BuildingBlocks.Core.Event;
namespace Flight;
using Aircrafts.Features.CreatingAircraft.V1;
using Aircrafts.Models.ValueObjects;
using Airports.Features.CreatingAirport.V1;
using Flights.Features.CreatingFlight.V1;
using Flights.Features.DeletingFlight.V1;
@ -34,7 +35,7 @@ public sealed class EventMapper : IEventMapper
{
return @event switch
{
FlightCreatedDomainEvent e => new CreateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
FlightCreatedDomainEvent e => new CreateFlightMongo(e.Id, e.FlightNumber, AircraftId.Of(e.AircraftId), e.DepartureDate, e.DepartureAirportId,
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
FlightUpdatedDomainEvent e => new UpdateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),

View File

@ -1,7 +1,6 @@
using System;
namespace Flight.Flights.Dtos;
public record FlightDto(Guid Id, string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
Enums.FlightStatus Status, decimal Price);

View File

@ -3,6 +3,7 @@ namespace Flight.Flights.Features.CreatingFlight.V1;
using System;
using System.Threading;
using System.Threading.Tasks;
using Aircrafts.Models.ValueObjects;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
@ -111,7 +112,7 @@ internal class CreateFlightHandler : ICommandHandler<CreateFlight, CreateFlightR
throw new FlightAlreadyExistException();
}
var flightEntity = Models.Flight.Create(request.Id, request.FlightNumber, request.AircraftId,
var flightEntity = Models.Flight.Create(request.Id, request.FlightNumber, AircraftId.Of(request.AircraftId),
request.DepartureAirportId, request.DepartureDate,
request.ArriveDate, request.ArriveAirportId, request.DurationMinutes, request.FlightDate, request.Status,
request.Price);

View File

@ -1,4 +1,4 @@
namespace Flight.Flights.Features.CreatingFlight.V1;
namespace Flight.Flights.Features.CreatingFlight.V1;
using System;
using System.Threading;

View File

@ -4,7 +4,6 @@ namespace Flight.Flights.Features;
using CreatingFlight.V1;
using DeletingFlight.V1;
using GettingAvailableFlights.V1;
using MassTransit;
using Models;
using UpdatingFlight.V1;
@ -15,7 +14,8 @@ public class FlightMappings : IRegister
public void Register(TypeAdapterConfig config)
{
config.NewConfig<Models.Flight, FlightDto>()
.ConstructUsing(x => new FlightDto(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate,
.ConstructUsing(x => new FlightDto(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId,
x.DepartureDate,
x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.Price));
config.NewConfig<CreateFlightMongo, FlightReadModel>()

View File

@ -3,6 +3,7 @@ namespace Flight.Flights.Features.UpdatingFlight.V1;
using System;
using System.Threading;
using System.Threading.Tasks;
using Aircrafts.Models.ValueObjects;
using Ardalis.GuardClauses;
using BuildingBlocks.Caching;
using BuildingBlocks.Core.CQRS;
@ -110,7 +111,7 @@ internal class UpdateFlightHandler : ICommandHandler<UpdateFlight, UpdateFlightR
}
flight.Update(request.Id, request.FlightNumber, request.AircraftId, request.DepartureAirportId,
flight.Update(request.Id, request.FlightNumber, AircraftId.Of(request.AircraftId), request.DepartureAirportId,
request.DepartureDate,
request.ArriveDate, request.ArriveAirportId, request.DurationMinutes, request.FlightDate, request.Status,
request.Price, request.IsDeleted);

View File

@ -6,11 +6,12 @@ namespace Flight.Flights.Models;
using Features.CreatingFlight.V1;
using Features.DeletingFlight.V1;
using Features.UpdatingFlight.V1;
using global::Flight.Aircrafts.Models.ValueObjects;
public record Flight : Aggregate<Guid>
{
public string FlightNumber { get; private set; }
public Guid AircraftId { get; private set; }
public AircraftId AircraftId { get; private set; }
public DateTime DepartureDate { get; private set; }
public Guid DepartureAirportId { get; private set; }
public DateTime ArriveDate { get; private set; }
@ -20,7 +21,7 @@ public record Flight : Aggregate<Guid>
public Enums.FlightStatus Status { get; private set; }
public decimal Price { get; private set; }
public static Flight Create(Guid id, string flightNumber, Guid aircraftId,
public static Flight Create(Guid id, string flightNumber, AircraftId aircraftId,
Guid departureAirportId, DateTime departureDate, DateTime arriveDate,
Guid arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
decimal price, bool isDeleted = false)
@ -52,7 +53,7 @@ public record Flight : Aggregate<Guid>
}
public void Update(Guid id, string flightNumber, Guid aircraftId,
public void Update(Guid id, string flightNumber, AircraftId aircraftId,
Guid departureAirportId, DateTime departureDate, DateTime arriveDate,
Guid arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
decimal price, bool isDeleted = false)
@ -75,7 +76,7 @@ public record Flight : Aggregate<Guid>
AddDomainEvent(@event);
}
public void Delete(Guid id, string flightNumber, Guid aircraftId,
public void Delete(Guid id, string flightNumber, AircraftId aircraftId,
Guid departureAirportId, DateTime departureDate, DateTime arriveDate,
Guid arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status,
decimal price, bool isDeleted = true)

View File

@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.TestBase;
using Flight.Api;
@ -26,7 +26,7 @@ public class CreateAircraftTests : FlightIntegrationTestBase
var response = await Fixture.SendAsync(command);
// Assert
response?.Id.Should().Be(command.Id);
response?.Id.Value.Should().Be(command.Id);
(await Fixture.WaitForPublishing<AircraftCreated>()).Should().Be(true);
}

View File

@ -1,4 +1,4 @@
using AutoBogus;
using AutoBogus;
namespace Integration.Test.Fakes;
@ -10,5 +10,6 @@ public class FakeCreateAircraftCommand : AutoFaker<CreateAircraft>
public FakeCreateAircraftCommand()
{
RuleFor(r => r.Id, _ => NewId.NextGuid());
RuleFor(r => r.ManufacturingYear, _ => 2000);
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Flight.Data;
using Flight.Flights.Enums;
@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore;
namespace Unit.Test.Common;
using global::Flight.Aircrafts.Models.ValueObjects;
using MassTransit;
public static class DbContextFactory
@ -45,16 +46,16 @@ public static class DbContextFactory
var aircrafts = new List<global::Flight.Aircrafts.Models.Aircraft>
{
global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft1, "Boeing 737", "B737", 2005),
global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft2, "Airbus 300", "A300", 2000),
global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft3, "Airbus 320", "A320", 2003)
global::Flight.Aircrafts.Models.Aircraft.Create(AircraftId.Of(_aircraft1), Name.Of("Boeing 737"), Model.Of("B737"), ManufacturingYear.Of(2005)),
global::Flight.Aircrafts.Models.Aircraft.Create(AircraftId.Of(_aircraft2), Name.Of("Airbus 300"), Model.Of("A300"), ManufacturingYear.Of(2000)),
global::Flight.Aircrafts.Models.Aircraft.Create(AircraftId.Of(_aircraft3), Name.Of("Airbus 320"), Model.Of("A320"), ManufacturingYear.Of(2003))
};
context.Aircraft.AddRange(aircrafts);
var flights = new List<global::Flight.Flights.Models.Flight>
{
global::Flight.Flights.Models.Flight.Create(_flightId1, "BD467", _aircraft1, _airportId1,
global::Flight.Flights.Models.Flight.Create(_flightId1, "BD467", AircraftId.Of(_aircraft1), _airportId1,
new DateTime(2022, 1, 31, 12, 0, 0),
new DateTime(2022, 1, 31, 14, 0, 0),
_airportId2, 120m,

View File

@ -1,4 +1,4 @@
using AutoBogus;
using AutoBogus;
namespace Unit.Test.Fakes;

View File

@ -1,4 +1,6 @@
namespace Unit.Test.Fakes;
namespace Unit.Test.Fakes;
using global::Flight.Aircrafts.Models.ValueObjects;
public static class FakeFlightCreate
{
@ -7,7 +9,7 @@ public static class FakeFlightCreate
var command = new FakeCreateFlightCommand().Generate();
return global::Flight.Flights.Models.Flight.Create(command.Id, command.FlightNumber,
command.AircraftId, command.DepartureAirportId, command.DepartureDate,
AircraftId.Of(command.AircraftId), command.DepartureAirportId, command.DepartureDate,
command.ArriveDate, command.ArriveAirportId, command.DurationMinutes,
command.FlightDate, command.Status, command.Price);
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Flight.Flights.Dtos;
using MapsterMapper;
@ -25,7 +25,8 @@ public class FlightMappingTests
yield return new object[]
{
// these types will instantiate with reflection in the future
typeof(global::Flight.Flights.Models.Flight), typeof(FlightDto)
//typeof(global::Flight.Flights.Models.Flight), typeof(FlightDto),
typeof(global::Flight.Flights.Models.FlightReadModel), typeof(FlightDto)
};
}
}

View File

@ -3,16 +3,58 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Passenger.Data.Configurations;
public class PassengerConfiguration: IEntityTypeConfiguration<Passengers.Models.Passenger>
using Passengers.Models.ValueObjects;
public class PassengerConfiguration : IEntityTypeConfiguration<Passengers.Models.Passenger>
{
public void Configure(EntityTypeBuilder<Passengers.Models.Passenger> builder)
{
builder.ToTable(nameof(Passenger));
builder.ToTable(nameof(Passengers.Models.Passenger));
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).ValueGeneratedNever();
builder.Property(r => r.Id).ValueGeneratedNever()
.HasConversion<Guid>(passengerId => passengerId.Value, dbId => PassengerId.Of(dbId));
// // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api
builder.Property(r => r.Version).IsConcurrencyToken();
builder.OwnsOne(
x => x.Name,
a =>
{
a.Property(p => p.Value)
.HasColumnName(nameof(Passengers.Models.Passenger.Name))
.HasMaxLength(50)
.IsRequired();
}
);
builder.OwnsOne(
x => x.PassportNumber,
a =>
{
a.Property(p => p.Value)
.HasColumnName(nameof(Passengers.Models.Passenger.PassportNumber))
.HasMaxLength(10)
.IsRequired();
}
);
builder.OwnsOne(
x => x.Age,
a =>
{
a.Property(p => p.Value)
.HasColumnName(nameof(Passengers.Models.Passenger.Age))
.HasMaxLength(3)
.IsRequired();
}
);
builder.Property(x => x.PassengerType)
.IsRequired()
.HasDefaultValue(Passengers.Enums.PassengerType.Unknown)
.HasConversion(
x => x.ToString(),
x => (Passengers.Enums.PassengerType)Enum.Parse(typeof(Passengers.Enums.PassengerType), x));
}
}

View File

@ -0,0 +1,148 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Passenger.Data;
#nullable disable
namespace Passenger.Data.Migrations
{
[DbContext(typeof(PassengerDbContext))]
[Migration("20230524195049_AddValueObject")]
partial class AddValueObject
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp without time zone")
.HasColumnName("created_at");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasColumnName("created_by");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnName("is_deleted");
b.Property<DateTime?>("LastModified")
.HasColumnType("timestamp without time zone")
.HasColumnName("last_modified");
b.Property<long?>("LastModifiedBy")
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<string>("PassengerType")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("Unknown")
.HasColumnName("passenger_type");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
.HasColumnName("version");
b.HasKey("Id")
.HasName("pk_passenger");
b.ToTable("passenger", (string)null);
});
modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b =>
{
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Age", "Age", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<int>("Value")
.HasMaxLength(3)
.HasColumnType("integer")
.HasColumnName("age");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Name", "Name", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.PassportNumber", "PassportNumber", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("passport_number");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.Navigation("Age");
b.Navigation("Name");
b.Navigation("PassportNumber");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,133 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Passenger.Data.Migrations
{
/// <inheritdoc />
public partial class AddValueObject : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "passport_number",
table: "passenger",
type: "character varying(10)",
maxLength: 10,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "passenger_type",
table: "passenger",
type: "text",
nullable: false,
defaultValue: "Unknown",
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AlterColumn<string>(
name: "name",
table: "passenger",
type: "character varying(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "last_modified",
table: "passenger",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "created_at",
table: "passenger",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "age",
table: "passenger",
type: "integer",
maxLength: 3,
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "passport_number",
table: "passenger",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(10)",
oldMaxLength: 10,
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "passenger_type",
table: "passenger",
type: "integer",
nullable: false,
oldClrType: typeof(string),
oldType: "text",
oldDefaultValue: "Unknown");
migrationBuilder.AlterColumn<string>(
name: "name",
table: "passenger",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(50)",
oldMaxLength: 50,
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "last_modified",
table: "passenger",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "created_at",
table: "passenger",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "age",
table: "passenger",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldMaxLength: 3,
oldNullable: true);
}
}
}

View File

@ -0,0 +1,148 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Passenger.Data;
#nullable disable
namespace Passenger.Data.Migrations
{
[DbContext(typeof(PassengerDbContext))]
[Migration("20230526085126_change-behavior")]
partial class changebehavior
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasColumnName("created_by");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean")
.HasColumnName("is_deleted");
b.Property<DateTime?>("LastModified")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_modified");
b.Property<long?>("LastModifiedBy")
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<string>("PassengerType")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("Unknown")
.HasColumnName("passenger_type");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
.HasColumnName("version");
b.HasKey("Id")
.HasName("pk_passenger");
b.ToTable("passenger", (string)null);
});
modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b =>
{
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Age", "Age", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<int>("Value")
.HasMaxLength(3)
.HasColumnType("integer")
.HasColumnName("age");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Name", "Name", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.PassportNumber", "PassportNumber", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("passport_number");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.Navigation("Age");
b.Navigation("Name");
b.Navigation("PassportNumber");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Passenger.Data.Migrations
{
/// <inheritdoc />
public partial class changebehavior : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "last_modified",
table: "passenger",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "created_at",
table: "passenger",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "last_modified",
table: "passenger",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "created_at",
table: "passenger",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
}
}
}

View File

@ -28,10 +28,6 @@ namespace Passenger.Data.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<int>("Age")
.HasColumnType("integer")
.HasColumnName("age");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
@ -52,18 +48,13 @@ namespace Passenger.Data.Migrations
.HasColumnType("bigint")
.HasColumnName("last_modified_by");
b.Property<string>("Name")
b.Property<string>("PassengerType")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasColumnName("name");
b.Property<int>("PassengerType")
.HasColumnType("integer")
.HasDefaultValue("Unknown")
.HasColumnName("passenger_type");
b.Property<string>("PassportNumber")
.HasColumnType("text")
.HasColumnName("passport_number");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint")
@ -74,6 +65,80 @@ namespace Passenger.Data.Migrations
b.ToTable("passenger", (string)null);
});
modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b =>
{
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Age", "Age", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<int>("Value")
.HasMaxLength(3)
.HasColumnType("integer")
.HasColumnName("age");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Name", "Name", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.OwnsOne("Passenger.Passengers.Models.ValueObjects.PassportNumber", "PassportNumber", b1 =>
{
b1.Property<Guid>("PassengerId")
.HasColumnType("uuid")
.HasColumnName("id");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("passport_number");
b1.HasKey("PassengerId")
.HasName("pk_passenger");
b1.ToTable("passenger");
b1.WithOwner()
.HasForeignKey("PassengerId")
.HasConstraintName("fk_passenger_passenger_id");
});
b.Navigation("Age");
b.Navigation("Name");
b.Navigation("PassportNumber");
});
#pragma warning restore 612, 618
}
}

View File

@ -1,7 +1,7 @@
using System.Reflection;
using BuildingBlocks.EFCore;
using Microsoft.EntityFrameworkCore;
using BuildingBlocks.Web;
using Microsoft.EntityFrameworkCore;
namespace Passenger.Data;

View File

@ -0,0 +1,12 @@
namespace Passenger.Exceptions;
using System;
using BuildingBlocks.Exception;
public class InvalidPassengerIdExceptions : BadRequestException
{
public InvalidPassengerIdExceptions(Guid passengerId)
: base($"PassengerId: '{passengerId}' is invalid.")
{
}
}

View File

@ -5,12 +5,13 @@ using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.Core;
using BuildingBlocks.Core.Event;
using BuildingBlocks.Web;
using Data;
using Humanizer;
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Data;
using Passenger.Passengers.Models.ValueObjects;
public class RegisterNewUserHandler : IConsumer<UserCreated>
{
@ -37,15 +38,15 @@ public class RegisterNewUserHandler : IConsumer<UserCreated>
_logger.LogInformation($"consumer for {nameof(UserCreated).Underscore()} in {_options.Name}");
var passengerExist =
await _passengerDbContext.Passengers.AnyAsync(x => x.PassportNumber == context.Message.PassportNumber);
await _passengerDbContext.Passengers.AnyAsync(x => x.PassportNumber.Value == PassportNumber.Of(context.Message.PassportNumber).Value);
if (passengerExist)
{
return;
}
var passenger = Passengers.Models.Passenger.Create(NewId.NextGuid(), context.Message.Name,
context.Message.PassportNumber);
var passenger = Passengers.Models.Passenger.Create(PassengerId.Of(NewId.NextGuid()), Name.Of(context.Message.Name),
PassportNumber.Of(context.Message.PassportNumber));
await _passengerDbContext.AddAsync(passenger);

View File

@ -1,3 +1,2 @@
namespace Passenger.Passengers.Dtos;
public record PassengerDto(Guid Id, string Name, string PassportNumber, Enums.PassengerType PassengerType, int Age);

View File

@ -0,0 +1,9 @@
namespace Passenger.Passengers.Exceptions;
using BuildingBlocks.Exception;
public class InvalidAgeException : BadRequestException
{
public InvalidAgeException() : base("Age Cannot be null or negative")
{
}
}

View File

@ -0,0 +1,10 @@
namespace Passenger.Passengers.Exceptions;
using BuildingBlocks.Exception;
public class InvalidNameException : BadRequestException
{
public InvalidNameException() : base("Name cannot be empty or whitespace.")
{
}
}

View File

@ -0,0 +1,10 @@
namespace Passenger.Passengers.Exceptions;
using BuildingBlocks.Exception;
public class InvalidPassportNumberException : BadRequestException
{
public InvalidPassportNumberException() : base("Passport number cannot be empty or whitespace.")
{
}
}

View File

@ -4,18 +4,20 @@ using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using BuildingBlocks.Web;
using Exceptions;
using FluentValidation;
using MapsterMapper;
using Microsoft.EntityFrameworkCore;
using Data;
using Dtos;
using Duende.IdentityServer.EntityFramework.Entities;
using Exceptions;
using FluentValidation;
using MapsterMapper;
using MassTransit;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Passenger.Passengers.Models.ValueObjects;
using Passenger.Passengers.ValueObjects;
public record CompleteRegisterPassenger
(string PassportNumber, Enums.PassengerType PassengerType, int Age) : ICommand<CompleteRegisterPassengerResult>,
@ -96,7 +98,8 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<Complet
Guard.Against.Null(request, nameof(request));
var passenger = await _passengerDbContext.Passengers.AsNoTracking().SingleOrDefaultAsync(
x => x.PassportNumber == request.PassportNumber, cancellationToken);
x => x.PassportNumber.Value.Equals(PassportNumber.Of(request.PassportNumber).Value), cancellationToken);
if (passenger is null)
{
@ -104,11 +107,11 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<Complet
}
var passengerEntity = passenger.CompleteRegistrationPassenger(passenger.Id, passenger.Name,
passenger.PassportNumber, request.PassengerType, request.Age);
passenger.PassportNumber, request.PassengerType, Age.Of(request.Age));
var updatePassenger = _passengerDbContext.Passengers.Update(passengerEntity);
var updatePassenger = _passengerDbContext.Passengers.Update(passengerEntity)?.Entity;
var passengerDto = _mapper.Map<PassengerDto>(updatePassenger.Entity);
var passengerDto = _mapper.Map<PassengerDto>(updatePassenger);
return new CompleteRegisterPassengerResult(passengerDto);
}

View File

@ -1,14 +1,15 @@
namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using Data;
using MapsterMapper;
using MediatR;
using Models;
using Models.ValueObjects;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Data;
public record CompleteRegisterPassengerMongoCommand(Guid Id, string PassportNumber, string Name,
Enums.PassengerType PassengerType, int Age, bool IsDeleted = false) : InternalCommand;
@ -34,14 +35,14 @@ internal class CompleteRegisterPassengerMongoHandler : ICommandHandler<CompleteR
var passengerReadModel = _mapper.Map<PassengerReadModel>(request);
var passenger = await _passengerReadDbContext.Passenger.AsQueryable()
.FirstOrDefaultAsync(x => x.PassengerId == passengerReadModel.PassengerId && !x.IsDeleted, cancellationToken);
.FirstOrDefaultAsync(x => x.PassengerId == PassengerId.Of(passengerReadModel.PassengerId) && !x.IsDeleted, cancellationToken);
if (passenger is not null)
{
await _passengerReadDbContext.Passenger.UpdateOneAsync(
x => x.PassengerId == passengerReadModel.PassengerId,
x => x.PassengerId == PassengerId.Of(passengerReadModel.PassengerId),
Builders<PassengerReadModel>.Update
.Set(x => x.PassengerId, passengerReadModel.PassengerId)
.Set(x => x.PassengerId, PassengerId.Of(passengerReadModel.PassengerId))
.Set(x => x.Age, passengerReadModel.Age)
.Set(x => x.Name, passengerReadModel.Name)
.Set(x => x.IsDeleted, passengerReadModel.IsDeleted)

View File

@ -1,18 +1,19 @@
namespace Passenger.Passengers.Features.GettingPassengerById.Queries.V1;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Web;
using Data;
using Dtos;
using FluentValidation;
using MapsterMapper;
using Ardalis.GuardClauses;
using BuildingBlocks.Web;
using Duende.IdentityServer.EntityFramework.Entities;
using Exceptions;
using FluentValidation;
using MapsterMapper;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Models.ValueObjects;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
@ -74,7 +75,7 @@ internal class GetPassengerByIdHandler : IQueryHandler<GetPassengerById, GetPass
var passenger =
await _passengerReadDbContext.Passenger.AsQueryable()
.SingleOrDefaultAsync(x => x.PassengerId == query.Id && x.IsDeleted == false, cancellationToken);
.SingleOrDefaultAsync(x => x.PassengerId == PassengerId.Of(query.Id) && x.IsDeleted == false, cancellationToken);
if (passenger is null)
{

View File

@ -6,19 +6,23 @@ using CompletingRegisterPassenger.V1;
using Dtos;
using MassTransit;
using Models;
using Passenger.Passengers.Models.ValueObjects;
public class PassengerMappings : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<CompleteRegisterPassengerMongoCommand, PassengerReadModel>()
.Map(d => d.Id, s => NewId.NextGuid())
.Map(d => d.PassengerId, s => s.Id);
.Map(d => d.Id, s => NewId.NextGuid())
.Map(d => d.PassengerId, s => PassengerId.Of(s.Id));
config.NewConfig<CompleteRegisterPassengerRequestDto, CompleteRegisterPassenger>()
.ConstructUsing(x => new CompleteRegisterPassenger(x.PassportNumber, x.PassengerType, x.Age));
config.NewConfig<PassengerReadModel, PassengerDto>()
.ConstructUsing(x => new PassengerDto(x.PassengerId, x.Name, x.PassportNumber, x.PassengerType, x.Age));
config.NewConfig<Passenger, PassengerDto>()
.ConstructUsing(x => new PassengerDto(x.Id.Value, x.Name.Value, x.PassportNumber.Value, x.PassengerType, x.Age.Value));
}
}

View File

@ -3,24 +3,31 @@ using BuildingBlocks.Core.Model;
namespace Passenger.Passengers.Models;
using Features.CompletingRegisterPassenger.V1;
using global::Passenger.Passengers.Models.ValueObjects;
using global::Passenger.Passengers.ValueObjects;
using Identity.Consumers.RegisteringNewUser.V1;
public record Passenger : Aggregate<Guid>
public record Passenger : Aggregate<PassengerId>
{
public Passenger CompleteRegistrationPassenger(Guid id, string name, string passportNumber, Enums.PassengerType passengerType, int age, bool isDeleted = false)
public PassportNumber PassportNumber { get; private set; } = default!;
public Name Name { get; private set; } = default!;
public Enums.PassengerType PassengerType { get; private set; }
public Age? Age { get; private set; }
public Passenger CompleteRegistrationPassenger(PassengerId id, Name name, PassportNumber passportNumber, Enums.PassengerType passengerType, Age age, bool isDeleted = false)
{
var passenger = new Passenger
{
Id = id,
Name = name,
PassportNumber = passportNumber,
PassengerType = passengerType,
Age = age,
Id = id,
IsDeleted = isDeleted
};
var @event = new PassengerRegistrationCompletedDomainEvent(passenger.Id, passenger.Name, passenger.PassportNumber,
passenger.PassengerType, passenger.Age, passenger.IsDeleted);
passenger.PassengerType, passenger.Age.Value, passenger.IsDeleted);
passenger.AddDomainEvent(@event);
@ -28,7 +35,7 @@ public record Passenger : Aggregate<Guid>
}
public static Passenger Create(Guid id, string name, string passportNumber, bool isDeleted = false)
public static Passenger Create(PassengerId id, Name name, PassportNumber passportNumber, bool isDeleted = false)
{
var passenger = new Passenger { Id = id, Name = name, PassportNumber = passportNumber, IsDeleted = isDeleted };
@ -38,10 +45,4 @@ public record Passenger : Aggregate<Guid>
return passenger;
}
public string PassportNumber { get; private set; }
public string Name { get; private set; }
public Enums.PassengerType PassengerType { get; private set; }
public int Age { get; private set; }
}

View File

@ -1,5 +1,4 @@
namespace Passenger.Passengers.Models;
namespace Passenger.Passengers.Models;
public class PassengerReadModel
{
public required Guid Id { get; init; }

View File

@ -0,0 +1,26 @@
namespace Passenger.Passengers.ValueObjects;
using Passenger.Passengers.Exceptions;
public record Age
{
public int Value { get; }
public Age(int value)
{
if (value <= 0)
{
throw new InvalidAgeException();
}
Value = value;
}
public static Age Of(int value)
{
return new Age(value);
}
public static implicit operator int(Age age)
{
return age.Value;
}
}

View File

@ -0,0 +1,24 @@
namespace Passenger.Passengers.Models.ValueObjects;
using Exceptions;
public record Name
{
public string Value { get; }
public Name(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidNameException();
}
Value = value;
}
public static Name Of(string value)
{
return new Name(value);
}
public static implicit operator string(Name name)
{
return name.Value;
}
}

View File

@ -0,0 +1,28 @@
namespace Passenger.Passengers.Models.ValueObjects;
using System;
using global::Passenger.Exceptions;
public record PassengerId
{
public Guid Value { get; }
private PassengerId(Guid value)
{
if (value == Guid.Empty)
{
throw new InvalidPassengerIdExceptions(value);
}
Value = value;
}
public static PassengerId Of(Guid value)
{
return new PassengerId(value);
}
public static implicit operator Guid(PassengerId passengerId)
{
return passengerId.Value;
}
}

View File

@ -0,0 +1,32 @@
namespace Passenger.Passengers.Models.ValueObjects;
using Exceptions;
public record PassportNumber
{
public string Value { get; }
public override string ToString()
{
return Value;
}
private PassportNumber(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidPassportNumberException();
}
Value = value;
}
public static PassportNumber Of(string value)
{
return new PassportNumber(value);
}
public static implicit operator string(PassportNumber passportNumber)
{
return passportNumber.Value;
}
}

View File

@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.TestBase;
using FluentAssertions;
@ -8,9 +8,9 @@ using Passenger.Data;
using Xunit;
namespace Integration.Test.Passenger.Features;
public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
{
public CompleteRegisterPassengerTests(
TestFixture<Program, PassengerDbContext, PassengerReadDbContext> integrationTestFactory) : base(integrationTestFactory)
{
@ -23,8 +23,8 @@ public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
var userCreated = new FakeUserCreated().Generate();
await Fixture.Publish(userCreated);
await Fixture.WaitForPublishing<UserCreated>();
await Fixture.WaitForConsuming<UserCreated>();
(await Fixture.WaitForPublishing<UserCreated>()).Should().Be(true);
(await Fixture.WaitForConsuming<UserCreated>()).Should().Be(true);
var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate();