系列导航
需求
中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发。
首先要定义和解决的问题是,根据TodoList
项目的需求,我们应该设计怎样的数据实体,如何去进行操作?
长文预警!包含大量代码
目标
在本文中,我们希望达到以下几个目标:
- 定义领域实体;
- 通过数据库操作领域实体;
原理和思路
虽然TodoList
是一个很简单的应用,业务逻辑并不复杂,至少在这个系列文章中我并不想使其过度复杂。但是我还是打算借此简单地涉及领域驱动开发(DDD)的基础概念。
首先比较明确的是,我们的实体对象应该有两个:TodoList
和TodoItem
,并且一个TodoList
是由多个TodoItem
的列表构成,除此以外在实际的开发中,我们可能还需要追踪实体的变更情况,比如需要知道创建时间/修改时间/创建者/修改者,这种需求一般作为审计要求出现,而对实体的审计又是一个比较通用的需求。所以我们会将实体分成两部分:和业务需求直接相关的属性,以及和实体审计需求相关的属性。
其次,对于实体的数据库配置,有两种方式:通过Attribute
或者通过IEntityTypeConfiguration
以代码的方式进行。我推荐使用第二种方式,将所有的具体配置集中到Infrastructure
层去管理,避免后续修改字段属性而去频繁修改位于Domain
层的实体对象定义,我们希望实体定义本身是稳定的。
最后,对于DDD来说有一些核心概念诸如领域事件,值对象,聚合根等等,我们都会在定义领域实体的时候有所涉及,但是目前还不会过多地使用。关于这些基本概念的含义,请参考这篇文章:。在我们的开发过程中,会进行一些精简,有部分内容也会随着后续的文章逐步完善。
实现
基础的领域概念框架搭建
所有和领域相关的概念都会进入到Domain
这个项目中,我们首先在Domain
项目里新建文件夹Base
用于存放所有的基础定义,下面将一个一个去实现。(另一种方式是把这些最基础的定义单独提出去新建一个SharedDefinition
类库并让Domain
引用这个项目。)
基础实体定义以及可审计实体定义
我这两个类都应该是抽象基类,他们的存在是为了让我们的业务实体继承使用的,并且为了允许不同的实体可以定义自己主键的类型,我们将基类定义成泛型的。
namespace TodoList.Domain.Base;
public abstract class AuditableEntity
{
public DateTime Created { get; set; }
public string? CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public string? LastModifiedBy { get; set; }
}
在Base
里增加Interface
文件夹来保存接口定义。
namespace TodoList.Domain.Base.Interfaces;
public interface IEntity
{
public T Id { get; set; }
}
除了这两个对象之外,我们还需要增加关于领域事件框架的定义。
namespace TodoList.Domain.Base;
public abstract class DomainEvent
{
protected DomainEvent()
{
DateOccurred = DateTimeOffset.UtcNow;
}
public bool IsPublished { get; set; }
public DateTimeOffset DateOccurred { get; protected set; } = DateTime.UtcNow;
}
namespace TodoList.Domain.Base.Interfaces;
public interface IHasDomainEvent
{
public List DomainEvents { get; set; }
}
我们还剩下Aggregate Root
, ValueObject
和Domain Service
以及Domain Exception
,其他的相关概念暂时就不涉及了。
namespace TodoList.Domain.Base.Interfaces;
// 聚合根对象仅仅作为标记来使用
public interface IAggregateRoot { }
ValueObject
的实现有几乎固定的写法,请参考:https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects
namespace TodoList.Domain.Base;
public abstract class ValueObject
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if (left is null ^ right is null)
{
return false;
}
return left?.Equals(right!) != false;
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable