前言: 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