项目地址
dbt
airflow
一、Application 层
1.1 定义CQRS的接口以及其他服务
1. Command
ICommand.cs
using Bookify. Domain. Abstractions ;
using MediatR ;
namespace Bookify. Application. Abstractions. Messaging ;
public interface ICommand : IRequest< Result>
{
}
public interface ICommand< TReponse> : IRequest< Result< TReponse> >
{
}
ICommandHandler.cs
namespace Bookify. Application. Abstractions. Messaging ;
public interface ICommandHandler< TCommand> : IRequestHandler< TCommand, Result>
where TCommand : ICommand
{
}
public interface ICommandHandler< TCommand, TResponse> : IRequestHandler< TCommand, Result< TResponse> >
where TCommand : ICommand< TResponse>
{
}
2. IQuery查询
using Bookify. Domain. Abstractions ;
using MediatR ;
namespace Bookify. Application. Abstractions. Messaging ;
public interface IQuery< TResponse> : IRequest< Result< TResponse> >
{
}
using Bookify. Domain. Abstractions ;
using MediatR ;
namespace Bookify. Application. Abstractions. Messaging ;
public interface IQueryHandler< TQuery, TResponse> : IRequestHandler< TQuery, Result< TResponse> >
where TQuery : IQuery< TResponse>
{
}
3. 当前时间服务接口
namespace Bookify. Application. Abstractions. Clock ;
public interface IDateTimeProvider
{
DateTime UtcNow { get ; }
}
4. 邮件发送服务接口
namespace Bookify. Application. Abstractions. Email ;
public interface IEmailService
{
Task SendAsync ( Domain. Users. Email recipient, string subject, string body) ;
}
1.2 ReserveBooking Command
1. 处理传入的参数
ReserveBookingCommand.cs
:返回值是Guid,参数时4个
using Bookify. Application. Abstractions. Messaging ;
namespace Bookify. Application. Bookings. ReserveBooking ;
public record ReserveBookingCommand (
Guid ApartmentId,
Guid UserId,
DateOnly StartDate,
DateOnly EndDate) : ICommand< Guid> ;
2. ReserveBookingCommandHandler
internal sealed class ReserveBookingCommandHandler : ICommandHandler< ReserveBookingCommand, Guid>
{
private readonly IUserRepository _userRepository;
private readonly IApartmentRepository _apartmentRepository;
private readonly IBookingRepository _bookingRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly PricingService _pricingService;
private readonly IDateTimeProvider _dateTimeProvider;
public ReserveBookingCommandHandler (
IUserRepository userRepository,
IApartmentRepository apartmentRepository,
IBookingRepository bookingRepository,
IUnitOfWork unitOfWork,
PricingService pricingService,
IDateTimeProvider dateTimeProvider)
{
_userRepository = userRepository;
_apartmentRepository = apartmentRepository;
_bookingRepository = bookingRepository;
_unitOfWork = unitOfWork;
_pricingService = pricingService;
_dateTimeProvider = dateTimeProvider;
}
public async Task< Result< Guid> > Handle ( ReserveBookingCommand request, CancellationToken cancellationToken)
{
User? user = await _userRepository. GetByIdAsync ( request. UserId, cancellationToken) ;
if ( user is null )
{
return Result. Failure < Guid> ( UserErrors. NotFound) ;
}
Apartment? apartment = await _apartmentRepository. GetByIdAsync ( request. ApartmentId, cancellationToken) ;
if ( apartment is null )
{
return Result. Failure < Guid> ( ApartmentErrors. NotFound) ;
}
var duration = DateRange. Create ( request. StartDate, request. EndDate) ;
if ( await _bookingRepository. IsOverlappingAsync ( apartment, duration, cancellationToken) )
{
return Result. Failure < Guid> ( BookingErrors. Overlap) ;
}
var booking = Booking. Reserve (
apartment,
user. Id,
duration,
_dateTimeProvider. UtcNow,
_pricingService) ;
_bookingRepository. Add ( booking) ;
await _unitOfWork. SaveChangesAsync ( cancellationToken) ;
return booking. Id;
}
}
3. BookingReservedDomainEvent
处理 BookingReservedDomainEvent 事件的逻辑,这里只是一个处理的函数,并没有自动执行,只有当发布了事件之后,才会触发
namespace Bookify. Application. Bookings. ReserveBooking ;
internal sealed class BookingReservedDomainEventHandler : INotificationHandler< BookingReservedDomainEvent>
{
private readonly IBookingRepository _bookingRepository;
private readonly IUserRepository _userRepository;
private readonly IEmailService _emailService;
public BookingReservedDomainEventHandler (
IEmailService emailService,
IUserRepository userRepository,
IBookingRepository bookingRepository)
{
_emailService = emailService;
_userRepository = userRepository;
_bookingRepository = bookingRepository;
}
public async Task Handle ( BookingReservedDomainEvent notification, CancellationToken cancellationToken)
{
Booking? booking = await _bookingRepository. GetByIdAsync ( notification. BookingId, cancellationToken) ;
if ( booking is null )
{
return ;
}
User? user = await _userRepository. GetByIdAsync ( booking. UserId, cancellationToken) ;
if ( user is null )
{
return ;
}
await _emailService. SendAsync (
user. Email,
"Booking reserved!" ,
"You have 10 minutes to confirm this booking" ) ;
}
}
1.3 Query使用Sql查询
1. 创建Dapper的链接接口
namespace Bookify. Application. Abstractions. Data ;
public interface ISqlConnectionFactory
{
IDbConnection CreateConnection ( ) ;
}
2. GetBookingQuery
GetBookingQuery
:传入BookingID,返回BookingResponse
using Bookify. Application. Abstractions. Messaging ;
namespace Bookify. Application. Bookings. GetBooking ;
public sealed record GetBookingQuery ( Guid BookingId) : IQuery< BookingResponse> ;
namespace Bookify. Application. Bookings. GetBooking ;
public sealed class BookingResponse
{
public Guid Id { get ; init; }
public Guid UserId { get ; init; }
public Guid ApartmentId { get ; init; }
public int Status { get ; init; }
public decimal PriceAmount { get ; init; }
public string PriceCurrency { get ; init; }
public decimal CleaningFeeAmount { get ; init; }
public string CleaningFeeCurrency { get ; init; }
public decimal AmenitiesUpChargeAmount { get ; init; }
public string AmenitiesUpChargeCurrency { get ; init; }
public decimal TotalPriceAmount { get ; init; }
public string TotalPriceCurrency { get ; init; }
public DateOnly DurationStart { get ; init; }
public DateOnly DurationEnd { get ; init; }
public DateTime CreatedOnUtc { get ; init; }
}
3. QueryHandler
using System. Data ;
using Bookify. Application. Abstractions. Data ;
using Bookify. Application. Abstractions. Messaging ;
using Bookify. Domain. Abstractions ;
using Dapper ;
namespace Bookify. Application. Bookings. GetBooking ;
internal sealed class GetBookingQueryHandler : IQueryHandler< GetBookingQuery, BookingResponse>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public GetBookingQueryHandler ( ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
}
public async Task< Result< BookingResponse> > Handle ( GetBookingQuery request, CancellationToken cancellationToken)
{
using IDbConnection connection = _sqlConnectionFactory. CreateConnection ( ) ;
const string sql = "" "
SELECT
id AS Id,
apartment_id AS ApartmentId,
user_id AS UserId,
status AS Status,
price_for_period_amount AS PriceAmount,
price_for_period_currency AS PriceCurrency,
cleaning_fee_amount AS CleaningFeeAmount,
cleaning_fee_currency AS CleaningFeeCurrency,
amenities_up_charge_amount AS AmenitiesUpChargeAmount,
amenities_up_charge_currency AS AmenitiesUpChargeCurrency,
total_price_amount AS TotalPriceAmount,
total_price_currency AS TotalPriceCurrency,
duration_start AS DurationStart,
duration_end AS DurationEnd,
created_on_utc AS CreatedOnUtc
FROM bookings
WHERE id = @BookingId
"" ";
BookingResponse? booking = await connection. QueryFirstOrDefaultAsync < BookingResponse> (
sql,
new
{
request. BookingId
} ) ;
return booking;
}
}
二、垂直切片
2.1 Validator中间件
1. 创建中间件
只作用于 Command 类型,进行验证,并且会自动注入验证器,不用每次手动添加validator对于command的请求
namespace Bookify. Application. Abstractions. Behaviors ;
public class ValidationBehavior< TRequest, TResponse>
: IPipelineBehavior< TRequest, TResponse>
where TRequest : IBaseCommand
{
private readonly IEnumerable< IValidator< TRequest> > _validators;
public ValidationBehavior ( IEnumerable< IValidator< TRequest> > validators)
{
_validators = validators;
}
public async Task< TResponse> Handle (
TRequest request,
RequestHandlerDelegate< TResponse> next,
CancellationToken cancellationToken)
{
if ( ! _validators. Any ( ) )
{
return await next ( ) ;
}
var context = new ValidationContext< TRequest> ( request) ;
var validationErrors = _validators
. Select ( validator => validator. Validate ( context) )
. Where ( validationResult => validationResult. Errors. Any ( ) )
. SelectMany ( validationResult => validationResult. Errors)
. Select ( validationFailure => new ValidationError (
validationFailure. PropertyName,
validationFailure. ErrorMessage) )
. ToList ( ) ;
if ( validationErrors. Any ( ) )
{
throw new Exceptions. ValidationException ( validationErrors) ;
}
return await next ( ) ;
}
}
2. 创建ValidationError
创建一个ValidationError
的类
namespace Bookify. Application. Exceptions ;
public sealed record ValidationError ( string PropertyName, string ErrorMessage) ;
实例化ValidationError,并且将错误存储到列表里
public sealed class ValidationException : Exception
{
public ValidationException ( IEnumerable< ValidationError> errors)
{
Errors = errors;
}
public IEnumerable< ValidationError> Errors { get ; }
3. 注册服务
注册中间件,在MR服务里
注册所有的validator
2.2 LoggingBehavior中间件
我们对Command 进行特殊单独的Logging逻辑,因为这里使用了Dapper直接写了sql,所以,没有必要使用日志记录Sql,这样提高了程序性能
1. 创建中间件
只对IBaseCommand实现的类进行特殊化logging处理
public class LoggingBehavior< TRequest, TResponse>
: IPipelineBehavior< TRequest, TResponse>
where TRequest : IBaseCommand
{
private readonly ILogger< TRequest> _logger;
public LoggingBehavior ( ILogger< TRequest> logger)
{
_logger = logger;
}
public async Task< TResponse> Handle (
TRequest request,
RequestHandlerDelegate< TResponse> next,
CancellationToken cancellationToken)
{
string name = request. GetType ( ) . Name;
try
{
_logger. LogInformation ( "Executing command {Command}" , name) ;
TResponse result = await next ( ) ;
_logger. LogInformation ( "Command {Command} processed successfully" , name) ;
return result;
}
catch ( Exception exception)
{
_logger. LogError ( exception, "Command {Command} processing failed" , name) ;
throw ;
}
}
}
2. 注册服务
configuration. AddOpenBehavior ( typeof ( LoggingBehavior< , > ) ) ;