mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-29 01:04:56 +08:00
Merge pull request #251 from meysamhadeli/refactor/refactor-core-domain
refactor: Refactor core domain in building-blocks
This commit is contained in:
commit
c93bd2902a
@ -16,11 +16,24 @@ services:
|
|||||||
command:
|
command:
|
||||||
- "postgres"
|
- "postgres"
|
||||||
- "-c"
|
- "-c"
|
||||||
- "wal_level=logical"
|
- "wal_level=logical"
|
||||||
networks:
|
networks:
|
||||||
- booking
|
- booking
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
# SqlServer
|
||||||
|
#######################################################
|
||||||
|
# sql:
|
||||||
|
# container_name: sql
|
||||||
|
# image: mcr.microsoft.com/mssql/server
|
||||||
|
# ports:
|
||||||
|
# - "1433:1433"
|
||||||
|
# environment:
|
||||||
|
# SA_PASSWORD: "Password@1234"
|
||||||
|
# ACCEPT_EULA: "Y"
|
||||||
|
|
||||||
|
|
||||||
#######################################################
|
#######################################################
|
||||||
# Rabbitmq
|
# Rabbitmq
|
||||||
#######################################################
|
#######################################################
|
||||||
|
|||||||
@ -30,10 +30,24 @@ services:
|
|||||||
command:
|
command:
|
||||||
- "postgres"
|
- "postgres"
|
||||||
- "-c"
|
- "-c"
|
||||||
- "wal_level=logical"
|
- "wal_level=logical"
|
||||||
networks:
|
networks:
|
||||||
- booking
|
- booking
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
# SqlServer
|
||||||
|
#######################################################
|
||||||
|
# sql:
|
||||||
|
# container_name: sql
|
||||||
|
# image: mcr.microsoft.com/mssql/server
|
||||||
|
# ports:
|
||||||
|
# - "1433:1433"
|
||||||
|
# environment:
|
||||||
|
# SA_PASSWORD: "Password@1234"
|
||||||
|
# ACCEPT_EULA: "Y"
|
||||||
|
|
||||||
|
|
||||||
#######################################################
|
#######################################################
|
||||||
# Jaeger
|
# Jaeger
|
||||||
#######################################################
|
#######################################################
|
||||||
@ -141,8 +155,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 6379:6379
|
- 6379:6379
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
booking:
|
booking:
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,7 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
|
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
|
||||||
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.3.3" />
|
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.3.3" />
|
||||||
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.3" />
|
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.3" />
|
||||||
|
<PackageReference Include="Sieve" Version="2.5.5" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
|
||||||
@ -99,6 +100,8 @@
|
|||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.2.2" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.2.2" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.2.2" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.2.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.2" />
|
||||||
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
|
<PackageReference Include="System.Linq.Async.Queryable" Version="6.0.1" />
|
||||||
<PackageReference Include="Testcontainers" Version="3.0.0" />
|
<PackageReference Include="Testcontainers" Version="3.0.0" />
|
||||||
<PackageReference Include="Testcontainers.EventStoreDb" Version="3.0.0" />
|
<PackageReference Include="Testcontainers.EventStoreDb" Version="3.0.0" />
|
||||||
<PackageReference Include="Testcontainers.MongoDb" Version="3.0.0" />
|
<PackageReference Include="Testcontainers.MongoDb" Version="3.0.0" />
|
||||||
@ -141,10 +144,9 @@
|
|||||||
<PackageReference Include="Google.Protobuf" Version="3.21.12" />
|
<PackageReference Include="Google.Protobuf" Version="3.21.12" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.51.0" />
|
<PackageReference Include="Grpc.Net.Client" Version="2.51.0" />
|
||||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.51.0" />
|
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.51.0" />
|
||||||
<PackageReference Update="AsyncFixer" Version="1.6.0" />
|
|
||||||
<PackageReference Update="Meziantou.Analyzer" Version="1.0.758" />
|
<PackageReference Update="Meziantou.Analyzer" Version="1.0.758" />
|
||||||
<PackageReference Remove="Microsoft.VisualStudio.Threading.Analyzers" />
|
<PackageReference Update="AsyncFixer" Version="1.6.0" />
|
||||||
<PackageReference Update="Microsoft.VisualStudio.Threading.Analyzers" Version="17.4.27" />
|
|
||||||
<PackageReference Update="Roslynator.Analyzers" Version="4.2.0" />
|
<PackageReference Update="Roslynator.Analyzers" Version="4.2.0" />
|
||||||
<PackageReference Update="Roslynator.CodeAnalysis.Analyzers" Version="4.2.0" />
|
<PackageReference Update="Roslynator.CodeAnalysis.Analyzers" Version="4.2.0" />
|
||||||
<PackageReference Update="Roslynator.Formatting.Analyzers" Version="4.2.0" />
|
<PackageReference Update="Roslynator.Formatting.Analyzers" Version="4.2.0" />
|
||||||
@ -154,6 +156,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Contracts" />
|
<Folder Include="Contracts" />
|
||||||
|
<Folder Include="Core\Pagination" />
|
||||||
<Folder Include="EventStoreDB\BackgroundWorkers" />
|
<Folder Include="EventStoreDB\BackgroundWorkers" />
|
||||||
<Folder Include="PersistMessageProcessor\Data\Configurations" />
|
<Folder Include="PersistMessageProcessor\Data\Configurations" />
|
||||||
<Folder Include="PersistMessageProcessor\Data\Migrations" />
|
<Folder Include="PersistMessageProcessor\Data\Migrations" />
|
||||||
|
|||||||
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace BuildingBlocks.Core.Model;
|
namespace BuildingBlocks.Core.Model;
|
||||||
|
|
||||||
public abstract record Aggregate : Aggregate<long>;
|
public abstract record Aggregate<TId> : Entity<TId>, IAggregate<TId>
|
||||||
|
|
||||||
public abstract record Aggregate<TId> : Audit, IAggregate<TId>
|
|
||||||
{
|
{
|
||||||
private readonly List<IDomainEvent> _domainEvents = new();
|
private readonly List<IDomainEvent> _domainEvents = new();
|
||||||
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
|
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
|
||||||
@ -22,8 +20,4 @@ public abstract record Aggregate<TId> : Audit, IAggregate<TId>
|
|||||||
|
|
||||||
return dequeuedEvents;
|
return dequeuedEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long Version { get; set; }
|
|
||||||
|
|
||||||
public required TId Id { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
namespace BuildingBlocks.Core.Model;
|
namespace BuildingBlocks.Core.Model;
|
||||||
|
|
||||||
public interface IAudit
|
public abstract record Entity<T> : IEntity<T>
|
||||||
{
|
{
|
||||||
|
public T Id { get; set; }
|
||||||
public DateTime? CreatedAt { get; set; }
|
public DateTime? CreatedAt { get; set; }
|
||||||
public long? CreatedBy { get; set; }
|
public long? CreatedBy { get; set; }
|
||||||
public DateTime? LastModified { get; set; }
|
public DateTime? LastModified { get; set; }
|
||||||
public long? LastModifiedBy { get; set; }
|
public long? LastModifiedBy { get; set; }
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
public long Version { get; set; }
|
||||||
}
|
}
|
||||||
@ -2,18 +2,12 @@
|
|||||||
|
|
||||||
namespace BuildingBlocks.Core.Model;
|
namespace BuildingBlocks.Core.Model;
|
||||||
|
|
||||||
public interface IAggregate : IAudit, IVersion
|
public interface IAggregate<T> : IAggregate, IEntity<T>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAggregate : IEntity
|
||||||
{
|
{
|
||||||
IReadOnlyList<IDomainEvent> DomainEvents { get; }
|
IReadOnlyList<IDomainEvent> DomainEvents { get; }
|
||||||
IEvent[] ClearDomainEvents();
|
IEvent[] ClearDomainEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IAggregate<out T> : IAggregate
|
|
||||||
{
|
|
||||||
T Id { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IVersion
|
|
||||||
{
|
|
||||||
long Version { get; set; }
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
namespace BuildingBlocks.Core.Model;
|
namespace BuildingBlocks.Core.Model;
|
||||||
|
|
||||||
public abstract record Audit : IAudit
|
public interface IEntity<T> : IEntity
|
||||||
|
{
|
||||||
|
public T Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IEntity: IVersion
|
||||||
{
|
{
|
||||||
public DateTime? CreatedAt { get; set; }
|
public DateTime? CreatedAt { get; set; }
|
||||||
public long? CreatedBy { get; set; }
|
public long? CreatedBy { get; set; }
|
||||||
6
src/BuildingBlocks/Core/Model/IVersion.cs
Normal file
6
src/BuildingBlocks/Core/Model/IVersion.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace BuildingBlocks.Core.Model;
|
||||||
|
|
||||||
|
public interface IVersion
|
||||||
|
{
|
||||||
|
long Version { get; set; }
|
||||||
|
}
|
||||||
36
src/BuildingBlocks/Core/Pagination/Extensions.cs
Normal file
36
src/BuildingBlocks/Core/Pagination/Extensions.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
namespace BuildingBlocks.Core.Pagination;
|
||||||
|
|
||||||
|
using Sieve.Models;
|
||||||
|
using Sieve.Services;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static async Task<IPageList<TEntity>> ApplyPagingAsync<TEntity>(
|
||||||
|
this IQueryable<TEntity> queryable,
|
||||||
|
IPageRequest pageRequest,
|
||||||
|
ISieveProcessor sieveProcessor,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
var sieveModel = new SieveModel
|
||||||
|
{
|
||||||
|
PageSize = pageRequest.PageSize,
|
||||||
|
Page = pageRequest.PageNumber,
|
||||||
|
Sorts = pageRequest.SortOrder,
|
||||||
|
Filters = pageRequest.Filters
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/Biarity/Sieve/issues/34#issuecomment-403817573
|
||||||
|
var result = sieveProcessor.Apply(sieveModel, queryable, applyPagination: false);
|
||||||
|
var total = result.Count();
|
||||||
|
result = sieveProcessor.Apply(sieveModel, queryable, applyFiltering: false,
|
||||||
|
applySorting: false); // Only applies pagination
|
||||||
|
|
||||||
|
var items = await result
|
||||||
|
.ToAsyncEnumerable()
|
||||||
|
.ToListAsync(cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return PageList<TEntity>.Create(items.AsReadOnly(), pageRequest.PageNumber, pageRequest.PageSize, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/BuildingBlocks/Core/Pagination/IPageList.cs
Normal file
16
src/BuildingBlocks/Core/Pagination/IPageList.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace BuildingBlocks.Core.Pagination;
|
||||||
|
|
||||||
|
public interface IPageList<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
int CurrentPageSize { get; }
|
||||||
|
int CurrentStartIndex { get; }
|
||||||
|
int CurrentEndIndex { get; }
|
||||||
|
int TotalPages { get; }
|
||||||
|
bool HasPrevious { get; }
|
||||||
|
bool HasNext { get; }
|
||||||
|
IReadOnlyList<T> Items { get; init; }
|
||||||
|
int TotalCount { get; init; }
|
||||||
|
int PageNumber { get; init; }
|
||||||
|
int PageSize { get; init; }
|
||||||
|
}
|
||||||
6
src/BuildingBlocks/Core/Pagination/IPageQuery.cs
Normal file
6
src/BuildingBlocks/Core/Pagination/IPageQuery.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace BuildingBlocks.Core.Pagination;
|
||||||
|
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
public interface IPageQuery<out TResponse> : IPageRequest, IRequest<TResponse>
|
||||||
|
where TResponse : class { }
|
||||||
9
src/BuildingBlocks/Core/Pagination/IPageRequest.cs
Normal file
9
src/BuildingBlocks/Core/Pagination/IPageRequest.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace BuildingBlocks.Core.Pagination;
|
||||||
|
|
||||||
|
public interface IPageRequest
|
||||||
|
{
|
||||||
|
int PageNumber { get; init; }
|
||||||
|
int PageSize { get; init; }
|
||||||
|
string? Filters { get; init; }
|
||||||
|
string? SortOrder { get; init; }
|
||||||
|
}
|
||||||
19
src/BuildingBlocks/Core/Pagination/PageList.cs
Normal file
19
src/BuildingBlocks/Core/Pagination/PageList.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace BuildingBlocks.Core.Pagination;
|
||||||
|
|
||||||
|
public record PageList<T>(IReadOnlyList<T> Items, int PageNumber, int PageSize, int TotalCount) : IPageList<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
public int CurrentPageSize => Items.Count;
|
||||||
|
public int CurrentStartIndex => TotalCount == 0 ? 0 : ((PageNumber - 1) * PageSize) + 1;
|
||||||
|
public int CurrentEndIndex => TotalCount == 0 ? 0 : CurrentStartIndex + CurrentPageSize - 1;
|
||||||
|
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||||
|
public bool HasPrevious => PageNumber > 1;
|
||||||
|
public bool HasNext => PageNumber < TotalPages;
|
||||||
|
|
||||||
|
public static PageList<T> Empty => new(Enumerable.Empty<T>().ToList(), 0, 0, 0);
|
||||||
|
|
||||||
|
public static PageList<T> Create(IReadOnlyList<T> items, int pageNumber, int pageSize, int totalItems)
|
||||||
|
{
|
||||||
|
return new PageList<T>(items, pageNumber, pageSize, totalItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -50,12 +50,16 @@ public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TRe
|
|||||||
nameof(EfTxBehavior<TRequest, TResponse>),
|
nameof(EfTxBehavior<TRequest, TResponse>),
|
||||||
typeof(TRequest).FullName);
|
typeof(TRequest).FullName);
|
||||||
|
|
||||||
var domainEvents = _dbContextBase.GetDomainEvents();
|
while (true)
|
||||||
|
{
|
||||||
|
var domainEvents = _dbContextBase.GetDomainEvents();
|
||||||
|
|
||||||
await _eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken);
|
if (domainEvents is null || !domainEvents.Any())
|
||||||
|
return response;
|
||||||
|
|
||||||
await _dbContextBase.ExecuteTransactionalAsync(cancellationToken);
|
await _dbContextBase.ExecuteTransactionalAsync(cancellationToken);
|
||||||
|
|
||||||
return response;
|
await _eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ public static class Extensions
|
|||||||
{
|
{
|
||||||
Expression<Func<IAggregate, bool>> filterExpr = e => !e.IsDeleted;
|
Expression<Func<IAggregate, bool>> filterExpr = e => !e.IsDeleted;
|
||||||
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()
|
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()
|
||||||
.Where(m => m.ClrType.IsAssignableTo(typeof(IAudit))))
|
.Where(m => m.ClrType.IsAssignableTo(typeof(IEntity))))
|
||||||
{
|
{
|
||||||
// modify expression to handle correct child type
|
// modify expression to handle correct child type
|
||||||
var parameter = Expression.Parameter(mutableEntityType.ClrType);
|
var parameter = Expression.Parameter(mutableEntityType.ClrType);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ using BuildingBlocks.Core.Model;
|
|||||||
|
|
||||||
namespace BuildingBlocks.EventStoreDB.Events
|
namespace BuildingBlocks.EventStoreDB.Events
|
||||||
{
|
{
|
||||||
public abstract record AggregateEventSourcing<TId> : Audit, IAggregateEventSourcing<TId>
|
public abstract record AggregateEventSourcing<TId> : Entity<TId>, IAggregateEventSourcing<TId>
|
||||||
{
|
{
|
||||||
private readonly List<IDomainEvent> _domainEvents = new();
|
private readonly List<IDomainEvent> _domainEvents = new();
|
||||||
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
|
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
|
||||||
@ -23,10 +23,6 @@ namespace BuildingBlocks.EventStoreDB.Events
|
|||||||
}
|
}
|
||||||
|
|
||||||
public virtual void When(object @event) { }
|
public virtual void When(object @event) { }
|
||||||
|
|
||||||
public long Version { get; protected set; } = -1;
|
|
||||||
|
|
||||||
public TId Id { get; protected set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,17 +3,15 @@ using BuildingBlocks.Core.Model;
|
|||||||
|
|
||||||
namespace BuildingBlocks.EventStoreDB.Events
|
namespace BuildingBlocks.EventStoreDB.Events
|
||||||
{
|
{
|
||||||
public interface IAggregateEventSourcing : IProjection, IAudit
|
using Microsoft.FSharp.Control;
|
||||||
|
|
||||||
|
public interface IAggregateEventSourcing : IProjection, IEntity
|
||||||
{
|
{
|
||||||
IReadOnlyList<IDomainEvent> DomainEvents { get; }
|
IReadOnlyList<IDomainEvent> DomainEvents { get; }
|
||||||
IDomainEvent[] ClearDomainEvents();
|
IDomainEvent[] ClearDomainEvents();
|
||||||
long Version { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IAggregateEventSourcing<out T> : IAggregateEventSourcing
|
public interface IAggregateEventSourcing<T> : IAggregateEventSourcing, IEntity<T>
|
||||||
{
|
{
|
||||||
T Id { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -398,11 +398,17 @@ public class TestWriteFixture<TEntryPoint, TWContext> : TestFixture<TEntryPoint>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<T> FindAsync<T>(Guid id)
|
public Task<T> FindAsync<T, TKey>(TKey id)
|
||||||
where T : class, IAudit
|
where T : class, IEntity
|
||||||
{
|
{
|
||||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<T> FirstOrDefaultAsync<T>()
|
||||||
|
where T : class, IEntity
|
||||||
|
{
|
||||||
|
return ExecuteDbContextAsync(db => db.Set<T>().FirstOrDefaultAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestReadFixture<TEntryPoint, TRContext> : TestFixture<TEntryPoint>
|
public class TestReadFixture<TEntryPoint, TRContext> : TestFixture<TEntryPoint>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using BuildingBlocks.Core.Event;
|
|||||||
using BuildingBlocks.Core.Model;
|
using BuildingBlocks.Core.Model;
|
||||||
using BuildingBlocks.EventStoreDB.Repository;
|
using BuildingBlocks.EventStoreDB.Repository;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
|
using Elasticsearch.Net;
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Flight;
|
using Flight;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@ -27,7 +28,7 @@ public record CreateBooking(Guid PassengerId, Guid FlightId, string Description)
|
|||||||
|
|
||||||
public record CreateBookingResult(ulong Id);
|
public record CreateBookingResult(ulong Id);
|
||||||
|
|
||||||
public record BookingCreatedDomainEvent(Guid Id, PassengerInfo PassengerInfo, Trip Trip) : Audit, IDomainEvent;
|
public record BookingCreatedDomainEvent(Guid Id, PassengerInfo PassengerInfo, Trip Trip) : Entity<Guid>, IDomainEvent;
|
||||||
|
|
||||||
public record CreateBookingRequestDto(Guid PassengerId, Guid FlightId, string Description);
|
public record CreateBookingRequestDto(Guid PassengerId, Guid FlightId, string Description);
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,7 @@ public class CreateFlightEndpoint : IMinimalEndpoint
|
|||||||
|
|
||||||
return Results.CreatedAtRoute("GetFlightById", new { id = result.Id }, response);
|
return Results.CreatedAtRoute("GetFlightById", new { id = result.Id }, response);
|
||||||
})
|
})
|
||||||
.RequireAuthorization()
|
// .RequireAuthorization()
|
||||||
.WithName("CreateFlight")
|
.WithName("CreateFlight")
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.Produces<CreateFlightResponseDto>(StatusCodes.Status201Created)
|
.Produces<CreateFlightResponseDto>(StatusCodes.Status201Created)
|
||||||
@ -68,7 +68,7 @@ public class CreateFlightEndpoint : IMinimalEndpoint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class CreateFlightValidator : AbstractValidator<CreateFlight>
|
public class CreateFlightValidator : AbstractValidator<CreateFlight>
|
||||||
{
|
{
|
||||||
public CreateFlightValidator()
|
public CreateFlightValidator()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
namespace Flight.Identity.Consumers.RegisterNewUser.V1;
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
@ -6,8 +8,6 @@ using MassTransit;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Flight.Identity.Consumers.RegisterNewUser.Consumes.V1;
|
|
||||||
|
|
||||||
public class RegisterNewUserConsumerHandler : IConsumer<UserCreated>
|
public class RegisterNewUserConsumerHandler : IConsumer<UserCreated>
|
||||||
{
|
{
|
||||||
private readonly AppOptions _options;
|
private readonly AppOptions _options;
|
||||||
@ -10,8 +10,10 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
|
using System;
|
||||||
using global::Flight.Data.Seed;
|
using global::Flight.Data.Seed;
|
||||||
using global::Flight.Flights.Features.DeletingFlight.V1;
|
using global::Flight.Flights.Features.DeletingFlight.V1;
|
||||||
|
using global::Flight.Flights.Models;
|
||||||
|
|
||||||
public class DeleteFlightTests : FlightIntegrationTestBase
|
public class DeleteFlightTests : FlightIntegrationTestBase
|
||||||
{
|
{
|
||||||
@ -24,7 +26,7 @@ public class DeleteFlightTests : FlightIntegrationTestBase
|
|||||||
public async Task should_delete_flight_from_db()
|
public async Task should_delete_flight_from_db()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var flightEntity = await Fixture.FindAsync<global::Flight.Flights.Models.Flight>( InitialData.Flights.First().Id);
|
var flightEntity = await Fixture.FindAsync<Flight, Guid>( InitialData.Flights.First().Id);
|
||||||
var command = new DeleteFlight(flightEntity.Id);
|
var command = new DeleteFlight(flightEntity.Id);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|||||||
@ -9,9 +9,11 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Integration.Test.Flight.Features;
|
namespace Integration.Test.Flight.Features;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using global::Flight.Data.Seed;
|
using global::Flight.Data.Seed;
|
||||||
using global::Flight.Flights.Features.UpdatingFlight.V1;
|
using global::Flight.Flights.Features.UpdatingFlight.V1;
|
||||||
|
using global::Flight.Flights.Models;
|
||||||
|
|
||||||
public class UpdateFlightTests : FlightIntegrationTestBase
|
public class UpdateFlightTests : FlightIntegrationTestBase
|
||||||
{
|
{
|
||||||
@ -24,7 +26,7 @@ public class UpdateFlightTests : FlightIntegrationTestBase
|
|||||||
public async Task should_update_flight_to_db_and_publish_message_to_broker()
|
public async Task should_update_flight_to_db_and_publish_message_to_broker()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var flightEntity = await Fixture.FindAsync<global::Flight.Flights.Models.Flight>( InitialData.Flights.First().Id);
|
var flightEntity = await Fixture.FindAsync<Flight, Guid>( InitialData.Flights.First().Id);
|
||||||
var command = new FakeUpdateFlightCommand(flightEntity).Generate();
|
var command = new FakeUpdateFlightCommand(flightEntity).Generate();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|||||||
@ -5,80 +5,88 @@ using Flight.Flights.Enums;
|
|||||||
using Flight.Seats.Enums;
|
using Flight.Seats.Enums;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Unit.Test.Common
|
namespace Unit.Test.Common;
|
||||||
|
|
||||||
|
using MassTransit;
|
||||||
|
|
||||||
|
public static class DbContextFactory
|
||||||
{
|
{
|
||||||
using MassTransit;
|
private static readonly Guid _airportId1 = NewId.NextGuid();
|
||||||
|
private static readonly Guid _airportId2 = NewId.NextGuid();
|
||||||
|
private static readonly Guid _aircraft1 = NewId.NextGuid();
|
||||||
|
private static readonly Guid _aircraft2 = NewId.NextGuid();
|
||||||
|
private static readonly Guid _aircraft3 = NewId.NextGuid();
|
||||||
|
private static readonly Guid _flightId1 = NewId.NextGuid();
|
||||||
|
|
||||||
public static class DbContextFactory
|
public static FlightDbContext Create()
|
||||||
{
|
{
|
||||||
private static readonly Guid _airportId1 = NewId.NextGuid();
|
var options = new DbContextOptionsBuilder<FlightDbContext>()
|
||||||
private static readonly Guid _airportId2 = NewId.NextGuid();
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()).Options;
|
||||||
private static readonly Guid _aircraft1 = NewId.NextGuid();
|
|
||||||
private static readonly Guid _aircraft2 = NewId.NextGuid();
|
|
||||||
private static readonly Guid _aircraft3 = NewId.NextGuid();
|
|
||||||
private static readonly Guid _flightId1 = NewId.NextGuid();
|
|
||||||
|
|
||||||
public static FlightDbContext Create()
|
var context = new FlightDbContext(options, currentUserProvider: null);
|
||||||
|
|
||||||
|
// Seed our data
|
||||||
|
FlightDataSeeder(context);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FlightDataSeeder(FlightDbContext context)
|
||||||
|
{
|
||||||
|
var airports = new List<global::Flight.Airports.Models.Airport>
|
||||||
{
|
{
|
||||||
var options = new DbContextOptionsBuilder<FlightDbContext>()
|
global::Flight.Airports.Models.Airport.Create(_airportId1, "Lisbon International Airport", "LIS",
|
||||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()).Options;
|
"12988"),
|
||||||
|
global::Flight.Airports.Models.Airport.Create(_airportId2, "Sao Paulo International Airport", "BRZ",
|
||||||
|
"11200")
|
||||||
|
};
|
||||||
|
|
||||||
var context = new FlightDbContext(options, currentUserProvider: null);
|
context.Airports.AddRange(airports);
|
||||||
|
|
||||||
// Seed our data
|
var aircrafts = new List<global::Flight.Aircrafts.Models.Aircraft>
|
||||||
FlightDataSeeder(context);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void FlightDataSeeder(FlightDbContext context)
|
|
||||||
{
|
{
|
||||||
var airports = new List<global::Flight.Airports.Models.Airport>
|
global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft1, "Boeing 737", "B737", 2005),
|
||||||
{
|
global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft2, "Airbus 300", "A300", 2000),
|
||||||
global::Flight.Airports.Models.Airport.Create(_airportId1, "Lisbon International Airport", "LIS", "12988"),
|
global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft3, "Airbus 320", "A320", 2003)
|
||||||
global::Flight.Airports.Models.Airport.Create(_airportId2, "Sao Paulo International Airport", "BRZ", "11200")
|
};
|
||||||
};
|
|
||||||
|
|
||||||
context.Airports.AddRange(airports);
|
context.Aircraft.AddRange(aircrafts);
|
||||||
|
|
||||||
var aircrafts = new List<global::Flight.Aircrafts.Models.Aircraft>
|
var flights = new List<global::Flight.Flights.Models.Flight>
|
||||||
{
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Aircraft.AddRange(aircrafts);
|
|
||||||
|
|
||||||
var flights = new List<global::Flight.Flights.Models.Flight>
|
|
||||||
{
|
|
||||||
global::Flight.Flights.Models.Flight.Create(_flightId1, "BD467", _aircraft1, _airportId1, new DateTime(2022, 1, 31, 12, 0, 0),
|
|
||||||
new DateTime(2022, 1, 31, 14, 0, 0),
|
|
||||||
_airportId2, 120m,
|
|
||||||
new DateTime(2022, 1, 31), FlightStatus.Completed,
|
|
||||||
8000)
|
|
||||||
};
|
|
||||||
context.Flights.AddRange(flights);
|
|
||||||
|
|
||||||
var seats = new List<global::Flight.Seats.Models.Seat>
|
|
||||||
{
|
|
||||||
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12A", SeatType.Window, SeatClass.Economy, _flightId1),
|
|
||||||
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12B", SeatType.Window, SeatClass.Economy, _flightId1),
|
|
||||||
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12C", SeatType.Middle, SeatClass.Economy, _flightId1),
|
|
||||||
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12D", SeatType.Middle, SeatClass.Economy, _flightId1),
|
|
||||||
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12E", SeatType.Aisle, SeatClass.Economy, _flightId1),
|
|
||||||
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12F", SeatType.Aisle, SeatClass.Economy, _flightId1)
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Seats.AddRange(seats);
|
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Destroy(FlightDbContext context)
|
|
||||||
{
|
{
|
||||||
context.Database.EnsureDeleted();
|
global::Flight.Flights.Models.Flight.Create(_flightId1, "BD467", _aircraft1, _airportId1,
|
||||||
context.Dispose();
|
new DateTime(2022, 1, 31, 12, 0, 0),
|
||||||
}
|
new DateTime(2022, 1, 31, 14, 0, 0),
|
||||||
|
_airportId2, 120m,
|
||||||
|
new DateTime(2022, 1, 31), FlightStatus.Completed,
|
||||||
|
8000)
|
||||||
|
};
|
||||||
|
context.Flights.AddRange(flights);
|
||||||
|
|
||||||
|
var seats = new List<global::Flight.Seats.Models.Seat>
|
||||||
|
{
|
||||||
|
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12A", SeatType.Window, SeatClass.Economy,
|
||||||
|
_flightId1),
|
||||||
|
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12B", SeatType.Window, SeatClass.Economy,
|
||||||
|
_flightId1),
|
||||||
|
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12C", SeatType.Middle, SeatClass.Economy,
|
||||||
|
_flightId1),
|
||||||
|
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12D", SeatType.Middle, SeatClass.Economy,
|
||||||
|
_flightId1),
|
||||||
|
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12E", SeatType.Aisle, SeatClass.Economy,
|
||||||
|
_flightId1),
|
||||||
|
global::Flight.Seats.Models.Seat.Create(NewId.NextGuid(), "12F", SeatType.Aisle, SeatClass.Economy,
|
||||||
|
_flightId1)
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Seats.AddRange(seats);
|
||||||
|
|
||||||
|
context.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Destroy(FlightDbContext context)
|
||||||
|
{
|
||||||
|
context.Database.EnsureDeleted();
|
||||||
|
context.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Unit.Test.Flight.Features.Domain
|
namespace Unit.Test.Flight.Features.Domains
|
||||||
{
|
{
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Unit.Test.Flight.Features.Domain;
|
namespace Unit.Test.Flight.Features.Domains;
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@ -1,13 +1,12 @@
|
|||||||
namespace Unit.Test.Flight.Features.Commands.CreateFlight;
|
namespace Unit.Test.Flight.Features.Handlers.CreateFlight;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using global::Flight.Flights.Dtos;
|
|
||||||
using global::Flight.Flights.Features.CreatingFlight.V1;
|
using global::Flight.Flights.Features.CreatingFlight.V1;
|
||||||
using Common;
|
using Unit.Test.Common;
|
||||||
using Fakes;
|
using Unit.Test.Fakes;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
[Collection(nameof(UnitTestFixture))]
|
[Collection(nameof(UnitTestFixture))]
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Unit.Test.Flight.Features.Commands.CreateFlight;
|
namespace Unit.Test.Flight.Features.Handlers.CreateFlight;
|
||||||
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using FluentValidation.TestHelper;
|
using FluentValidation.TestHelper;
|
||||||
@ -63,7 +63,7 @@ public sealed class IdentityContext : IdentityDbContext<User, Role, Guid,
|
|||||||
public IReadOnlyList<IDomainEvent> GetDomainEvents()
|
public IReadOnlyList<IDomainEvent> GetDomainEvents()
|
||||||
{
|
{
|
||||||
var domainEntities = ChangeTracker
|
var domainEntities = ChangeTracker
|
||||||
.Entries<Aggregate>()
|
.Entries<IAggregate>()
|
||||||
.Where(x => x.Entity.DomainEvents.Any())
|
.Where(x => x.Entity.DomainEvents.Any())
|
||||||
.Select(x => x.Entity)
|
.Select(x => x.Entity)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user