写点什么

编写高质量可维护的代码:数据建模

  • 2021-05-01
  • 本文字数:3819 字

    阅读完需:约 13 分钟

编写高质量可维护的代码:数据建模

什么是数据建模


数据建模是一种用于定义和分析数据的要求和其需要的相应支持的信息系统的过程。


随着前端页面的交互变得更加细腻复杂,原本存放于服务端的状态放置在了前端,类似 flux、redux、mobx、dva、rematch、vuex 的状态管理库也成了每个项目的标配。


因为分层理念的普及,前端工程师们需要把更多精力放在数据管理上,数据建模也成了基本功。


而建模的产物是数据模型,数据模型是定义数据如何输入和输出的一种模型,其主要作用是为信息系统提供数据的定义和格式。


数据模型包括数据结构、数据操作、数据完整性约束条件这三要素。


简单理解就是数据模型提供了一个“模具”,数据按照预先的设计和约束进行放置。


三要素

数据完整性约束条件


好的数据结构必须要有约束,例如描述同一个状态的字段有时候是字符串,有时候是数字,这样的话就容易造成预期之外的情况。添加约束可以最大限度保障这份数据是干净整齐的。


// status 是字符串的时候不通过if (status === 1) {  ...}
复制代码


// 按照一定约束model.define(  'user',  {    name: {       field: 'name',      type: STRING(64),      allowNull: false,       comment: '姓名', },    sex: {   field: 'sex',      type: INTEGER(1),      allowNull: false,      comment: '性别',    }  });
复制代码


数据结构


描述模型本身的性质之外,还需通过某些字段表达模型(表)和模型之间的关联。

数据操作


在数据结构上对数据或者数据之间的关联关系的操作。


领域驱动设计


在围绕着数据模型进行应用开发的时候,我们会思考如何进行建模呢?


实际上,软件开发行业中已经积累了一些方法论,例如领域驱动设计 (DDD) 就被广泛采用。


在进行软件开发前,通常需要先进行业务知识梳理,而后到达软件设计的层面,最后才是开发。而在业务知识梳理的过程中,我们必然会形成某个领域知识。根据领域知识来一步步驱动软件设计,就是领域驱动设计的基本概念。简单来说领域驱动设计就是关注精简的业务模型及实现的匹配


分层架构


按照领域驱动设计的分层架构可以将应用进行分层


  • UI 层:负责向用户展现信息以及解释用户命令。

  • 应用层:用来协调应用的活动。它不包含业务逻辑;它不保留业务对象的状态;但它保有应用任务的进度状态。

  • 领域层:业务软件的核心所在。在这里保留业务对象的状态,对业务对象和它们状态的持久化被委托给了基础设施层。

  • 基础设施层:为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用。



按照这个分层,越往左边代码变动越频繁。随着业务复杂,应用层和领域层的边界变得模糊,领域之间也容易交错在一起。


良好的设计应该避免层与层之间产生过多依赖,如果代码没有被清晰隔离到某层中,它会迅即混乱和难以维护。


通过分层架构和高内聚低耦合的设计思想,最终实现系统与需求有较好的一致性,在业务迭代中快速响应需求变更。


实体


实体在领域模型中是必需的对象,并且它们应该在建模过程开始时就被考虑。例如要实现一个“猫”的概念,我们可能会去创造一个 Cat 的类,这个 Cat 可能包含名称、性别、品种等属性,但是这些属性都不足以区分这只猫,所以我们需要创建一个唯一不重复的 ID 来区分他们,也就区分实体的标识符。


创建 ID 的方式有很多种,它可以是主键、可以来自外部、也可以由系统自己产生,但它必须符合模型中的身份差别。


值对象


用来描述领域的特殊方面,且没有标识符的一个对象,叫做值对象。例如画布上的一个点 Customer 会跟姓名、省份、城市、区、街道相关。最好是将地址分离出来,保留对地址的引用,因为它们都是同一个址属性。



服务


你可以简单地将行为理解成一种服务。例如你去商店购买商品,你的朋友也可以去购买商品。如果将购买这个能力作为一个属性放在 Person 这个实体里显然有点不对劲,因为“去购买”这个功能并不属于你和你的朋友(实体或者值对象),同时去购买也可能涉及到商品对象。


