之前我写了几篇关于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. python的序列类

    1,我们常见的数据结构有哪些是序列类 序列类型的分类: ①  容器序列:list,tuple,deque(可以防止任意的类型的容器) ②  扁平序列:str,bytes,bytearray,array ...

  2. [leetcode]11. Container With Most Water存水最多的容器

    Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). ...

  3. WebApi上传文件

    上网搜了下Web Api上传文件的功能,发现都写的好麻烦,就自己写了一个,比较简单,直接上传文件就可以,可以用Postman测试. 简单的举例 /// <summary> /// 超级简单 ...

  4. 深入理解 requestAnimationFrame

    在Web应用中,实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,css3 可以使用 transition 和 animation 来实现,html5 中 ...

  5. shell脚本编写informix数据库中表的导入和导出

    表的导入: 第一行:是指此脚本使用/bin/bash来解释执行. 第四行:定义一个list,里面存放表的名称,之间用空格隔开. 第七行:dbaccess tofpe(数据库名) <<EOF ...

  6. 小米open-falcon监控系统接入手册

    一.新项目接入 0.官方文档: https://book.open-falcon.org/zh_0_2/usage/getting-started.html 1.联系运维人员确定可以使用监控系统: ( ...

  7. Configuration Error: deployment source 'SocietyManage:war exploded' is not valid

    Configuration Error: deployment source 'SocietyManage:war exploded' is not valid 原因:没有下图的底下的红色框的内容.( ...

  8. Java程序员职业生涯规划完整版:从程序员到CTO( 摘)

    在技巧方面无论我们怎么学习,总感觉需要晋升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样我们清楚的知道我们大概处于那个阶段和水平. Java程序员 高等特性 反射.泛型. ...

  9. oracle表空间扩容方法

    1.使用navicat连接要扩容的数据库,进入其他-表空间 2.添加数据文件和设置配置项即可

  10. Python自动化开发 - MySQL(一)

    本节内容 一.概述 二.下载安装 三.数据库操作 四.数据表操作 五.表内容操作 一.概述 1.什么是数据库 ? 答:数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 2.什么 ...