尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
后端 CRUD 的工程化抽象:增删改查也能写出架构感
后端 CRUD 的工程化抽象增删改查也能写出架构感一、CRUD 被低估的能力CRUD 是后端开发中最基础的操作。也正因为它基础很多人觉得它不值得讨论——不就是写几个 SQL 嘛。但这个态度本身就是问题。在后端实习中我发现一个 CRUD 模块写得好不好差别不在代码量上而在新增一个字段时需要改几个地方1 个还是 5 个当需要加缓存时是在 Controller 里硬编码还是通过装饰器当需要做软删除时是改所有 SQL 还是抽象出一层好的 CRUD 抽象让变更只发生在一个地方。这篇文章讨论如何把 CRUD 写得有架构感。二、CRUD 的抽象分层flowchart TD A[Controller 层] --|参数校验 响应格式化| B[Service 层] B --|业务逻辑编排| C[Repository 层] C --|数据访问抽象| D[数据源] B --|横切关注点| E[AOP 层] E -- E1[日志记录] E -- E2[权限校验] E -- E3[缓存管理] E -- E4[事务管理]三、实现泛型 CRUD 基类from abc import ABC, abstractmethod from typing import TypeVar, Generic, Optional from dataclasses import dataclass from datetime import datetime from functools import wraps import hashlib import json T TypeVar(T) # 实体类型 class NotFoundError(Exception): 资源不存在异常 pass class DuplicateError(Exception): 资源重复异常 pass class BaseRepository(Generic[T], ABC): 泛型 CRUD Repository 基类 提供标准的 CRUD 操作子类只需实现少数几个抽象方法。 这种设计让所有实体的 Repository 保持一致的接口风格。 工程优势 - 新增实体只需继承并实现 _table_name 等方法 - 通用逻辑如软删除在基类中一次实现 - 子类可以覆盖任意方法做定制 property abstractmethod def _table_name(self) - str: 子类必须提供表名 ... abstractmethod def _row_to_entity(self, row: dict) - T: 子类必须实现将数据库行转换为实体 ... abstractmethod def _entity_to_row(self, entity: T) - dict: 子类必须实现将实体转换为数据库行 ... abstractmethod def _execute(self, sql: str, params: list) - any: 数据库执行入口依赖注入 ... # 标准 CRUD 操作 def find_by_id(self, entity_id: int) - Optional[T]: 按 ID 查询 sql ( fSELECT * FROM {self._table_name} fWHERE id ? AND deleted_at IS NULL ) rows self._execute(sql, [entity_id]) if not rows: return None return self._row_to_entity(rows[0]) def find_by_id_or_raise(self, entity_id: int) - T: 按 ID 查询不存在时抛异常 entity self.find_by_id(entity_id) if entity is None: raise NotFoundError( f{self._table_name} with id{entity_id} not found ) return entity def find_all( self, filters: Optional[dict] None, order_by: Optional[str] None, limit: Optional[int] None, offset: int 0, ) - list[T]: 带条件的分页查询 Args: filters: 过滤条件如 {status: active, user_id: 123} order_by: 排序如 created_at DESC limit: 每页数量 offset: 偏移量 sql fSELECT * FROM {self._table_name} WHERE deleted_at IS NULL params [] # 动态构建 WHERE 条件使用参数化查询防止 SQL 注入 if filters: for key, value in filters.items(): sql f AND {key} ? params.append(value) if order_by: sql f ORDER BY {order_by} if limit is not None: sql f LIMIT ? OFFSET ? params.extend([limit, offset]) rows self._execute(sql, params) return [self._row_to_entity(row) for row in rows] def create(self, entity: T) - T: 创建实体 row self._entity_to_row(entity) # 设置创建时间 row[created_at] datetime.now() row[updated_at] datetime.now() columns , .join(row.keys()) placeholders , .join([?] * len(row)) sql ( fINSERT INTO {self._table_name} ({columns}) fVALUES ({placeholders}) ) self._execute(sql, list(row.values())) # 返回创建的实体实际应从数据库获取自增 ID 等 return entity def update(self, entity_id: int, updates: dict) - T: 部分更新实体 只更新提供的字段不覆盖未提供的字段。 # 先检查实体是否存在 self.find_by_id_or_raise(entity_id) updates[updated_at] datetime.now() set_clause , .join(f{k} ? for k in updates) sql ( fUPDATE {self._table_name} fSET {set_clause} fWHERE id ? AND deleted_at IS NULL ) params list(updates.values()) [entity_id] self._execute(sql, params) return self.find_by_id(entity_id) def delete(self, entity_id: int, soft: bool True) - None: 删除实体 Args: soft: True软删除设置 deleted_atFalse物理删除 self.find_by_id_or_raise(entity_id) if soft: sql ( fUPDATE {self._table_name} fSET deleted_at ?, updated_at ? fWHERE id ? ) now datetime.now() self._execute(sql, [now, now, entity_id]) else: sql fDELETE FROM {self._table_name} WHERE id ? self._execute(sql, [entity_id]) def count(self, filters: Optional[dict] None) - int: 统计数量 sql fSELECT COUNT(*) as cnt FROM {self._table_name} WHERE deleted_at IS NULL params [] if filters: for key, value in filters.items(): sql f AND {key} ? params.append(value) result self._execute(sql, params) return result[0][cnt] if result else 0 # ---- 装饰器增强缓存层 ---- def cached(ttl: int 300): Repository 方法缓存装饰器 用法在需要缓存的方法上添加 cached(ttl300) def decorator(func): wraps(func) def wrapper(self, *args, **kwargs): # 生成缓存键 cache_key ( f{self._table_name}:{func.__name__}: f{hashlib.md5(str((args, kwargs)).encode()).hexdigest()} ) # 尝试从缓存读取 cached_value self._cache_get(cache_key) if cached_value is not None: return cached_value # 缓存未命中执行原方法 result func(self, *args, **kwargs) self._cache_set(cache_key, result, ttl) return result return wrapper return decorator四、工程价值4.1 变更隔离当需要给所有实体加「软删除」时只要在基类的delete方法中实现一次所有子类自动获得该能力。这就是单一变更点的设计——修改只发生在一个地方。4.2 接口一致性所有子类的 CRUD 方法签名一致团队成员切换维护不同实体时不需要重新熟悉接口。4.3 测试简化基类的通用逻辑如 find_all 的动态查询构建只需测试一次。子类的测试可以专注于自定义逻辑。五、总结把 CRUD 写好不是把增删改查写得更快而是让变更与扩展更安全。抽象的价值不在当前——在一个月后需要加一个新字段、三个月后需要加缓存、半年后需要做软删除时——你会发现好的抽象让这些变更只影响一个地方而不需要在 50 个文件里逐行修改。
RELATED

