DDD:订单管理 之 如何组织代码
背景
系统开发最难的是职责的合理分配,或者叫:“如何合理的组织代码”,今天说一个关于这方面问题的示例,希望大家多批评。
示例背景
参考数据字典
需求
- OrderCode必须唯一。
- Total = Sum(Subtotal)。
- 订单有三种状态:【未提交】、【待审核】和【已审核】,合理的状态迁移有:【未提交】----》【待审核】和【待审核】----》【已审核】,只有处于【未提交】状态的订单能修改。
- 订单和订单项中的状态必须合法,规则自己定义。
示例实现
项目结构
- Application:应用层,负责领域逻辑的封装。主要角色:ApplicationService、CommandHandler。
- Boostrap:启动管理层,负责启动过程管理,如:注册Ioc、初始化配置。主要角色:BootstrapListener。
- Commands:命令层,是一个契约层。主要角色:Comamnd、DTO。
- Controllers:控制器层,边界层。主要角色:Controller。
- Domain:领域层,负责领域逻辑的组织。主要角色:Aggregate、Entity、ValueObject、Factory、DomainService、IRepository、IUnitOfWork。
- Events:事件层,是一个契约层,跨聚合流程可以采用。主要角色:Event。
- EventSubscribers:事件监听层。主要角色:EventSubscriber。
- Infrastructure:基础设施层。主要角色:Repository、QueryService、UnitOfWork。
- Query:查询层,为UI的查询提供服务,主要角色:QueryService。
项目整体采用简单的CQRS架构,Command端采用DDD组织,Query直接从数据库返回dynamic类型。Event可以用来处理跨聚合通信,也可以用来处理长事务或离线事务。
重点介绍的领域层
采用状态模式处理状态迁移。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Happy.Example.Domain.TestOrders
{
public interface ITestOrderState
{
void AddTestOrderDetail(TestOrderDetail testOrderDetail); void UpdateTestOrderDetail(Guid testOrderDetailId, decimal subtotal); void RemoveTestOrderDetail(Guid testOrderDetailId); void Commit(); void Verify(); string Status { get; } TestOrder TestOrder { set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Happy.Example.Domain.TestOrders
{
public partial class TestOrder
{
private abstract class TestOrderState : ITestOrderState
{
public virtual void AddTestOrderDetail(TestOrderDetail testOrderDetail)
{
this.ThrowInvalidOperationException();
} public virtual void UpdateTestOrderDetail(Guid testOrderDetailId, decimal subtotal)
{
this.ThrowInvalidOperationException();
} public virtual void RemoveTestOrderDetail(Guid testOrderDetailId)
{
this.ThrowInvalidOperationException();
} public virtual void Commit()
{
this.ThrowInvalidOperationException();
} public virtual void Verify()
{
this.ThrowInvalidOperationException();
} public abstract string Status { get; } public TestOrder TestOrder { protected get; set; } private void ThrowInvalidOperationException()
{
throw new InvalidOperationException(string.Format("处于【{0}】的订单不能执行此操作!", this.Status));
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Happy.Example.Domain.TestOrders
{
public partial class TestOrder
{
private sealed class UnCommitted : TestOrderState
{
internal static readonly string UnCommittedStatus = "未提交"; public override void AddTestOrderDetail(TestOrderDetail testOrderDetail)
{
this.TestOrder.DoAddTestOrderDetail(testOrderDetail);
} public override void UpdateTestOrderDetail(Guid testOrderDetailId, decimal subtotal)
{
this.TestOrder.DoUpdateTestOrderDetail(testOrderDetailId, subtotal);
} public override void RemoveTestOrderDetail(Guid testOrderDetailId)
{
this.TestOrder.DoRemoveTestOrderDetail(testOrderDetailId);
} public override void Commit()
{
this.TestOrder.DoCommit();
} public override string Status
{
get { return UnCommittedStatus; }
}
}
}
}
采用封装集合手法处理Total的同步问题。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using Happy.Domain;
using Happy.Domain.Tree;
using Happy.Infrastructure.ExtentionMethods;
using Happy.Example.Events.TestOrders; namespace Happy.Example.Domain.TestOrders
{
public partial class TestOrder : AggregateRoot<Guid>
{
private ITestOrderState _orderState; protected TestOrder() { } public TestOrder(string orderCode, string customer)
{
orderCode.MustNotNullAndNotWhiteSpace("orderCode");
customer.MustNotNullAndNotWhiteSpace("customer"); this.Id = Guid.NewGuid();
this.OrderCode = orderCode;
this.Customer = customer;
this.OrderState = TestOrderStateFactory.CreateUnCommittedTestOrderState(this);
this.TestOrderDetails = new List<TestOrderDetail>();
} public virtual System.String OrderCode { get; protected set; }
public virtual System.String Customer { get; protected set; }
public virtual System.Decimal Total { get; protected set; }
public virtual System.String Status { get; protected set; }
public virtual ICollection<TestOrderDetail> TestOrderDetails { get; protected set; } private ITestOrderState OrderState
{
get
{
if (_orderState == null)
{
_orderState = TestOrderStateFactory.Create(this, this.Status);
} return _orderState;
}
set
{
_orderState = value;
this.Status = value.Status;
}
} public void AddTestOrderDetail(TestOrderDetail testOrderDetail)
{
this.OrderState.AddTestOrderDetail(testOrderDetail);
} public void UpdateTestOrderDetail(Guid testOrderDetailId, decimal subtotal)
{
this.OrderState.UpdateTestOrderDetail(testOrderDetailId, subtotal);
} public void RemoveTestOrderDetail(Guid testOrderDetailId)
{
this.OrderState.RemoveTestOrderDetail(testOrderDetailId);
} public void Commit()
{
this.OrderState.Commit();
} public void Verify()
{
this.OrderState.Verify();
} private void DoAddTestOrderDetail(TestOrderDetail testOrderDetail)
{
this.TestOrderDetails.Add(testOrderDetail); this.Total += testOrderDetail.Subtotal;
} private void DoUpdateTestOrderDetail(Guid testOrderDetailId, decimal subtotal)
{
var testOrderDetail = this.TestOrderDetails.First(x => x.Id == testOrderDetailId); this.Total -= testOrderDetail.Subtotal;
testOrderDetail.Subtotal = subtotal;
this.Total += testOrderDetail.Subtotal;
} private void DoRemoveTestOrderDetail(Guid testOrderDetailId)
{
var testOrderDetail = this.TestOrderDetails.First(x => x.Id == testOrderDetailId); this.TestOrderDetails.Remove(testOrderDetail);
this.Total -= testOrderDetail.Subtotal;
} private void DoCommit()
{
this.OrderState = TestOrderStateFactory.CreatePendingVerificationTestOrderState(this);
} private void DoVerify()
{
this.OrderState = TestOrderStateFactory.CreateVerifiedTestOrderState(this); this.PublishEvent(new TestOrderVerified());
}
}
}
效果图
背景
写这个简单的Demo过程,遇到了很多小的决策,这篇文章就看做一个开头吧,后边重点介绍每个决策点,在一篇文章中真的很难说完,喜欢看代码的朋友,先去https://happy.codeplex.com/下载。
DDD:订单管理 之 如何组织代码的更多相关文章
- RDIFramework.NET V3.3 WinForm版新增订单管理主从表事例
功能描述 无论什么系统,除了常规的单表处理外,主从表的应用都是非常普遍的,RDIFramework.NET V3.3 WinForm版本中新增了一个主从表的事例供大家参考.主从表的界面设计大同小异,主 ...
- Flask实战-留言板-安装虚拟环境、使用包组织代码
Flask实战 留言板 创建项目目录messageboard,从GreyLi的代码中把Pipfile和Pipfile.lock文件拷贝过来,这两个文件中定义了虚拟环境中需要安装的包的信息和位置,进入m ...
- 一个ssm综合小案例-商品订单管理----写在前面
学习了这么久,一直都是零零散散的,没有把知识串联起来综合运用一番 比如拦截器,全局异常处理,json 交互,RESTful 等,这些常见技术必须要掌握 接下来呢,我就打算通过这么一个综合案例把这段时间 ...
- Unity3D如何有效地组织代码?(转)
问题: Unity3D可以说是高度的Component-Based Architecture,同时它的库提供了大量的全局变量.如何来组织代码呢? 答: - Unity有一些自身的约定,譬如项目里的Ed ...
- 【共享单车】—— React后台管理系统开发手记:城市管理和订单管理
前言:以下内容基于React全家桶+AntD实战课程的学习实践过程记录.最终成果github地址:https://github.com/66Web/react-antd-manager,欢迎star. ...
- Unity3D如何有效地组织代码?
本文整理自知乎,原文链接:http://www.zhihu.com/question/21070379 问题: Unity3D可以说是高度的Component-Based Architecture,同 ...
- 使用 Git 来管理 Xcode 中的代码片段
使用 Git 来管理 Xcode 中的代码片段 代码片段介绍 xcode4 引入了一个新 feature: code snippets,在整个界面的右下角,可以通过快捷键:cmd + ctrl + o ...
- 如何管理你的 Javascript 代码
今天不聊技术的问题,咱们来聊聊在前端开发中如何管理好自己的 Javascript 代码.首先,咱们先来说说一般都有哪些管理方式?我相信 seajs . requirejs 对于前端开发者而言都不陌 ...
- Python基础-修改excel、redis、接口开发、组织代码
pymysql模块补充内容 1. 游标.description():显示表的字段属性 (什么是游标:游标用于交互式应用,就好比word里的光标一样,要修改某个地方,要先把光标移动到这里) 用好这个方法 ...
随机推荐
- PHP读取流文件
$filepath = 'http://www.vip.com/down'; $fp = fopen($filepath,"r"); Header("Content-ty ...
- loghelper.cs 代码
唉,网上到处找一圈,真是麻烦,自己结合别人写的,重新整理一个. 这个破玩意最大的作用就是写微信那种没法顺利断点调试的程序的时候,在需要的地方写日志,然后去查看.真是回到当年用DW4写php的年代了,可 ...
- windows xp/7命令提示符强制结束指定进程
开始----“运行 ”输入cmd ,然后在命令提示符下输入tasklist,出现如下列表: Image Name PID Session Name ...
- android: 播放视频
播放视频文件其实并不比播放音频文件复杂,主要是使用 VideoView 类来实现的.这个 类将视频的显示和控制集于一身,使得我们仅仅借助它就可以完成一个简易的视频播放器. VideoView 的用法和 ...
- c#之第三课
学习获取终端输入的参数并且打印,以及使用循环. using System; public class CommandLine { public static void Main(string[] ar ...
- LM-Sensors unable to load driver module
Fix - sort of - for LM-Sensors unable to load driver module In short: In /etc/default/grub set GRUB_ ...
- InstallShield 2012 Spring优惠升级到最新版本(2015.4.30之前)
InstallShield 2012 Spring即将EOF,所以仍在使用InstallShield 2012 Spring的用户请注意下面内容: InstallShield 2012 Spring升 ...
- mysqld.exe 占了400M内存的问题
最近遇到了服务器总是停机的问题,虽然它自己只有2G的内存,不过实际部署的应用访问量非常小,也不至于2G就不够用,所以开始了给服务器瘦身的计划. 看着任务管理器里面的各个进程,发现吃内存最厉害的是mys ...
- Revit中如何添加水平仰视平面视图
在Revit平面视图中视角是俯视视角,但是在一些特殊的情况下,我们可能需要创建仰视视角的平面视图,例如我们需要向上看天花板的灯具布置的时候,下面举例说明添加仰视平面视图的方法. 如图在模型中有一楼板跟 ...
- easyui tree 编辑后保留原先状态
$(function () { var selected = $('#depttree').tree('getSelected'); $('#depttree').tree({ checkbox: f ...