模拟考 B · Book Reviews SubgraphMock exam B · Book Reviews Subgraph
图书评论 subgraph。它既不拥有 Author 也不拥有 Book —— 两者都由 Catalog subgraph 提供,本服务只往它们身上挂 reviews 和 averageRating。Book 用的是复合 key(isbn + edition),这是真实项目里很常见、但比单字段 key 更容易写错的情况。A book reviews subgraph. It owns neither Author nor Book — both come from the Catalog subgraph, and this service only attaches reviews and averageRating to them. Book uses a composite key (isbn plus edition), which is common in real projects and easier to get wrong than a single-field key.
与真实 Task 1 相同的考点:entity 与 @key、__resolveReference、字段 resolver 的 parent、schema 可空性决定的兜底策略、DataLoader 防 N+1 及其长度/顺序契约、结构化错误与 correlation id、以及「catch 不要吞掉已结构化错误」。新增三个考点:复合 @key、可空标量字段(null 与 0 的区别)、以及一处「batch 函数用了 filter」的埋雷。The same points as the real Task 1: entity and @key, __resolveReference, the parent argument of a field resolver, the fallback the schema nullability forces on you, DataLoader against N+1 and its length and order contract, structured errors with a correlation id, and not letting a catch swallow an already structured error. Three points are new: a composite @key, a nullable scalar field (where null and 0 differ), and one planted bug where the batch function uses filter.
- Author.reviews:按 author.id 取全部评论。schema 是 [Review!]!,绝不返回 nullAuthor.reviews: fetch all reviews by author.id. The schema says [Review!]!, so never return null
- Author.averageRating:用 ratingDataSource.computeAverage 计算。schema 是 Float(可空),没有评论时返回 nullAuthor.averageRating: compute it with ratingDataSource.computeAverage. The schema says Float, which is nullable, so return null when there are no reviews
- 两个都要 try/catch + 结构化错误 + correlationId 日志Both need try/catch, a structured error, and a correlationId in the log
- catch 第一行必须放行已经是 GraphQLError 的错误The first line of the catch must let an error through if it is already a GraphQLError
- Book.__resolveReference:Book 的 @key 是 "isbn edition" 两个字段,返回的对象必须同时保留这两个Book.__resolveReference: the @key on Book is the two fields "isbn edition", so the object you return has to keep both
- Book.reviews:必须同时按 isbn 和 edition 过滤 —— 只按 isbn 会把其他版本的评论混进来Book.reviews: filter by isbn and edition together — filtering by isbn alone mixes in reviews of other editions
- 注意 edition 是 Int、isbn 是 String,别把两者的类型弄错Note that edition is an Int and isbn a String; do not mix the types up
- Review.reviewer:用 loaders.reviewerLoader 防 N+1,不许直接调数据源Review.reviewer: use loaders.reviewerLoader to avoid N+1; do not call the data source directly
- schema 里 reviewer 可空,找不到时返回 null(测试断言 toBeNull)reviewer is nullable in the schema, so return null when there is no match (the test asserts toBeNull)
- 修好 createReviewerLoader 里的两处问题:方法名,以及那个会破坏长度/顺序契约的 filterFix the two problems in createReviewerLoader: the method name, and the filter that breaks the length and order contract
- Query.review:用 reviewLoader;找不到抛带 REVIEW_NOT_FOUND code 的 GraphQLErrorQuery.review: use reviewLoader; when there is no match, throw a GraphQLError carrying the REVIEW_NOT_FOUND code
- Query.reviews:校验 authorId;schema 是 [Review!]! 所以兜底 []Query.reviews: validate authorId; the schema says [Review!]!, so fall back to []
- 两个都带 correlationId 日志Both need a correlationId in the log
- 它注释说「提供作参考」,但它是坏的 —— 自己找出并修好全部问题Its comment says it is provided for reference, but it is broken — find and fix everything wrong with it
- 至少有三处:数据源键名、insertReview 的调用方式、以及 catch 吞掉结构化错误There are at least three: the data source key name, the way insertReview is called, and a catch that swallows a structured error
- 还有一处最隐蔽:测试直接调用 resolvers.Mutation.createReview,并断言返回对象上有 reviewer.displayName;insertReview 只返回评论本身,所以要先用 reviewerLoader 查出 reviewer,再附到返回值上One more is the least obvious: the test calls resolvers.Mutation.createReview directly and asserts that the returned object carries reviewer.displayName. insertReview returns only the review itself, so look the reviewer up with reviewerLoader first and attach it to the return value
- npm test 全部 14 个测试通过All 14 tests pass under npm test
- 自己写一个 verify 脚本:查 _service 的 SDL、用 _entities 分别解析 Author 和 Book(后者要传两个 key 字段)Write a verify script yourself: read the SDL from _service, and resolve Author and Book separately through _entities (the second one takes two key fields)
- 在日志里确认 reviewerLoader 的批量合并真的发生了(一行 Batching,N 大于 1)。同一个列表里有不同作者的评论,N 才会大于 1:数据里 978-2 第 1 版有两位作者的评论Confirm in the log that reviewerLoader really did batch (one Batching line, with N greater than 1). N only goes above 1 when one list holds reviews by different authors: in the data, edition 1 of 978-2 has reviews by two authors
这一页故意不给运行环境There is deliberately no runner on this page
不是做不到。是这一档的意义就在于什么都不给。Coding 题里那 11 道浏览器沙箱把文件、依赖、测试全备好了 —— 你只要写函数体。真实考试不是这样:你会拿到一个空文件夹或者一份跑不起来的脚手架,自己 npm install、自己读报错、自己决定文件放哪。Not because we cannot. Because getting nothing is the point of this tier. The 11 browser sandboxes under Coding hand you files, deps and tests. A real assessment does not: you get an empty folder or a scaffold that does not build, and you install, read the errors and lay out the files yourself.
所以下面给足了三样东西:能直接抄的命令、完整的文件树、起始态和做对之后各该看到什么(都是实测数字)。搭不起来不该是这道题的难点。So below you get three things: commands you can paste, the full file tree, and what the tests actually print before and after (both measured). Getting set up should not be the hard part.
去哪跑:本机 + VS Code 最接近真实考试,要先装好 Node。装不了 Node 就用 StackBlitz —— 它把 Node 编译进了浏览器,所以这种要 npm test 的服务端项目它也能跑(本站用的 Sandpack 不行,浏览器 iframe 里没有 Node)。Where: local VS Code is closest to the real thing; install Node first. No Node? Use StackBlitz — its WebContainers run Node in the browser, so even this server-side project works there.
Tests: 10 failed, 4 passed, 14 total —— 那 4 个「通过」里有假通过,空实现恰好满足了断言,别当成做对了Tests: 10 failed, 4 passed, 14 total — some of those 4 passes are false: an empty implementation happens to satisfy the assertion. Do not read them as progress.看不到这个就是环境没搭对,先解决它再动手。If you do not see this, the setup is wrong. Fix that first.Tests: 14 passed, 14 totalTests: 14 passed, 14 total这个数字是参考解法在本机实测出来的,不是估的。Measured from the reference solution on a real machine.讲解里会直接说出每一处陷阱在哪。请确认你已经在本机把这套题做完、跑过测试、按 rubric 自评过,再打开。The walkthrough names every trap outright. Only open it once you have finished the paper locally, run the tests, and scored yourself against the rubric.