通常在Application
层的IRequestHandler
中涉及到查询、排序、分页会有一大段代码。
public class SomeRequest : IRequest<DDResponseWrapper<SomeRequestDto>>
{
//是否加载一对多关系
public bool LoadChildren {get;set;}
public bool IsPagingEnabled{get;set;}
public int Page{get;set;}
public int PageSize{get;set;}
...
}
public class SomeRequestDto
{
}
public class SomeRequestHandler : IRequestHandler<SomeRequest, DDResponseWrapper<SomeRequestDto>>
{
private readonly IBreakerRepo _breakerRepo;
public SomeRequestHandler(IBreakerRepo breakerRepo)
{
_breakerRepo = breakerRepo;
}
public asyn Task<DDResponseWrapper<SomeRequestDto>> Handle(SomeRequest request, CancellationTolen canellationTolen)
{
var breakers = _breakerRepo.GetAll();
breakers = breakers.OrderBy(t=>t.Name);
if(request.LoadChildren)
{
breakers = breakers.Include(t => t.BreakerData);
}
if(request.IsPagingEnablled)
{
breakers = breakers.Skip(request.Page).Take(request.PageSize);
}
if(!string.IsNullOrEmpty(request.Name))
{
breakers = breakers.Where(t=>t.Name.Contains(request.Name));
}
......
}
}
是否可以把这部分逻辑放到基础设施层、或者领域层呢?然后通过调用
var breakers =await _breakerRepo.ListAsync(request)
替换以上逻辑?在DDD
中,有一种Specification Patter
模式特别适合这种场景,可以把相关逻辑封装在领域层,这样根据模块化,也更方便单元测试。
//在领域层定义Specification<T>
public class CustomerSpec : Specification<Customer>
{
public CustomerSpec(CustomerFilter filter)
{
...
}
}
//应用
var spec = new CustomerSpec();
var breakers = await _breakerRepo.ListAsync(spec);
Ardalis.Specification
遵循了Specification Pattern
,很好地解决了上述问题。
<PackageReference Include="Ardalis.Specification" Version="4.2.0" />
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="4.2.0" />
一对多关系
public class Customer : IAggregateRoot
{
public int Id { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public string Address { get; private set; }
public IEnumerable<Store> Stores => _stores.AsEnumerable();
private readonly List<Store> _stores = new List<Store>();
public Customer(string name, string email, string address)
{
Guard.Against.NullOrEmpty(name, nameof(name));
Guard.Against.NullOrEmpty(email, nameof(email));
this.Name = name;
this.Email = email;
this.Address = address;
}
......
}
public class Store
{
public int Id { get; private set; }
public string Name { get; private set; }
public string Address { get; private set; }
public int CustomerId { get; private set; }
public Store(string name, string address)
{
Guard.Against.NullOrEmpty(name, nameof(name));
this.Name = name;
this.Address = address;
}
}
查询条件
public class BaseFilter
{
public bool LoadChildren { get; set; }
public bool IsPagingEnabled { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
}
public class CustomerFilter : BaseFilter
{
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
}
定义Specification
相关
namespace Sample.Core.Specifications
{
public class CustomerSpec : Specification<Customer>
{
public CustomerSpec(CustomerFilter filter)
{
Query.OrderBy(t => t.Name)
.ThenByDescending(t => t.Address);
if (filter.LoadChildren)
Query.Include(x => x.Stores);
if (filter.IsPagingEnabled)
Query.Skip(PaginationHelper.CalculateSkip(filter))
.Take(PaginationHelper.CalculateTake(filter));
if (!string.IsNullOrEmpty(filter.Name))
Query.Where(t => t.Name == filter.Name);
if (!string.IsNullOrEmpty(filter.Email))
Query.Where(x => x.Email == filter.Email);
if (!string.IsNullOrEmpty(filter.Address))
Query.Search(x => x.Address, "%" + filter.Address + "%");
}
}
}
实现Ardalis.Specification
的一个接口
using Ardalis.Specification;
namespace Sample.Core.Interfaces
{
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
{
}
}
IRepository
的实现
using Ardalis.Specification.EntityFrameworkCore;
namespace Sample.Infra.Data
{
public class MyRepository<T> : RepositoryBase<T>, IRepository<T> where T : class, IAggregateRoot
{
private readonly SampleDbContext dbContext;
public MyRepository(SampleDbContext dbContext) : base(dbContext)
{
this.dbContext = dbContext;
}
// Not required to implement anything. Add additional functionalities if required.
}
}
注册
services.AddScoped(typeof(MyRepository<>));
Application
层或UI
层调用private readonly IRepository<Customer> customerRepository;
var spec = new CustomerSpec();
var customers = await customerRepository.ListAsync(spec);
源码位置:F:\demos\CleanArchitecture\UseSpecification