ChuanGoing 2019-11-11
距离上篇近两个月时间, 一方面时因为其他事情耽搁, 另一方面也是之前准备不足, 关于领域驱动有几个地方没有想通透, 也就没有继续码字. 目前网络包括园子里大多领域驱动设计的文章, 关于仓储者一层都没有详细的说明, 只是简单的一笔带过: 领域驱动不关心具体的持久化如何落地. 但是, 作为 "猿人类" 就不可避免的绕不开持久化. 本篇将会简略的介绍利用 Dapper 这个轻量级的 ORM 来实现如何持久化.
本篇学习曲线:
1. 领域模型
2. 领域仓储
3. 简单服务实现
领域模型设计
我这里用常见的电商业务模型来介绍领域模型的设计, 因篇幅有限, 这里主要介绍订单与订单明细模型, 基于如下业务:
1. 创建订单时生成订单号, 计算商品的总价同时生成订单明细.
2. 根据订单号获取订单信息
我的这个例子基本上涵盖了领域模型的主要几种业务组合: 领域 - 实体 - 值对象, 这几个基本概念这里不做赘述, 园子里一搜一大堆.
- public partial class Order : DomainEntity<Guid>
- {
- /// <summary>
- /// 订单流水号
- /// </summary>
- public string Sn { get; private set; }
- /// <summary>
- /// 总价
- /// </summary>
- public decimal TotalPrice { get; private set; }
- /// <summary>
- /// 状态
- /// </summary>
- public OrderStatus Status { get; private set; }
- /// <summary>
- /// 支付时间
- /// </summary>
- public long PaymentTime { get; private set; }
- /// <summary>
- /// 过期时间
- /// </summary>
- public long ExpireTime { get; private set; }
- /// <summary>
- /// 备注
- /// </summary>
- public string Description { get; private set; }
- /// <summary>
- /// 用户
- /// </summary>
- public Guid UserId { get; private set; }
- public string Adress { get; private set; }
- /// <summary>
- /// 订单明细
- /// </summary>
- [Ignore]
- public List<OrderItem> OrderItems { get; private set; }
- }
订单
- public partial class OrderItem : Entity<Guid>
- {
- /// <summary>
- /// 订单编号
- /// </summary>
- public Guid OrderId { get; private set; }
- /// <summary>
- /// 商品编号
- /// </summary>
- public Guid ProductId { get; private set; }
- /// <summary>
- /// 商品单价
- /// </summary>
- public decimal Price { get; private set; }
- /// <summary>
- /// 数量
- /// </summary>
- public int Count { get; private set; }
- /// <summary>
- /// 加入时间
- /// </summary>
- public long JoinTime { get; private set; }
- }
订单详情
可以看到 Order 类为 DomainEntity(领域实体 - 聚合根),OrderItem 为 Entity(实体),Order 和 OrderItem 组成一个业务聚合. 为什么这么划分呢? 有两方面的原因:
1. 本例不涉及复杂业务, 没有直接针对订单明细的业务操作
2. 订单明细依赖于订单, 生命周期随着订单主体产生和消逝
订单和订单明细都被设计为 "partial", 因为到目前为止, 我们的实体类还是 POCO, 也就是通常所说的贫血模型. 因此, 为了赋予模型活力, 我们需要为其添加某些行为:
- public partial class Order
- {
- public void Add(Guid userId, Adress adress, string description, List<OrderItem> orderItems)
- {
- Sn = Guid.NewGuid().ToString("N");
- TotalPrice = orderItems.Sum(i => i.Price * i.Count);
- Status = OrderStatus.TobePaid;
- ExpireTime = DateTimeOffset.Now.AddMinutes(-30).ToUnixTimeMilliseconds();
- UserId = userId;
- Adress = adress.ToString();
- Description = description;
- orderItems.ForEach(i =>
- {
- i.SetOrder(this);
- });
- SetItems(orderItems);
- }
- public void Pay()
- {
- Status = OrderStatus.Paid;
- PaymentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- }
- public void SetItems(List<OrderItem> orderItems)
- {
- OrderItems = orderItems;
- }
- }
订单行为
这样, 我们给领域赋予了某些行为.
领域仓储
结合我上篇 ASP.NET Core 系列之 --2.ORM 初探: Dapper 实现 MySQL 数据库各类操作介绍的仓储示例, 这里为领域模型单独设计了 "领域仓储层"
- public class OrderRepository : DapperRepository<Order, Guid>, IOrderRepository
- {
- public OrderRepository(IComponentContext container, IDapperDbContext dbContext)
- : base(container, dbContext)
- {
- }
- public Order GetBySn(string sn)
- {
- var order = QuerySingleOrDefault<Order>("SELECT * FROM `Order` WHERE `Sn`=@Sn;", new
- {
- Sn = sn
- });
- if (order != null)
- {
- order.SetItems(Query<OrderItem>("SELECT * FROM `OrderItem` WHERE `OrderId`=@OrderId;", new
- {
- OrderId = order.Id
- }).ToList());
- }
- return order;
- }
- }
领域仓储
领域仓储实现了 Domain 中定义的接口
- public interface IOrderRepository : IRepository<Order, Guid>, IScopeInstance
- {
- Order GetBySn(string sn);
- }
订单仓储接口定义
值得注意的时, 领域仓储层这里起到了承上启下的作用, 隔离了领域对于持久化层的直接依赖.
简单服务实现
接下来, 我们实现如何在业务服务层调用领域仓储实现数据的持久化. 新建 Application 项目:
定义订单接口服务
- public interface IOrderService : IScopeInstance
- {
- void Add(OrderViewModel order);
- OrderViewResult Get(string sn);
- }
订单服务实现
- public void Add(OrderViewModel view)
- {
- if (view == null) new Exception("参数无效");
- var order = new Order();
- Mapper.Map(view, order);
- order.Add(view.UserId, view.Adress, view.Description, order.OrderItems);
- _repo.Insert(order);
- order.OrderItems.ForEach(i => _itemRepo.Insert(i));
- }
- public OrderViewResult Get(string sn)
- {
- var order = _repo.GetBySn(sn);
- OrderViewResult result = new OrderViewResult();
- return order == null ? null : Mapper.Map(order, result);
- }
在 webapi 项目控制器文件夹下新建 OrderController
- public class OrderController : Controller
- {
- private readonly IOrderService _service;
- public OrderController(IOrderService service)
- {
- _service = service;
- }
- [HttpPost]
- public void Add([FromBody]OrderViewModel order)
- {
- _service.Add(order);
- }
- [HttpGet]
- public OrderViewResult Get([FromQuery]string sn)
- {
- return _service.Get(sn);
- }
- }
具体代码请看篇末源代码链接
这样, 我们实现了领域模型的持久化.
回顾
回顾一下本篇内容, 分别介绍了: 领域模型, 领域仓储, 简单服务的实现, 然后利用 postman 模拟 http 请求演示了数据的创建与获取.
本篇只是简单的介绍了领域服务及相关概念, 后面有机会再做详细讨论, 下篇将介绍日志模块和权限模块.
代码
本篇涉及的源码在 GitHub 的 https://github.com/ChuanGoing/Start.git https://github.com/ChuanGoing/Start 的 Domain 分支可以找到.
来源: https://www.cnblogs.com/ChuanGoing/p/11624228.html