Schema Stitching 与 Federation 差异

解读

国内 PHP 面试里问 GraphQL 的“Schema Stitching 与 Federation 差异”,并不是要你背概念,而是考察:

  1. 你是否真的在生产环境落地过微服务/多端 BFF(Backend For Frontend);
  2. 面对多团队并行开发,你如何保证 API 版本零侵入、字段零冲突;
  3. 当 QPS 涨到万级,你的网关层是继续堆 Stitching 还是迁移到 Federation,如何做灰度、回滚、监控。

一句话:面试官想听你“踩过哪些坑、为什么换方案、PHP 里怎么落地”。

知识点

  1. Schema Stitching(模式缝合)

    • 核心思想:在网关层把多个子服务的 SDL 字符串手动 merge,resolver 用远程 HTTP 调用(graphql-php + Guzzle)拼接结果。
    • 典型 PHP 库:rebing/graphql-laravel + stitcher 插件,或自写 SchemaBuilder。
    • 优点:上手快,老项目改造只需加一层网关;缺点:冲突字段需人工 rename,类型重复需手动 transform,上线后“谁改谁翻车”。
  2. Federation(联邦)

    • 核心思想:各子服务按 Apollo Federation 规范暴露 _entities_service,网关用 Router(rust 版或 Node.js 版)自动拼 Supergraph,PHP 端只需实现 resolver 级联。
    • 官方 PHP 实现:apollo-federation-php(GitHub 2.3k star),与 Laravel/Symfony 无缝集成。
    • 优点:字段归属声明式,冲突自动检测,支持分布式追踪;缺点:PHP 进程常驻(RoadRunner/Swoole)才配玩,团队需统一 CI 流程。
  3. 国内落地差异

    • Stitching 在 2018-2020 年电商大促中用得最多,因为 PHP-FPM 存量大、改造小;但 2021 年后阿里、京东、美团逐步切 Federation,解决“商品域涨价、订单域优惠券”字段互踩问题。
    • 面试常追问:如何做“热加载”?PHP 没有原生动态 schema,Federation 需把 .graphql 文件编译成 OPcache 可缓存的 PHP 数组,再配 Envoy 做 schema 版本灰度。

答案

“我在上一家公司用 PHP 7.4 + Laravel 搭建营销中台,最早采用 Schema Stitching:

  1. 用 rebing/graphql-laravel 把优惠券、商品、库存三个子服务的 schema 通过 buildSchemaFromSDL() 合并,网关层写 TypeMerger 解决字段冲突;
  2. 上线后发现大促期间商品域加字段 promotionTags,优惠券域也加了同名不同类型,导致网关 500,回滚一次损失 300 万 GMV;
  3. 随后迁移到 Federation:商品服务用 apollo-federation-php 暴露 @key(fields: "skuId"),优惠券服务通过 @extends 扩展,网关换成 Apollo Router,PHP 端只写 resolver;
  4. 灰度方案:在 Nginx + Consul 做流量 5% 实验,对比 p99 延迟从 120 ms 降到 65 ms,字段冲突降为 0;
  5. 代码层面,把 .graphql sdl 预编译成 schema.cache.php,OPcache 命中 99%,CPU 降 18%。

结论:Stitching 适合‘一人一把梭’的快速原型;Federation 适合‘多团队、高并发、长期演进’的国内电商场景。”

拓展思考

  1. 如果公司仍跑 PHP-FPM,无法常驻,Federation 的 __resolveReference 每次请求都要重新建连接,怎么优化? → 用 PHP-PM 或 RoadRunner 做进程池,把 DataLoader 池化,连接复用率可提升 6 倍。

  2. 国内部分云厂商(阿里云 MSE、腾讯云 TSE)已推出“托管 Federation 网关”,但只支持 Node.js/Rust,PHP 子服务如何无缝接入? → 在 CI 阶段把 rover supergraph compose 做成 Composer Script,自动上传 schema,PHP 侧零改造即可被聚合。

  3. 当业务继续拆分出“用户画像”服务,字段重名但含义不同(如 User.name 在登录域是昵称,在风控域是实名),Federation 的 @tag@policy 如何配合 ACL? → 结合 Laravel Gate 定义字段级策略,网关层通过 @tag(name: "internal") 统一鉴权,实现“同样字段、不同权限”的细粒度隔离。