点赞表问题:我们为何采用多态
Source: Dev.to
介绍
几天前,我在给一个应用添加 Community(社区)板块。用户需要能够:
- 创建帖子
- 留下评论
- 为帖子点赞
- 为评论点赞
我们还有一个独立的 News(新闻)板块,新需求是用户也应该能够为新闻文章点赞。
初始方案:分别的点赞表
如果只能对一种实体(例如新闻)进行点赞,模式会非常简单:
CREATE TABLE news_like (
user_id BIGINT NOT NULL REFERENCES users(id),
news_id BIGINT NOT NULL REFERENCES news(id),
liked_at TIMESTAMP NOT NULL DEFAULT now()
);
因为我们有三种实体——posts(帖子)、comments(评论)和 news(新闻)——我们考虑创建三张独立的表:
post_likescomment_likesnews_likes
每张表都会有适当的外键关系,从而得到:
- 强大的关系完整性
- 简单的联接
- 明确的结构
虽然这是最“纯粹的关系型”做法,但感觉重复。将来若再加入可点赞的实体,就需要再建一张表,导致模式水平膨胀。
多态点赞表
我们没有使用多张表,而是创建了一张 多态 的 likes 表,能够引用任意资源类型。
CREATE TABLE likes (
user_id BIGINT NOT NULL REFERENCES users(id),
resource_id UUID NOT NULL,
resource_type TEXT NOT NULL CHECK (resource_type IN ('POST','COMMENT','NEWS')),
liked_at TIMESTAMP NOT NULL DEFAULT now()
);
列说明
user_id– 点赞者(外键指向users)。resource_id– 被点赞项目的 UUID(没有外键)。resource_type– 项目的类型(POST、COMMENT或NEWS)。liked_at– 点赞的时间戳。
(resource_id, resource_type) 这对组合唯一标识被点赞的实体。该设计让我们只维护一张集中式表,易于扩展并在系统中复用。
权衡
主要的缺点是失去了对 resource_id 的直接外键约束。PostgreSQL 不能对指向多张表的外键进行强制约束,因此引用完整性必须在应用代码中处理。
统计特定资源点赞数的示例查询:
SELECT COUNT(*)
FROM likes
WHERE resource_id = 'some-uuid'
AND resource_type = 'POST';
如果需要同时获取资源详情和其点赞数,可能需要单独的查询或在应用层加入逻辑。对我们而言,这种权衡是可以接受的,因为我们并不会在点赞上进行大量跨实体的联接查询。
结论
关键的洞察是 “点赞”是一种跨资源的行为,而不是仅与帖子、评论或新闻紧耦合的特性。以多态方式建模把 “点赞” 当作可复用的系统能力,从而减少了模式重复,并在应用增长时降低了后续重构的工作量。