前言: DDD的基础知识这里就不讲了,网上有很多,无外乎 架构从以外的三层变成四层,有聚合、实体、值对象、领域服务等这些概念,我也是最近看了很多,但无从下手,正好我们现有的项目是基于ABP
框架的,虽说也支持DDD,也是分为了4个项目,有领域有Domain,但感觉还是个三层项目,想了想,最大的问题还是收到新的任务后,总是从数据库开始,然后T4生成实体,没有太多的去进行领域划分。所以本次
我们使用EFCore的CodeFirst和ABPVnext来体验一下怎么在项目中真正的运用DDD。
--------------------------------------------------------------------------------------------------------------------------------------------------
新建项目
我这里使用ABP的CLI命令创建的,当然也可以直接在网站(https://www.abp.io/get-started)上下载
我这里由于没有装mssql,所以切换到了mysql,具体可参考https://docs.abp.io/zh-Hans/abp/latest/Entity-Framework-Core-MySQL
切换后,我们使用下面这俩命令创建一下数据库
Add-Migration "Init"
Update-DataBase
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
重点来了
接下来我们正式从一个订单的领域开始, 首先看一张图
首先我们先创建个地址的值对象
/// <summary>
/// 地址(值对象)
/// </summary>
public class Address : ValueObject
{
public String Street { get; private set; }
public String City { get; private set; }
public String State { get; private set; }
public String Country { get; private set; }
public String ZipCode { get; private set; }
public Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
protected override IEnumerable<object> GetAtomicValues()
{
// Using a yield return statement to return each element one at a time
yield return Street;
yield return City;
yield return State;
yield return Country;
yield return ZipCode;
}
}
创建实体
/// <summary>
/// 订单明细实体
/// </summary>
public class OrderItem : Entity
{
public virtual Guid OrderId { get; protected set; }
public virtual Guid ProductId { get; protected set; }
public virtual int Count { get; protected set; }
protected OrderItem()
{
}
internal OrderItem(Guid orderId, Guid productId, int count)
{
OrderId = orderId;
ProductId = productId;
Count = count;
}
internal void ChangeCount(int newCount)
{
Count = newCount;
}
public override object[] GetKeys()
{
return new Object[] { OrderId, ProductId };
}
}
创建订单聚合根
/// <summary>
/// 订单聚合根
/// </summary>
public class Order : AggregateRoot<Guid>
{
public virtual string ReferenceNo { get; protected set; }
public virtual int TotalItemCount { get; protected set; }
public virtual DateTime CreationTime { get; protected set; }
public virtual List<OrderItem> OrderLines { get; protected set; }
public Address Address { get; protected set; }//值对象
public string UserName { get; protected set; }
protected Order()
{
}
public Order(Guid id, string referenceNo, string userName, Address address)
{
Check.NotNull(referenceNo, nameof(referenceNo));
Id = id;
ReferenceNo = referenceNo;
Address = address;
OrderLines = new List<OrderItem>();
CreationTime = DateTime.Now;
UserName = userName;
}
public void AddProduct(Guid productId, int count)
{
if (count <= 0)
{
throw new ArgumentException(
"You can not add zero or negative count of products!",
nameof(count)
);
}
var existingLine = OrderLines.FirstOrDefault(ol => ol.ProductId == productId);
if (existingLine == null)
{
OrderLines.Add(new OrderItem(this.Id, productId, count));
}
else
{
existingLine.ChangeCount(existingLine.Count + count);
}
TotalItemCount += count;
}
}
然后在Context中添加
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public static void ConfigureDDDTest(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
/* Configure your own tables/entities inside here */
//builder.Entity<YourEntity>(b =>
//{
// b.ToTable(DDDTestConsts.DbTablePrefix + "YourEntities", DDDTestConsts.DbSchema);
// //...
//});
builder.Entity<Order>(b =>
{
b.ToTable(DDDTestConsts.DbTablePrefix + "Orders", DDDTestConsts.DbSchema);
b.ConfigureByConvention();
b.OwnsOne(o => o.Address, a => {
a.WithOwner();
});
//b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
builder.Entity<OrderItem>(b =>
{
b.ToTable(DDDTestConsts.DbTablePrefix + "OrderLines", DDDTestConsts.DbSchema);
b.ConfigureByConvention();
b.HasKey(x => new { x.OrderId, x.ProductId });
//b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
}
此时我们再执行迁移命令,会发现数据库中多了俩张表,值对象在聚合中
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这时候我们执行个创建的方法是,会出现这个错误。
这个时候只需要把 base.OnModelCreating(builder);,移到builder.ConfigureDDDTest();下面即可
自定义仓储的话,我们需要把接口定义在领域层,把实现放在基础设施层
public class OrderRepository : EfCoreRepository<DDDTestDbContext, Order, Guid>, IOrderRepository
{
public OrderRepository(IDbContextProvider<DDDTestDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Order> Get(Guid orderId)
{
var order = await DbContext
.Orders
.Include(x => x.Address)
.FirstOrDefaultAsync(o => o.Id == orderId);
if (order == null)
{
order = DbContext
.Orders
.Local
.FirstOrDefault(o => o.Id == orderId);
}
if (order != null)
{
await DbContext.Entry(order)
.Collection(i => i.OrderLines).LoadAsync();
}
return order;
}
}
整体实现下来,不知道有没有点对DDD的感觉呢?
参考:
https://www.cnblogs.com/richieyang/p/5373250.html
https://cn.abp.io/blog/Abp/Abp-vNext-Announcement
https://docs.abp.io/en/abp/latest/
https://github.com/abpframework/abp/issues/2640
https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-domain-model
https://github.com/dotnet-architecture/eShopOnContainers
来源:oschina
链接:https://my.oschina.net/u/4395911/blog/3305556