什么是畛域服务呢?畛域服务就是畛域对象自身的服务,通常是通过多个聚合以实现单个聚合无奈解决的逻辑。
一.畛域服务实际
接下来将聚合根Issue中的AssignToAsync()办法[将问题调配给用户],剥离到畛域服务当中。如下:
// ABP当中的畛域服务类通常都是以Manager结尾的public class IssueManager : DomainService{ private readonly IRepository<Issue,Guid> _issueRepository; // 在构造函数中注入须要的仓储 public IssueManager(IRepository<Issue,Guid> issueRepository) { _issueRepository = issueRepository; } public async Task AssignToAsync(Issue issue, AppUser user) { // 通过仓储获取调配给该用户的,并且没有敞开的Issue的数量 var openIssueCount = await _issueRepository.CountAsync(i => i.AssignedUserId == user.id && !i.IsClosed); // 如果超过3个,那么抛出异样 if (openIssueCount > 3) { throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); } issue.AssignedUserId = user.Id; }}
须要阐明的是通常不须要为畛域服务IssueManager在创立一个接口IIssueManager。
二.应用服务实际
应用服务的输出和输入通常都是DTO,其中的难点是辨别畛域逻辑和应用逻辑,即哪些服务放在畛域层实现,哪些服务放在应用层来实现。
namespace IssueTracking.Issues{ public class IssueAppService :ApplicationService.IIssueAppService { private readonly IssueManager _issueManager; private readonly IRepository<Issue,Guid> _issueRepository; private readonly IRepository<AppUser,Guid> _userRepository; public IssueAppService( IssueManager issueManager, IRepository<Issue,Guid> issueRepository, IRepository<AppUser,Guid> userRepository ) { _issueManager=issueManager; _issueRepository=issueRepository; _userRepository=userRepository; } [Authorize] public async Task AssignAsync(IssueAssignDto input) { var issue=await _issueRepository.GetAsync(input.IssueId); var user=await _userRepository.GetAsync(inpu.UserId); await _issueManager.AssignToAsync(issue,user); await _issueRepository.UpdateAsync(issue); } }}
在上述代码中,为什么最初执行_issueRepository.UpdateAsync(issue)
呢?其中有2层含意,第1层是Issue通过_issueManager.AssignToAsync(issue,user)
产生了变动,须要进行更新操作(从下图可知Issue聚合根中蕴含AssignedUserId字段);第2层是EF Core中有状态变更跟踪,Update并不是必须的,然而还是倡议显式调用Update,用来适配其它的数据库提供程序。
三.数据传输对象DTO实际
DTO的实质是在应用层和展现层传递状态数据,通常应用层的输出和输入都是DTO,这样做的最大益处就是不裸露实体的结构设计。
1.输出DTO实际
(1)不要重用输出DTO
不应用的属性不要定义在输出DTO中;不要重用输出DTO有2种形式:一种形式是为每个应用服务办法定义特定的输出DTO,另一种形式是不要应用DTO继承。上面是谬误的输出DTO实际,理由详见正文:
public interface IUserAppService : IApplicationService{ Task CreateAsync(UserDto input); //Id在该办法中没有用到 Task UpdateAsync(UserDto input); // Password在该办法中没有用到 Task ChangePasswordAsync(UserDto input); // CreationTime在该办法中没有用到}public class UserDto{ public Guid Id { get; set; } public string UserName { get; set; } public string Email { get; set; } public string Password { get; set; } public DateTime CreateTime { get; set; }}
上面是正确的输出DTO实际:
public interface IUserAppService : IApplicationService{ Task CreateAsync(UserCreationDto input); Task UpdateAsync(UserUpdateDto input); Task ChangePasswordAsync(UserChangePasswordDto input);}public class UserCreationDto{ public string UserName { get; set; } public string Email { get; set; } public string Password { get; set; }}public class UserUpdateDto{ public Guid Id { get; set; } public string UserName { get; set; } public string Email { get; set; }}public class UserChangePasswordDto{ public Guid Id { get; set; } public string Password { get; set; }}
(2)输出DTO中的验证逻辑
次要是在DTO外部通过数据注解个性、FluentValidation,或者实现IValidatableObject接口等形式来执行简略的验证。须要留神的是不要在DTO中执行畛域验证,比方检测用户名是否惟一的验证等。上面在输出DTO中应用数据注解个性:
namespace IssueTracking.Users{ public class UserCreationDto { [Required] [StringLength(UserConsts.MaxUserNameLength)] public string UserName {get;set;} [Required] [EmailAddress] [StringLength(UserConsts.MaxEmailLength)] public string Email{get;set;} [Required] [StringLength(UserConsts.MaxEmailLength,MinimumLength=UserConsts.MinPasswordLength)] public string Password{get;set;} }}
ABP会主动验证输出DTO中的注解,如果验证失败,那么抛出AbpValidationException异样,并且返回400状态码。集体倡议应用FluentValidation形式进行验证,而不是申明式的数据注解,这样做的长处是将验证规定和DTO类彻底分来到。
2.输入DTO实际
输入DTO最佳实际:次要是尽可能的复用输入DTO,然而切记不能把输出DTO作为输入DTO;输入DTO能够蕴含更多的属性;Create和Update办法返回DTO。上面是谬误的输入DTO实际:
public interface IUserAppService:IApplicationService{ UserDto Get(Guid id); List<UserNameAndEmailDto> GetUserNameAndEmail(Guid id); List<string> GetRoles(Guid id); List<UserListDto> GetList(); UserCreateResultDto Create(UserCreationDto input); UserUpdateResultDto Update(UserUpdateDto input);}
上面是正确的输入DTO实际:
public interface IUserAppService:IApplicationService{ UserDto Get(Guid id); List<UserDto> GetList(); UserDto Create(UserCreationDto input); UserDto Update(UserUpdateDto input);}public class UserDto{ public Guid Id{get;set;} public string UserName{get;set;} public string Email{get;set;} public DateTiem CreationTime{get;set;} public List<string> Roles{get;set;}}
阐明:删除GetUserNameAndEmail()和GetRoles()办法,因为它们与Get()办法反复了,即它们的性能都能够通过Get()办法来实现。
3.对象映射工具
为什么须要对象映射工具呢?因为实体和DTO具备雷同或者类似的属性,如果手工解决实体和DTO间的转换,那么效率是非常低的,因而须要对象映射工具高效的实现实体和DTO间的转换。
在ABP中应用的对象映射框架是AutoMapper,官网的倡议是:仅对实体到输入DTO做主动对象映射,不倡议输出DTO到实体做主动对象映射。因为DTO是实体的局部或者全副字段,本人揣测前者是比拟确定的,而因为简单的业务规定让后者的映射充斥了不确定性。具体为什么不应用输出DTO到实体做主动对象映射的起因参考[1]。
主动对象映射在应用服务层中实现,该类须要继承自Profile类:
尽管官网不倡议输出DTO到实体做主动对象映射,然而在通常的实际中还是较多应用CreateOrUpdateXXXDto到实体XXX的主动对象映射:
对于FluentValidation和AutoMapper这2个库就不独自在这里开展讲了,前面独自文章进行解说操作和原理。
参考文献:
[1]基于ABP Framework实现畛域驱动设计:https://url39.ctfile.com/f/25... (拜访明码: 2096)
[2]FluentValidation官网文档:https://docs.fluentvalidation...
[3]FluentValidation GitHub:https://github.com/FluentVali...
[4]AutoMapper官网文档:http://automapper.org/
[5]AutoMapper GitHub:https://github.com/AutoMapper...
本文由mdnice多平台公布