文章目录 项目地址 一、Application 层 1.1 定义CQRS的接口以及其他服务 1. Command 2. IQuery查询 3. 当前时间服务接口 4. 邮件发送服务接口 1.2 ReserveBooking Command 1. 处理传入的参数 2. ReserveBookingCommandHandler 3. BookingReservedDomainEvent 1.3 Query使用Sql查询 1. 创建Dapper的链接接口 2. GetBookingQuery 3. QueryHandler 二、垂直切片 2.1 Validator中间件 1. 创建中间件 2. 创建ValidationError 3. 注册服务 2.2 LoggingBehavior中间件
项目地址
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 = "" "SELECTid 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 CreatedOnUtcFROM bookingsWHERE 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< , > ) ) ;