模拟考 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
- 还有一处最隐蔽:insertReview 内部要用 reviewer.displayName 写审计日志,所以传进去之前必须先把 reviewer 查出来附上One more is the least obvious: insertReview writes an audit log using reviewer.displayName, so the reviewer has to be looked up and attached before it is passed in
- 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)Confirm in the log that reviewerLoader really did batch (one Batching line, with N greater than 1)
这一页故意不给运行环境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.