公司动态
成为全栈·产品篇·领域建模:一个文章系统有哪些实体、什么关系
成为全栈·产品篇·领域建模一个文章系统有哪些实体、什么关系本文目标带你做一件当纯前端时几乎从没做过的事——为数据「建模」而不是「渲染」。我会用本系列这个真实文章系统作案例把核心实体和它们的关系拆给你看并讲清几个「不对这意味着什么」的建模决策。读完你会明白为什么说「设计是常量」而实体关系就是那个不变量。前置知识建议先读 技术选型不是投票技术选型。本文是「设计是常量」里那个不变量的具体展开。你从没做过的事建模当纯前端时你面对数据是这样的constarticleawaitfetch(/api/articles/1).then(rr.json())returnh1{article.title}/h1div{article.content}/div你消费一个对象、把它渲染出来。对象长什么样是别人定好的。建模反过来现在问你——「要做一个文章系统数据该怎么组织」没有人给你现成的对象。你要自己决定有哪些「东西」值得单独记录、它们之间什么关系、每个字段是什么类型、状态怎么流转。这就是领域建模。它练的不是「消费 渲染」而是「抽象 约束」。我身边不少前端转全栈卡住卡的就是这第一步——不是语法不会是从没被要求「凭空把世界拆成表」。一、从需求到实体建模的第一步建模的起点是问对问题系统里有哪些「值得单独记录状态」的事物「值得单独记录」是关键。一个文章的标题不值得单独建表它依附于文章但「用户」值得——它有独立生命周期注册、登录、改资料、禁用。「评论」也值得——它有作者、内容、审核状态。把需求在脑子里过一遍凡是「能独立存在、有自己属性、会被增删改查」的基本就是实体。剩下的要么是实体的字段要么是实体之间的关系。关系只有三种记住就行一对一一个 A 对应一个 B少见多数可合并。一对多一个 A 对应多个 B最常见用「B 上挂 A 的外键」表达。多对多多个 A 对应多个 B用一张中间表表达比如「文章 ↔ 标签」。把这三种关系画出来更直观二、这个系统的核心实体把「文章系统」这个需求拆开本系列定下的核心实体如下完整定义见 02-领域模型与API契约这里只给总览┌─────────┐ ┌──────────┐ ┌────────┐ │ User │───1:N─▶│ Article │─N:N──▶│ Tag │ │ (作者/会员)│ │ (文章) │ └────────┘ └─────────┘ └────┬─────┘ │ 1:N │ 1:N ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Comment │ │ Category │─N:1─▶│ Category │ (自关联支持层级) │ (评论) │ │ (分类) │ │ (父分类) │ └──────────┘ └──────────┘ └──────────┘ ┌──────────┐ ┌────────────┐ ┌────────────┐ │ Favorite │◀─N:1─│ User │─1:N─▶│ ReadingLog │ │ (收藏) │ │ │ │ (阅读历史) │ └──────────┘ └───────────┘ └────────────┘ ┌──────────┐ ┌────────────┐ │ Like │ │ Attachment │ (附件一等实体) └──────────┘ └────────────┘实体一句话职责User系统只有一类账户靠role区分 admin / editor / memberArticle核心实体内容存 Markdown 源文渲染交给前端Category分类支持无限层级自关联Tag标签与文章多对多Comment评论有自己的审核状态Favorite会员收藏Like会员点赞ReadingLog阅读历史阅读量统计的底层Attachment上传的附件/图片是一等实体不是内部影子表ASCII 图是文字兜底下面这张可视化版本看得更清楚你看这已经不是「一个文章对象」了——它是一张关系网。作为前端你以前只摸过这张网里被裁出来的那一片 JSON建模让你看见整张网。三、几个「不对这意味着什么」的建模决策实体列表好列难的是决策背后的取舍。举四个本系列真实做过的决定让你感受建模的思维重量1. 用户不拆表靠role区分。会员中心和后台管理员本可以建两张表。但我们只用一张User表靠role字段区分权限。理由只有一个最大化「同一套 API 服务多端」的复用率。代价是权限判断要写在授权层这正是契约里x-authz要机器化保证的事。如果你当初拆成两张表后面六个端都要为「两套用户」写两套逻辑——地基就歪了。2. 文章内容存 Markdown 源文不存渲染后的 HTML。Article.content存的是 Markdown 源文渲染由前端负责。这是关注点分离存储层只管「数据是什么」表现层管「怎么画」。如果存了 HTML哪天换个前端框架旧 HTML 就成包袱。3. 主键统一整数自增不引入 uuid。所有实体主键用整数自增。在 SQLite 下是INTEGERPostgreSQL 下是BIGINT但语义一致。理由本期规模用不着 uuid 的分布式优势简单够用就是最优。过早上 uuid反而增加跨数据库适配的复杂度。4. 状态要显式建模成状态机。文章有三态draft / pending / published评论有三态approved / rejected / reviewing。它们不是随便写的字符串而是业务流程的可建模部分——哪些转移合法比如pending → published可以published → pending也可以但得有规则要在契约里机器化定义。状态机建模错了业务就会出「文章卡在审核态出不去」这种事故。顺手提醒建模时最容易栽的三个坑你写自己项目时能避开过度拆分什么都想建表结果十张表 join 一次查询。先问「它能独立存在吗」不能就做字段。搞反关系方向一对多时外键挂错边查询要反向扫全表。记住「多的那一侧挂对方 id」。把状态当普通字符串用自由文本存状态后面到处if (status xxx)一改名全崩。状态该是受约束的枚举最好机器化校验。坑的本质都是「没把关系和约束想清楚就动手」——建模慢一点后面省十倍返工。四、为什么建模是全栈最难的跃迁前端练的是「给定结构消费它、渲染它」——输入是确定的。建模练的是「没有结构创造结构并扛住它带来的约束」——输出要自己负责。而且它贵。用一个真实系统串起全栈 说过API 契约和领域模型是整个工程唯一的硬地基一旦定歪后面六个子项目全部返工。你前面学的 Hono、Drizzle、Next.js 都能边做边改唯独实体关系和契约不能——因为它们是所有端共用的「常量」。所以这一篇你不必背下每个字段但要建立一种新直觉看到需求先想「有哪些实体、什么关系、状态怎么流转」而不是直接想「页面怎么画」。这个直觉一旦长出来你再回头看前端那些「列表页」「详情页」会发现它们在你眼里已经变成了「某实体的集合视图」和「某实体的单个视图」——那一刻你就已经是全栈视角了。举个最小的例子体会这层追问产品说「用户能收藏文章」。前端会想「加个爱心按钮」建模者会想「要不要独立 favorite 表、用户删了收藏怎么办、文章删了收藏级联吗、收藏要不要进阅读历史」。这串追问就是建模的起点——它不写在页面上却决定了你后面写不写得出干净的后端。所以别嫌建模慢它在替你挡住最贵的返工。建模练的就是这种「先问清楚再动手」的肌肉练熟了后面的代码反而写得快。小结建模是「为数据建模而非渲染」——从空白需求拆出实体和关系这是纯前端极少练的能力。实体 值得单独记录状态的事物关系只有一对一、一对多、多对多三种。本系统的核心实体是一张关系网User / Article / Category / Tag / Comment / Favorite / Like / ReadingLog / Attachment不是孤立的文章对象。真正的建模功力在决策取舍单用户表靠 role、Markdown 源文、整数主键、状态机——每个决定都有代价和理由。建模是全栈最难的跃迁也是唯一不能返工的地基它长出的「先想实体再想页面」的直觉就是全栈视角本身。延伸阅读《技术选型不是投票——本文说的「设计是常量」就是实体关系这个不变量。{{LINK:M0-05}}《契约先行》——实体定好之后下一步是把它们暴露成接口。订阅这个专栏本系列专栏https://blog.csdn.net/fungleo/category_13204651.html订阅看全部篇章完整项目仓库https://github.com/fengcms/become-a-full-stack-developer