实体(Entity)
- 具有一个唯一标识,有一个生命周期
- 即便两个实体所有属性都一样但只要唯一标识不一样也认为是不同的实体。
- 为什么实体需要一个唯一标识呢?
- 实体最好只保留核心属性和方法,通过寻找关联发现其他一些实体或值对象,将属性或行为转移到其他关联的实体或值对象上。
- 需要根据业务规则来设计实体的属性和方法,不要总是get;set;,要正确为实体分配职责,不能贫血也不能分配不该分配的职责;
- 高内聚、低耦合原则
- 信息专家模式:将职责分配给拥有执行该职责所需信息的对象
DDD中的3个臭皮匠
这里的3个臭皮匠其实就是:Entity、ValueObject、Aggregate。我们要提炼出业务中的精华,合理的抽象为这3个概念,并且这种抽象是需随着领域里的概念变化而变化的。这3者的结合运用会让我们的项目活起来,这是DDD的核心。这里再把这3个概念重新梳理一下。
- Entity(实体): 每个实体是唯一的,并且可以相当长的一段时间内持续地变化。我们可以对实体做多次修改,故一个实体对象可能和它先前的状态大不相同。但是,由于它们拥有相同的身份标识,他们依然是同一个实体。
- ValueObject(值对象):值对象用于度量和描述事物,当你只关心某个对象的属性时,该对象便可作为一个值对象。实体与值对象的区别在于唯一的身份标识和可变性。
- Aggregate(聚合):聚合类是实体的升级,是由一组与生俱来就密切相关实体和值对象组合而成的,这整个组合的最上层实体就是聚合。
多租户
IMayHaveTenant
我们可能需要在租户和租户之间共享一个实体类型。因此,一个实体可能会被一个租户或租主拥有。IMayHaveTenant接口也定义了TenantId(类似于IMustHaveTenant),但在这种情况下是nullable。
using System;
namespace Rdf.Domain.Entities
{
public interface IMayHaveTenant
{
/// <summary>
/// TenantId of this entity.
/// </summary>
Guid? TenantId { get; set; }
}
}
IMustHaveTenant
该接口通过定义TenantId属性来区分不同租户的实体。
using System;
namespace Rdf.Domain.Entities
{
public interface IMustHaveTenant
{
/// <summary>
/// TenantId of this entity.
/// </summary>
Guid TenantId { get; set; }
}
}
激活/未激活
一些实体需要标记为激活的或未激活的。这样,你就可以根据实体的激活或者未激活状态来采取行动。你可以实现IPassivable接口来达到目的。该接口定义了IsActive属性。 如果实体在第一次创建时是激活的,那么你可以在构造函数中将IsActive设置为true。
namespace Rdf.Domain.Entities
{
public interface IPassivable
{
/// <summary>
/// True: This entity is active.
/// False: This entity is not active.
/// </summary>
bool IsActive { get; set; }
}
}
软删除
软删除是将一个实体标记为已删除的通常使用的模式,而不是直接从数据库中删除。
namespace Rdf.Domain.Entities
{
public interface ISoftDelete
{
/// <summary>
/// Used to mark an Entity as 'Deleted'.
/// </summary>
bool IsDeleted { get; set; }
}
}
实体基类
using System;
namespace Rdf.Domain.Entities
{
/// <summary>
/// 泛型实体基类
/// </summary>
/// <typeparam name="TPrimaryKey">主键类型</typeparam>
public abstract class Entity<TPrimaryKey>
{
/// <summary>
/// 主键
/// </summary>
public virtual TPrimaryKey Id { get; set; }
}
/// <summary>
/// 定义默认主键类型为Guid的实体基类
/// </summary>
public abstract class Entity : Entity<Guid> { }
}
审计
ICreationAudited
namespace Rdf.Domain.Entities.Auditing
{
public interface ICreationAudited<T> : IHasCreationTime
{
/// <summary>
/// Id of the creator user of this entity.
/// </summary>
T CreatorUserId { get; set; }
}
}
IHasCreationTime
using System;
namespace Rdf.Domain.Entities.Auditing
{
/// <summary>
/// IHasCreationTime使得使用一个通用的属性来描述一个实体的“创建时间”信息成为可能。
/// </summary>
public interface IHasCreationTime
{
/// <summary>
/// Creation time of this entity.
/// </summary>
DateTime CreationTime { get; set; }
}
}
IDeletionAudited
namespace Rdf.Domain.Entities.Auditing
{
public interface IDeletionAudited<T> : IHasDeletionTime
{
/// <summary>
/// Which user deleted this entity?
/// </summary>
T DeleterUserId { get; set; }
}
}
IHasDeletionTime
using System;
namespace Rdf.Domain.Entities.Auditing
{
public interface IHasDeletionTime : ISoftDelete
{
/// <summary>
/// Deletion time of this entity.
/// </summary>
DateTime? DeletionTime { get; set; }
}
}
IModificationAudited
namespace Rdf.Domain.Entities.Auditing
{
public interface IModificationAudited<T> : IHasModificationTime
{
/// <summary>
/// Last modifier user for this entity.
/// </summary>
T LastModifierUserId { get; set; }
}
}
IHasModificationTime
using System;
namespace Rdf.Domain.Entities.Auditing
{
public interface IHasModificationTime
{
/// <summary>
/// The last modified time for this entity.
/// </summary>
DateTime? LastModificationTime { get; set; }
}
}
IAudited
namespace Rdf.Domain.Entities.Auditing
{
public interface IAudited<T> : ICreationAudited<T>
, IModificationAudited<T>
{
}
}
IFullAudited
namespace Rdf.Domain.Entities.Auditing
{
/// <summary>
/// 如果你想为一个实体实现所有的审计接口(创建,修改和删除),
/// 那么可以直接实现IFullAudited,因为它继承了所有的这些接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IFullAudited<T> : IAudited<T>
, IDeletionAudited<T>
{
}
}