深入浅出-应用服务
应用服务实现应用程序的用例,将领域层逻辑公开给表示层。
从表示层(可选)调用应用服务,DTO (数据传对象) 作为参数. 返回(可选)DTO给表示层。
示例
图书实体
假设你有一个Book
实体(聚合根), 如下所示:
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;
}
}
Book
实体中定义MaxNameLength
限制Name
属性的最大长度。Book
构造函数与ChangeName
确保Name
属性值的有效性. 请注意,Name
的setter不是public。
ABP不会强制开发者这样设计实体, 可以将所有的属性设置Public set/get. 由你来决定是否全面实施DDD。
IBookAppService接口
在ABP中应用程序服务应该实现IApplicationService接口
. 推荐每个应用程序服务创建一个接口:
public interface IBookAppService : IApplicationService
{
Task CreateAsync(CreateBookDto input);
}
我们将实现Create方法作为示例. CreateBookDto定义如下:
public class CreateBookDto
{
[Required]
[StringLength(Book.MaxNameLength)]
public string Name { get; set; }
public BookType Type { get; set; }
public float? Price { get; set; }
}
有关DTO更的教程,请参见数据传输对象文档
BookAppService(实现)
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
(参见Guid生成文档)。BookAppService
按照预期实现了IBookAppService。
BookAppService
注入了IRepository<Book, Guid>
(请参见仓储)在CreateAsync方法内部使用仓储将新实体插入数据库。CreateAsync
使用Book
实体的构造函数从给定的Input值创建新的Book
对象。
数据传输对象
应用服务使用并返回DTO而不是实体. ABP不会强制执行此规则. 但是将实体暴露给表示层(或远程客户端)存在重大问题, 所以不建议返回实体。
有关更多信息, 请参见DTO文档。
对象到对象映射
CreateBook
方法使用参数CreateBookDto
对象手动创建Book
实体. 因为Book
实体的构造函数强制执行(我们是这样设计的)。
但是在很多情况下使用自动对象映射从相似对象设置对象的属性更加方便实用. ABP提供了一个对象到对象映射基础设施,使其变得更加容易。
让我们创建另一种获取Book
的方法. 首先,在IBookAppService
接口中定义方法:
public interface IBookAppService : IApplicationService
{
Task CreateAsync(CreateBookDto input);
Task<BookDto> GetAsync(Guid id); //New method
}
BookDto
是一个简单的DTO类, 定义如下:
public class BookDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public float? Price { get; set; }
}
我们创建一个Automapper的Profile类. 例如:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
}
}
然后使用AbpAutoMapperOptions
注册配置文件:
[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
//Add all mappings defined in the assembly of the MyModule class
options.AddMaps<MyModule>();
});
}
}
AddMaps
注册给定类的程序集中所有的配置类,通常使用模块类. 它还会注册 attribute 映射. 更多信息请参考对象到对象映射文档
然后你可以实现GetAsync
方法. 如下所示:
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return book.MapTo<BookDto>();
}
MapTo
扩展方法通过复制具有相同命名的所有属性将Book
对象转换为BookDto
对象。
MapTo
的另一种替代方法是使用IObjectMapper
服务:
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return ObjectMapper.Map<Book, BookDto>(book);
}
虽然第二种语法编写起来有点困难,但是如果你编写单元测试,它会更好地工作. 有关更多信息,请参阅对象到对象映射文档.
验证
自动验证应用服务方法的输入(如ASP.NET Core 控制器的actions). 你可以使用标准数据注释属性或自定义验证方法来执行验证. ABP还确保输入不为空。
请参阅验证文档了解更多信息。
授权
可以对应用程序服务方法使用声明性和命令式授权。
请参阅授权文档了解更多信息。
CRUD应用服务
如果需要创建具有Create,Update,Delete和Get方法的简单CRUD应用服务,则可以使用ABP的基类轻松构建服务. 你可以继承CrudAppService。
示例:
创建继承ICrudAppService
接口的IBookAppService
接口。
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> //Used to update a book
{
}
ICrudAppService
有泛型参数来获取实体的主键类型和CRUD操作的DTO类型(它不获取实体类型,因为实体类型未向客户端公开使用此接口)。
为应用程序服务创建一个接口是最佳做法,但是ABP框架并不强制你这么做,你可以跳过接口部分。
ICrudAppService
声明以下方法:
public interface ICrudAppService<
TEntityDto,
in TKey,
in TGetListInput,
in TCreateInput,
in TUpdateInput>
: IApplicationService
where TEntityDto : IEntityDto<TKey>
{
Task<TEntityDto> GetAsync(TKey id);
Task<PagedResultDto<TEntityDto>> GetListAsync(TGetListInput input);
Task<TEntityDto> CreateAsync(TCreateInput input);
Task<TEntityDto> UpdateAsync(TKey id, TUpdateInput input);
Task DeleteAsync(TKey id);
}
示例中使用的DTO类是BookDto
和CreateUpdateBookDto:
public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }
public BookType Type { get; set; }
public float Price { get; set; }
}
public class CreateUpdateBookDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookType Type { get; set; } = BookType.Undefined;
[Required]
public float Price { get; set; }
}
DTO类的Profile类.
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
}
}
CreateUpdateBookDto
由创建和更新操作共享,但你也可以使用单独的DTO类。
最后BookAppService
实现非常简单:
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}
CrudAppService
实现了ICrudAppService
接口中声明的所有方法. 然后,你可以添加自己的自定义方法或覆盖和自定义实现。
CrudAppService
有不同数量泛型参数的版本,你可以选择适合的使用。
AbstractKeyCrudAppService
CrudAppService
要求你的实体拥有一个Id属性做为主键. 如果你使用的是复合主键,那么你无法使用它。
AbstractKeyCrudAppService
实现了相同的 ICrudAppService
接口,但它没有假设你的主键。
示例
假设你有实体 District
,它的CityId
和 Name
做为复合主键,使用 AbstractKeyCrudAppService
时需要你自己实现 DeleteByIdAsync
和 GetEntityByIdAsync
方法:
public class DistrictAppService
: AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
protected override async Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}
这个实现需要你创建一个类做为复合键:
public class DistrictKey
{
public Guid CityId { get; set; }
public string Name { get; set; }
}