
看到 Shopify 这条消息在 Hacker News 上冲到 1272 分的时候我第一反应不是打开评论区看热闹而是把复盘原文逐段啃完。说实话这个分数放在全球开发者社区已经属于现象级讨论。大家争论的核心早就不再是“React Native 好不好用”而是一家覆盖几百万商家的全球电商公司为什么在投入多年之后算完一笔账决定把“双端复用”这条路砍掉。这件事对我的触动不在技术层面而在架构层面。我花了两天时间把 Shopify 给的替代方向——无头架构Headless Architecture——在自己维护的一个 Agent 驱动的电商 Demo 里完整复现了一遍从 GraphQL API 的建模到把接口封装成 AI Agent 能调用的工具再到多 Agent 协作和权限安全。这篇文章不聊漂亮话只讲我复现过程中做了什么、为什么这么做、踩了哪些坑。1. 事件背景Shopify 为什么砍掉 React Native1.1 大家都在吵什么HN 那 1272 分背后实际上是两个阵营的正面对撞。一边认为 React Native 把“一套代码跑双端”的梦想实现了另一边则认为这种抽象最终会变成“一套代码两边都不讨好”。Shopify 的复盘没有含糊其辞它直接给出了结论在这个业务规模下React Native 带来的跨端收益cover 不住它带来的复杂度成本。这个结论和很多人的直觉相反但电商场景恰恰是最不适合盲目跨端复用的场景之一。原因很简单电商 App 不是“内容展示型”应用而是典型的“任务密集型”应用——用户要浏览、搜索、加购、支付、看库存、追踪物流商家要管理商品、订单、改价、打折。每一步都有极深的业务校验和原生能力依赖。我在实际开发中体会很深的点是RN 项目在 Demo 阶段跑得飞快放到真实电商流量下就会露馅。Demo 里你只需要渲染 10 个商品卡片真实场景是几千个 SKU 的瀑布流加图片懒加载加上购物车状态同步、库存实时刷新、支付流程的 SDK 对接。每多一种原生能力RN 桥接层的复杂度就高一分。1.2 为什么崩在“电商”这个场景技术层面拆一下。React Native 的思路是通过桥接层调用原生组件文本输入、滚动列表、导航、权限申请都有对应映射。问题出在两层第一层是性能损耗在复杂列表场景下被放大。商品瀑布流的滚动、图片懒加载、焦距计算RN 的调度和原生手势处理之间总隔着一条沟通链路。用户感知到的是“有点卡”但真实原因不是 RN 引擎本身不行而是跨层通信的开销在电商这种高频交互场景里被充分暴露了。第二层是深度定制跟不上。支付 SDK、指纹校验、扫码识别、动态优惠券的视觉组件RN 的第三方封装往往滞后一两个版本。Shopify 自己的判断很清醒与其花同样的人力去跟进原生能力的桥接不如直接写原生然后把原来的业务逻辑统一收归到一个内核里。还有一个容易被忽略的原因React Native 的生态问题和版本碎片化。你敢不敢在发版前升级 RN 版本升级一次所有第三方原生模块都要重新验证。而业务不会等你升级电商大促的节奏也没法容忍“为了框架升级停两周开发”。我见过不止一个团队在 RN 项目里为了避开某个原生模块的 bug长期锁定旧版本最后债务越积越深。1.3 这次迁移的真正价值HN 热榜的 1272 分与其说是投给“React Native 值不值得用”不如说是投给“技术选型应该服从业务模型”。Shopify 真正做的动作是把移动端重新定位成一个“客户端”而不是“另一个计算平台”。这意味着一切笨重的业务逻辑都向后端收缩客户端只负责渲染和交互。这就引出了我这次复现的核心无头架构。Shopify 做移动端回归原生同时把面向商家的能力全部 API 化把 Storefront API、Hydrogen 这些无头能力推向台前。这种“前端与内核解耦”的架构正好也是 AI Agent 最需要的形态——因为 Agent 不会点按钮它只认接口。2. 无头架构拆解从“页面”到“接口”的范式转换2.1 传统电商和无头电商到底差在哪很多人会把 Shopify、WordPress、自建站放在一起比哪个好用这是典型的传统建站思路。那是一种“选工具”的思考方式买主题、拖组件、开店上线。而无头电商完全是另一条路你要的不是一个开箱即用的界面而是一套没有固定皮囊的业务内核。传统电商像一栋毛坯交付的房子管道进户、墙体固定你要住进去只能在这套结构里装修改造。无头电商像什么都预留好的设备舱水电、暖通、信号接口都标准化了外接什么操作面板完全由你决定。WordPress 也在搞无头化通过 REST API 把内容输出给任意前端Shopify 的 Storefront API 同理。所以“什么工具更好用”的讨论会越来越模糊真正重要的是“你想让多少种界面共享同一套业务内核”。无头电商的前端可以是手机 App、Web 页面、小程序、门店收银机甚至是一个 AI Agent。每种端都从同一套 API 读取数据和执行业务逻辑。这样做的好处是边际成本低每新增一个端不用重新实现一遍商品、订单、支付逻辑只需要做一层适配。2.2 Agent 为什么必须配无头架构Agent 的本质是“感知-决策-执行”循环。它的感官不是摄像头而是 API 返回的结构化数据它的手不是鼠标而是工具调用。如果某个系统只提供 UI 不提供 APIAgent 就很难绕过 UI 去完成任务。强行让 Agent 做视觉解析点按钮成本极高、稳定性极差还有安全风险。所以我做 Agent 接入时会把“有没有干净的 API”作为第一优先级来判断一个系统能不能自动化。一个页面再好看如果背后没有结构化的接口对 Agent 来说就是一座没有入口的围城。无头架构把商品、购物车、订单全部变成可编程的 GraphQL Mutation 和 Query等于给 Agent 铺了一条无障碍通道。我在复现时直接验证了一个场景让 Agent 模拟一个采购助理收到需求“找一款 500 元以内的无线耳机下单两副”。在无头架构里Agent 只需要连续调用三个工具搜索商品、加购物车、创建结算全程没有任何 UI 点击。这个流程放在传统电商后端里哪怕你有 API也希望 Agent 能有点击验证码和图片识别能力这就完全不同了。2.3 Agent 能跑起来的四个核心要素一个真正能让 Agent 稳定工作的无头架构我认为至少包含四个要素要素说明缺了会怎样语义清晰的 API按业务领域建模Schema 明确Agent 不知道传什么参数幻觉率飙升事件与订阅库存变动、订单状态变化能主动推送Agent 只能轮询实时性差还浪费额度权限作用域每个 Agent 最小权限不能越权Agent 一个幻觉可能删掉整个库可观测性Agent 的每次调用有 trace 有日志出了问题只能“猜”事故无法复盘四个要素里前两个决定 Agent 能不能干活后两个决定你敢不敢让它干活。很多团队卡在第一步API 是拼接出来的老系统字段含义混乱地址都不一样LLM 再怎么调都容易传错参数。这种项目想接 Agent第一步应该是整理 API 契约而不是选个 Agent 框架。3. 复现实操从 API 到 Agent 的完整链路3.1 第一步领域建模与 GraphQL API 设计我复现的时候没有直接接 Shopify 的闭环毕竟要真实店铺、要订阅服务而是按 Storefront API 的设计范式自己搭了一个“商品-购物车-订单”最小闭环。技术栈用的 PostgreSQL NestJS TypeGraphQL换成 Fastify Apollo 也一样。核心 Schema 片段可以直接抄type Product { id: ID! title: String! handle: String! price: Money! inventory: InventoryStatus! } type ProductConnection { nodes: [Product!]! pageInfo: PageInfo! } type Query { products(first: Int!, after: String): ProductConnection! product(id: ID!): Product } type Mutation { cartCreate(input: CartCreateInput!): Cart! cartLinesAdd(cartId: ID!, lines: [CartLineInput!]!): Cart! checkoutCreate(cartId: ID!): Checkout! }这里的设计决策值得展开说。分页我用了 cursor 分页first/after不是 offset 分页。电商目录动辄几十万商品offset 深翻页在数据库层面会越来越慢cursor 分页基于索引定位稳定且快。时间字段统一用 ISO 8601不要用时间戳人可读、跨语言解析也方便。Money 类型建议用字符串加币种不要用浮点数。浮点数跨系统传输、计算金额时会有精度问题金额这种事宁可多传几个 bit也不能丢一分钱。Agent 在调用购物车工具时如果传的是浮点金额网关层的实参校验就得先拦一道。3.2 第二步把 API 封装成 Agent Tools无头 API 只是地基Agent 上面还需要一层“工具适配器”。我采用了类 MCPModel Context Protocol的思路每个工具都要声明名称、描述、参数 schema、执行函数。LLM 会根据这些声明决定何时调用、传什么参数。一个商品搜索工具的封装TypeScript 写法大概是// agent-tools/productSearchTool.ts import { gql } from graphql-request; const ProductSearchQuery gql query ProductSearch($query: String!, $limit: Int!) { products(first: $limit, query: $query) { nodes { id title price { amount currencyCode } inventory { available units } } } } ; export const productSearchTool { name: product_search, description: 按关键词搜索商品返回含价格和库存的商品列表, parameters: { type: object, properties: { query: { type: string, description: 商品关键词如无线耳机, }, limit: { type: number, description: 返回条数默认为10最大50, }, }, required: [query], }, async execute(agentId: string, args: { query: string; limit?: number }) { const limit Math.min(args.limit ?? 10, 50); // 在网关层校验 Agent 的 scope await assertAgentScope(agentId, catalog:read); try { const data await gqlClient.request(ProductSearchQuery, { query: args.query, limit, }); return data.products ?? { nodes: [] }; } catch (err) { // 工具内部异常绝对不能裸抛给 LLM return { error: PRODUCT_SEARCH_FAILED, message: err.message, hint: 请尝试更换关键词后重试, }; } }, };这里有几个我实践下来至关重要的点常规文档里不会写。第一工具描述和参数 schema 是给 LLM 看的不是给人看的。描述写得越模糊模型越容易传错参数。我做过对比把“查询商品库存”的描述从“query product stock”改成“查询指定商品或关键词商品的实时可售库存返回可售数量与在途数量适合售前咨询和下单前核验”调用准确率提升非常明显。第二异常处理必须结构化。LLM 拿到堆栈信息时会尝试乱猜拿到一个结构化的错误对象错误码 可读信息 重试建议它会大概率遵照建议换一种方式重试。这是把不可控的失败变成可控的反馈。第三工具执行函数尽量幂等。Agent 有时会重复调用同一个工具如果这个工具有副作用比如创建订单就必须做去重。我给所有写操作加了一个 idempotencyKey 参数重复请求用同一个 key 直接返回第一次的结果。这个机制在复现测试里救了我好几次。3.3 第三步Agent 记忆体系怎么搭光有工具还不够Agent 要有记性否则复杂任务里它会忘了自己做到哪一步。很多人在学习 Agent 课程时总会遇到“记忆体系怎么实现”的问题我的落地方式是三级记忆短期记忆是当前会话的上下文。就算上下文窗口堆到 200K token也不能把整个会话历史全塞进去。我的做法是窗口滑动保留最近几轮对话加最新工具调用结果的摘要更早的内容压缩成一段摘要会话结束时持久化。中期记忆是任务级别的。一个采购助理 Agent 正在处理的订单任务、执行到哪一步、下一步计划是什么必须落库。我用 PostgreSQL 存了 task 表和 step 表每一步工具调用的输入输出都记录在内。这样 Agent 中途崩溃重启后也能从持久化状态接着跑而不是从头再来。长期记忆是用户画像和领域知识。用户的购买偏好、历史订单、常购商品Agent 决策时要能快速检索。我用 pgvector 做了向量索引把用户消息和商品描述 embedding 后存向量库查询时按相似度召回。这一步对回答“我是不是买过类似的东西”这种问题非常关键。短期、中期、长期三种记忆的存储介质和查询频率不一样不能放在同一个表里。短期高频访问用 Redis 这类内存库中期用业务表记录每一步执行痕迹长期用向量库承载语义检索。这个分级方案在我测试长链路任务时明显减少了 token 消耗和上下文污染。3.4 第四步多 Agent 协作怎么组织单 Agent 只能做线性任务真实商业流程里要按角色拆。我复现的是三个 Agent 协作导购 Agent负责商品推荐和咨询、履约 Agent负责购物车、下单、支付、风控 Agent负责校验订单合规和高频操作限制。它们之间怎么通信我只用了很朴素的“黑板模式”共享一份任务状态表每个 Agent 读取自己需要的上下文写入自己的执行结果。导购 Agent 把选好的商品 SKU 列表写入任务表履约 Agent 读取后开始建购物车、下单依赖关系靠任务状态字段约束。这个设计的好处是没有循环依赖出了问题定位很快。坏处是 Agent 之间不能实时对话但电商流程本来就不是高频对话式协作更多是流程接力。流程接力用事件表驱动比 Agent 间直连异步调用稳定得多。很多人问到底该不该上重框架来编排 Agent。我建议先搞清楚一个概念Agent 是决策大脑Harness 是执行脚手架和运行时。如果你连业务模型都没理清楚上来就套一个重型 Agent 编排框架只会让链路更黑。先让流程用最简单的方式跑通再考虑要不要引入编排层。我在实践里发现多 Agent 场景最怕的不是模型能力不够而是 Agent 之间互相抢资源或重复执行。所以每个 Agent 要有独立作用域和互斥锁。履约 Agent 在处理订单时导购 Agent 无论收到什么请求都不能对这笔订单做写操作。这个互斥不能靠提示词约束要靠数据层的行锁——提示词负责“想”数据层负责“不能”。3.5 第五步权限、安全与可观测性给 Agent 开通 API 权限时我见过太多人直接给一个服务端全权限的 token这是灾难性的。Agent 毕竟是 LLM 驱动它可能有幻觉、可能被提示注入、可能做出超预期操作。权限设计的逻辑必须是默认拒绝最小开放。我在网关层做了两层控制。第一层是身份层每个 Agent 分配一个专属 clientId所有请求带 JWT。第二层是作用域层从组织、模块、动作三个维度控制权限。比如导购 Agent 可以有 catalog:read、cart:write、orders:read但绝没有 discount:write 和 customer:delete。可观测性方面我把所有 Agent 调用的输入输出做了全量审计落库并且给每个规划循环生成一个 traceId。线上排查时拿出 traceId 就能看到 Agent 从开始规划到执行完工具的全部链路哪一步慢、哪一步错、哪一步触发了限流一目了然。没有这套审计日志Agent 系统出了事故你连“它到底做了什么”都不知道这是最恐怖的事。4. 常见问题与排查技巧实录4.1 “Agent 执行被终止”到底怎么排查热词里那个 agent execution terminated due to error翻译过来就是“Agent 执行被终止”我复现过程里至少遇到五次。最常见的原因工具抛了未捕获异常LLM 拿到错误后重试两次都失败最终整体终止。排查思路就是三条线先看 traceId确认是哪个工具出的错再复现该工具的输入参数看是不是缺了边界校验最后看是不是上游服务不可用比如 Redis 挂掉或限流。把工具层异常全部改成结构化返回之后这个“终止”问题能减少大半。还要注意一个问题Agent 框架在报错时如果错误信息里没有上下文你根本不知道是哪次工具调用导致的。所以工具执行函数里要记录完整上下文包括参数、原始返回、报错码。日志打不全查问题全靠猜这是 Agent 开发里最痛苦的事。4.2 限流、缓存与降级的组合拳Agent 的调用模式和人不一样。人浏览商品会犹豫、会慢慢点Agent 在一个规划循环里可能瞬间调用五六个工具 API。如果按单个请求限流Agent 几秒钟内就会被 429 压死整个任务直接崩掉。我的做法是在网关层按“会话”维度做令牌桶每个 Agent 会话每秒允许 20 个 API 请求突发可以到 50。缓存策略也做了调整商品基础信息缓存 60 秒库存信息缓存 5 秒订单状态不缓存。这个组合套装下去复现 Demo 在高频测试下跑得很稳。这个顺序也重要先限流后缓存。限流要在网关最前面缓存要在 API 服务层。如果缓存走的是 CDN要注意 Agent 查询的实时性要求——库存这类数据不能缓存太久否则 Agent 给出的“可售数量”是陈旧数据用户下单时才发现没货体验极差。4.3 React Native 项目真要迁移时怎么处理如果你正在维护一个 RN 项目因为 Shopify 的事开始焦虑我的建议是别急着推倒重来。先用审计方式把项目拆开哪些页面是纯内容渲染哪些是强交互。内容渲染类页面商品详情、活动页继续用 RN 没问题强交互类页面支付、扫码、导航逐步退回原生壳。迁移时最值得保留的是业务逻辑层和设计系统API client、状态管理、主题 token 可以原样迁移到新架构丢掉的只是跨端 UI 组件那层抽象。另外RN 项目常见的首屏白屏问题往往是 bundle 加载和渲染时序的问题迁移原生壳时正好一起治掉。框架不是你的资产业务可复用层才是。这个认知什么时候想明白都不晚。很多人舍不得框架本质是舍不得已经写的代码但那些绑死在 UI 抽象里的代码跨端复用的价值本来就没你想象的那么大。4.4 无头架构的适用边界与我的判断最后说一下边界。无头架构是不是所有电商都必须上不是。如果只是一个小体量的独立站一个月卖几百单传统架构又快又稳没必要为了“未来可能接 AI”提前搞一套复杂 API。无头架构的价值建立在多端需求和自动化需求上。你有 App、Web、小程序、Agent 四个客户端一套内核 API 摊薄下来非常划算你确定要上 Agent 自动化无头是必经之路。反过来的情况也有如果你只有一个展示性官网连交易都在第三方平台要去做一个无头架构就是纯纯的过度设计。我个人这几年的体会是架构选型的核心不是追热点而是算清楚系统真正的边界在哪里。Shopify 放弃 React Native不代表 React Native 是坏技术它提醒我们的是技术在服务业务时选型要服从于真实的运行环境。无头架构让我在 Agent 接入这件事上省了无数“点击模拟”的功夫因为一切能力本身就是接口。给 Agent 用的系统最好的不是“带界面的系统”而是“能把界面剥离的系统”。