Abp vNext - 应用开发系列之领域层

在系列教程中,我们会构建一个基于ABP的Web应用程序,用于管理书籍及其作者列表

使用到的技术:

  • Entity Framework Core
  • Angular

本教程分为以下部分:

前言

在前面的部分中,我们使用了ABP基础设施来快速创建了一些服务:

  • 使用 CrudAppService 基类,而不是手动开发用于标准创建、读取、更新和删除操作的应用程序服务。
  • 使用通用存储库完全自动化数据库层。

那么,对于后面的Author部分

  • 手动创建领域服务和应用服务接口,以了解如何手动实现。
  • 实现领域驱动设计(DDD)最佳实践。

开发将逐层完成,以一次性专注于单个层。在实际项目中,可以像前面部分一样按功能(垂直)开发应用程序功能。

Entity

Acme.BookStore.Domain 项目中创建一个 Authors 文件夹(命名空间),并新建一个 Author 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;

namespace Acme.BookStore.Authors;

/// <summary>
/// 作者
/// </summary>
public class Author : FullAuditedAggregateRoot<Guid>
{
/// <summary>
/// Name of the author.
/// </summary>
public string Name { get; private set; }

/// <summary>
/// Birth date of the author.
/// </summary>
public DateTime BirthDate { get; set; }

/// <summary>
/// Short biography of the author.
/// </summary>
public string ShortBio { get; set; }

private Author ()
{
}

internal Author(
Guid id,
string name,
DateTime birthDate,
string? shortBio = null
) : base(id)
{
SetName(name);
birthDate = BirthDate;
ShortBio = shortBio;
}

internal Author ChangeName(string name)
{
SetName(name);
return this;
}

private void SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(
name,
nameof(name),
maxLength: AuthorConsts.MaxNameLength
);
}
}
  • 继承自 FullAuditedAggregateRoot<Guid> ,使用soft delete,并记录所有审计信息
  • Name - private set,限制从类中设置此属性,有两种方法可以设置名称:
    • SetName
    • ChangeName

两种方法都会校验名称

  • ChangeName方法设置为internal, 强制仅在领域层使用方法
  • Check类为ABP帮助类,可以检查方法参数

.Domain.Shared项目中创建AuthorConsts类:

1
2
3
4
5
6
namespace Acme.BookStore.Authors;

public class AuthorConsts
{
public const int MaxNameLength = 64;
}

Repository

Acme.BookStore.Domain 项目的 Authors 文件夹(命名空间)中创建IAuthorRepository接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore.Authors;

public interface IAuthorRepository : IRepository<Author, Guid>
{
Task<Author> FindByNameAsync(string name);

Task<List<Author>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null
);
}
  • IAuthorRepository 扩展了标准 IRepository<Author, Guid> 接口,因此所有标准存储库方法也可用于 IAuthorRepository
  • FindByNameAsync 用于 AuthorManager 按姓名查询作者。
  • GetListAsync 将在应用程序层中用于获取要在 UI 上显示的列出、排序和筛选的作者列表。

Domain

Acme.BookStore.Domain 项目的 Authors 文件夹(命名空间)中创建一个 AuthorManager 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System.Threading.Tasks;
using System;
using Volo.Abp;
using Volo.Abp.Domain.Services;

namespace Acme.BookStore.Authors;

public class AuthorManager : DomainService
{
private readonly IAuthorRepository _authorRepository;

public AuthorManager(IAuthorRepository authorRepository)
{
_authorRepository = authorRepository;
}

public async Task<Author> CreateAsync(
string name,
DateTime birthDate,
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(
Author author,
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);
}
}

DDD 提示:除非确实需要领域服务方法并执行一些核心业务规则,否则不要引入领域服务方法

这两种方法都会检查是否已经存在具有给定名称的作者,并抛出在 Acme.BookStore.Domain 项目( Authors 文件夹中)中定义的特殊业务异常, AuthorAlreadyExistsException 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
using Volo.Abp;

namespace Acme.BookStore.Authors;

public class AuthorAlreadyExistsException : BusinessException
{
public AuthorAlreadyExistsException(string name)
: base(BookStoreDomainErrorCodes.AuthorAlreadyExists)
{
WithData("name", name);
}
}

BusinessException 是一种特殊的异常类型。在需要时抛出与领域层相关的异常。它由ABP框架自动处理,方便本地化。WithData(...) 方法用于向异常对象提供其他数据,将用于本地化消息或用于某些其他操作。

Acme.BookStore.Domain.Shared 项目中打开并 BookStoreDomainErrorCodes 更改,如下所示:

1
2
3
4
5
6
7
namespace Acme.BookStore;

public static class BookStoreDomainErrorCodes
{
/* You can add your business exception error codes here, as constants */
public const string AuthorAlreadyExists = "BookStore:00001";
}

打开 Acme.BookStore.Domain.Shared 项目 Localization/BookStore/en.json 并添加以下配置:

1
"BookStore:00001": "There is already an author with the same name: {name}"

抛出 AuthorAlreadyExistsException 时,用户会在 UI 上看到此错误消息。

目录

这部分涵盖了书店应用程序的作者功能的领域层。下图展示了此部分中创建/更新的主要文件:

folder

下一节

教程下一节