大话领域驱动设计——领域层
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大话领域驱动设计——领域层相关的知识,希望对你有一定的参考价值。
概述
在DDD中,业务逻辑主要分布在领域层和应用层两层,他们包含不同的业务逻辑。这一篇,我们先对领域层做详细的讲解分析。
领域层实现了领域或系统的,与用户界面上的用户交互(用例)无关的核心业务逻辑。
总览
领域层主要包含以下组件:
实体(Entity):实体是一个具有自己的属性(状态、数据)和实现在这些属性上执行的业务逻辑的方法的对象。实体由其唯一标识符(Id)表示。具有不同id的两个实体对象被视为不同的实体。
聚合和聚合根(Aggregate&Aggregate Root):聚合是通过集合根对象绑定在一起的对象集(实体和值对象)。聚合根是具有一些附加职责的实体的特定类型。
值对象(Value Object):值对象是另一种由其属性而不是唯一的Id来标识的域对象。这意味着两个具有相同属性的值对象将被视为相同的对象。值对象通常被实现为不可变对象,而且大多数值对象都比实体要简单得多。
领域服务(Domain Service):领域服务是一个实现域的核心业务规则的无状态服务。它依赖于多个聚合(实体)类型或某些外部服务,用于领域逻辑的实现。
仓储(Repository)接口:仓储是一个类似于集合的接口,它被领域层和应用层用于访问数据持久化系统(数据库)。它对业务代码隐藏了DBMS的复杂性。领域层包含仓储的接口声明。
规约(Specification):规约用于为实体和其他业务对象定义已命名的、可重用的且可组合的过滤器。
领域事件(Domain Event):领域事件是在领域特定事件发生时以松散耦合的形式通知其他服务的一种方法。
在ABP框架下,领域层包含以下两个项目:
Domain是包含所有组件(实体、聚合根、值对象、领域服务、规约、存储库接口等)的基本领域层。
Domain.Shared是一个轻量的项目,它包含了属于领域层的一些类型,但与所有其他层共享。例如,它可能包含一些与域对象相关的常量和枚举,但需要被其他层重用。
实现细节
1
实体
在ABP中,所有的实体类均需实现ABP框架中定义的接口 IEntity 。ABP中将实体分为两种用法:有默认主键(Id)的实体和没有默认主键的实体。
有默认主键的实体在 IEntity 接口基础上封装了 IEntity<TKey> 接口,其中,定义了TKey类型的主键,名称为Id。其默认实现类继承自 Entity<TKey> 。我们使用时,需以泛型方式定义主键的类型。
无默认主键的实体类直接实现IEntity接口,其默认实现类继承自 Entity ,在 Entity 中定义了虚方法 object[] GetKeys() 。该方法用于设定一个或多个属性为主键。使用无默认主键的实体时,我们必须实现GetKeys方法指定该实体类对应数据库表的主键。
在ABP中,对于实体定义了默认增删改操作的审计属性,通过 ICreationAuditedObject , IModificationAuditedObject 和 IDeletionAuditedObject 三个接口进行约束,分别包含对添加(添加人,添加时间),修改(修改人、修改时间),删除(删除人、删除时间、是否被软删除)操作记录的审计属性。ABP为ICreationAuditedObject接口创建了默认实现基类 CreationAuditedEntity ,同时还创建了包含以上三个接口所有实现的基类 FullAuditedEntity , CreationAuditedEntity 和 FullAuditedEntity 均包含有默认主键和无默认主键两种用法,以是否定义泛型来区分。
ABP中定义了 ISoftDelete 接口,其中包含 IsDeleted 属性,表示该条数据是否被软删除。当实体实现ISoftDelete接口时,ABP的仓储会自动将删除方法转为软删除,即只将IsDeleted属性设置为true,使其部不被查询到,但并不在数据库中对数据进行物理删除。
2
聚合根
在ABP中聚合根需实现I AggregateRoot ,其默认基类为 AggregateRoot ,同时 AggregateRoot 也实现了 IEntity 接口。 IAggregateRoot 本身未包含任何属性或者方法的声明,其主键声明继承自IEntity接口。
和实体类相同,聚合根也定义了有默认主键和无默认主键两种用法,通过是否传入泛型来进行区分。
聚合根也可以实现 ICreationAuditedObject , IModificationAuditedObject 和 IDeletionAuditedObject 对增删改操作进行审计记录,其中实现接口 ICreationAuditedObject 的基类为 CreationAuditedAggregateRoot ,实现所有审计接口的基类为 FullAuditedEntity 。
3
值对象
值对象的定义相对来说非常简单,在ABP中,定义了值对象的基类 ValueObject ,定义值对象必须继承ValueObject ValueObject 并实现ValueObject中定义的抽象方法 GetAtomicValues() 。
GetAtomicValues 方法用于检查两个值对象是否相等,其用法为通过 yield return 返回一组属性,表示如果两个值对象这些属性值都相等,则这两个为相等的值对象。示例代码如下:
protected override IEnumerable<object> GetAtomicValues() yield return Street; yield return CityId; yield return Number;
4
领域服务
领域服务的接口和实现均包含在领域层Domain项目中,其中领域服务接口声明继承自ABP提供的 IDomainService 接口,领域服务实现类继承自ABP提供的 DomainService 类。
在 IDomainService 接口中未声明任何属性和方法,我们如果需要用到领域服务,可完全依据自己的需求定义其中的内容。
在ABP框架中,领域服务通过自动依赖注入的方式以瞬态生命周期被注册到IOC容器中,即每次使用都会生成一个新的实例。我们只需要保证其所在类库中含有继承自 AbpModule 的模块定义类,即可实现自动依赖注入。
5
仓储接口
在领域层Domain项目中,只会声明仓储的接口,其实现需要在基础设施层的EntityFrameworkCore项目中编写。
ABP框架已经为我们提供了默认的仓储接口和实现,在大多数情况下,我们不需要编写任何仓储接口或实现类的代码,只需要在使用时注入 IRepository<TEntity, TKey> ,其中的两个泛型分别为实体/聚合根的类型和主键数据类型。
如果我们需要自定义仓储,则在Domain层定义其接口声明,仓储接口声明需要继承自 IRepository 接口。
6
规约
ABP提供了规约的接口 ISpecification<T> 和实现类基类 Specification<T> ,其中泛型为需要约束的实体或聚合根的类型。
编写规约时必须实现 ISpecification 接口中定义的 ToExpression 方法,该方法用来声明规约中数据约束的表达式。
同时, ISpecification 接口中还定义了 IsSatisfiedBy 方法,用于判断一个对象是否满足此规约,在 Specification 类中已提供了此方法的默认实现。
7
领域事件
ABP提供了本地事件总线和分布式事件总线两种方案,其中本地事件总线常用于我们DDD中传统意义的领域事件实现,而分布式事件总线常用于分布式或微服务系统的事件处理。
ABP本地领域事件使用主要分为订阅和发布两个部分:
发布和订阅的事件或对应关系以其传输的数据类型为区分。对于每种事件,我们都需要单独定义一个事件类型的简单类(ABP未提供统一基类,直接继承自Object即可)用于存放传输的数据,即使不需要传输数据,我们也要定义一个空类来区分事件类型。该类型命名通常以Event结尾。
在DomainService、ApplicationService、Controller等可以使用依赖注入的组件中,我们可以注入 ILocalEventBus 接口并调用 PublishAsync 方法来发布事件,在Entity和Aggregate Root中无法使用依赖注入,ABP官方给他们提供了一个基础方法 AddLocalEvent 用于发布事件。
在订阅事件时我们需要定义一个类,实现 ILocalEventHandler<T> 其中的泛型为前面所说的事件类型定义,并实现接口中的 HandleEventAsync 方法用于处理事件。
END
关注我获得
更多精彩
以上是关于大话领域驱动设计——领域层的主要内容,如果未能解决你的问题,请参考以下文章