ABP VNext 微服务搭建入门(2)-- 从领域开始对象建模
DDD的好处
相对于传统的数据驱动设计,基于领域驱动设计的代码可以提现通用语言,更具可读性,更能准确表达业务。
一、确定领域、拆分子域
常见电商系统拆分
领域:电商
子域:销售、商品、用户、商家、订单等
核心域:销售
通用域:非业务模块,如日志子域
支撑域:物流、商品等
二、限界上下文(语境)
当划分子域之后,每个子域都对应有各自的上下文。在销售子域和商品子域所在的上下文语境中,商品就是商品,无二义性。但如果子域对应多个上下文的时候,就要考虑一下是不是子域能否继续划分。
*三、领域对象
领域对象是服务的提供方,而不是数据容器,提供业务行为,而不是数据。
领域模型(实体)
领域模型在代码中提现为实体,映射到真实数据表。实体有唯一标识,具有可变性。如订单就是唯一的,且订单状态会随业务行为而改变。
领域模型应该是充血模型而不是贫血模型。贫血模型只包含属性的 get set 方法。充血模型更加丰富,包含业务逻辑。
abp已经定义了 Entity 系列类,实现即可。
值对象
值对象没有唯一标识,且不可变。如订单地址就是不可变,而收货地址是可变的且拥有唯一标识。abp已经定义了 ValueObject 类,实现即可。
聚合根
聚合根是主实体,子实体都不可能孤立存在,它们必须依附于一个聚合根存在。如订单就是聚合根,物流信息是依赖于订单的子实体。abp已经定义了 AggregateRoot 系列类,实现即可。
聚合
一个聚合中可以包含多个实体和值对象。聚合是持久化的基本单位,它和仓储具有一一对应的关系。如一个完整的订单聚合就包含物流等信息。
示例
public class BlogPost: AggregateRoot<int>
{
private BlogPost()
{
// just for EF
}
//使用参数化的构造函数可以确保我们的领域模型在实例化时有效
public BlogPost(string title, string summary, string body)
{
if (string.IsNullOrWhiteSpace(title))
{
throw new ArgumentException("Title is required");
}
...
Title = title;
Summary = summary;
Body = body;
DateAdded = DateTime.UtcNow;
}
//引入更改状态的方法使我们能够集中业务逻辑并简化调用代码
public void Publish()
{
if (Status == BlogPostStatus.Draft || Status == BlogPostStatus.Archived)
{
if (Status == BlogPostStatus.Draft)
{
DatePublished = DateTime.UtcNow;
}
Status = BlogPostStatus.Published;
}
}
private string title;
[Required]
public string Title
{
get { return title; }
set
{
//内部集中校验
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Title must contain a value");
}
title = value;
}
}
[Required]
[StringLength(500)]
//清除公共属性setter确保我们的模型在其整个生命周期内保持有效状态
public string Summary { get;private set; }
[Required]
public string Body { get;private set; }
public DateTime DateAdded { get;private set; }
public DateTime? DatePublished { get;private set; }
public BlogPostStatus Status { get;private set; }
//使用值对象 生成 AdvertisingFee_Currency 和 AdvertisingFee_Amount 列
public Money AdvertisingFee { get; private set; }
...
}
public class Money:ValueObject
{
[StringLength(3)]
public string Currency { get; private set; }
public int Amount { get; private set; }
private Money()
{
// just for EF
}
public Money(string currency, int amount)
{
// todo validation
Currency = currency;
Amount = amount;
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return Currency;
yield return Amount;
}
}
public class BlogContext : DbContext
{
...
public DbSet<BlogPost> BlogPosts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogPost>().OwnsOne(x => x.AdvertisingFee);
}
}