当前位置:首页 > 文章列表 > 数据库 > MySQL > 数据库schema不是CRUD服务的一切

数据库schema不是CRUD服务的一切

来源:SegmentFault 2023-01-21 16:25:01 0浏览 收藏

来到golang学习网的大家,相信都是编程学习爱好者,希望在这里学习数据库相关编程知识。下面本篇文章就来带大家聊聊《数据库schema不是CRUD服务的一切》,介绍一下MySQL、架构、后端、databaes、architecture,希望对大家的知识积累有所帮助,助力实战开发!

谨以本文向我脑海中那些不成熟的想法致敬。

序言

受疫情影响呆在家中的这段时间里,我收尾了《Clean Architecture》。这本书给了我许多新知识和启发,包括本文的中心论点——数据库schema不是CRUD服务的一切,也是在读书过程中想到的。在书中,作者的原话是

But the database is not the data model

它出现在书中第六部分《Details》的第一个章节中。作者认为,从架构的角度来看,数据库不是一个实体而是一个细节,不足以成为架构中的一个元素。他甚至打了个比方:数据库对于架构而言,就好像门把手对于房子一般。并且,作者进一步澄清了他的观点:他口中所说的数据库,不是指的数据模型。应用内的数据结构对架构而言至关重要,但数据库并不是数据模型。

这不禁让我回忆起了自己早期写设计文档的套路。

在我的从业生涯早期(说得好像我从业很久了一样),每当需要开发一个新的Web服务时,必须先写一份简要的设计文档,向上级清楚地表达我的实现思路,包括:

  1. 如何与其它服务协作完成产品提出的需求;
  2. 服务的接口描述;
  3. 数据的存储结构;
  4. 关键的算法等。

那时候的我会先考虑数据的存储结构,然后定义接口,最后才是与其它服务的协作。这些早期设计文档的其中一个特点是:接口的响应格式,与数据的存储结构是相同的。

比方说我要设计一个网上商城的订单服务,可能会提供如下查询特定订单的接口

{
  order: {
    id: 'F122663A-A5DC-451A-9B79-92DCE2EE41F1',
    price: '100.00',
    products: [
      {
        id: 1,
        name: 'MacBook Pro',
        price: '19999.00'
      },
      {
        id: 2,
        name: 'iPhone',
        price: '6999.00'
      }
    ]
  }
}

为了保存这种“不平坦”的对象,将会用MongoDB作为存储——除了文档的主键

_id
之外,其它字段在接口和存储之间一一对应。

不仅仅是响应格式,在这个Web服务内所操作的也是同样结构的对象。用MongoDB Node.JS Driver获得的订单恰好是一个JS对象,它与collection的文档有着一模一样的结构。之后这些对象会在代码内到处流通,不加修饰地使用。定义了数据库schema(就算是用MongoDB也有一套脑内的schema)后,其它的一切也就跟着确定了。

业界甚至有工具可以直接从数据库得到API,比如postgrest

很多时候,数据库schema成了一个应用内事实上的数据模型。但是,即便它们可以偶然一样,也不要认为它们总应该一样。

适合存储,不一定适合计算

以MySQL为例,在

CREATE TABLE
语句中某一列的类型实际上决定的是存储时分配的空间的多少。但适合存储的类型,并不一定也适合业务逻辑的运算。

比如说,要在MySQL中存储“开关型”的数据,即诸如“是否启用”或“是否已支付”这样非此即彼的状态时,通常定义为

TINYINT
类型,用0表示逻辑假(“未启用”和“未支付”),1表示逻辑真(“已启用”和“已支付”)。但对代码而言,比起用数值类型,布尔类型才是更恰当的选择。尤其是当所选择的语言并没有将0与
false
、1与
true
等价起来的时候——在Common Lisp中,
(if 0 1 2)
的求值结果为1。

适合计算,不一定适合存储

通常数据结构在内存中比在磁盘上要容易表达得多,所以代码中使用的数据结构会比数据库中存储的要灵活不少,这同样造成了两者的不匹配。

以我自己开发的提醒工具

cuckoo
为例,应用内有两种对象:任务和提醒。任务描述了要做的事情,提醒描述了在什么时候该告诉用户。显然,提醒是一个依赖于任务的弱实体。在cuckoo的代码中,任务是

Task
类的实例对象,有一个名为
remind
的成员变量存储着提醒。

但这样的结构不方便存储在MySQL中。遵照关系型数据库设计的第一范式,任务和提醒分别被存储在

t_task
t_remind
表中,两者通过
t_task.remind_id
联系起来。

当然,也可以在一开始就用MongoDB来存储这些数据(甚至可以用对象数据库?不过我没玩过)。尤其是cuckoo只是一个小玩意儿,MySQL和MongoDB都足以胜任。但作为一名有理想的程序员,在做设计的时候,不应该让低层细节过分干预高层策略。(在《Clean Architecture》中,越是接近I/O的越是low-level,反之则是high-level。)

面向业务逻辑,而非存储结构

业务逻辑和规则才是一个服务的核心,应该把更多精力花在实现业务逻辑的数据结构和算法上。

