鼎鼎知识库
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

3、Specification Pattern模式解决查询问题.md 5.5KB

从一段代码说起

通常在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