ABP Framework教程:图书管理系统开发(6) - 作者领域层设计

ABP Framework教程:图书管理系统开发(6) - 作者领域层设计

abp Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation. abp 项目地址: https://gitcode.com/gh_mirrors/abp1/abp

前言

在之前的教程中,我们已经学习了如何使用ABP框架的基础设施快速构建服务:

  • 使用CrudAppService基类简化标准CRUD应用服务的开发
  • 使用泛型仓储自动实现数据库层操作

在"作者管理"功能部分,我们将:

  • 手动实现部分功能,展示在需要时的自定义开发方式
  • 实践领域驱动设计(DDD)的最佳实践

本教程采用分层开发的方式,每次专注于一个特定层次。在实际项目中,我们通常会采用垂直开发方式(按功能模块开发)。通过这种方式,您可以体验两种不同的开发模式。

作者实体设计

Acme.BookStore.Domain项目中创建Authors文件夹,并添加Author类:

public class Author : FullAuditedAggregateRoot<Guid>
{
    public string Name { get; private set; }
    public DateTime BirthDate { get; set; }
    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
        );
    }
}

设计要点解析

  1. 继承关系

    • 继承自FullAuditedAggregateRoot<Guid>,使实体具备软删除和完整审计功能
    • 软删除意味着删除操作不会真正从数据库移除记录,而是标记为已删除
  2. 属性封装

    • Name属性使用private set限制外部直接修改
    • 提供两种修改名称的方式:
      • 构造函数中初始化
      • 通过ChangeName方法修改
  3. 访问控制

    • 构造函数和ChangeName方法标记为internal,限制只能在领域层使用
    • 使用AuthorManager领域服务来管理这些操作
  4. 参数校验

    • 使用ABP提供的Check工具类进行参数验证
    • 验证失败时会抛出ArgumentException

常量定义

Acme.BookStore.Domain.Shared项目中定义作者相关常量:

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

将常量定义在共享项目中,便于在DTO等其他层中复用。

领域服务:AuthorManager

由于Author的构造和名称修改方法都是internal的,我们需要创建领域服务来管理这些操作:

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);
    }
}

关键设计考虑

  1. 唯一性约束

    • 在创建和修改作者名称时检查名称是否已存在
    • 确保系统中作者名称唯一
  2. 业务异常处理

    • 当名称冲突时抛出AuthorAlreadyExistsException
    • 使用ABP的业务异常机制,便于后续处理和本地化
  3. DDD实践建议

    • 仅在真正需要执行业务规则时才引入领域服务
    • 保持领域服务的精简和专注

业务异常定义

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

BookStoreDomainErrorCodes中定义错误代码:

public static class BookStoreDomainErrorCodes
{
    public const string AuthorAlreadyExists = "BookStore:00001";
}

添加本地化资源:

"BookStore:00001": "已存在同名作者: {name}"

作者仓储接口

定义IAuthorRepository接口:

public interface IAuthorRepository : IRepository<Author, Guid>
{
    Task<Author> FindByNameAsync(string name);
    
    Task<List<Author>> GetListAsync(
        int skipCount,
        int maxResultCount,
        string sorting,
        string filter = null
    );
}

接口设计说明

  1. 基础继承

    • 继承自IRepository<Author, Guid>,获得标准仓储方法
  2. 自定义方法

    • FindByNameAsync:按名称查询作者
    • GetListAsync:获取分页、排序和过滤的作者列表
  3. 实际应用建议

    • 在真实项目中,可以优先使用泛型仓储提供的查询能力
    • 本教程展示自定义仓储方法的实现方式,便于学习

总结

本教程完成了作者管理功能的领域层设计,主要实现了:

  1. 作者实体:封装业务规则,控制修改方式
  2. 领域服务:强制执行业务规则,维护名称唯一性
  3. 仓储接口:定义数据访问契约

通过分层开发的方式,我们专注于领域模型的设计和实现,为后续的应用层和数据库集成打下坚实基础。下一部分我们将实现仓储的具体逻辑和数据库集成。

abp Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation. abp 项目地址: https://gitcode.com/gh_mirrors/abp1/abp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姬如雅Brina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值