- 关于本教程
-
- 下载源代码
- 介绍
- 作者实体
- AuthorManager:领域服务
- IAuthorRepository
- 结论
- 下一部分
本教程基于版本3.1
在本教程系列中,您将构建一个名为Acme.BookStore的基于ABP的Web应用程序。该应用程序用于管理书籍及其作者的列表。它是使用以下技术开发的:
- 实体框架核心作为ORM提供者。
- MVC/Razor页面作为UI框架。
本教程分为以下部分:
第1部分:创建服务器端
第2部分:书籍列表页面
第3部分:创建、更新和删除书籍
第4部分:集成测试
第5部分:授权
第6部分:作者:领域层(此部分)
第7部分:作者:数据库集成
第8部分:作者:应用程序层
第9部分:作者:用户界面
第10部分:书与作者的关系
下载源代码MVC (Razor Pages) UI with EF Core
介绍在前面的部分中,我们使用了ABP基础结构轻松构建了一些服务。
-
使用CrudAppService基类,而不是为标准的创建,读取,更新和删除操作手动开发应用程序服务。
-
使用通用存储库来完全自动化数据库层。
对于“作者”部分;
-
我们将手动执行某些操作,以显示在需要时如何执行此操作。
-
我们将实现一些域驱动设计(DDD)最佳实践。
开发将逐层完成,以一次集中在一个单独的层上。在真实的项目中,您将按照前面各部分的功能(垂直)来开发应用程序功能。这样,您将体验两种方法。
作者实体在Acme.BookStore.Domain项目中创建一个Authors文件夹(名称空间),并在其中添加一个Author类:
using System; using JetBrains.Annotations; using Volo.Abp; using Volo.Abp.Domain.Entities.Auditing; namespace Acme.BookStore.Authors { 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) { Name = Check.NotNullOrWhiteSpace( name, nameof(name), maxLength: AuthorConsts.MaxNameLength ); } } }
-
继承自FullAuditedAggregateRoot,可以对实体进行软删除(这意味着在删除它时,它不会在数据库中删除,而只是标记为已删除),并具有所有的审计属性。
-
对于Name属性的private set限制从此类之外设置此属性。有两种设置名称的方法(在两种情况下,我们都会验证名称):
- 在构造函数中,同时创建一个新作者。
- 稍后使用ChangeName方法来更新名称。
-
constructor和ChangeName方法是internal迫使仅在领域层使用这些方法,使用AuthorManager将在后面说明。
-
Check类是ABP Framework实用程序类,可在检查方法参数时提供帮助(在无效的情况下它会抛出ArgumentException异常)。
AuthorConsts是一个简单的类,位于Acme.BookStore.Domain.Shared项目的Authors名称空间(文件夹)下:
namespace Acme.BookStore.Authors { public static class AuthorConsts { public const int MaxNameLength = 64; } }
在Acme.BookStore.Domain.Shared项目内部创建了此类,因为稍后我们将在数据传输对象(DTO)上重用该类。
AuthorManager:领域服务Author构造函数和ChangeName方法是internal的,因此它们只能在领域层中使用。在Acme.BookStore.Domain项目的Authors文件夹(名称空间)中创建一个AuthorManager类:
using System; using System.Threading.Tasks; using JetBrains.Annotations; 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( [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); } } }
- AuthorManager强制创建作者并以受控方式更改作者的姓名。应用程序层(稍后将介绍)将使用这些方法。
DDD技巧:除非确实需要领域服务方法并执行一些核心业务规则,否则请不要引入领域服务方法。对于这种情况,我们需要此服务能够强制唯一名称约束。
两种方法都会检查是否存在具有给定名称的作者,并引发在Acme.BookStore.Domain项目(在Authors文件夹中)中定义的特殊业务异常AuthorAlreadyExistsException,如下所示:
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并进行如下更改:
namespace Acme.BookStore { public static class BookStoreDomainErrorCodes { public const string AuthorAlreadyExists = "BookStore:00001"; } }
这是一个唯一的字符串,表示您的应用程序引发的错误代码,并且可以由客户端应用程序处理。对于用户,您可能想对其进行本地化。打开Acme.BookStore.Domain.Shared项目内部的Localization/BookStore/en.json,并添加以下条目:
"BookStore:00001": "There is already an author with the same name: {name}"
然后打开BookStoreDomainSharedModule并在ConfigureServices方法内添加以下代码块:
Configure<AbpExceptionLocalizationOptions>(options => { options.MapCodeNamespace("BookStore", typeof(BookStoreResource)); });
每当您抛出AuthorAlreadyExistsException时,最终用户将在UI上看到一条不错的错误消息。
IAuthorRepositoryAuthorManager注入IAuthorRepository,因此我们需要对其进行定义。在Acme.BookStore.Domain项目的Authors文件夹(名称空间)中创建此新接口:
using System; using System.Collections.Generic; using System.Threading.Tasks; 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接口,因此IAuthorRepository也可以使用所有标准存储库方法。
-
FindByNameAsync在AuthorManager中用于按名称查询作者。
-
GetListAsync将在应用程序层中使用,以获取列出的,经过排序和筛选的作者列表,以显示在UI上。
我们将在下一部分中实现此存储库。
这两种方法似乎都没有必要,因为标准存储库已经具有IQueryable并且可以直接使用它们,而不是定义这样的自定义方法。你是对的,就像在一个真实的应用程序中一样。但是,对于本学习教程,解释在真正需要时如何创建自定义存储库方法是很有用的。
结论
这部分涵盖了书店应用程序作者功能的领域层。下图突出显示了在此部分中创建/更新的主要文件:
请参阅本教程的下一部分。
