二代水务系统架构设计分享——DDD+个性化
系统要求
C/S架构的单体桌面应用,可以满足客户个性化需求,易于升级和维护。相比于一代Winform,界面要求美观,控件丰富可定制。
解决方案
依托.Net6开发平台,采用模块化思想设计(即分而治之的策略),每个模块采用DDD分层设计。前端选用WPF + Prism 框架,后端选用ABP + EF框架,数据库选择SQL Server。
业务拆分
核心领域:包含用户管理、客户管理、表具管理、方案管理、抄表管理
通用领域:包含权限、菜单、个人中心、参数配置、审计日志、数据字典
支撑领域:包含数据查询、统计报表、消息管理、STS安全、工单、自动升级
业务建模
通过业务拆分,水务领域已经被划分为若干子领域(即模块)。每个模块可以看成是一个限界上下文,在此边界内可以进一步拆分更细粒度的单元(我们把最小的业务单元叫做聚合)。一个模块也可能只有一个聚合,如:数据字典。
先拿简单的用户管理来说,用户(User)、角色(Role)、组织(Organization)三者关系紧密、彼此协作。一个用户可以拥有多个角色,一个组织可以拥有多个用户,因此可以归属到用户界限上下文,它们既是实体又是聚合根。
再拿复杂一点的客户管理来说,客户可分为预付费和后付费两种。对于预付费客户来说,可以按量或按金额购水。对于后付费客户来说,每月会生成一笔月账单,客户需要及时充值,否则就会欠费。因此,在客户这个业务边界内,可以确定客户(Customer)、客户类型(CustomerType)、交易记录(Transactions)、月账单(MonthlyBilling)等领域对象。其中水费的计算是一个非常复杂的业务逻辑,涉及到阶梯价格、附加费、债务、当月历史售水交易等多个领域实体,所以需要借助领域服务来完成这笔交易。
系统设计
系统设计包含两部分:全局设计和局部设计。
全局设计是从框架角度来进行整体设计。系统框图如下:

虚线框部分为笔者设计的Lapis.Framework框架,文件组织结构如下:

其中,Laison.Lapis.XXX文件夹代表基础业务模块(Laison.Lapis.Shared除外),如:审计日志、消息管理等,每个模块就是一个独立的C#解决方案。Laison.Lapis是一个底层类库,封装各种通用基础功能,供上层调用。Laison.Lapis.Shared称之为共享模块,用于封装和业务无关,可以被业务模块共享使用的功能,如: 抽象基类、接口、虚方法等。因为是单体应用设计,所以模块之间少不了相互引用的关系。模块划分越多,引用关系也会变得越复杂。
局部设计是从业务角度来进行模块设计,每个模块按照DDD来分层:基础设施层、领域层、应用层和表现层。下面是用户管理模块(Laison.Lapis.Identity)的分层设计:

Laison.Lapis.Identity.UI:表现层,包含前端页面、控件和一些UI逻辑。
Laison.Lapis.Identity.Application.Contracts:应用接口层,负责定义应用接口和DTO。
Laison.Lapis.Identity.Application:应用层,用于实现应用接口,包含各种应用逻辑。
Laison.Lapis.Identity.Domain:领域层,负责业务逻辑处理,包含实体、值对象、仓储接口等领域模型。
Laison.Lapis.Identity.Domain.Shared:领域共享层,包含了一些枚举和常量的定义,可供其它任意层调用。
Laison.Lapis.Identity.EntityFramework:实体框架层(EF),包含实体的映射配置和仓储的实现。
Laison.Lapis.Identity.HttpApi.Host 和 Laison.Lapis.Identity.Shell:即宿主和壳,分别用来启动前/后端应用程序。
在ABP框架的世界里,每一分层就是一个模块单元,它是代码层面的模块,而Identity是业务层面的模块,后者包含前者,最终以DLL的形式来呈现。
系统如何运行
但随着系统不断迭代,模块数量也再随之增长。假设1个模块会生成6个DLL(采用DDD分层),那么20个模块就会有120个DLL,听上去有点吓人。每次模块发生变化,就要重新打包、发布、升级120个Nuget包,这显然不符合常理(非常耗时)。后来笔者采取了一种折中的处理方式:将通用稳定性强的模块保留在Lapis.Framework中,把易于变化的模块下沉到项目中管理,这样就能避免之前的尴尬,但却牺牲了模块代码的复用性。项目最终代码结构如下:

