之前我写了几篇关于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 - 1); } } for (int index = 0; index < submitItemsCount; index++) { var item = receiptItems[index]; if (index >= currentItemsCount) { this.ReceiptItems.Add(new ReceiptItem()); } this.ReceiptItems[index].RowNum = index + 1; this.ReceiptItems[index].UserID = item.UserID; this.ReceiptItems[index].Money = item.Money; } this.Totaled(); } private void Totaled() { if (this.ReceiptItems != null && this.ReceiptItems.Count > 0) { 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 >= 0 ? 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