【ABP】项目示例(2)——聚合根和实体
聚合根和实体
在上一章节中,已经完成了项目搭建的前置准备,在这一章节中,实现领域层的聚合根和实体
创建名称为General.Backend.Domain的标准类库,分别新建名称为Entities、Services、IRepositories和Specifications的文件夹,用于存放实体和聚合根、领域服务、仓储接口和规约。
本项目使用ABP相关的Nuget包的版本为8.3.0,为保持版本一致,后续其他ABP相关的Nuget包都使用该版本。
在程序包管理控制台选中General.Backend.Domain,执行以下命令安装ABP领域相关的Nuget包。
Install-Package Volo.Abp.Ddd.Domain -v 8.3.0
新建名称为GeneralDomainModule的领域模块类
public class GeneralDomainModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
}
}
创建名称为General.Backend.Domain.Shared的标准类库,分别新建名称为Enums和Consts的文件夹,用于存放枚举和常量。
在程序包管理控制台选中General.Backend.Domain.Shared,执行以下命令安装ABP领域共享相关的Nuget包。
Install-Package Volo.Abp.Ddd.Domain.Shared -v 8.3.0
新建名称为GeneralDomainSharedModule的领域共享模块类
public class GeneralDomainSharedModule : AbpModule
{
}
General.Backend.Domain添加项目引用General.Backend.Domain.Shared
用户聚合
在Enums文件夹下新建名称为FrozenStatus的用户冻结状态枚举
/// <summary>
/// 冻结状态
/// </summary>
public enum FrozenStatus
{
/// <summary>
/// 未冻结
/// </summary>
UnFrozen = 1,
/// <summary>
/// 已冻结
/// </summary>
Frozen = 2
}
在Consts文件夹下新建名称为UserConsts的用户常量类,定义用户领域使用到的常量,用于数据库表配置和常规校验
public static class UserConsts
{
public const string UserTableName = "user";
public const string UserTableComment = "用户表";
public const string UserRoleTableName = "user_role";
public const string UserRoleTableComment = "用户角色表";
public const string AdminAccount = "admin";
public const string AdminName = "Admin";
public const int MaxLoginErrorCount = 5;
public const int MinAccountLength = 2;
public const int MaxAccountLength = 32;
public const int MinPasswordLength = 6;
public const int MaxPasswordLength = 32;
public const int MinNameLength = 1;
public const int MaxNameLength = 32;
public const int MaxContactLength = 64;
public const int MaxAddressLength = 64;
}
在Entities文件夹下新建名称为User的用户聚合根类和UserRole的用户角色实体类,业务逻辑为一个用户拥有一个或者多个角色,用户的角色需要通过用户聚合来管理
/// <summary>
/// 用户
/// </summary>
public class User : FullAuditedAggregateRoot<Guid>
{
/// <summary>
/// 账号
/// </summary>
public virtual string Account { get; private set; } = string.Empty;
/// <summary>
/// 密码
/// </summary>
public virtual string Password { get; private set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public virtual string Name { get; private set; } = string.Empty;
/// <summary>
/// 联系方式
/// </summary>
public virtual string? Contact { get; set; }
/// <summary>
/// 地址
/// </summary>
public virtual string? Address { get; set; }
/// <summary>
/// 登录错误次数
/// </summary>
public virtual byte LoginErrorCount { get; private set; }
/// <summary>
/// 是否冻结(1:未冻结,2:已冻结)
/// </summary>
public virtual FrozenStatus IsFrozen { get; private set; }
/// <summary>
/// 用户关联角色列表
/// </summary>
public virtual ICollection<UserRole> UserRoles { get; private set; } = [];
protected User()
{
}
public User(
Guid id,
[NotNull] string account,
[NotNull] string password,
[NotNull] string name,
[CanBeNull] string? contact = null,
[CanBeNull] string? address = null)
{
Id = id;
SetAccount(account);
SetPassword(password);
SetName(name);
IsFrozen = FrozenStatus.UnFrozen;
Contact = Check.Length(contact, nameof(contact), UserConsts.MaxContactLength);
Address = Check.Length(address, nameof(address), UserConsts.MaxAddressLength);
}
private void SetAccount([NotNull] string account)
{
Account = Check.Length(account, nameof(account), UserConsts.MaxAccountLength, UserConsts.MinAccountLength)!;
}
internal virtual void SetPassword([NotNull] string password)
{
Password = Check.Length(password, nameof(password), UserConsts.MaxPasswordLength, UserConsts.MinPasswordLength)!;
}
public virtual void SetName([NotNull] string name)
{
Name = Check.Length(name, nameof(name), UserConsts.MaxNameLength, UserConsts.MinNameLength)!;
}
internal virtual bool CheckPassword([NotNull] string password)
{
Check.NotNullOrEmpty(password, nameof(password));
if (Password != password)
{
TryFrozen();
return false;
}
UnFrozen();
return true;
}
public virtual void UnFrozen()
{
LoginErrorCount = 0;
IsFrozen = FrozenStatus.UnFrozen;
}
private bool TryFrozen()
{
var isSuccess = false;
if (Account != UserConsts.AdminAccount)
{
LoginErrorCount += 1;
if (LoginErrorCount >= UserConsts.MaxLoginErrorCount)
{
IsFrozen = FrozenStatus.Frozen;
isSuccess = true;
}
}
return isSuccess;
}
public virtual void SetRoles(ICollection<UserRole> userRoles)
{
UserRoles = userRoles;
}
}
/// <summary>
/// 用户角色
/// </summary>
public class UserRole : Entity<Guid>, IHasCreationTime
{
/// <summary>
/// 用户Id
/// </summary>
public virtual Guid UserId { get; private set; }
/// <summary>
/// 角色Id
/// </summary>
public virtual Guid RoleId { get; private set; }
/// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreationTime { get; private set; }
protected UserRole()
{
}
internal UserRole(
Guid id,
Guid userId,
Guid roleId)
{
Id = id;
UserId = userId;
RoleId = roleId;
}
}
角色聚合
在Entities文件夹下新建名称为Role的角色聚合根类和RoleMenus的角色菜单实体类,业务逻辑为一个角色拥有一个或者多个菜单,用户拥有的菜单需要通过角色聚合来管理
/// <summary>
/// 角色
/// </summary>
public class Role : FullAuditedAggregateRoot<Guid>
{
/// <summary>
/// 编码
/// </summary>
public virtual string Code { get; private set; } = string.Empty;
/// <summary>
/// 名称
/// </summary>
public virtual string Name { get; private set; } = string.Empty;
/// <summary>
/// 描述
/// </summary>
public virtual string? Remark { get; set; }
/// <summary>
/// 角色关联菜单列表
/// </summary>
public virtual ICollection<RoleMenu> RoleMenus { get; private set; } = [];
protected Role()
{
}
public Role(
Guid id,
[NotNull] string code,
[NotNull] string name,
[CanBeNull] string? remark = null)
{
Id = id;
Code = Check.Length(code, nameof(code), RoleConsts.MaxCodeLength, RoleConsts.MinCodeLength)!;
SetName(name);
Remark = Check.Length(remark, nameof(remark), RoleConsts.MaxRemarkLength);
}
internal virtual void SetName(
[NotNull] string name)
{
Name = Check.Length(name, nameof(name), UserConsts.MaxNameLength, UserConsts.MinNameLength)!;
}
public virtual void SetMenus(ICollection<RoleMenu> roleMenus)
{
RoleMenus = roleMenus;
}
}
/// <summary>
/// 角色菜单
/// </summary>
public class RoleMenu : Entity<Guid>, IHasCreationTime
{
/// <summary>
/// 角色Id
/// </summary>
public virtual Guid RoleId { get; private set; }
/// <summary>
/// 菜单编码
/// </summary>
public virtual string MenuCode { get; private set; } = string.Empty;
/// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreationTime { get; private set; }
protected RoleMenu()
{
}
internal RoleMenu(
Guid id,
Guid roleId,
[NotNull] string menuCode)
{
Id = id;
RoleId = roleId;
MenuCode = Check.Length(menuCode, nameof(menuCode), RoleConsts.MaxMenuCodeLength, RoleConsts.MinMenuCodeLength)!;
}
}
同样地在Consts文件夹下新建名称为RoleConsts的角色常量类,定义角色领域使用到的常量,用于数据库表配置和常规校验
public class RoleConsts
{
public const string RoleTableName = "role";
public const string RoleTableComment = "角色表";
public const string RoleMenuTableName = "role_menu";
public const string RoleMenuTableComment = "角色菜单表";
public const string AdminRoleCode = "Admin";
public const string AdminRoleName = "Admin";
public const int MaxCodeLength = 32;
public const int MinCodeLength = 1;
public const int MaxNameLength = 32;
public const int MinNameLength = 1;
public const int MaxMenuCodeLength = 64;
public const int MinMenuCodeLength = 1;
public const int MaxRemarkLength = 255;
}
菜单聚合
在Entities文件夹下新建名称为Menu的菜单聚合根类
/// <summary>
/// 菜单
/// </summary>
public class Menu : BasicAggregateRoot<Guid>, IHasCreationTime
{
/// <summary>
/// 编码
/// </summary>
public virtual string Code { get; private set; } = string.Empty;
/// <summary>
/// 父编码
/// </summary>
public virtual string ParentCode { get; private set; } = string.Empty;
/// <summary>
/// 名称
/// </summary>
public virtual string Name { get; private set; } = string.Empty;
/// <summary>
/// 类型
/// </summary>
public virtual string Type { get; private set; } = string.Empty;
/// <summary>
/// 层级
/// </summary>
public virtual int Level { get; private set; }
/// <summary>
/// 图标
/// </summary>
public virtual string? Icon { get; private set; }
/// <summary>
/// 路由地址
/// </summary>
public virtual string? UrlAddress { get; private set; }
/// <summary>
/// 组件地址
/// </summary>
public virtual string? ComponentAddress { get; private set; }
/// <summary>
/// 排序
/// </summary>
public virtual int Sort { get; private set; }
/// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreationTime { get; private set; }
/// <summary>
/// 子菜单
/// </summary>
public List<Menu> SubMenu { get; private set; } = [];
protected Menu()
{
}
public Menu(
[NotNull] string code,
[NotNull] string parentCode,
[NotNull] string name,
[NotNull] string type,
int level,
int sort,
[CanBeNull] string? icon = null,
[CanBeNull] string? urlAddress = null,
[CanBeNull] string? componentAddress = null)
{
Code = Check.Length(code, nameof(code), MenuConsts.MaxCodeLength, MenuConsts.MinCodeLength)!;
ParentCode = Check.Length(parentCode, nameof(parentCode), MenuConsts.MaxParentCodeLength, MenuConsts.MinParentCodeLength)!;
Name = Check.Length(name, nameof(name), MenuConsts.MaxNameLength, MenuConsts.MinNameLength)!;
Type = Check.Length(type, nameof(type), MenuConsts.MaxTypeLength, MenuConsts.MinTypeLength)!;
Level = level;
Sort = sort;
Icon = Check.Length(icon, nameof(icon), MenuConsts.MaxIconLength);
UrlAddress = Check.Length(urlAddress, nameof(urlAddress), MenuConsts.MaxUrlAddressLength);
ComponentAddress = Check.Length(componentAddress, nameof(componentAddress), MenuConsts.MaxComponentAddressLength);
}
private Menu(
[NotNull] string code)
{
Code = Check.Length(code, nameof(code), MenuConsts.MaxCodeLength, MenuConsts.MinCodeLength)!;
}
internal static Menu BuildRoot()
{
return new Menu(
MenuConsts.MenuRootCode);
}
internal static Menu BuildCatalog(
[NotNull] string code,
[NotNull] string name,
[NotNull] string icon,
[NotNull] string urlAddress,
int sort)
{
return new Menu(
code,
MenuConsts.MenuRootCode,
name,
MenuConsts.CatalogTypeName,
1,
sort,
icon,
urlAddress);
}
internal static Menu BuildMenu(
[NotNull] string code,
[NotNull] string parentCode,
[NotNull] string name,
[NotNull] string icon,
[NotNull] string urlAddress,
[NotNull] string componentAddress,
int sort)
{
return new Menu(
code,
parentCode,
name,
MenuConsts.MenuTypeName,
2,
sort,
icon,
urlAddress,
componentAddress);
}
internal static Menu BuildFunc(
[NotNull] string code,
[NotNull] string parentCode,
[NotNull] string name,
int sort)
{
return new Menu(
code,
parentCode,
name,
MenuConsts.FuncTypeName,
3,
sort);
}
public void SetSubMenu(
[NotNull] List<Menu> subMenus)
{
Check.NotNull(subMenus, nameof(subMenus));
foreach (var parentCode in subMenus.Select(menu => menu.ParentCode))
{
Check.Equals(Code, parentCode);
}
SubMenu = subMenus;
}
}
同样地在Consts文件夹下新建名称为MenuConsts的菜单常量类
public class MenuConsts
{
public const string MenuTableName = "menu";
public const string MenuTableComment = "菜单表";
public const string MenuRootCode = "root";
public const string CatalogTypeName = "C";
public const string MenuTypeName = "M";
public const string FuncTypeName = "F";
public const int MaxCodeLength = 64;
public const int MinCodeLength = 1;
public const int MaxParentCodeLength = 64;
public const int MinParentCodeLength = 1;
public const int MaxNameLength = 32;
public const int MinNameLength = 1;
public const int MaxTypeLength = 8;
public const int MinTypeLength = 1;
public const int MaxIconLength = 32;
public const int MaxUrlAddressLength = 64;
public const int MaxComponentAddressLength = 64;
}
上面已经对用户、角色和菜单领域创建了相应的聚合根,相关的业务逻辑以聚合根和实体中方法来实现
解决方案的目录结构现如下

在下一章节中,使用仓储作为领域模型和数据模型的桥梁,将领域模型持久化到数据库中的数据模型中
【ABP】项目示例(2)——聚合根和实体的更多相关文章
- 基于ABP实现DDD--聚合和聚合根实践
在下面的例子中涉及Repository.Issue.Label.User这4个聚合根,接下来以Issue聚合为例进行分析,其中Issue聚合是由Issue[聚合根].Comment[实体].Iss ...
- 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则
目录 前言 聚合 聚合和聚合根原则 包含业务原则 单个单元原则 事务边界原则 可序列化原则 聚合和聚合根最佳实践 只通过ID引用其他聚合 用于 EF Core 和 关系型数据库 保持聚合根足够小 聚合 ...
- 初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob存储
Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 聚合根 仓储 领域服务 BLOB存储 应用服务 单元测试 模块引用 最后 前言 在前两节中介绍了ABP模块开发的基本步 ...
- DDD:使用EntityFramework的话,如果只为聚合根设计仓储,其它实体如何处理?
背景 DDD中只有聚合根可以有仓储,仓储负责整个聚合持久化的相关生命周期,在不使用工作单元或POCO的情况下,我们可以让Order内部直接调用DAL操作OrderItem.我们也可以让Order跟踪所 ...
- DDD的实体、值对象、聚合根的基类和接口:设计与实现
1 前置阅读 在阅读本文章之前,你可以先阅读: 什么是DDD 2 实现值对象 值对象有两个主要特征:它们没有任何标识.它们是不可变的. 我们举个例子:小明是"浙江宁波"人,小红也是 ...
- 从壹开始微服务 [ DDD ] 之六 ║聚合 与 聚合根 (下)
前言 哈喽大家周二好,上次咱们说到了实体与值对象的简单知识,相信大家也是稍微有些了解,其实实体咱们平时用的很多了,基本可以和数据库表进行联系,只不过值对象可能不是很熟悉,值对象简单来说就是在DDD领域 ...
- DDD中聚合、聚合根的含义以及作用
聚合与聚合根的含义 聚合: 聚合往往是一些实体为了某项业务而聚类在一起形成的集合 , 举个例子, 社会是由一个个的个体组成的,象征着我们每一个人.随着社会的发展,慢慢出现了社团.机构.部门等组织,我们 ...
- DDD之4聚合和聚合根
聚合就是归类的意思,把同类事物统一处理: 聚合根也就是最抽象,最普遍的特性: 背景 领域建模的过程回顾: 那么问题来了? 为什么要在限界上下文和实体之间增加聚合和聚合根的概念,即作用是什么? 如何设计 ...
- 关于ABP聚合根类AggregateRoot的思考
AggregateRoot和Entity的区别 AggregateRoot继承于Entity,并实现了IGeneratesDomainEvents接口 public class AggregateRo ...
- Abp 领域事件简单实践 <四> 聚合根的领域事件
聚合根有个 DomainEvents 属性. 首先聚合根是一个实体.这个实体的仓储有变化(增删改)的时候,会触发这个DomainEvents 里的事件.就像EventBus.Trigger一样. pu ...
随机推荐
- Educational Codeforces Round 155 (Rated for Div
B. Chips on the Board 题解:贪心 显然我们可以把题意转化为:对于任意一个\((i,j)\),我们可以花费\(a_{i,j}\)的代价占据第\(i\)行和第\(j\)列,求占据所有 ...
- Windows 禁用笔记本键盘
背景 笔记本键盘+机械键盘组合如下图: 由此产生一个问题: 笔记本键盘现在的用处是什么? 没什么用,那我们何不把桌面的位置利用起来? 这样怎么样? ===> 为了防止放东西时候误触,我们需要把笔 ...
- 渗透测试-Kioptix Level 1靶机getshell及提权教程
声明! 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无 ...
- 指针, C语言的精髓
指针, C语言的精髓 莫队先咕几天, 容我先讲完树剖 (因为后面树上的东西好多都要用树剖求 LCA). 什么是指针 保存变量地址的变量叫做指针. 这是大概的定义, 但是Defad认为这个定义不太好理解 ...
- electron postinstall$ node install.js报错
本来以为是文件路径错了执行失败,手动去执行了下install.js,还是报错,但是不一样是连接超时 试了几种办法,简单直接就是如下方法 1:从项目node_modules中找到electron下的in ...
- 微信开发者工具请求接口 Provisional headers are shown
前情 最近全权负责公司小程序项目的开发,使用的uniapp技术栈. 坑 在和服务端联调的时候发现,接口pending很久,而且时不时的报Provisional headers are shown,而且 ...
- Flex 弹性布局备忘录
概述 Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性 这也是我目前用的最多的一种布局方案,相比Grid布局此种布局方案相对较简单, ...
- 从FTP到Feem:文件传输技术的革新
Feem是一个开源的文件传输协议,旨在提供高效.安全.快速的文件传输服务.与传统的FTP和HTTP协议相比,Feem具有许多优势,如支持任意大小的文件传输.支持实时传输和断点续传等. Feem_v4. ...
- 3.QMainWindow
QMainWindow介绍 QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar),多个工具栏(tool bars),多个铆接部件(dock widgets),一个状 ...
- 关于Qt高分屏缩放几个知识点
在windows上经常遇到高分屏缩放的问题,很头疼,貌似这东西就是windows首发的. 在Qt4时代的程序遇到高分屏缩放,不作任何处理,毕竟Qt4时代(2010年以前)出来的时候几乎还没高分屏缩放这 ...