以网上商城中常见的优惠券功能为例。优惠券服务所管理的优惠券往往有着各种效果、条件,以及限制。为了保持灵活性,优惠券类(下称

Coupon
)的实例对象中会有三种接口类型的成员变量:
  1. Effect
    类型的变量
    effect
    ,负责实现优惠效果的计算逻辑;
  2. Condition
    数组类型的变量
    conditions
    ,负责实现使用条件的检查逻辑;
  3. Restriction
    数组类型的变量
    restrictions
    ,负责实现使用限制的检查逻辑。

三个接口可以有各种各样的实现——定额减免、折扣减免、某年月日前可用、不可用于电子产品,等等。如此,优惠券功能具备了极大的灵活性,业务可以随心所欲,产品可以为所欲为,老板数钱数到手软,公司业绩蒸蒸日上。

那么如何存储

Effect
Condition
Restriction
Coupon
类的实例对象呢?没有唯一的选择,既可以存储在MySQL中,也可以存储在MongoDB中,或者别的什么数据库中。不管这些数据最终如何持久化,都不会影响作为高层策略的优惠券业务逻辑。反过来,如果在代码中处理的不是类、接口,以及实例对象,而是直接从数据库中取出来的、贫血模型的行(或文档),处理起来就不是很优雅了——可以预见 ,代码中会充斥着许多的
if-else
判断逻辑。

数据库只是帮忙从磁盘中读取数据的软件,它的schema不应该直接成为应用的数据模型。

Interface Segregation Principle

不应该在HTTP接口的响应中直接暴露数据库的schema。

不说别的,光是数据库schema与接口规格所使用的命名规则就足以造成差异了。也许在MySQL中用

snake case
命名一列,却又在HTTP响应的JSON对象中用
camel case
命名字段。

此外,除非这些接口仅仅实现增删查改、没有任何的业务逻辑或规则,否则一个服务更应当提供与业务需求恰好契合的接口。仍然以上文的优惠券服务为例,尽管内部可能

Effect
Condition
Restriction
Coupon
等诸多概念,但煮不在乎用户不在乎,他们只想看到用人话说出来的优惠券效果以及使用规则——用户甚至不关心条件和限制有何不同。

如果优惠券服务直接将数据库中的行(或文档)序列化成JSON返回给调用者,会导致封装的泄露。每一个查询优惠券的调用方,都必须了解优惠券的内部表示形式,必须知道效果由

effect
描述、用券后的订单金额是多少、
conditions
中有关于过期与否的信息,等等。每增加一个优惠券服务的使用者,就相应地增加一套描述这些内容的代码。甚至当优惠券服务自身重构的时候,也许牵连到众多的调用方。

如果直接将存储结构暴露给调用者的话,又何必再做一个Web服务呢。

切勿矫枉过正

的确存在这样的例子,数据库schema、数据模型,以及HTTP响应结构三者相同。这是因为比起维护数据库schema与数据模型的转换规则,以及DTO与数据模型的转换规则而言,在领域代码中直接使用数据库schema来表达数据模型的成本更低一点。尽管数据库schema不是Web服务的一切,但很多时候可以因地制宜地妥协一下。

阅读原文

以上就是《数据库schema不是CRUD服务的一切》的详细内容,更多关于mysql的资料请关注golang学习网公众号!

版本声明
本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
Spring源码深度解析之通篇死磕Spring源码Spring源码深度解析之通篇死磕Spring源码
上一篇
Spring源码深度解析之通篇死磕Spring源码
Mysql 共享锁、排他锁 与 事务隔离级别详解
下一篇
Mysql 共享锁、排他锁 与 事务隔离级别详解
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • 笔灵AI生成答辩PPT:高效制作学术与职场PPT的利器
    笔灵AI生成答辩PPT
    探索笔灵AI生成答辩PPT的强大功能,快速制作高质量答辩PPT。精准内容提取、多样模板匹配、数据可视化、配套自述稿生成,让您的学术和职场展示更加专业与高效。
    14次使用
  • 知网AIGC检测服务系统:精准识别学术文本中的AI生成内容
    知网AIGC检测服务系统
    知网AIGC检测服务系统,专注于检测学术文本中的疑似AI生成内容。依托知网海量高质量文献资源,结合先进的“知识增强AIGC检测技术”,系统能够从语言模式和语义逻辑两方面精准识别AI生成内容,适用于学术研究、教育和企业领域,确保文本的真实性和原创性。
    22次使用
  • AIGC检测服务:AIbiye助力确保论文原创性
    AIGC检测-Aibiye
    AIbiye官网推出的AIGC检测服务,专注于检测ChatGPT、Gemini、Claude等AIGC工具生成的文本,帮助用户确保论文的原创性和学术规范。支持txt和doc(x)格式,检测范围为论文正文,提供高准确性和便捷的用户体验。
    30次使用
  • 易笔AI论文平台:快速生成高质量学术论文的利器
    易笔AI论文
    易笔AI论文平台提供自动写作、格式校对、查重检测等功能,支持多种学术领域的论文生成。价格优惠,界面友好,操作简便,适用于学术研究者、学生及论文辅导机构。
    40次使用
  • 笔启AI论文写作平台:多类型论文生成与多语言支持
    笔启AI论文写作平台
    笔启AI论文写作平台提供多类型论文生成服务,支持多语言写作,满足学术研究者、学生和职场人士的需求。平台采用AI 4.0版本,确保论文质量和原创性,并提供查重保障和隐私保护。
    35次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码