之前我写了几篇关于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模拟案例分析的更多相关文章

  1. ENode框架Conference案例分析系列之 - 业务简介

    前言 ENode是一个应用开发框架.通过ENode,我们可以方便的开发基于DDD+CQRS+EventSourcing+EDA架构的应用程序.之前我已经写了很多关于ENode的架构以及设计原理的文章, ...

  2. CSS3-3D制作案例分析实战

    一.前言 上一节,介绍了基础的CSS3 3D动画原理实现,也举了一个小小的例子来演示,但是有朋友跟我私信说想看看一些关于CSS3 3D的实例,所以在这里为了满足一下大家的需求,同时也为了以后能够更好的 ...

  3. K米APP案例分析

    关于 K米 -- 的案例分析 产品 K米的APP (全国KTV点歌,手机直播,互动,交友,预订)的Android客户端 第一部分 调研,评测 评测: 软件的bug,功能评测,黑箱测试 • 下载并使用, ...

  4. 第三次个人作业——关于K米(Andorid)的案例分析

    第三次个人作业--关于K米(Andorid)的案例分析 1.K米简介 官方网址:http://www.ktvme.com/ 2.评测 2.1.上手体验 带着找bug的心态,兴致勃勃地开始体验 K米.打 ...

  5. 161220、使用Spring AOP实现MySQL数据库读写分离案例分析

    一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...

  6. [Mugeda HTML5技术教程之15]案例分析:制作移动教育课件

    本文档要分析的案例是一个一氧化碳还原氧化铜的教育小课件,从中可以体会一些Mugeda API的用法和使用Mugeda动画制作移动教育课件的方法.Mugeda为移动教育领域和移动数字出版领域提供理想的教 ...

  7. DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能

    DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能 一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己 ...

  8. 一个特殊的SQL Server阻塞案例分析

    上周,在SQL Server数据库下面遇到了一个有意思的SQL阻塞(SQL Blocking)案例.其实个人对SQL Server的阻塞还是颇有研究的.写过好几篇相关文章. 至于这里为什么要总结一下这 ...

  9. 170301、使用Spring AOP实现MySQL数据库读写分离案例分析

    使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...

随机推荐

  1. js 加减乘除失精

    js 计算失精是因为js 先将10十进制代码转化为2进制,再计算导致 具体解决方案: 1. 加 function accAdd(arg1,arg2){ var r1,r2,m; ].length}} ...

  2. Vimtutor中文版

    ================================================================================      欢     迎     阅  ...

  3. 互联网公司的面试官是如何360°无死角考察候选人的?[z]

    [z]https://juejin.im/post/5c0e47ebf265da614e2be9a7 一.写在前面 最近收到不少读者反馈,说自己在应聘一些中大型互联网公司的Java工程师岗位时遇到了不 ...

  4. bittorrent 学习(一) 种子文件分析与bitmap位图

    终于抽出时间来进行 BITTORRENT的学习了 BT想必大家都很熟悉了,是一种文件分发协议.每个下载者在下载的同时也在向其他下载者分享文件. 相对于FTP HTTP协议,BT并不是从某一个或者几个指 ...

  5. 主机性能监控之wmi 获取系统信息及内存性能信息

    标 题: 主机性能监控之wmi 获取系统信息及内存性能信息作 者: itdef链 接: http://www.cnblogs.com/itdef/p/3990240.html 欢迎转帖 请保持文本完整 ...

  6. httpd安装与配置(编译安装)

    httpd简介 httpd是Apache超文本传输协议(HTTP)服务器的主程序.被设计为一个独立运行的后台进程,它会建立一个处理请求的子进程或线程的池. 通常,httpd不应该被直接调用,而应该在类 ...

  7. 【Node.js】安装及使用

    Node.js是在Chrome的V8 JavaScript引擎上构建的JavaScript运行时.Node.js使用事件驱动的非阻塞I / O模型,使其轻量且高效.Node.js的软件包生态系统npm ...

  8. 2019.02.21 bzoj2300: [HAOI2011]防线修建(set+凸包)

    传送门 题意:动态维护凸包周长. 思路: 见这篇求面积的吧反正都是一个套路. 代码: #include<bits/stdc++.h> #define int long long #defi ...

  9. vue全局后置钩子afterEach

    beforeEach是路由跳转前执行的,afterEach是路由跳转后执行的. afterEach只有两个参数  afterEach((to,from)=>{}) 例子: router.afte ...

  10. 源码管理工具Git-windows平台使用Gitblit搭建Git服务器

    原文地址:https://blog.csdn.net/smellmine/article/details/52139299 搭建Git服务器,请参照上面链接. 注意: 第十二步:以Windows Se ...