应用服务实现应用程序的用例, 将领域层逻辑公开给表示层.
假设你有一个Book实体(聚合根), 如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Book : AggregateRoot <Guid >{ public const int MaxNameLength = 128 ; public virtual string Name { get ; protected set ; } public virtual BookType Type { get ; set ; } public virtual float ? Price { get ; set ; } protected Book () { } public Book (Guid id, [NotNull] string name, BookType type, float ? price = 0 ) { Id = id; Name = CheckName(name); Type = type; Price = price; } public virtual void ChangeName ([NotNull] string name ) { Name = CheckName(name); } private static string CheckName (string name ) { if (string .IsNullOrWhiteSpace(name)) { throw new ArgumentException($"name can not be empty or white space!" ); } if (name.Length > MaxNameLength) { throw new ArgumentException($"name can not be longer than {MaxNameLength} chars!" ); } return name; } }
IBookAppService接口 在ABP中应用程序服务应该实现IApplicationService接口. 推荐每个应用程序服务创建一个接口:
1 2 3 4 public interface IBookAppService : IApplicationService { Task CreateAsync (CreateBookDto input ) ; }
我们将实现Create方法作为示例. CreateBookDto定义如下:
1 2 3 4 5 6 7 8 9 10 public class CreateBookDto { [Required ] [StringLength(Book.MaxNameLength) ] public string Name { get ; set ; } public BookType Type { get ; set ; } public float ? Price { get ; set ; } }
BookAppService(实现) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class BookAppService : ApplicationService , IBookAppService { private readonly IRepository<Book, Guid> _bookRepository; public BookAppService (IRepository<Book, Guid> bookRepository ) { _bookRepository = bookRepository; } public async Task CreateAsync (CreateBookDto input ) { var book = new Book( GuidGenerator.Create(), input.Name, input.Type, input.Price ); await _bookRepository.InsertAsync(book); } }
BookAppService继承了基类ApplicationService· 这不是必需的, 但是ApplicationService提供了应用服务常见的需求(比如本示例服务中使用的GuidGenerator). 如果不继承它, 我们需要在服务中手动注入IGuidGenerator
BookAppService按照预期实现了IBookAppService
BookAppService 注入了 IRepository<Book, Guid>(请参见仓储)在CreateAsync方法内部使用仓储将新实体插入数据库.
CreateAsync使用Book实体的构造函数从给定的Input值创建新的Book对象
数据传输对象 应用服务使用并返回DTO而不是实体. ABP不会强制执行此规则. 但是将实体暴露给表示层(或远程客户端)存在重大问题, 所以不建议返回实体.
CRUD应用服务 如果需要创建具有Create,Update,Delete和Get方法的简单CRUD应用服务,则可以使用ABP的基类轻松构建服务. 你可以继承CrudAppService.
创建继承ICrudAppService接口的IBookAppService接口.
1 2 3 4 5 6 7 8 9 public interface IBookAppService : ICrudAppService < //Defines CRUD methods BookDto , //Used to show books Guid , //Primary key of the book entity PagedAndSortedResultRequestDto , //Used for paging /sorting on getting a list of books CreateUpdateBookDto , //Used to create a new book CreateUpdateBookDto > { }
ICrudAppService 有泛型参数来获取实体的主键类型和CRUD操作的DTO类型(它不获取实体类型,因为实体类型未向客户端公开使用此接口).
最后BookAppService实现非常简单:
1 2 3 4 5 6 7 8 9 10 public class BookAppService : CrudAppService <Book , BookDto , Guid , PagedAndSortedResultRequestDto , CreateUpdateBookDto , CreateUpdateBookDto >, IBookAppService { public BookAppService (IRepository<Book, Guid> repository ) : base (repository ) { } }
CrudAppService实现了ICrudAppService接口中声明的所有方法. 然后,你可以添加自己的自定义方法或重写和自定义实现.
AbstractKeyCrudAppService CrudAppService 要求你的实体拥有一个Id属性做为主键. 如果你使用的是复合主键,那么你无法使用它.
AbstractKeyCrudAppService 实现了相同的 ICrudAppService 接口,但它没有要求实体有主键.
假设你有实体 District,它的CityId 和 Name 做为复合主键,使用 AbstractKeyCrudAppService 时需要你自己实现 DeleteByIdAsync 和 GetEntityByIdAsync 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class DistrictAppService : AbstractKeyCrudAppService <District , DistrictDto , DistrictKey > { public DistrictAppService (IRepository<District> repository ) : base (repository ) { } protected async override Task DeleteByIdAsync (DistrictKey id ) { await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name); } protected async override Task<District> GetEntityByIdAsync (DistrictKey id ) { return await AsyncQueryableExecuter.FirstOrDefaultAsync( Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name) ); } }
这个实现需要你创建一个类做为复合键:
1 2 3 4 5 6 public class DistrictKey { public Guid CityId { get ; set ; } public string Name { get ; set ; } }
生命周期 应用服务的生命周期是transient的,它们会自动注册到依赖注入系统.
总结
将实体对象作为方法参数,而不是其id值。如果接受其id值,则需要从领域服务内的数据库中检索实体。这种方法使应用程序代码在同一请求(用例)的不同位置多次加载同一实体,这是低效的,并导致错误。