单元测试部分
DDD分层部分
- Application是应用层中必需的,它实现了Application.Contracts项目中定义的接口
- Application.Contracts包含接口的定义及接口依赖的DTO,此项目可以被展现层或其它客户端应用程序引用
- 展现层:Web或者HttpApi.Host
- HttpApi包含了HTTP API的定义.它通常包含MVC Controller 和 Model(如果有).因此,你可以在此项目中提供HTTP API
- HttpApi.Client当C#客户端应用程序需要调用HttpApi的API时,这个项目非常有用.客户端程序仅需引用此项目就可以通过依赖注入方式,远程调用应用服务.它是通过ABP框架的动态C#客户端API代理系统来实现的.
在解决方案文件夹test下, 有一个名为HttpApi.Client.ConsoleTestApp的控制台程序. 它演示了如何使用HttpApi.Client项目来远程调用应用程序公开的API. 因为只是演示, 你可以删除此项目, 再或者你认为HttpApi不需要, 同样可以删除.
- 远程服务层:HttpApi+Client,可以通过一些内部代理直接调用。HttpApi是api接口,项目也是前后端分离的项目
- 基础设施层:EFCore。基础设施,封装了repository、uint of work
- 其他:Migrator,迁移工具,数据库初始化、种子数据
1、领域层
实体
普通实体
public class Book : AuditedAggregateRoot<Guid>
{
public Guid AuthorId { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
实体+表现业务规则
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
private Author()
{
//函数私有,不允许随便构造
/* This constructor is for deserialization / ORM purpose */
}
//工厂构造
internal Author(
Guid id,
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
: base(id)
{
SetName(name);
BirthDate = birthDate;
ShortBio = shortBio;
}
internal Author ChangeName([NotNull] string name)
{
SetName(name);
return this;
}
private void SetName([NotNull] string name)
{
//NotNullOrWhiteSpace表达规则
Name = Check.NotNullOrWhiteSpace(
name,
nameof(name),
maxLength: AuthorConsts.MaxNameLength
);
}
}
领域服务
对领域的延伸,不仅仅是基础增删改查,还要做一定的限制
//DomainService是ABP里的类
public class AuthorManager : DomainService
{
private readonly IAuthorRepository _authorRepository;
public AuthorManager(IAuthorRepository authorRepository)
{
_authorRepository = authorRepository;
}
public async Task<Author> CreateAsync(
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
var existingAuthor = await _authorRepository.FindByNameAsync(name);
if (existingAuthor != null)
{
throw new AuthorAlreadyExistsException(name);
}
return new Author(
GuidGenerator.Create(),
name,
birthDate,
shortBio
);
}
public async Task ChangeNameAsync(
[NotNull] Author author,
[NotNull] string newName)
{
Check.NotNull(author, nameof(author));
Check.NotNullOrWhiteSpace(newName, nameof(newName));
var existingAuthor = await _authorRepository.FindByNameAsync(newName);
if (existingAuthor != null && existingAuthor.Id != author.Id)
{
throw new AuthorAlreadyExistsException(newName);
}
author.ChangeName(newName);
}
}
Shared
放大家共享的东西
常量
public static class AuthorConsts
{
public const int MaxNameLength = 64;
}
枚举
public enum BookType
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
2、应用层
领域层
- Domain是领域层中必需的,它包含之前介绍的构建组成(实体,值对象,领域服务,规约,仓储接口等)。它依赖 .Domain.Shared 项目,因为项目中会用到它的一些常量,枚举和定义其他对象.
解决方案的领域层. 它主要包含 实体, 集合根, 领域服务, 值类型, 仓储接口 和解决方案的其他领域对象.
例如 Book 实体和 IBookRepository 接口都适合放在这个项目中.
xxx.Domain
<PackageReference Include="Volo.Abp.Identity.Domain" Version="5.3.3" />
<PackageReference Include="Volo.Abp.IdentityServer.Domain" Version="5.3.3" />
xxx.Domain.Shared
- Domain.Shared是领域层中很薄的项目,它只包含领域层与其它层共享的数据类型的定义.例如,枚举,常量,错误码等。
- 项目包含常量,枚举和其他对象,这些对象实际上是领域层的一部分,但是解决方案中所有的层/项目中都会使用到。例如 BookType 枚举和 BookConsts 类 (可能是 Book 实体用到的常数字段,像MaxNameLength)都适合放在这个项目中.
- 该项目不依赖解决方案中的其他项目. 其他项目直接或间接依赖该项目
<PackageReference Include="Volo.Abp.Identity.Domain.Shared" Version="5.3.3" />
<PackageReference Include="Volo.Abp.IdentityServer.Domain.Shared" Version="5.3.3" />
实体。BasicAggregateRoot 是创建根实体的最简单的基础类. Guid 是这里实体的主键 (Id).
using System;
using Volo.Abp.Domain.Entities;
namespace TodoApp
{
public class TodoItem : BasicAggregateRoot<Guid>
{
public string Text { get; set; }
}
}
基础设施层
Arim.Poi.IdentityService.EntityFrameworkCore。
这是集成EF Core的项目. 它定义了 DbContext 并实现 .Domain 项目中定义的仓储接口.
- 它依赖 .Domain 项目,因为它需要引用实体和仓储接口.
<PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" Version="5.3.3" />
<PackageReference Include="Volo.Abp.Identity.EntityFrameworkCore" Version="5.3.3" />
<PackageReference Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="5.3.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets>
</PackageReference>
<Folder Include="Migrations" />
打开在 TodoApp.EntityFrameworkCore 项目中 EntityFrameworkCore 文件夹中的 TodoAppDbContext 类,
public DbSet<TodoItem> TodoItems { get; set; }
//为实体类添加映射代码
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
...
/* Configure your own tables/entities inside here */
builder.Entity<TodoItem>(b =>
{
b.ToTable("TodoItems");
});
}
应用层
Arim.Poi.IdentityService.Application
项目包含 .Application.Contracts 项目的 应用服务 接口实现.
例如 BookAppService 类适合放在这个项目中.
- 它依赖 .Application.Contracts 项目, 因为它需要实现接口与使用DTO.
- 它依赖 .Domain 项目,因为它需要使用领域对象(实体,仓储接口等)执行应用程序逻辑.
<PackageReference Include="Volo.Abp.Identity.Application" Version="5.3.3" />
Arim.Poi.IdentityService.Application.Contracts
<PackageReference Include="Volo.Abp.Identity.Application.Contracts" Version="5.3.3" />
Contract层:为应用程序服务定义接口、定义DTO。
项目主要包含 应用服务 interfaces 和应用层的 数据传输对象 (DTO). 它用于分离应用层的接口和实现. 这种方式可以将接口项目做为约定包共享给客户端.
例如 IBookAppService 接口和 BookCreationDto 类都适合放在这个项目中.
- 它依赖 .Domain.Shared 因为它可能会在应用接口和DTO中使用常量,枚举和其他的共享对象.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace TodoApp
{
public interface ITodoAppService : IApplicationService
{
Task<List<TodoItemDto>> GetListAsync();
Task<TodoItemDto> CreateAsync(string text);
Task DeleteAsync(Guid id);
}
}
传输对象,应用程序服务 通常获取并返回 DTO(数据传输对象) 而不是实体. 因此, 我们应该在这里定义DTO类
using System;
namespace TodoApp
{
public class TodoItemDto
{
public Guid Id { get; set; }
public string Text { get; set; }
}
}
Application层,实现类。
该类继承自ABP框架的ApplicationService类, 并实现了之前定义的 ITodoAppService接口。ABP为实体提供默认的泛型 仓储. 我们可以使用它们来执行基本的数据库操作. 此类中 注入 的 IRepository<TodoItem, Guid>, 它就是 TodoItem 实体的默认存储库.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace TodoApp
{
public class TodoAppService : ApplicationService, ITodoAppService
{
private readonly IRepository<TodoItem, Guid> _todoItemRepository;
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository)
{
_todoItemRepository = todoItemRepository;
}
// TODO: Implement the methods here...
//获取代办事项列表
public async Task<List<TodoItemDto>> GetListAsync()
{
var items = await _todoItemRepository.GetListAsync();
return items
.Select(item => new TodoItemDto
{
Id = item.Id,
Text = item.Text
}).ToList();
}
//创建一个新的待办事项
public async Task<TodoItemDto> CreateAsync(string text)
{
var todoItem = await _todoItemRepository.InsertAsync(
new TodoItem {Text = text}
);
return new TodoItemDto
{
Id = todoItem.Id,
Text = todoItem.Text
};
}
//删除代办事项
public async Task DeleteAsync(Guid id)
{
await _todoItemRepository.DeleteAsync(id);
}
}
}
远程服务层
Arim.Poi.IdentityService.HttpApi
用于定义API控制器.
大多数情况下,你不需要手动定义API控制器,因为ABP的动态API功能会根据你的应用层自动创建API控制器. 但是,如果你需要编写API控制器,那么它是最合适的地方.
- 它依赖 .Application.Contracts 项目,因为它需要注入应用服务接口.
<PackageReference Include="Volo.Abp.Identity.HttpApi" Version="5.3.3" />
Arim.Poi.IdentityService.HttpApi.Client
定义C#客户端代理使用解决方案的HTTP API项目. 可以将上编辑共享给第三方客户端,使其轻松的在DotNet应用程序中使用你的HTTP API(其他类型的应用程序可以手动或使用其平台的工具来使用你的API).
<PackageReference Include="Volo.Abp.Account.HttpApi.Client" Version="5.3.3" />
<PackageReference Include="Volo.Abp.Identity.HttpApi.Client" Version="5.3.3" />
<EmbeddedResource Include="***generate-proxy.json" />
<Content Remove="***generate-proxy.json" />
UI层
Arim.Poi.IdentityService.HttpApi.Host
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<Compile Remove="Logs**" />
<Content Remove="Logs**" />
<EmbeddedResource Remove="Logs**" />
<None Remove="Logs**" />