保证服务的单一性和隔离非常重要,注意区分领域服务和应用服务。决定一个服务所应归属的层是非常困难的事情,我们在设计阶段建立模型时,需要确保领域层保持从其他层中隔离开来。


模块


模块是一种被用来作为组织相关概念和任务以便降低复杂性的方法,通常情况下由功能或者逻辑上属于一体的元素构成,以保证高内聚,同时通过接口的形式暴露给第三方以降低模块之间的耦合。


聚合


聚合是针对数据变化可以考虑成一个单元的一组相关对象。聚合基于(有且仅有)一个实体(根),聚合通过这个根被外部访问,它可以引用任意聚合或者被其他聚合引用。以下是一个简单的聚合例子:客户作为聚合的根,其他信息都是客户内部的,如果需要地址则将地址的拷贝传递出去( Javascript 中特别需要注意)。



工厂


工厂用来封装对象创建所必需的知识,它们对创建聚合特别有用。工厂方法是一个对象的方法,包含并隐藏了创建其他对象的必要知识。


资源库


资源库作为一个全局可访问对象的存储点而存在。它是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口,提供给领域层进行领域对象的访问。


前端的数据建模


数据建模和后端的工作关联较为紧密,前端的数据模型更多是依赖后端传递的数据传输对象(DTO)进行二次构建。无论二次构建是发生在服务端聚合阶段还是用户端 AJAX 请求完成阶段,前端都需要参与一定的数据清洗,并应用到前端的数据模型之上。


领域划分


现在你可以开始尝试划分你应用内的业务领域。以一个商城为例子,它可能会包括用户、商品、货架、订单、结算、账户等内容。



每一个业务领域都可以至少拆分成一个领域,按照业务领域来组织代码,例如在交易领域中按照以下目录结构划分:


src  modules    ...    trading             # 交易领域      components/         # 组件      models/             # models      pages/              # 页面      redux/              # redux      services/           # 交易模块相关api      styles/             # 交易模块样式      index.ts  ...
复制代码


概念模型


数据建模的前提是对业务的充分理解,充分理解业务相当于在更高的视角去看待业务之间的关系,有利于更好地完成模型建设。


尝试回想一下你所维护的业务(应用)场景,你是否清晰业务场景和业务对象之间的关系以及具体交互?


使用思维导图梳理出概念模型,这个阶段可以不用严格遵守三要素,目标清晰表达现实世界就行。



定义模型


定义模型可以依据概念模型,补充细节和关联关系,例如简单定义一个营销商品:



以上展示了商场货架上划分的一块活动区域,规则是满 XX 减 XX,再将参与该活动的商品在区域内进行上架。


降低复杂度


在大部分情况下,特别是展示逻辑这块,前端不应该是重逻辑的。


以商品为例,不同商品的营销类型背后隐藏着复杂的价格体系,尽管是同一种营销类型,商品在不同的状态展示的价格也不一定相同。你可以想象这背后的字段,以及计算规则。


假如后端把这些字段、各种 price 和规则一股脑抛给你,先不谈前后端对称问题,光挑字段都能让你目瞪狗呆。



遇到类似情况更好的办法是:尽量避免在前端(用户端)去处理复杂的业务判断,在聚合层或者让后端同学给你处理好这些展示逻辑。


特别是在 C 端场景下,数据直出显得更加重要,同时前端同学也有更多时间去做性能优化(早点下班不香么?)。


另外一个好处是假如出现展示问题,你只要确定读取的字段正确,剩下的仅需一个人排查就够了;


// Badconst switchPrice = product => {  switch(product.status) {    case 0:     return product.priceA;    case 1:     return product.priceB;    case 2:     return product.priceB;    default:     return  product.priceBase;  }}<Price value={switchPrice(product)}/>     // Good<Price value={product.price}/>
复制代码


逻辑分层


设计上需要区分应用逻辑(业务逻辑)和展示逻辑。应用层注重对领域层的调度,是业务逻辑的实现,展示层专注渲染和交互动作。


