动手做Get your hands on it
练习跟着课文走 —— 每节课尾都有本课的练习。这一页是全部练习的总库,想集中刷题的时候来。 每个练习都写清了它来自哪一节,卡住了就回去看那一节。Practice follows the lessons — every lesson ends with the exercises for that lesson. This page is the whole library, for when you want to drill in one sitting. Each exercise names the lesson it came from, so you can go back when you stall.
已筛到你正在学的《Federation 考试》。想看全部就点上面的「全部」。Filtered to Federation exam — the course you are on. Use “All” above to see everything.
练习Exercises
筛出 2 个练习(共 148 个)。Showing 2 of 148.空目录开始,搭出一个 Apollo Federation subgraph, 实现四个 resolver 加一个 mutation,让 10 个测试全过, 并且 _service 和 _entities 都能正常工作。不要打开源项目的 orderResolvers.js。
Starting from an empty directory, build an Apollo Federation subgraph. Write four resolvers plus one mutation, get all 10 tests passing, and make both _service and _entities work. Do not open orderResolvers.js from the source project.
- 用 @apollo/server + @apollo/subgraph 起一个 subgraph,监听 4000Start a subgraph with @apollo/server + @apollo/subgraph, listening on 4000
- schema 从 .graphql 文件读入,用 buildSubgraphSchema 组装Read the schema from a .graphql file and assemble it with buildSubgraphSchema
- 每个请求构造 context:三个数据源、两个 DataLoader、一个 correlationIdBuild the context per request: three data sources, two DataLoaders, one correlationId
- correlationId 优先取请求头 x-correlation-id,没有就生成Take correlationId from the x-correlation-id request header, and generate one when it is absent
- 实现 User.__resolveReference:把 representation 变成本地对象Write User.__resolveReference: turn the representation into a local object
- 实现 User.orders:按 user.id 取订单,[Order!]! 所以绝不返回 nullWrite User.orders: read orders by user.id; the type is [Order!]!, so never return null
- 实现 Order.shippingInfo:必须走 DataLoader 防 N+1;可空,找不到返回 nullWrite Order.shippingInfo: it must go through the DataLoader to prevent N+1; it is nullable, so return null when nothing is found
- 实现 Query.order:走 DataLoader;找不到抛带 ORDER_NOT_FOUND 的 GraphQLErrorWrite Query.order: go through the DataLoader; when nothing is found, throw a GraphQLError carrying ORDER_NOT_FOUND
- 实现 Query.orders:校验 userId;[Order!]! 所以兜底 []Write Query.orders: validate userId; the type is [Order!]!, so fall back to []
- 实现 Mutation.createOrder:先查商品价格补全 items,再创建;校验失败抛 INVALID_INPUTWrite Mutation.createOrder: look up product prices to complete items first, then create; throw INVALID_INPUT when validation fails
- 两个 DataLoader 的 batch 函数:返回数组的长度与顺序必须和 keys 一一对应The batch function of both DataLoaders: the array it returns must match keys in both length and order
- 所有 resolver 都用 try/catch,catch 第一行放行已有的 GraphQLErrorWrap every resolver in try/catch, and let an existing GraphQLError pass through on the first line of catch
- 所有日志和错误 extensions 里带上 correlationIdCarry correlationId in every log line and in the extensions of every error
这一关的意义就在于「没有答案也能写出来」。请确认你已经在本机建好文件、跑过验证命令,再打开参考答案对照。The whole point of this level is writing it with no answer in front of you. Create the files on your machine and run the verification commands first, then come back and compare.
给你 OrderService 的方法签名和五个测试。 自己搭一个 Spring Boot 项目,写出六个端点。不要打开源项目的 OrderController.java。
You are given the method signatures of OrderService and five tests. Set up a Spring Boot project yourself and write six endpoints. Do not open OrderController.java from the source project.
- Spring Boot 3.3 + Java 17,依赖 web / validation / actuator / testSpring Boot 3.3 + Java 17, with the web / validation / actuator / test dependencies
- 一个 @RestController,构造器注入 OrderServiceOne @RestController, with OrderService injected through the constructor
- GET /api/orders:?userId= 传了就按用户过滤,没传返回全部;200GET /api/orders: filter by user when ?userId= is given, return everything when it is not; 200
- GET /api/orders/{id}:200;找不到时由全局异常处理器给出 404(控制器不要 catch)GET /api/orders/{id}: 200; when nothing is found, the global exception handler answers 404 (do not catch it in the controller)
- GET /api/orders/user/{userId}:200GET /api/orders/user/{userId}: 200
- POST /api/orders:@Valid 校验请求体;成功返回 201 CreatedPOST /api/orders: validate the request body with @Valid; on success return 201 Created
- PATCH /api/orders/{id}/status:body 是 {"status":"..."};转成 OrderStatus;缺失或非法值返回 400;成功 200PATCH /api/orders/{id}/status: the body is {"status":"..."}; convert it to OrderStatus; a missing or invalid value returns 400; on success 200
- DELETE /api/orders/{id}:204 No ContentDELETE /api/orders/{id}: 204 No Content
- 六个端点都用 SLF4J 打日志,并带上 MDC 里的 correlationIdAll six endpoints log through SLF4J and carry the correlationId from MDC
- 自己写一个 CorrelationIdFilter:读 X-Correlation-ID 头,没有就生成 UUID,放进 MDC,finally 里清理Write your own CorrelationIdFilter: read the X-Correlation-ID header, generate a UUID when it is absent, put it in MDC, and clear it in finally
- 自己写 GlobalExceptionHandler:EntityNotFoundException → 404,MethodArgumentNotValidException → 400Write your own GlobalExceptionHandler: EntityNotFoundException → 404, MethodArgumentNotValidException → 400
这一关的意义就在于「没有答案也能写出来」。请确认你已经在本机建好文件、跑过验证命令,再打开参考答案对照。The whole point of this level is writing it with no answer in front of you. Create the files on your machine and run the verification commands first, then come back and compare.