相关推荐

用 AI 重构刷题笔记:从零散记录到结构化知识库

用 AI 重构刷题笔记:从零散记录到结构化知识库

用 AI 重构刷题笔记:从零散记录到结构化知识库 一、你的刷题笔记是不是"写了一大堆,回头看啥也没记住" 记笔记是好事,但形式决定效果。散乱的、按时间顺序堆砌的笔记,回顾时几乎没有检索能力。你需要知道"我有哪些…

📅 2026/8/22 4:00:31
编程Agent决策仲裁层(DAL):轻量级序列决策建模实践

编程Agent决策仲裁层(DAL):轻量级序列决策建模实践

1. 这不是一次常规的“模型微调”,而是一次面向编程Agent工作流的系统性工程重构Composer 2 ≈ Kimi K2.5 一层针对“编程Agent”的强化微调——这个标题里藏着三个容易被误读的关键点:第一,“≈”不是等于,而是指在特定任务集&am…

📅 2026/9/20 18:42:55
33 SFT、RLHF、DPO:模型对齐到底在对齐什么

33 SFT、RLHF、DPO:模型对齐到底在对齐什么

专栏:大模型应用开发:从原理到生产 篇号:33 内容标签:模型对齐、SFT、RLHF、DPO、大模型 前几篇我们讲的是微调。 微调解决一个核心问题: 模型会不会按你的方式做事。 但在真实业务里,“会做”还不够。 一个模型可能会回复客服问题。 但语气冷冰冰。 它可能知道退货…

📅 2026/9/20 13:16:16
MORE NEWS

更多资讯

📰

从环境配置到训练曲线:DQN与DDQN在Atari上的完整工程实践

简介:基于 Python 在 gym Atari 环境中实现 DQN 算法及其变体的完整工程,面向深度学习课程设计、强化学习入门者,以及需要在普通笔记本上跑通强化学习实验的开发者,内容覆盖环境搭建、算法公式推导、模型训练、效果可视化等整个实…

📰

DQN源码实战:在Atari Breakout上复现训练与避坑指南

简介:面向游戏AI与深度强化学习入门者,这份基于DQN的Atari Breakout智能体设计项目,将理论、代码与文档整合为可直接运行的完整方案,适合计算机、人工智能、自动化等专业学生作为毕设、课设或项目初期演示使用。内容从深度学习与强…

📰

基于gym Atari的DQN实战:环境配置、算法原理与变体实现详解

简介:基于 Python 在 gym Atari 环境中实现 DQN 及其变体的课程设计资源包,面向深度学习初学者、强化学习课程设计者及需快速上手 PyTorch 的开发者,解决从框架阅读、算法补全到训练验证的完整实践问题。压缩包共 255 个文件,以 P…

📰

AI实战入门:从命令行到本地部署的30天可交付路径

1. 这不是“科普文”,而是一份AI从业者的现场手记我带过三届人工智能方向的毕业设计,也给制造业、医疗影像、金融风控团队做过AI落地咨询,还亲手调过从ResNet-50到Qwen2-7B的上百个模型。所以当看到“人工智能AI专业详解及未来发展全景”这个…

📰

磁力链接转种子文件:3分钟永久保存稀缺资源教程

1. 为什么磁力链接会"消失",而种子文件不会先说个很多人都有过的糟心经历:辛辛苦苦在网上找到一个稀缺资源,复制了一串 magnet:?xturn:btih: 开头的磁力链接,结果第二天打开下载工具,提示"获取种子信息…

📰

基于改进Unet的多模态MRI融合脑梗死分割实战

简介:本资源面向计算机、人工智能、电子信息等专业的在校学生与教研人员,提供一套基于改进Unet、融合MRI多模态图像不同特征实现脑梗死区分割的完整Python项目,可作为毕业设计、课程设计、大作业或初期项目立项的参考方案。压缩包共68个文件&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