在一个大型项目中,同一个 Model 可能被多处引用,你很难确定谁最终会对同一份数据进行怎样的操作。


同时 Model 中仅保留数据源的抽象结构,而不修改数据源的内容。


// 在视图层只做展示逻辑处理// 组件A...<> <span>日期:{format(res.date, 'YYYY-MM-DD')}</span></>
// 组件B...<> <span>日期:{format(res.date, 'YYYY-MM')}</span></>
复制代码


统一字段


在设计模型的时候,尽可能与后端保持统一字段。比如某些表单场景在回显和提交的时候要多一层转换,后期维护会带来多一层心智负担。在前后端分离的开发模式下,不一定能保证后端会先给出字段,我的习惯是标记字段,等联调的时候全局替换一下就行了。


简化字段、明确语义、改变不合理的前后端交互是做好数据建模的基础,否则你将花费大量时间去理解这些字段背后的含义和计算规则。


小结


没有一个十全十美的数据模型可以适用任何需求场景,模型的落地需要综合考虑业务实际场景和技术选型。在构建模型的过程中,锻炼系统性思考能力、从更高的视角看待业务,才能创造出一个生命周期更长的模型。



头图:Unsplash

作者:村雨

原文:https://mp.weixin.qq.com/s/RLuIX4O31PhcPCgMWivi7w

原文:编写高质量可维护的代码:数据建模

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-05-01 04:041630

评论

发布
暂无评论
发现更多内容

向往优雅的代码

Janenesome

编码习惯 读书

从引用聊一聊 Java 垃圾回收

Rayjun

Java 引用 对象

ArrayList 源码分析

读钓

Java 源码分析 jdk源码

Tomcat学习分享

印無印

tomcat

鄙视链 & 全栈

伯薇

学习 能力提升 全栈

Service Provider Interface介绍

Skysper

spi

【ARTS】Week 1

Amos

ARTS 打卡计划

数据与广告系列二:计算广告和推荐系统

黄崇远@数据虫巢

数据挖掘 大数据 互联网 广告 推荐系统

其实,还是让我挺震惊的,程序员的换行率竟然高达 40%

非著名程序员

程序员 程序人生 自我思考

Kubernetes 资料集合

倪朋飞

学习 Kubernetes 架构模式

leetcode练级-只出现一次的数字

幸福三寸日光

算法 LeetCode js

【应用异常监控利器Sentry搭建与学习笔记】

卓丁

Docker Sertry Fasthttp CI/CD Go 语言

ARTS week1

紫枫

ARTS 打卡计划

ARTS打卡Week 01

teoking

android WebRTC

字节跳动:高级人才的五个基本素质

池建强

人才培养

我的 Windows Terminal 配置

FeiLong

Windows Terminal

转行程序员浅谈Linux下的多线程编程

WB

Linux 程序员 多线程

Linux如何调试内存泄漏

泰伦卢

c c++ C#

编程入门整理

紫枫

读书笔记

《陆蓉行为金融学讲义》 - 读后感

石云升

读书笔记 投资 行为金融学 理性 公平

Python 3.6.1 官方文档练习——初入江湖(三)

小匚

Python python教程

青春期的打油诗

印無印

随笔

Algorithm week 1: Merge Two Sorted Lists

猫吃小怪兽

算法 链表 ARTS 打卡计划

Spring Data R2DBC 入门

稻草鸟人

MySQL WebFlux springboot R2DBC

关于工作的一点总结

印無印

工作思路

Lucene的Smart CN实现分词、停用词、扩展词

Page

中文分词 lucene 停用词 扩展词 SmartCN

音视频会议系统-Janus的安装与布署

音视频专家-李超

音视频 WebRTC

宏在C++中的替代解决方案

老王同学

c++ 模板 template

修改Tomcat窗口的名称

阡陌r

Java tomcat 踩坑 实施

“数据资产”究竟是“数据”还是“资产”

马踏飞机747

大数据 数据中台 数据治理 数据资产

leetcode练级-只出现一次的数字 升级版

幸福三寸日光

算法 LeetCode js

编写高质量可维护的代码:数据建模_语言 & 开发_政采云前端团队_InfoQ精选文章