• 推荐 为每个 聚合根 创建一个应用服务.
    • 推荐application.contracts层中为每一个应用服务定义一个.
    • 推荐 继承 IApplicationService 接口 .
    • 推荐 接口名称使用AppService 后缀 (如: IProductAppService).
    • 推荐 为服务创建输入输出DTO(数据传输对象).
    • 不推荐 服务中含有返回实体的方法.
    • 推荐 根据定义DTO.

    输出

    • 避免 为相同或相关实体定义过多的输出DTO. 为实体定义 基础详细 DTO.
    基础DTO
    • 直接包含实体中所有的原始属性.
      • 例外: 出于安全原因,可以排除某些属性(像 User.Password).
    • 包含实体中所有子集合, 每个集合项都是一个简单的关系DTO.

    示例:

    详细DTO
    • 直接包含实体中所有的 原始属性.
      • 例外-1: 出于安全原因,可以排除某些属性(像 User.Password).
      • 例外-2: 推荐 排除引用属性(如上例中的 MilestoneId). 为其添加引用属性的详细信息.
    • 为每个引用属性添加其基本DTO .
    • 包含实体的所有子集合, 集合中的每项都是相关实体的基本DTO.

    示例:

    1. [Serializable]
    2. public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
    3. {
    4. public string Title { get; set; }
    5. public string Text { get; set; }
    6. public MilestoneDto Milestone { get; set; }
    7. }
    8. [Serializable]
    9. public class MilestoneDto : ExtensibleEntityDto<Guid>
    10. {
    11. public string Name { get; set; }
    12. public bool IsClosed { get; set; }
    13. [Serializable]
    14. public class LabelDto : ExtensibleEntityDto<Guid>
    15. {
    16. public string Name { get; set; }
    17. public string Color { get; set; }
    18. }

    输入

    • 不推荐 在输入DTO中定义未在服务类中使用的属性.
    • 不推荐 在应用服务方法之间共享输入DTO.
    • 不推荐 继承另一个输入DTO类.
      • 可以 继承自抽象基础DTO类, 并以这种方式在不同的DTO之间共享一些属性. 但是在这种情况下需要非常小心, 因为更新基础DTO会影响所有相关的DTO和服务方法. 所以避免这样做是一种好习惯.

    方法

    • 推荐 为异步方法使用 Async 后缀.
    • 不推荐 在方法名中重复实体的名称.
      • 例如: 在 IProductAppService 中定义GetAsync(...) 而不是 GetProductAsync(...) .
    获取单一实体
    • 推荐 使用 GetAsync 做为方法名.
    • 推荐 使用id做为方法参数.
    • 返回 详细DTO. 示例:
    1. Task<QuestionWithDetailsDto> GetAsync(Guid id);
    获取实体集合
    • 推荐 使用 GetListAsync 做为方法名.
    • 推荐 如果需要获取单个DTO可以使用参数进行 过滤, 排序分页.
      • 推荐 尽可能让过滤参数可选.
      • 推荐 将排序与分页属性设置为可选, 并且提供默认值.
      • 推荐 限制最大页数大小 (基于性能考虑).
    • 推荐 返回 详细DTO集合. 示例:
    创建一个新实体
    • 推荐 使用 CreateAsync 做为方法名.
    • 推荐 使用专门的输入DTO来创建实体.
    • 推荐 DTO类从 ExtensibleObject 类继承(或任何实现 ExtensibleObject的类) 以允许在需要时传递额外的属性.
    • 推荐 使用 data annotations 进行输入验证.
      • 尽可能在领域之间共享常量(通过domain shared package定义的常量).
    • 推荐 只需要创建实体的最少信息, 但是提供了其他可选属性.
    1. Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);

    输入DTO:

    1. [Serializable]
    2. {
    3. [StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
    4. public string Title { get; set; }
    5. [StringLength(QuestionConsts.MaxTextLength)]
    6. public string Text { get; set; } //Optional
    7. public Guid? CategoryId { get; set; } //Optional
    8. }
    更新已存在的实体
    • 推荐 使用 UpdateAsync 做为方法名.
    • 推荐 使用专门的输入DTO来更新实体.
    • 推荐 DTO类从 ExtensibleObject 类继承(或任何实现 ExtensibleObject的类) 以允许在需要时传递额外的属性.
    • 推荐 获取实体的id做为分离的原始参数. 不要包含更新DTO.
    • 推荐 使用 data annotations 进行输入验证.
      • 尽可能在领域之间共享常量(通过domain shared package定义的常量).
    • 推荐 返回更新实体的详细DTO.
    删除已存在的实体
    • 推荐 使用 DeleteAsync 做为方法名.
    • 推荐 使用原始参数 id. 示例:
    1. Task DeleteAsync(Guid id);
    其他方法
    • 可以 定义其他方法以对实体执行操作. 示例:
    1. Task<int> VoteAsync(Guid id, VoteType type);

    此方法为试题投票并返回试题的当前分数.

    应用服务实现

    • 推荐 开发完全独立于web层的应用层.
    • 推荐应用层实现应用服务接口.
      • 推荐 使用命名约定. 如: 为 IProductAppService 接口创建 ProductAppService 类.
      • 推荐 继承自 ApplicationService 基类.
    • 推荐 将所有的公开方法定义为 virtual, 以便开发人员继承和覆盖它们.
    • 不推荐 定义 private 方法. 应该定义为 protected virtual, 这样开发人员可以继承和覆盖它们.

    使用仓储

    • 推荐 使用专门设计的仓储 (如 IProductRepository).
    • 不推荐 使用泛型仓储 (如 IRepository<Product>).z`

    查询数据

    • 不推荐 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.

    额外的属性

    • 推荐 使用 MapExtraPropertiesTo 扩展方法 (参阅) 或配置对象映射 () 以允许应用开发人员能够扩展对象和服务.

    操作/删除 实体

    • 推荐 总是从数据库中获取所有的相关实体以对他们执行操作.
    • 推荐 更新实体后调用存储的Update/UpdateAsync方法.因为并非所有数据库API都支持更改跟踪和自动更新.

    使用其他应用服务

    • 不推荐 使用相同 模块/应用程序 的其他应用服务. 相反;
      • 使用领域层执行所需的任务.
      • 提取新类并在应用程序服务之间共享, 在必要时代码重用. 但要小心不要结合两个用例. 它们在开始时可能看起来相似, 但可能会随时间演变为不同的方向. 请谨慎使用代码共享.
    • 可以 在以下情况下使用其他应用服务;
      • 它们是另一个模块/微服务的一部分.
      • 当前模块仅引用已使用模块的application contracts.