DDD简明入门之道 - 开篇
犹豫了很久才写下此文,一怕自己对DDD的理解和实践方式有偏差,二怕误人子弟被贻笑大方,所以纰漏之处还望各位谅解。不啰嗦,马上进入正题,如果你觉得此文不错就点个赞吧。
概述“Domain-Driven Design领域驱动设计”简称DDD,是一套综合软件系统分析和设计的面向对象建模方法。关于DDD的学习资料园子里面有很多,大家可以自行参考,这里不过多介绍。
核心DDD的核心是领域对象的建模,说白了就是怎么样从业务需求中抽象出我们需要的数据结构,通过这些数据结构之间的相互作用来实现我们的业务功能。这里的所说的数据结构是广义的,Domain里面的每一个类其实就是一个数据结构。这里说的有点抽象了,接下来我们将通过一个具体业务需求的开发来展开。
案例假设需要开发一个电商平台,我们把平台按功能拆分成多个子系统,子系统之间以微服务形式进行交互调用。拆分后的子系统大致如下:
产品系统(PMS)
订单系统(OMS)
交易系统(TMS)
发货系统(DMS)
其他系统...
而你将会负责订单系统的开发工作,订单系统需要支撑的业务包括用户下单、支付、平台发货、用户确认收货、用户取消订单等业务场景,下面我们就围绕这些场景来对订单业务进行建模。
订单建模 //订单信息 public class Order { public int Id{get;set;} public string OrderNo{get;set;} public OrderStatus Status{get;set;} public Address Address{get;set;} public List<OrderLine> Lines{get;set;} public decimal ShippingFee{get;set;} public decimal Discount{get;set;} public decimal GoodsTotal{get;set;} public decimal DueAmount{get;set;} } //订单状态 public enum OrderStatus { PendingPayment = 0, PendingShipment = 10, PendingReceive = 20, Received = 30, Cancel = 40 } //地址 public class Address { public string FullName{get;set;} public string FullAddress{get;set;} public string Tel{get;set;} } OrderLine.cs //订单明细 public class OrderLine { public int Id{get;set;} public int SkuId{get;set;} public string SkuName{get;set;} public string Spec{get;set;} public int Qty{get;set;} public decimal Cost{get;set;} public decimal Price{get;set;} public decimal Total{get;set;} } Txn.cs //交易信息 public class Txn { .... } Shipment.cs //发货信息 public class Shipment { .... } 模型改进类似上面的模型我们在传统的三层中经常使用,模型中只包含简单的业务属性,这些业务属性的赋值将会在服务层中去进行。这些模型只是用来装数据的壳子,或者叫做容器,完全就是为了和数据库表建立对应关系而存在的。还记得DataTable时代吗?我们完全可以连上面这些模型都不要也是一样可以操作数据库表的。
Class 不等于 OO
给模型赋予行为
深度面向对象编程
/// <summary> /// 订单信息 /// </summary> public class Order { private List<OrderLine> _lines; public Order() { _lines = new List<OrderLine>(); } /// <summary> /// 创建订单(简单工厂) /// </summary> /// <param></param> /// <param></param> /// <param></param> /// <returns></returns> public static Order Create(string orderNo, Address address, SaleSkuInfo[] skus) { Order order = new Order(); order.OrderNo = orderNo; order.Address = address; order.Status = OrderStatus.PendingPayment; foreach(var sku in skus) { order.AddLine(sku.Id,sku.Qty); } order.CalculateFee(); return order; } /// <summary> /// Id /// </summary> public int Id{get; private set;} /// <summary> /// 订单号 /// </summary> public string OrderNo{get; private set;} /// <summary> /// 订单状态 /// </summary> public OrderStatus Status{get; private set;} /// <summary> /// 收货地址 /// </summary> public Address Address{get; private set;} /// <summary> /// 订单明细 /// </summary> public List<OrderLine> Lines { get{return this._lines;} private set { this._lines = value; } } /// <summary> /// 运费 /// </summary> public decimal ShippingFee { get; private set; } /// <summary> /// 折扣金额 /// </summary> public decimal Discount{ get; private set; } /// <summary> /// 商品总价值 /// </summary> public decimal GoodsTotal { get; private set; } /// <summary> /// 应付金额 /// </summary> public decimal DueAmount { get; private set; } /// <summary> /// 实付金额 /// </summary> public decimal ActAmount { get; private set; } /// <summary> /// 添加明细 /// </summary> /// <param></param> /// <param></param> public void AddLine(int skuId, int qty) { var product = ServiceProxy.ProductService.GetProduct(new GetProductRequest{SkuId = skuId}); if(product == null) { throw new SkuNotFindException(skuId); } OrderLine line = new OrderLine(skuId, product.SkuName, product.Spec, qty, product.Cost, product.Price); this._lines.Add(line); } /// <summary> /// 订单费用计算 /// </summary> public void CalculateFee() { this.CalculateGoodsTotal(); this.CalculateShippingFee(); this.CalculateDiscount(); this.CalculateDueAmount(); } /// <summary> /// 订单支付 /// </summary> /// <param></param> public void Pay(decimal money) { if (money <= 0) { throw new ArgumentException("支付金额必须大于0"); } this.ActAmount += money; if (this.ActAmount >= this.DueAmount) { if (this.Status == OrderStatus.PendingPayment) { this.Status = OrderStatus.PendingShipment; } } } /// <summary> /// 计算运费 /// </summary> private decimal CalculateShippingFee() { //够买商品总价值小于100则收取8元运费 this.ShippingFee = this.CalculateGoodsTotal() > 100 ? 0 : 8m; return this.ShippingFee; } /// <summary> /// 计算折扣 /// </summary> private decimal CalculateDiscount() { this.Discount = decimal.Zero; //todo zhangsan 暂未实现 return this.Discount; } /// <summary> /// 计算商品总价值 /// </summary> private decimal CalculateGoodsTotal() { this.GoodsTotal = this.Lines.Sum(line => line.CalculateTotal()); return this.GoodsTotal; } /// <summary> /// 计算应付金额 /// </summary> /// <returns></returns> private decimal CalculateDueAmount() { this.DueAmount = this.CalculateGoodsTotal() + CalculateShippingFee() - CalculateDiscount(); return this.DueAmount; } }