Abp vnext Web应用程序开发教程 6 —— 作者:领域层

关于本教程

本教程基于版本3.1

在本教程系列中,您将构建一个名为Acme.BookStore的基于ABPWeb应用程序。该应用程序用于管理书籍及其作者的列表。它是使用以下技术开发的:

  • 实体框架核心作为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<Guid>,可以对实体进行软删除(这意味着在删除它时,它不会在数据库中删除,而只是标记为已删除),并具有所有的审计属性。

  • 对于Name属性的private set限制从此类之外设置此属性。有两种设置名称的方法(在两种情况下,我们都会验证名称):

    • 在构造函数中,同时创建一个新作者。
    • 稍后使用ChangeName方法来更新名称。
  • constructorChangeName方法是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上看到一条不错的错误消息。

IAuthorRepository

AuthorManager注入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<Author, Guid>接口,因此IAuthorRepository也可以使用所有标准存储库方法。

  • FindByNameAsyncAuthorManager中用于按名称查询作者。

  • GetListAsync将在应用程序层中使用,以获取列出的,经过排序和筛选的作者列表,以显示在UI上。

我们将在下一部分中实现此存储库。

这两种方法似乎都没有必要,因为标准存储库已经具有IQueryable并且可以直接使用它们,而不是定义这样的自定义方法。你是对的,就像在一个真实的应用程序中一样。但是,对于本学习教程,解释在真正需要时如何创建自定义存储库方法是很有用的。

结论

这部分涵盖了书店应用程序作者功能的领域层。下图突出显示了在此部分中创建/更新的主要文件:
在这里插入图片描述

下一部分

请参阅本教程的下一部分

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值