多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI 软件开发实战教程(五):把产品规则变成能落地的工程架构

AI 软件开发实战教程(五):把产品规则变成能落地的工程架构 “AI 软件开发实战教程”系列第 5 篇产品规划批准以后不急着创建项目和安装框架先把并发、幂等、隐私、后台任务和测试边界说清楚让后面的开发计划真正可执行。上一篇完成了关键外部服务验证“邻行”也通过了正式产品规划审批。现在终于可以讨论技术了。这一步最容易变成一张技术名词清单前端用什么后端用什么数据库用什么要不要 Redis要不要消息队列要不要微服务。但这些名字本身不是架构。邻行真正需要回答的是两个乘客同时确认最后一个座位时谁成功用户重复点击“想和对方联系”时会不会重复交换和重复提醒第三方提醒超时以后站内候选是否还存在后台任务晚运行几分钟时已经截止的信息会不会继续匹配微信号和喵码会不会出现在页面源码、日志或管理后台将来删除账户时已经交换过的联系方式怎样清理产品规划中的 56 个验收场景怎样进入自动测试和真机验收。如果这些问题没有答案即使项目已经能启动也只是“选好了工具”还没有形成可以落地的工程方案。架构不是从框架开始而是从风险开始这次先从产品规划中挑出最难补救的失败方式。风险一最后一个座位被占两次假设车主只剩一个座位两个乘客几乎同时完成双方确认。如果代码按下面的顺序运行查询剩余座位还有 1 个 → 保存乘客甲确认 查询剩余座位还有 1 个 → 保存乘客乙确认两个请求都可能看到旧数据最后得到两个“确认成功”。这不是页面提示能够修复的问题而是数据库并发问题。因此架构规定双方确认、座位占用和满员状态必须在同一个数据库事务中发生并锁定同一条车主信息。两个请求竞争时后取得锁的一方必须重新读取座位而不是继续使用事务外查到的旧结果。产品规则“最后一个座位不能被两个配对同时确认”到这里才真正变成了工程约束。风险二外部提醒失败污染产品事实喵提醒验证已经证明超时不代表第三方一定没有发送相同请求也确实会产生重复消息。如果发布候选的代码直接调用第三方创建候选 → 调用喵提醒 → 收到成功后保存数据库那么一次超时就会把系统推入两难回滚候选会丢失本来成立的产品事实自动重试可能让用户收到两条提醒继续等待会把网页请求和第三方稳定性绑在一起。最终采用的是“业务事件先落库渠道发送后处理”同一数据库事务 ├─ 保存候选 ├─ 保存唯一业务事件 ├─ 保存双方站内提醒 └─ 保存双方外部发送待办 提交 ↓ 后台工作进程再调用第三方这样第三方失败只改变“发送结果”不会改变候选是否成立。这也是为什么架构里需要区分三个容易混在一起的概念候选成立 ≠ 已创建提醒 ≠ 第三方已接受风险三重复操作制造重复事实移动网络下用户可能连续点击浏览器也可能因为超时再次提交后台任务可能在保存结果前中断并重新执行。如果只靠“按钮点击后禁用”防重复换一个请求入口问题仍然存在。所以邻行把幂等放到数据库事实层同一发布请求拥有稳定幂等键同一对信息在同一规则版本下只有一个候选同一候选只有一次联系方式交换同一业务事件键只能写入一次同一事件与接收者只有一条站内提醒重复请求返回已经存在的结果而不是再次制造结果。页面防重复仍然可以提升体验但唯一约束才是最后一道保障。风险四敏感资料在非预期位置泄露微信号只允许在合法候选中交换喵码只允许后端触发提醒。因此“数据库字段加密”还不够。架构继续规定微信号、喵码和交换时的微信号快照使用应用层认证加密页面在完成权限判断前不把敏感字段装入模板上下文普通日志不记录请求体、Cookie、微信号、喵码和第三方完整请求地址社区管理员的普通后台没有查看私密资料的入口喵码设置页只显示是否已经设置不把原值解密回显账户删除时不仅清理当前资料也清理过去交换记录中的加密快照自动测试要主动断言页面正文、源码和日志中“不包含”这些值。安全不是最后跑一次扫描而是决定哪些数据在什么时刻能够进入哪一层。为什么首版选择一个“朴素”的系统邻行当前有两个微信群成员总数不到七百且可能重复活跃时每天约 20–30 条消息。这个规模当然可能增长但现在没有证据证明它需要微服务、Kafka、独立搜索、实时推送集群或复杂前端状态管理。首版最终选择Python 3.12Django 5.2 LTSPostgreSQL 17Django 服务端页面少量原生 JavaScript一个独立的 Django 后台工作进程当时先用 Docker Compose 定义 Web、Worker 和 PostgreSQL 的共同契约后续实现又补齐了 SQLite 本地源码运行以及 Gunicorn、systemd、Nginx 的源码生产部署。架构约束是运行组件一致不是强制所有环境都用 Docker。选择 Django 不是因为它能自动解决业务问题而是因为它已经提供账号、会话、表单校验、CSRF 防护、数据库迁移和管理后台。对一个小型受控试用项目这能减少重复搭基础设施的时间。架构初稿曾选择 Python 3.13V0 检查真实开发机后发现系统原生提供 3.12而 Django 5.2 LTS 同样正式支持因此把基线收敛为能够立即运行完整测试的 Python 3.12。选择 PostgreSQL 也不是为了追求“更专业”。它直接支持这个产品已经确定需要的事务、唯一约束和行锁在 SQLite 上通过的测试不能证明最后一个座位的真实并发正确。Django 官方把 5.2 标记为 LTS支持到 2028 年 4 月并建议始终使用当前安全补丁PostgreSQL 17 支持到 2029 年 11 月。这里选择长期支持系列同时在依赖锁定时跟随当前补丁而不是把调研当天的补丁号永远写死。参考资料Django 版本与支持周期地址https://www.djangoproject.com/download/参考资料PostgreSQL 版本策略地址https://www.postgresql.org/support/versioning/为什么没有先上消息队列外部提醒需要后台发送很容易让人直接想到 Redis 和 Celery。但邻行首先需要的能力只有业务事务中可靠创建发送待办一个工作进程能够互斥领取进程中断后待办仍然存在每次发送结果可以查询截止、过期和数据保留任务可以重复执行而不重复制造事件。PostgreSQL 的待办表已经能够满足这些要求。后台进程使用行锁领取一小批到期记录。SKIP LOCKED可以让多个消费者跳过正在处理的行PostgreSQL 官方也明确说明它适合队列式表但不适合普通业务查询因为它看到的并不是一致数据视图。参考资料PostgreSQL 锁定子句地址https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE所以架构只允许在提醒待办表使用它。如果以后待办延迟持续超过服务目标再根据真实瓶颈决定是否加入专用消息队列。现在增加 Redis 和 Celery 只会增加部署、监控、故障恢复和本地开发成本并不会让产品规则更正确。后台任务不能成为时间规则的唯一保障邻行有两个重要时刻匹配截止和出发时间。后台工作进程可能因为部署、故障或进程调度晚运行几分钟。如果系统只依靠一条定时任务把状态从“开放”改成“截止”就可能出现已经到 08:20 → 截止任务还没有运行 → 页面仍把信息当成开放 → 08:22 又创建了新候选正确做法是双重约束查询和每个关键操作都用服务器当前时间判断是否已经截止后台任务负责补写截止结果、发送提醒和整理持久化状态。前者保证规则立即生效后者保证后续事件最终完成。这也是架构中的一个通用原则定时任务负责“最终处理”不能成为权限和业务有效性的唯一判断者。把模块按业务事实拆开虽然首版是一个应用也不能把所有逻辑都写进页面处理函数。邻行按业务职责拆成几个模块accounts 登录、邀请注册、私密资料和账户删除 communities 社区、地点词表和路线兼容规则 trips 发布、查找、编辑和状态 matching 候选、联系方式交换、同行确认和座位 notifications 业务事件、站内提醒、渠道发送和后台任务 audit 最小安全审计与保留 web 移动页面组合与公共说明这不是把单体假装成微服务。这些模块仍然在一个代码库、一个部署单元和一个数据库中运行。划分的价值是明确谁能修改哪类事实以及事务应该在哪里开始和结束。例如页面处理函数不能自己修改候选、座位和提醒三张表。它应该调用一个应用服务由这个服务完成权限判断、锁定、状态变化和事件写入。先决定数据事实再决定页面怎样显示架构阶段还没有设计具体页面但一些数据选择已经会影响交互。例如联系方式交换保存“交换当时的微信号快照”。如果只在页面打开时读取对方当前微信号那么对方后来修改资料后已经发生过的交换内容会悄悄变化。这既不利于审计也会让双方看到的事实不稳定。因此系统保存当时披露值的加密快照并在账户删除时一并擦除。再例如每条出行信息使用随机短编号而不是用户的固定昵称。这样同一成员发布多条信息时其他人不能在交换前用公开编号稳定追踪这个人的出行规律。这些决定不是数据库设计师的个人偏好而是产品规划中的隐私边界在工程层的表达。测试策略也属于架构如果架构文档只画运行图却没有说明怎样证明它正确开发阶段仍然会把测试留到最后。邻行把测试分为六层纯规则测试时间窗口、地点兼容、状态变化和提醒分类服务测试邀请码、发布幂等、候选唯一、交换、删除和事件一致性PostgreSQL 并发测试最后一个座位、重复交换和多工作进程领取HTTP 集成测试认证、跨社区、CSRF、表单错误和源码不泄密Playwright 浏览器测试两个独立用户完成发布、候选、交换和确认iOS 与 Android 微信真机验收验证登录保持、复制、返回微信和分享预览。Playwright 可以模拟移动设备并覆盖 Chromium、WebKit也可以用两个隔离的浏览器上下文测试车主和乘客。参考资料Playwright pytest 插件地址https://playwright.dev/python/docs/test-runners但它不能证明微信内置浏览器成品已经可用。所以产品规划中的 AC-31 至 AC-35 在自动化完成后只能标记为“已经准备好人工验证”必须由真实 iOS 和 Android 微信环境最终通过。让每一条产品验收进入开发计划产品规划已经有 56 个验收场景。架构没有再复制一套新的需求而是规定开发计划为它们建立稳定编号AC-01到AC-56每个任务写明自己负责哪些场景以及用哪一层测试证明。这个映射解决两个问题不会出现代码写完了却不知道哪一条产品规则已经实现不会出现看板全部完成了仍有验收场景无人负责。其中几条必须保留特殊证明方式隐私场景要有“看不到敏感值”的否定断言最后一个座位要在 PostgreSQL 做真实并发测试微信浏览器场景只能由真机最终通过常规自动测试不能使用真实喵码和真实第三方发送。TDD 不是“先随便写几个测试”而是先知道哪条产品事实将用什么证据证明。AI 无人值守推进也要保留诚实边界这次工程架构是在产品负责人休息期间根据已经批准的事实源自动完成的。原计划中的 gstack 工程评审是一套交互式流程需要产品负责人逐项选择和确认。如果为了让流程看起来完整让 AI 自己模拟提问、再假装用户已经同意就会产生一份虚假的评审记录。所以本节点做了两件事完成不依赖新产品选择的架构设计和反向自检明确记录正式交互式工程评审尚未执行醒来后可以选择复核。用户授权 AI 继续工作不等于授权 AI 编造人的意见。同样的边界也适用于后面的真机验收、群管理员同意、隐私和法律审查代码可以准备好AI 可以生成清单但不能替真实的人和设备提供证据。本节点真正交付了什么工程架构完成以后项目新增的不是一串框架名称而是一组可以约束实现的决定使用适合当前规模的模块化单体明确产品事实与外部发送结果的边界明确最后一个座位的锁和事务明确发布、候选、交换和提醒的幂等约束明确敏感数据的加密、授权、日志和删除范围明确后台任务延迟时产品规则仍然成立明确 56 个验收场景怎样进入测试明确自动化不能冒充微信真机、社区同意和法律审查。下一步不是立即堆业务代码而是先完成移动端页面体验设计再把产品规则、架构决定和 56 个验收场景拆成纵向开发任务和看板。到那时每一项任务才会同时拥有用户能完成的动作、系统必须守住的规则、要先写的失败测试和可以验收的结果。写在最后一个小产品不需要复杂架构但需要清楚架构。所谓“清楚”不是图画得多漂亮而是遇到重复、并发、超时、泄露、删除和任务延迟时团队已经知道哪一个事实不能错、由哪一层保证、用什么测试证明。产品规划告诉我们要做什么。工程架构的作用是让这些句子在真实故障和真实并发中仍然成立。本篇验证摘要首版采用 Django 模块化单体、PostgreSQL 和数据库驱动的后台工作进程跨实体状态变化使用显式事务第三方调用放在业务事实提交之后幂等键、唯一约束和状态机共同处理重复提交与重复事件SQLite 只承担快速测试最后座位等并发结论仍需使用 PostgreSQL 双连接验证架构只覆盖已确认的首版能力没有提前加入消息队列、支付、地图或站内聊天。附录相关工具与仓库gstack仓库garrytan/gstack地址https://github.com/garrytan/gstackdev-harness仓库Dev-Wiki/dev-harness地址https://github.com/Dev-Wiki/dev-harnessUI UX Pro Max Skill仓库nextlevelbuilder/ui-ux-pro-max-skill地址https://github.com/nextlevelbuilder/ui-ux-pro-max-skill
返回列表