个性化定制
企业个性化功能定制是一个普遍而又绕不开的话题,假如能用一套代码把所有客户的需求全部覆盖,当然是最理想的状态(现实几乎不可能,除非通用标准化)。所以笔者从设计一开始就是围绕如何提高代码的复用,将差异化分散到项目中的思路来展开,这也是采用模块化设计的初衷。至于个性化如何来实施?根据笔者已知的做法有以下两种:1. 通过版本管理开分支的方式,2. 简单粗暴,每个项目都单独复制一套标准版,然后在上面做定制化修改。 除此之外,笔者似乎没有想到更好的办法。至于选择哪一种,就要根据项目实际情况来考量。
结束语
无论是模块化设计还是DDD,都是强调从业务领域角度出发,根据业务的发展,合理划分业务边界,采用分治策略,降低业务和软件开发的复杂度,持续调整现有架构,以保持架构和代码的生命力。 因此,世界上并不存在一劳永逸的架构,软件设计本身就是一个平衡取舍的过程,只有合适的才是最好的,这也许就是架构设计的魅力所在吧!如果你有好的建议或不同想法,欢迎评论区留言。
二代水务系统架构设计分享——DDD+个性化的更多相关文章
- 万级TPS亿级流水-中台账户系统架构设计
万级TPS亿级流水-中台账户系统架构设计 标签:高并发 万级TPS 亿级流水 账户系统 背景 业务模型 应用层设计 数据层设计 日切对账 背景 我们需要给所有前台业务提供统一的账户系统,用来支撑所有前 ...
- 基于Struts2,Spring4,Hibernate4框架的系统架构设计与示例系统实现
笔者在大学中迷迷糊糊地度过了四年的光景,心中有那么一点目标,但总感觉找不到发力的方向. 在四年间,尝试写过代码结构糟糕,没有意义的课程设计,尝试捣鼓过Android开发,尝试探索过软件工程在实际开发中 ...
- 新零售SaaS架构:多租户系统架构设计
什么是多租户? 多租户是SaaS领域的特有产物,在SaaS服务中,租户是指使用SaaS系统的客户,租户不同于用户,例如,B端SaaS产品,用户可能是某个组织下的员工,但整个企业组织是SaaS系统的租户 ...
- PetShop的系统架构设计
<解剖PetShop>系列 一.PetShop的系统架构设计 http://www.cnblogs.com/wayfarer/archive/2007/03/23/375382.html ...
- petshop4.0 具体解释之中的一个(系统架构设计)
前言:PetShop是一个范例,微软用它来展示.Net企业系统开发的能力.业界有很多.Net与J2EE之争,很多数据是从微软的PetShop和Sun的PetStore而来.这样的争论不可避免带有浓厚的 ...
- NET ERP系统架构设计
解析大型.NET ERP系统架构设计 Framework+ Application 设计模式 我对大型系统的理解,从数量上面来讲,源代码超过百万行以上,系统有超过300个以上的功能,从质量上来讲系统应 ...
- Unity3D手游开发日记(2) - 技能系统架构设计
我想把技能做的比较牛逼,所以项目一开始我就在思考,是否需要一个灵活自由的技能系统架构设计,传统的技能设计,做法都是填excel表,技能需要什么,都填表里,很死板,比如有的技能只需要1个特效,有的要10 ...
- 图数据库 Nebula Graph 的数据模型和系统架构设计
Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,而且能够提供极高的 ...
- 5G 融合计费系统架构设计与实现(一)
5G 融合计费系统架构设计与实现(一) 随着5G商用临近,5G的各个子系统也在加紧研发调试,本人有兴全程参与5G中的融合计费系统(CCS)的设计.开发.联调工作.接下来将用几篇文章介绍我们在CCS实现 ...
- [转]【转】大型高性能ASP.NET系统架构设计
大型高性能ASP.NET系统架构设计 大型动态应用系统平台主要是针对于大流量.高并发网站建立的底层系统架构.大型网站的运行需要一个可靠.安全.可扩展.易维护的应用系统平台做为支撑,以保证网站应用的平稳 ...
随机推荐
- 【Python基础】数据类型与类型转换
五种基本数据类型 在 Python 中,基本数据类型是指不可变对象的数据类型.以下是 Python 中的基本数据类型: 整数类型(int):表示整数,例如 1.2.3 等等. 浮点数类型(float) ...
- 基于python爬虫技术对于淘宝的数据分析的设计与实现
本篇仅在于交流学习 本文主要介绍通过 selenium 模块和 requests 模块,同时让机器模拟人在浏览器上的行为,登录指定的网站,通过网站内部的搜索引擎来搜索自己相应的信息,从而获取相应关键字 ...
- Django transaction.atomic 事务的使用
函数 transaction.atomic 数据库的读写操作中,事务在保证数据的安全性和一致性方面起着关键的作用,而回滚正是这里面的核心操作. 遇到并发的时候常常会因为接口的访问顺序或者其他情况,导致 ...
- stl-----map去重,排序,计数
一.map erase()删除函数:可以迭代器删除,关键字删除,成片删除. 例:1.iter=mapStu.find(1); mapStu.erase(iter); 2.int n = mapStu. ...
- [MAUI程序设计]界面多态与实现
目录 需求一:在不同设备上使用不同 UI 外观 定义控件 UI 外观 基于平台自定义配置 需求二:在不同数据类别中使用不同的 UI 外观 定义视图 UI 外观 创建数据模板 创建选择器 定义数据 需求 ...
- vue全家桶进阶之路24:Mock
Mock 是一个 JavaScript 库,用于生成随机数据或模拟 HTTP 请求响应,用于前端开发中的单元测试.功能测试.集成测试等场景. Mock 可以生成各种类型的数据,包括字符串.数字.布尔值 ...
- [ABC268C] Chinese Restaurant
[ABC268C] Chinese Restaurant 声明:以上的所有操作都会再做一次\(%n+n)%n\),比如\(i - 1\)会变成\(((i-1)%n+n)%n\) 题意 有 \(n\) ...
- pytorch学习笔记——timm库
当使用ChatGPT帮我们工作的时候,确实很大一部分人就会失业,当然也有很大一部分人收益其中.我今天继续使用其帮我了解新的内容,也就是timm库.毫不夸张的说,Chat GPT比百分之80的博客讲的更 ...
- MYSQL数据库的创建和删除
打开Windows命令行,输入登录用户和密码 mysql -h localhost -u root -p 创建新数据 CREATE DATABASE zoo; 查看系统中的数据库 SHOW DATAB ...
- ChatGPT教我用200行代码写一个简版Vue框架 - OpenTiny
AI 是未来最好的老师 最近,我正在准备一份关于 Vue 基础的学习材料.期间我突发奇想:能否利用现在热门的 ChatGPT 帮我创建学习内容?其实 Vue 本身不难学,特别是基础用法,但是,如果你想 ...