有想法 x 也有做法
日常开发时,面对前人传承下来的专案,改 A 坏 B ,改 B 坏 C,
各种系统模组疯狂耦合与随意分类,乱糟糟的系统架构,开发得很痛苦 !
基本上,专案发展到这个程度,
完全修复,不是不可能,只是成本很大 :
修复的收成本 > 修复的收益
你我都知道,在实务上的时程安排 :
「有空再修」-> 美丽,但不切实际的幻想
也许,有机会砍掉重练,或者在其他的专案,有机会重头开始 !
但当面对全新的专案时,不希望重蹈覆彻
不是很确定该怎麽做 !?
我软件设计的「学习之路」,是从「无瑕的程序码 整洁的软件设计与架构篇」,这本书开始。
部分的理解内容,阐述在「软件的本质」。
但当时,还有一个疑问,书本内有很多理论与重要的设计原则。
不能很好的结合在日常开发中,中间的转换过程遇到了瓶颈。
直到在「Spring Cloud 微服务架构 开发实战」
提到的架构的规划方法 :
领域驱动设计
补足了我实作层面遇到的问题 !
让我可以将设计的理论原则,结合到 Java Spring 的专案中。
据此延伸,架构的规划是没有语言与平台的局限性。
开发 Android 与 iOS 的 App 时,也可以完全套用这一套软件设计的方法论。
软件提供的服务,一定是会对映现实世界的某件事物。但开发者与使用者,看待软件的角度,通常是不一样的。如何在两者之间,使用共同的语言,沟通会是一个挑战。
Domain Driven Design 简称 DDD
以领域知识为核心建立的模型,领域专家与开发人员,可以透过这个模型进行交流。
确保最终设计出来的东西,是双方共同想要达到的结果
领域驱动设计的分层结构,会分成四个部份。
越往上,离使用者越接近,是客户能够理解的部分。
越往下,则离程序语言与平台越接近,是开发者搭建系统的技术实作。
用户接口层 / 表示层(Presentaion)
从字面意思上理解,就是 UI 介面。
这一层,负责向用户显示信息或解释用户指令。
除了给人看的介面,也有可能是给机器看的介面,使用者可能会透过另外一个系统来访问你的系统。
可以理解为这一层就是飞机的机场或货轮的港口,负责国内外的进出口平台 :
进出的可以是人也可以是货物
定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。
用机场来说明的话,就是要上飞机前的海关检查程序。
每一个站点,要调用的领域物件与服务,就是稽核人员、查核护照与检查物品的动作。当出现异常时,依据程序,通知保安与巡警到达现场。
整套协调任务、分配工作的流程,就是「应用」
应用 = 流程
「应用」代表的是各种任务的背後的流程
用户介面层 : 只需要知道,要完成他的工作,需要调用哪一个应用。
应用层 : 只需要知道,要完成他的流程,需要调用领域层的哪个物件与服务。
「应用层」只管流程步骤,本身并不介入任务的实际执行。
模型层(Model Layer)
主要负责表达业务概念、业务状态信息及业务规则
以海关的稽核人员来说 :
业务 : 查核旅客的护照与身上的物品
业务状态讯息 : 查核哪些资讯与违禁物的清单
业务规则 : 有违禁品时 要进行通报
至於要调配哪一位,保安与巡警到达现场,则是管理中心 应用层的职责。
为上面各层提供通用的技术能力
例如:
可以理解为,海关安检时用的:
层与层之间的调用,是由上而下,并且可以跨层呼叫
但不会出现,由下而上或者平行呼叫的情况出现
就像是要请其他的部门协助支援:
一定是循组织规章程序作业,递交申请或者是向上通报,而不是直接跑到对方部门 ,在对方主管都不知情的情况下,要求同事协助帮忙。
这个服务调用之间的顺序与流程,也有一些原则可以遵循:
元件耦合性原则:
ADP : 无循环依赖原则
SDP : 稳定依赖原则
SAP : 稳定抽象原则
这部分,是属於「元件耦合性」的问题,後续会再独立介绍。
领域驱动设计,可以理解为,以领域层为核心,驱动整个系统的设计方法。
在跟领域专家讨论与建构模型的顺序:
(领域专家不需要去管技术实作)
领域层的模型建立,还缺了三个东西,用来描述这个模型 :
物件导向概念中的**「物件」,并且带有「标示符」**的对象。
POJO 物件
public class AppInfo {
private Long id; // <- 「标示符」
private String name;
private String version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
「标示符」
指的是当中的属性有唯一识别码,在经过软件的各个分层结构时,仍然保持一致。
例如:
相似於实体(Entity),两者的差异在於值对象,没有「标示符」。
POJO 物件
public class AppInfo {
private String name; // <- 没有「标示符」
private String version;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
为什麽没有标示符 ?
因为有时候,人们对於某个对象是什麽并不感兴趣,
只关心他所拥有的属性,能够描述领域的特殊方面,即可完成任务。
跟实体与值对象不太一样,在与领域专家规划模型时,会发现领域中有些方面是很难映射成对象。
尤其是领域中的动作,不属於任何对象,却代表了一个重要行为,此种行为通常会跨越若干的对象。
将此行为加入到任一对象中会破坏对象。
对象 = 物件 = 实体 / 值对象
最佳实做的方式是将它当成一个服务(Service)
可以简单地理解:
实体与值对象 -> 名词
服务 -> 动词
名词与动词两者互相搭配,描述了领域中的一项任务。
Java 最常见的 Spring 框架示范如何调整
一般 Spring 框架,书籍中的范例,通常会切分成三个阶层 :
优势
缺点
套用领域驱动设计的四个分层
用户界面层 (User Interface)
对应的是 Controller 保持不变
应用层 与 领域层 对应的是 Service
拆分成两个 :
基础设施层,对应的是 DAO
DAO 该算是基础设施层的一个子项模组
所以在基础设施层,划分一个数据库的区块存放
Infrastructure/repository
基础设施层,细节部分
领域层,细节的部分:
会在切分 实体 Entity 资料夹 与 值对象 Value Object 资料夹
此部分可以分类的更细
元件内聚性原则:
REP : 再使用性 - 发布等价原则
CCP : 共同封闭原则
CRP : 共同重复使用原则
这部分,是属於「元件内聚性」的问题,後续会再独立介绍。
应用层,细节的部分:
由於应用层,代表的是一连串动作的最终结果,返回的结果,可能包含了很多,实体与值对象的内容。
所以,会在应用层内,划分一个 DTO 资料夹
DTO 代表的是 Data Transfer Object,资料传输物件
资料传输物件,用於应用层的服务,资料物件的输入与输出。
通常,会以领域层的实体或值对象继承实作:
public class AppInfoDTO extends AppInfo {
private String groupId;
private String groupName;
private String groupDescription;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public String getGroupDescription() {
return groupDescription;
}
public void setGroupDescription(String groupDescription) {
this.groupDescription = groupDescription;
}
}
除了,直接扩展,省去了重新建构的时间以外,两者之间也建立了强关联,
一眼就可以看出这个传输物件,是属於哪一类的领域项目。
资料访问服务,细节部分:
若持久化框架,映射的「持久化物件」允许继承(ORM 框架 : MyBatis )
持久化物件:
PO , Persistent Object
同样会让该物件继承领域层的实体或值对象
public class AppInfoPO extends AppInfo {
public String createUser;
public String createTime;
public String modifyUser;
public String modifyTime;
public String getCreateUser() {
return createUser;
}
public void setCreateUser(String createUser) {
this.createUser = createUser;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getModifyUser() {
return modifyUser;
}
public void setModifyUser(String modifyUser) {
this.modifyUser = modifyUser;
}
public String getModifyTime() {
return modifyTime;
}
public void setModifyTime(String modifyTime) {
this.modifyTime = modifyTime;
}
}
扩增的部分,通常是资料库资料表才会特别纪录的数据:
以上的安排规划,使得领域层的服务,只会依赖於同层的领域物件;
不需要基础设施层的支持,就可以单独的进行单元测试。
这种安排规划,也可以套用在其他种程序语言与平台上
Android
iOS
伊隆 • 马斯克 :
如果你就读工程科系,并且对设计东西很有想法的话
自己创业,是个相对简单的事情。
你需要的就只是找几个和你理念相同的夥伴。
-- Youtube : 伊隆‧玛斯克对学生和大学毕业生的终极建议 - 如何拥有成功人生
你如果对设计有想法的话,现在更有了作法,起码明天上班时,就可以试着去优化你的系统。
我这个结合了书籍知识以及个人经验的,专案结构的规划方案,也是经过了一段时间的讨论,
才被团队逐渐认可,并且套用到下一个新的专案项目中。
事情并非一簇可几,但起码有了方向。
并且我认为软件设计的思维模式,也是一种大局观的思考方式 :
如果你可以组织映射现实的软件架构,那麽你当然也可以组织现实的实际事物。
未来的各种挑战,说不定哪时候就可以派上用场。
>>: 2021最新Canonical终极指南,短短的语法让你的SEO功力倍增提升网站能见度
在昨天的Profile页面中,我们可以看到有照片的讯息,那我们今天主要要来做的就是~ 把手机相簿里...
连假第二天,在这边先祝大家连假快乐啦,那延续昨天,我们现在已经把String的List拿到了,现在...
Shimmer iOS Swift的话是类似SkeletonView 一般用在等待的时候 像是API...
针对我们所谓的Mobile Security(移动装置安全、行动装置安全)。 经常会联想到智慧型手机...
前面我们有讲过 C# 中的函数,今天我们补充一点。 在 C# 中,支持一种函数叫做 “匿名函数”,即...