ABP的项目结构

269 阅读6分钟

单元测试部分

DDD分层部分

  • Application是应用层中必需的,它实现了Application.Contracts项目中定义的接口
  • Application.Contracts包含接口的定义及接口依赖的DTO,此项目可以被展现层或其它客户端应用程序引用
  • 展现层:Web或者HttpApi.Host
  • HttpApi包含了HTTP API的定义.它通常包含MVC ControllerModel(如果有).因此,你可以在此项目中提供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**" />