|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- # 从一段代码说起
-
- 通常在`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`
|