应用服务
应用服务接口
- 在application.contracts层中为每一个应用服务定义一个接口
- 接口要继承IApplicationService接口
- 接口名称的后缀为AppService 如IProdectAppService
- 为服务创建数据传输对象 DTO
- 服务中的方法 不能返回实体
输出
- 不要定义过多的输出DTO 为实体定义基础和详细DTO
基础DTO
- 为聚合根定义一个基础DTO
- 直接包含实体中所有的属性,可以排除一些属性(如 Password)
- 实体中的子集合的项目也是一个简单的关系DTO
[Serializable]
public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
public Guid? MilestoneId { get; set; }
public Collection<IssueLabelDto> Labels { get; set; }
}
[Serializable]
public class IssueLabelDto
{
public Guid IssueId { get; set; }
public Guid LabelId { get; set; }
}
详细DTO
- 如果实体持有对其它聚合根的引用,那么应该为其定义详细DTO
- 直接包含实体中的所有的原始属性: 除了像Password等不便暴露的,外键属性为XXXId的,为其添加引用属性的详细信息
- 为每个引用属性添加基本DTO
- 包含实体的所有子集合,集合中每项都是相关实体的基本DTO
[Serializable]
public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
public MilestoneDto Milestone { get; set; }
public Collection<LabelDto> Labels { get; set; }
}
[Serializable]
public class MilestoneDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public bool IsClosed { get; set; }
}
[Serializable]
public class LabelDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public string Color { get; set; }
}
输入
- 服务类中不使用的属性不要定义在DTO中
- 不要在应用服务方法之间共享输入DTO
- 不要继承另一个DTO类,可以继承自抽象DTO类,并以这种方式在不同的DTO之间共享一些属性,但是这样做有弊端,如果更新基础DTO会影响所有相关的DTO和服务方法
方法
- 为异步方法使用Async后缀
- 不用在方法名中重复实体的名称 :如在IProductAppService中定义GetAsync() 而不是GetProductAsync()
查询单一实体
- 使用GetAsync做为方法名
- 使用Id做为方法参数
- 返回详细DTO
Task<QuestionWithDetailsDto> GetAsync(Guid id);
查询实体集合
- 使用GetListAsync做为方法名
- 如果有必要,使用一个DTO参数来过滤,排序,分页
- 如果有可能,实现过滤选项
- 实现排序和分页
- 限制每页显示的最大条目
- 返回一个详细DTO的集合
Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
新建一个实体对象
- 方法名字使用CreateAsync
- 方法的参数为输入DTO
- DTO对象继承自ExtensibleObject 或其它实现了IHasExtraProperties接口的类,以允许传入额外的属性
- 使用数据声明(data annotations )来验证输入
- 创建完成后,返回详细DTO
- 用最少的信息来创建实体,其它可选属性设为可选
Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
[Serializable]
public class CreateQuestionDto : ExtensibleObject
{
[Required]
[StringLength(QuestionConsts.MaxTitleLength,
MinimumLength = QuestionConsts.MinTitleLength)]
public string Title { get; set; }
[StringLength(QuestionConsts.MaxTextLength)]
public string Text { get; set; } //Optional
public Guid? CategoryId { get; set; } //Optional
}
更新实体
- 方法名为UpdateAsync
- 输入参数为特定的input DTO
- DTO继承自ExtensibleObject 或其它实现了IHasExtraProperties接口的类来传递扩展属性
- 将Id做为单独的参数
- 使用data annotations 进行输入验证
-尽可能在领域之间共享常量
- 返回实体的详细DTO
Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto);
删除
Task DeleteAsync(Guid id);
其他方法
Task<int> VoteAsync(Guid id, VoteType type);
应用服务的实现
- 要与Web层完全隔离
- 在Application层实现应用服务接口
- 使用命名约定 如为IProductAppService接口创建ProductAppService类
- 应用服务继承自ApplicationService基类
- 将所有的公开方法定义为virtual,以便开发人员继承和覆盖它
- 不要定义private 方法,应该定义为protected virtual ,这样开发人员可以继承和覆盖它们
仓储
- 要使用专门设计的仓储 如IProductRepository
- 不要使用泛型仓储 如IRepository
查询数据
- 不要在Application的方法中使用Linq或Sql查询来自数据库的数据,那是仓储干的活
额外的属性
- 使用 MapExtraPropertiesTo 扩展方法 (参阅) 或配置对象映射 (MapExtraProperties) 以允许应用开发人员能够扩展对象和服务
操作、删除实体
- 要从数据库中获取所有相关实体以对他们执行操作
- 更新实体后调用存储的Update 或UpdateAsync方法,因为并非所有数据库的API支持更改跟踪和自动更新
使用其他ApplicationService
- 不要使用相同 模块的其他应用服务, 使用领域层执行所需服务,提取新类并在应用程序服务之间共享, 在必要时代码重用. 但要小心不要结合两个用例. 它们在开始时可能看起来相似, 但可能会随时间演变为不同的方向. 请谨慎使用代码共享.
- 可以在以下情况下使用其他应服务
- 是另一个模块或微服务的一部分
- 当前模块仅引用已使用模块的application contracts