Migration: DTO-first gql execution(specs/018)
概述
specs/018 把 entity-first gql 的 serialize 统一到 response_builder 心智模型
(从 gql selection 动态构建 DTO schema → Resolver 解析),删除 legacy dict-based
_serialize 路径与 use_response_builder flag。entity-first 开发体验不变
(用户仍写 @query -> list[Entity]),改动是 executor 内部实现。
Breaking:use_response_builder flag 移除
之前(018 早期 opt-in 阶段):
GraphQLHandler(base=..., use_response_builder=True) # opt-in 新路径
GraphQLHandler(base=..., use_response_builder=False) # legacy dict-based(默认)
现在(Phase 7 T028 后,唯一路径):
如果代码里传了 use_response_builder=...,移除该参数即可。
flag 是 specs/018 引入的 opt-in 开关,从未进入正式 release(从 master 看是新 增代码),所以移除不影响任何已发布版本的用户。018 早期 flag 默认
False(legacy), 移除后默认 response_builder,等价于之前的use_response_builder=True——行为已经 过全量测试零回归验证(当前 1471 passed / 6 skipped,含 24 个 federation e2e)。
性能
build_response_model 加了 LRU 缓存(上限 1024),动态 model class 按 gql
selection 复用——消除 per-entity pydantic.create_model 开销(缓存前占 flag-on
serialize 73% cumtime,导致 flag-on 比 legacy 慢 10–30×)。
缓存 key:(entity, model_name, selection 结构 repr, federation_namespace type ids)。
(021 起用 _selection_structure_repr——只含 sub_fields 树,排除 name/alias/
arguments,所以 dict 与 FieldSelection 两种输入同 key,动态分页参数永不碎片化缓存。)
- paged 字段是 plain
result_type(020 删了 019 的PAGED_MARKER——功能性无人读), cache key 不含 paged 维(field_treerepr 已区分 paged 形状)——动态limit/offset/order/direction不碎片化缓存(018 旧方案按repr区分值, 100 独特 limit 撑 100 个 model;020 plain 恒定 1 个,实验 56× 优势)。 - gql selection 是离散的(用户固定几个 query 模式),正常命中率很高;LRU 上限兜底 防止其他维度的循环/恶意调用膨胀。
- benchmark 见
specs/018-dto-first-gql-execution/benchmark-baseline.md。
内部架构变化(贡献者参考)
- β federation dispatch:从
QueryExecutor._bfs_resolve搬到Resolver._bfs_dispatch_entity_fields(US3 / T016-T018)。fetch_remote_subtree的调用方收敛到只在 Resolver 内部(grep -rn fetch_remote_subtree src/)。 - fetch primitive 对称(US4):
fetch_remote_subtree(β)+fetch_dto_subtree(γ)对称,set_dto_page_params的唯一调用方收敛到fetch_dto_subtree。 - pagination dispatch 走 paged_provider 闭包(019):entity-first 路径的 paged
参数由 executor 构造的
paged_provider闭包算(rel default + gql args merge), per-call 透传给_bfs_dispatch_entity_fields,Resolver 不读field_sel.arguments(gql 知识只停在 executor)。Resolver 删了_extract_entity_page_args/_extract_entity_order_direction;_load_entity_field_paginated改读job.paged。 model 是纯形状(paged 字段 plainresult_type,020 删 marker),真值在 provider—— 形状和值分离,cache key 不含 paged 维。