Skip to content

Latest commit

 

History

History
62 lines (43 loc) · 4.21 KB

File metadata and controls

62 lines (43 loc) · 4.21 KB

业务编码规范

针对 app/modules/<域>/ 业务开发。框架已强制的(dishka DI / import-linter 分层 / outbox-inbox 自动化 / TaskResult 抽象 / FastStream 自动埋点)不在此列。

1. 基本纪律

  • 异步优先:IO 一律 async,阻塞调用放线程池
  • 引包先评估:支持 async + 活跃维护 + 最新稳定版,不满足换选型
  • 不过度设计:只做当前必要的;无 2+ 同类项不预留目录
  • 密钥:SecretStr + .env(本地)/ Secrets Manager(生产),不硬编码

2. 模块与入口

  • 跨模块调用经 public.py:from app.modules.<域>.public import X,禁止直 import 内部
  • 三入口(HTTP/consumer/scheduler)只做协议翻译,业务逻辑只写一份落 service.py
  • 对外 HTTP 调用收敛到 app/integrations/<provider>/,客户端轻薄,不自动重试
  • DI 不挂业务 Provider:service / router 直接 FromDishka[session_factory / EventRegistry / ...] 取 infrastructure 资源
  • Worker consumer / Scheduler cron 需外部 client 时,handler 声明 *, integrations: Integrations,内部 integrations.get(<Client>) 取;多实例拆类(<A>Client / <B>Client),不用 (type, name) 二元 key;不在 handler 内自建 httpx client

3. 事务边界

  • service 接收 session_factory(不是 session);入口层不管事务
  • 写表(单/多统一):async with sf() as s, s.begin(): ...;只读:async with sf() as s: ...
  • Consumer 长任务只在写 DB 步骤开 session

4. 异常处理

  • 只捕获自定义异常(AppError 体系)做业务处理;不吞异常、不给默认值、不捕获 Exception 兜底
  • 事务回滚后必须重抛;其他异常自然向上抛由全局处理器兜底

5. 消息契约

  • 契约收口在 events.py:每事件一个 Pydantic 类,routing_key 只出现一次
  • 发布:await events.X.publish(session, payload)(同事务 outbox);schema 演进只增字段 + 带 message_version

6. 业务队列命名

  • routing_key 格式 <domain>.<entity>.<action>(如 example.widget.requested),action 可按订阅粒度继续细分;不带 app_name 前缀;domain 对应 app/modules/<domain>/ 目录名
  • queue 名 当前 == routing_key(引擎 RabbitQueue(routing_key) 直接绑定);多 app 共享 vhost 撞车时再分离为 <appname>.<domain>.<entity>.<action>(延后)

7. 数据库表设计

  • 主键 BIGINT GENERATED BY DEFAULT AS IDENTITY(业务自然唯一用联合主键);时间戳 TIMESTAMPTZ(不用 TIMESTAMP);JSON 用 JSONB
  • 状态/枚举 VARCHAR(N) 存字符串(如 pending/dead),不用魔法 INT,可选值在 COMMENT 列全
  • 索引 按查询模式建,优先部分索引,命名 idx_<表>_<语义>;默认值 有业务含义的写进 DDL(DEFAULT)
  • COMMENT 全字段覆盖:每张表配 COMMENT ON TABLE,每个字段(含 id/created_at/updated_at 等通用列)都配 COMMENT ON COLUMN。注释只描述"字段装的是什么"(语义、数据来源、枚举值域),不带架构方案文案(实现要点、用途、不变量、设计决策等)
  • updated_at 手动刷新:框架不自动刷新 updated_at(model 不挂 onupdate、不建触发器)。每个 UPDATE 写路径必须显式带 updated_at=func.now()(见 app/modules/example/repository.py)。

8. 多库事务边界

  • 不提供跨库事务(XA / two-phase commit)。session.begin() 只保证单库原子
  • 跨库一致性使用 outbox 最终一致,但须理解边界:
    • outbox 表在主 PG,只保证"主库事务内 outbox 落地 → 消息必达"
    • 业务库写 + 主库 outbox 写本身跨库,无法单事务原子
    • 正确姿势:业务库写 → 消费侧幂等 + inbox 去重承接。不要指望业务库写与 outbox 写原子绑定

9. 日志

  • 业务模块统一 structlog:log = structlog.get_logger(__name__),上下文走 kwargs(log.info("module.event", key=value),kwargs 直接进 event_dict)
  • 不要 stdlib logging.getLogger + extra={...}:extra 经 structlog ProcessorFormatter 不渲染,字段全部丢失
  • 事件名 <模块>.<动作>(点号分隔,如 example.created、outbox.dead)
  • 例外:app/infrastructure/observability/logging.py 配置 root logger / handler 时仍用 stdlib API(那是 stdlib 入口)