用DDD模拟案例分析
之前我写了几篇关于DDD的介绍和一些小例子说明,我想这对于介绍DDD还是有些模糊,甚至还不知道怎么用DDD来分析设计。昨天和园友讨论也发现没有例子很难说明,所以今天我模拟了一个案例,同时这个案例也是真实的。在写此文时我并没有给出最终的解决方案,是用来和园友交流的,我会不定时把我们讨论的结果作更新,如果你喜欢的话可能需要随时关注,但我想这个结果可能也不会很快。
好了,说下这个需求的背景吧:
本人酷爱乒乓球运动,有机会参加了一个民间组织。此组织人员众多,而且固定活动的场所会有多个,成员可以选择其中的任何一个场所活动。新进成员或余额不足的成员会一次性交纳100左右的活动费,每次活动均由管理成员对参加活动的人员进行记录,然后去更新成员的余额,管理员初期采用手工记帐,统计非常麻烦,时间久了如果各个管理员之间的信息同步不及时的话就会让统计变得非常麻烦,就会造成会员不清楚自己的余额和消费情况,所以产生了去做一个管理软件的想法。目前虽然实现了一个,但没有使用DDD,以数据库为中心,用到现在也挺稳定。 那么我就想用DDD如何去做。
场景:
1.在活动现场有成员向管理员交纳预存款时,管理员会记录成员信息及编号。线下该管理员需要在系统中创建一个收款单,记录缴费的成员及金额。
2.现场管理员会记录参与当前活动的成员信息及编号。线下该管理员需要在系统中创建一个付款单,记录活动的成员及金额,同时单据上要实际标明实际场地费用。
业务需求:
1.只有是记帐用户才能创建收付款单据(即拥有account的user)。
2.只有具有CFO角色权限的user创建收付款单据时才能选择其他account,否则他只能选自己的account。
3.同样修改单据时只CFO角色权限的user才能变更account。
4.只有具有CFO角色权限的user才能作废所有的单据,否则只能作废自己的单据。
相关知识点:
1.用户是指参加此组织的所有成员(包括场地管理员和总管理员)
2.角色是指为某些用户定义了一些具有某些操作权限的小组。
3.帐户是指为某一个用户指定了一个收付款帐号(此帐户只能关联一个用户),组织成员交的活动经费会存入到此帐户下,场地费用也会从此帐户支出。
4.目前管理中权限角色分为普通用户(Nothing),只能查看自己的余额;记帐员(Cashier),可以创建或修改收付款单据,但在录入时只能选择自己的帐号。大管理员(CFO)没有记录的限制。
5.每个收付款单据会对应一个基金单据。(由于每次活动基本固定消费为5元/人,如此次有20人参加活动,而实际租场地费用为80元,这样多出来的20元会划入球队基金用于举办一些活动这样子的)
我先简单设计了一下,不一定是合理的,仅供参考
这是另一种画图
为了方便你的思考,帖上代码,你也可以先自己去实现一下,我也很期待你的方案。
using System;
using System.Collections.Generic;
using System.Linq; namespace Domain
{
public enum RoleType
{
Nothing,
Cashier,
CFO
} public class Role
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Grants { get; private set; } public RoleType GetRoleType()
{
return RoleType.Nothing;
}
} public class User
{
public Guid Id { get; private set; }
public string Code { get; private set; }
public string Name { get; private set; }
public Guid RoleID { get; private set; }
public Guid AccountID { get; private set; }
public decimal Balance { get; }
} public class Account
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public decimal Balance { get; }
} public class DocumentType
{
public Guid Id { get; private set; }
public string Name { get; private set; }
} /// <summary>
/// 单据收付款类型
/// </summary>
public enum ReceiptType
{
PAY,
RCV
} public class Receipt
{
public Receipt(Guid id)
{
this.Id = id;
this.ReceiptItems = new List<ReceiptItem>();
} public Guid Id { get; private set; }
public string ReceiptNo { get; private set; }
public Guid DocumentTypeID { get; private set; }
public DateTime Date { get; private set; }
public ReceiptType ReceiptType { get; private set; }
public Guid AccountID { get; private set; }
public bool IsCancel { get; private set; }
public string Remark { get; private set; }
public decimal TotalAmount { get; private set; }
public decimal FactAmount { get; private set; }
/// <summary>
/// 差额
/// </summary>
public decimal Profit
{
get { return this.FactAmount - this.TotalAmount; }
}
public virtual IList<ReceiptItem> ReceiptItems { get; private set; } public void Canceled()
{
if (this.IsCancel)
throw new Exception("单据已经作废,不能再次作废。"); this.IsCancel = true;
} private void ChangeDetails(IList<ReceiptItem> receiptItems)
{
var currentItemsCount = this.ReceiptItems.Count;
var submitItemsCount = receiptItems.Count; if (currentItemsCount > submitItemsCount) {
for (int index = currentItemsCount; index > submitItemsCount; index--) {
this.ReceiptItems.RemoveAt(index - );
}
} for (int index = ; index < submitItemsCount; index++) {
var item = receiptItems[index];
if (index >= currentItemsCount) {
this.ReceiptItems.Add(new ReceiptItem());
} this.ReceiptItems[index].RowNum = index + ;
this.ReceiptItems[index].UserID = item.UserID;
this.ReceiptItems[index].Money = item.Money;
} this.Totaled();
} private void Totaled()
{
if (this.ReceiptItems != null && this.ReceiptItems.Count > ) {
this.TotalAmount = this.ReceiptItems.Sum(p => p.Money);
}
} public class ReceiptItem
{
public Guid ReceiptID { get; set; }
public int RowNum { get; set; }
public Guid UserID { get; set; }
public decimal Money { get; set; }
}
} /// <summary>
/// 基金单据
/// </summary>
public class Fund
{
public Fund(Guid id)
{
this.Id = id;
} public Guid Id { get; private set; }
public string PropertyNo { get; private set; }
public Guid DocumentTypeID { get; private set; }
public decimal Money { get; private set; }
public DateTime Date { get; private set; }
public ReceiptType ReceiptType { get; private set; }
public Guid AccountID { get; private set; }
public bool IsCancel { get; private set; }
public string Remark { get; private set; } public void Canceled()
{
if (this.IsCancel)
throw new Exception("单据已经作废,不能再次作废。"); this.IsCancel = true;
} public static Fund CreateByReceipt(Receipt receipt)
{
Fund property = new Fund(receipt.Id); property.PropertyNo = receipt.ReceiptNo;
property.Date = receipt.Date;
property.AccountID = receipt.AccountID;
property.ReceiptType = receipt.Profit >= ? ReceiptType.RCV : ReceiptType.PAY;
property.DocumentTypeID = receipt.DocumentTypeID;
property.Money = Math.Abs(receipt.Profit);
property.Remark = receipt.Remark; return property;
}
}
}
最后为了方便你的理解,我画了一个ui帮助说明,创建一个付款单需要录入哪些数据
注:现金帐户就是account的列表。
期待园友们的讨论。。。
项目已初步开发完成。源码请直通https://github.com/imyounghan/ppqclub
用DDD模拟案例分析的更多相关文章
- ENode框架Conference案例分析系列之 - 业务简介
前言 ENode是一个应用开发框架.通过ENode,我们可以方便的开发基于DDD+CQRS+EventSourcing+EDA架构的应用程序.之前我已经写了很多关于ENode的架构以及设计原理的文章, ...
- CSS3-3D制作案例分析实战
一.前言 上一节,介绍了基础的CSS3 3D动画原理实现,也举了一个小小的例子来演示,但是有朋友跟我私信说想看看一些关于CSS3 3D的实例,所以在这里为了满足一下大家的需求,同时也为了以后能够更好的 ...
- K米APP案例分析
关于 K米 -- 的案例分析 产品 K米的APP (全国KTV点歌,手机直播,互动,交友,预订)的Android客户端 第一部分 调研,评测 评测: 软件的bug,功能评测,黑箱测试 • 下载并使用, ...
- 第三次个人作业——关于K米(Andorid)的案例分析
第三次个人作业--关于K米(Andorid)的案例分析 1.K米简介 官方网址:http://www.ktvme.com/ 2.评测 2.1.上手体验 带着找bug的心态,兴致勃勃地开始体验 K米.打 ...
- 161220、使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- [Mugeda HTML5技术教程之15]案例分析:制作移动教育课件
本文档要分析的案例是一个一氧化碳还原氧化铜的教育小课件,从中可以体会一些Mugeda API的用法和使用Mugeda动画制作移动教育课件的方法.Mugeda为移动教育领域和移动数字出版领域提供理想的教 ...
- DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能 一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己 ...
- 一个特殊的SQL Server阻塞案例分析
上周,在SQL Server数据库下面遇到了一个有意思的SQL阻塞(SQL Blocking)案例.其实个人对SQL Server的阻塞还是颇有研究的.写过好几篇相关文章. 至于这里为什么要总结一下这 ...
- 170301、使用Spring AOP实现MySQL数据库读写分离案例分析
使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...
随机推荐
- 小强学渲染之OpenGL的CPU管线
读到这里,应该对OpenGL渲染管线有了初步简单了解.下面着重分析CPU管线,即逻辑控制中心做了什么,这部分还是容易理解的.如下图: 一,将数据加载到显存中. 这是由GPU是访问显存中的数据决定的.因 ...
- Metasploit用法大全
Metasploit用户接口msfconsoleArmitage: KaliGUI启动:armitage命令启动 Metasploit功能程序msfvenom集成了载荷生成器.载荷编码器.空指令生成 ...
- FortiGate设置E-mail告警
1.配置邮件服务器 2.配置告警
- 【.Net】 大文件可使用的文本分组统计工具(附带源码,原创)
本工具可实现的效果: 1.读取大文件(大于1GB) 2.根据分隔符分割后的列分组 3.速度快. 4.处理过程中,可以随时停止处理,操作不卡死. 5.有对当前内存的实时监测,避免过多占用内存,影响系统运 ...
- vue 需求 data中的数据之间的调用
我遇到过这种情况 就是在我的data中 会有数据调用data中的其他数据 如图 我的alertInfoType需要拿到screeningCondition中type的值 用过vue的都知道 我是不 ...
- 浅谈卷积和C++实现
1 信号处理中的卷积 无论是信号处理.图像处理还是其他一些领域,我们经常会在一些相互关联的数据处理中使用卷积.卷积可以说是算法中一个非常重要的概念.这个概念最早起源于信号处理之中. 假设对于一个线性系 ...
- Difference Among Mercedes Star Diagnostic Tool MB Star C3 C4 C5 C6
Mercedes Star Diagnostic Tool newly update to MB Star C6.There are many star diangostic tool in the ...
- 锻造(forging)
--九校联考24OI__D1T1 题目背景 勇者虽然武力值很高,但在经历了多次战斗后,发现怪物越来越难打,于是开始思考是不是自己平时锻炼没到位,于是苦练一个月后发现--自己连一个史莱姆都打不过了. 勇 ...
- python基础之Day12
一.闭包函数 什么是闭包函数? 闭:函数是一个内部函数 包:指的是该函数包含对外部作用域(非全局作用域)名字的引用. 给函数传值的方式有两种: 1.使用参数直接给函数传值 2.包给函数 1 2 3 4 ...
- Oracle partition by 使用说明
--用法详解 0.select * from wmg_test; ---测试数据 1.select v1,v2,sum(v2) over(order by v2) as sum --按 ...