后端 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 个文件里逐行修改。

相关新闻

最新新闻

Poppins字体革命:免费开源的多语言几何字体终极指南

Poppins字体革命:免费开源的多语言几何字体终极指南

Poppins字体革命:免费开源的多语言几何字体终极指南 【免费下载链接】Poppins Poppins, a Devanagari Latin family for Google Fonts. 项目地址: https://gitcode.com/gh_mirrors/po/Poppins 🎯 价值主张:为什么Poppins是跨语言设计…

2026/7/11 10:01:10
Unity粒子特效进阶:Texture Sheet Animation原理与实战应用

Unity粒子特效进阶:Texture Sheet Animation原理与实战应用

1. 项目概述:为什么你需要精通Texture Sheet Animation? 在Unity里做特效,粒子系统是绕不开的核心工具。但很多开发者,尤其是刚入行的朋友,常常止步于调整颜色、大小、速度这些基础参数,对于粒子系统里那个…

2026/7/11 10:01:10
【RT-DETR涨点改进】23 RT-DETR 的 C++ 推理引擎实战:从 ONNX 到 TensorRT 的完整加速链路

【RT-DETR涨点改进】23 RT-DETR 的 C++ 推理引擎实战:从 ONNX 到 TensorRT 的完整加速链路

23 RT-DETR 的 C++ 推理引擎实战:从 ONNX 到 TensorRT 的完整加速链路 上篇我们聊了C++推理优化的底层思维,把延迟从47ms压到22ms。但坦白说,那只是“单步优化”。 如果你现在去生产环境跑一下,大概率会遇到一个尴尬场景:模型在Python里跑得飞起,一换C++就崩,或者精度…

2026/7/11 10:01:10
3种NAND Flash ECC方案对比:汉明码 vs BCH vs LDPC,纠错能力与开销实测

3种NAND Flash ECC方案对比:汉明码 vs BCH vs LDPC,纠错能力与开销实测

NAND Flash ECC技术深度解析:从汉明码到LDPC的实战演进 在数据存储的世界里,可靠性从来不是可选项而是必选项。想象一下,当你按下保存按钮时,那些看似普通的0和1正穿越NAND Flash芯片中数以亿计的存储单元,而其中任何一…

2026/7/11 10:01:10
为什么你的批量请求总在第97次失败?DeepSeek API并发限制的隐藏规则、埋点日志与自适应退避公式

为什么你的批量请求总在第97次失败?DeepSeek API并发限制的隐藏规则、埋点日志与自适应退避公式

更多请点击: https://kaifayun.com 第一章:为什么你的批量请求总在第97次失败? 这并非巧合,而是典型的“边界条件击穿”现象。当服务端配置了连接池上限、令牌桶速率限制或数据库连接数硬限(如 PostgreSQL 的 max_c…

2026/7/11 10:01:10
Haar小波变换图像压缩实战:Python/OpenCV 3步实现,PSNR 35dB+

Haar小波变换图像压缩实战:Python/OpenCV 3步实现,PSNR 35dB+

Haar小波变换图像压缩实战:Python/OpenCV 3步实现35dB高质量压缩 当我们需要在有限存储空间保存高分辨率图像时,图像压缩技术显得尤为重要。传统JPEG压缩在较高压缩比时会出现明显的块状伪影,而基于Haar小波变换的压缩方法能更好地保留图像边…

2026/7/11 9:56:09

月新闻