DrillLab
练习Practice

动手做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.

0 / 148个做对过you got right

练习Exercises

筛出 148 个练习 · 第 9 / 13 页。Showing 148 · page 9 / 13.
来自From TODO 2 · Order.shippingInfoTODO 2 · Order.shippingInfo · Federation 考试Federation exam
L1认出来Spot it为什么不能直接调数据源Why you cannot just call the data source

dataSources.shippingDataSource.getShippingInfo(parent.id) 能让两条测试都通过。为什么还是错的?

dataSources.shippingDataSource.getShippingInfo(parent.id) makes both tests pass. So why is it still wrong?

先选一个选项Pick an option first
来自From TODO 3 & 4 · Query.order 与 Query.ordersTODO 3 & 4 · Query.order and Query.orders · Federation 考试Federation exam
L2填空Fill the blanks补全两个 Query resolverFill in both Query resolvers

四个空横跨两个 resolver。注意它们数据来源不同、兜底策略不同。

Four blanks across two resolvers. Note they read from different places and need different fallbacks.

JSsrc/resolvers/orderResolvers.js4 个空4 blanks
1// schema: order(id: ID!): Order (可空)
2async order(_, { id }, { dataSources, loaders, correlationId }) {
3 try {
4 const order = await loaders..load(id);
5
6 if (!order) {
7 throw new GraphQLError(`Order not found: ${id}`, {
8 extensions: { code: ErrorCodes., correlationId }
9 });
10 }
11 return order;
12 } catch (error) {
13 if (error instanceof GraphQLError) throw error;
14 throw new GraphQLError('Failed to fetch order', {
15 extensions: { code: ErrorCodes.SERVICE_ERROR, correlationId }
16 });
17 }
18}
19
20// schema: orders(userId: ID!): [Order!]! (双重非空)
21async orders(_, { }, { dataSources, correlationId }) {
22 try {
23 const orders = await dataSources.orderDataSource.getOrdersByUserId(userId);
24 return orders ?? ;
25 } catch (error) {
26 if (error instanceof GraphQLError) throw error;
27 throw new GraphQLError('Failed to fetch orders', {
28 extensions: { code: ErrorCodes.SERVICE_ERROR, correlationId }
29 });
30 }
31}
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From TODO 3 & 4 · Query.order 与 Query.ordersTODO 3 & 4 · Query.order and Query.orders · Federation 考试Federation exam
L3写整块Write a block不看答案,自己写出两个 Query resolverWrite both Query resolvers yourself, without looking at the answer

两个函数一起写。注意它们的数据来源、兜底策略、 context 解构都不一样。

Write both functions together. They differ in where they read from, what they fall back to, and what they destructure out of context.

要求Requirements
  • Query.order 用 orderLoader 取数据Query.order reads through orderLoader
  • Query.order 找不到时抛带 ORDER_NOT_FOUND code 的 GraphQLErrorWhen Query.order finds nothing, it throws a GraphQLError carrying the ORDER_NOT_FOUND code
  • Query.orders 用 orderDataSource.getOrdersByUserId 取数据Query.orders reads through orderDataSource.getOrdersByUserId
  • Query.orders 校验 userId,非法时抛 INVALID_INPUTQuery.orders validates userId and throws INVALID_INPUT when it is not valid
  • Query.orders 绝不返回 null(schema 是 [Order!]!)Query.orders never returns null (the schema says [Order!]!)
  • 两个都用 try/catch,catch 里先放行已有的 GraphQLErrorBoth use try/catch, and the catch lets an existing GraphQLError through first
  • 两个都在日志里带上 correlationIdBoth include correlationId in their log line
JavaScriptsrc/resolvers/orderResolvers.js
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 三处埋雷:怎么系统地找出来The three planted bugs: how to find them systematically · Federation 考试Federation exam
L3Debug LabDebug LabDebug Lab · Cannot read properties of undefinedDebug Lab · Cannot read properties of undefined

Mutation.createOrder 的测试挂了。 报错说在读一个 undefined 的属性。自己分诊。

The Mutation.createOrder test fails. The error says something read a property of undefined. Diagnose it yourself.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
● Order Resolvers › Mutation.createOrder resolver › should create a new order successfully GraphQLError: Failed to create order 91 | } catch (error) { 92 | console.error(`[${correlationId}] Error creating order:`, error.message); > 93 | throw new GraphQLError('Failed to create order', { # 往上翻,console.error 打出的原始错误是: console.error [test-correlation-id] Error creating order: Cannot read properties of undefined (reading 'createOrder')● Order Resolvers › Mutation.createOrder resolver › should create a new order successfully GraphQLError: Failed to create order 91 | } catch (error) { 92 | console.error(`[${correlationId}] Error creating order:`, error.message); > 93 | throw new GraphQLError('Failed to create order', { # Scroll up: the raw error that console.error printed is console.error [test-correlation-id] Error creating order: Cannot read properties of undefined (reading 'createOrder')
JavaScriptsrc/resolvers/orderResolvers.js示意Illustrative
1const order = await dataSources.orderAPI.createOrder({ userId, items });
2
3// 参考:index.js 里 context 的 return
4// return {
5// dataSources: { orderDataSource, inventoryDataSource, shippingDataSource },
6// loaders: { shippingInfoLoader, orderLoader },
7// correlationId
8// };
1const order = await dataSources.orderAPI.createOrder({ userId, items });
2
3// For reference: what the context function in index.js returns
4// return {
5// dataSources: { orderDataSource, inventoryDataSource, shippingDataSource },
6// loaders: { shippingInfoLoader, orderLoader },
7// correlationId
8// };
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 三处埋雷:怎么系统地找出来The three planted bugs: how to find them systematically · Federation 考试Federation exam
L3Debug LabDebug LabDebug Lab · 错误码不对(不报错的那种 bug)Debug Lab · The wrong error code (the kind of bug that throws nothing)

代码跑得通,没有异常。但测试说错误码不对。 这是三处埋雷里最值得理解的一处。

The code runs and raises no exception, but the test says the error code is wrong. Of the three planted bugs, this is the one most worth understanding.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
● Order Resolvers › Error handling › should return structured error for validation failures expect(received).toBe(expected) // Object.is equality Expected: "INVALID_INPUT" Received: "SERVICE_ERROR" # 测试代码: # const input = { userId: '789', items: [] }; ← 空 items,应该被校验拦下 # try { # await resolvers.Mutation.createOrder({}, input, context); # throw new Error('Should have thrown an error'); # } catch (error) { # expect(error.extensions.code).toBe('INVALID_INPUT'); # }● Order Resolvers › Error handling › should return structured error for validation failures expect(received).toBe(expected) // Object.is equality Expected: "INVALID_INPUT" Received: "SERVICE_ERROR" # The test code: # const input = { userId: '789', items: [] }; ← empty items, validation should stop it # try { # await resolvers.Mutation.createOrder({}, input, context); # throw new Error('Should have thrown an error'); # } catch (error) { # expect(error.extensions.code).toBe('INVALID_INPUT'); # }
JavaScriptsrc/resolvers/orderResolvers.js示意Illustrative
1try {
2 if (!userId || !items || items.length === 0) {
3 throw new GraphQLError('Invalid order input', {
4 extensions: { code: ErrorCodes.INVALID_INPUT, correlationId }
5 });
6 }
7 const order = await dataSources.orderDataSource.createOrder(userId, pricedItems);
8 return order;
9} catch (error) {
10 console.error(`[${correlationId}] Error creating order:`, error.message);
11 throw new GraphQLError('Failed to create order', {
12 extensions: { code: ErrorCodes.SERVICE_ERROR, correlationId }
13 });
14}
1try {
2 if (!userId || !items || items.length === 0) {
3 throw new GraphQLError('Invalid order input', {
4 extensions: { code: ErrorCodes.INVALID_INPUT, correlationId }
5 });
6 }
7 const order = await dataSources.orderDataSource.createOrder(userId, pricedItems);
8 return order;
9} catch (error) {
10 console.error(`[${correlationId}] Error creating order:`, error.message);
11 throw new GraphQLError('Failed to create order', {
12 extensions: { code: ErrorCodes.SERVICE_ERROR, correlationId }
13 });
14}
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 先看懂给你的东西:Spring 的几个注解和一条请求链路Understand what you are given: a few Spring annotations and the path one request takes · Federation 考试Federation exam
L1认出来Spot it找不到订单时该怎么处理What to do when the order is not found

getOrderById 端点里,orderService.getOrderById(id) 找不到时会抛EntityNotFoundException。 控制器应该怎么写?

In the getOrderById endpoint, orderService.getOrderById(id) throws EntityNotFoundException when it finds nothing. How should the controller be written?

先选一个选项Pick an option first
来自From 先看懂给你的东西:Spring 的几个注解和一条请求链路Understand what you are given: a few Spring annotations and the path one request takes · Federation 考试Federation exam
L1认出来Spot it这三个参数注解各从哪取值Where each of these parameter annotations reads from

请求是 PATCH /api/orders/7/status, body 是 {"status":"SHIPPED"}
方法签名是 updateOrderStatus(@PathVariable Long id, @RequestBody Map<String,String> statusUpdate)idstatusUpdate 分别是什么?

The request is PATCH /api/orders/7/status with the body {"status":"SHIPPED"}.
The method signature is updateOrderStatus(@PathVariable Long id, @RequestBody Map<String,String> statusUpdate). What are id and statusUpdate?

先选一个选项Pick an option first
来自From 六个端点:状态码就是这道题的全部Six endpoints: the status codes are the whole task · Federation 考试Federation exam
L1认出来Spot itPOST 创建成功该返回什么What a successful POST should return

POST /api/orders 成功创建了一个订单。 该返回哪个状态码,怎么写?

POST /api/orders created an order successfully. Which status code should it return, and how do you write that?

先选一个选项Pick an option first
来自From 六个端点:状态码就是这道题的全部Six endpoints: the status codes are the whole task · Federation 考试Federation exam
L1认出来Spot it为什么 return null 能骗过三个测试Why return null fools three of the tests

baseline 状态下六个端点全是 return null, 五个测试却通过了三个。为什么?

At the baseline all six endpoints are just return null, yet three of the five tests pass. Why?

先选一个选项Pick an option first
来自From 六个端点:状态码就是这道题的全部Six endpoints: the status codes are the whole task · Federation 考试Federation exam
L2填空Fill the blanks补全三个关键端点的状态码与调用Fill in the status codes and calls of three key endpoints

五个空。第 2、4、5 个是这道题真正的得分点。

Five blanks. Numbers 2, 4 and 5 are where the credit in this question actually is.

JAVAOrderController.java5 个空5 blanks
1@GetMapping("/api/orders/{id}")
2public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
3 // 找不到时 service 抛 EntityNotFoundException,交给全局处理器
4 return ResponseEntity.(orderService.getOrderById(id));
5}
6
7@PostMapping("/api/orders")
8public ResponseEntity<Order> createOrder(@Valid @RequestBody CreateOrderRequest request) {
9 Order created = orderService.createOrder(request);
10 return ResponseEntity.status(HttpStatus.).body(created);
11}
12
13@PatchMapping("/api/orders/{id}/status")
14public ResponseEntity<Order> updateOrderStatus(
15 @PathVariable Long id,
16 @RequestBody Map<String, String> statusUpdate) {
17 String raw = statusUpdate.get("status");
18 if (raw == null || raw.isBlank()) {
19 throw new ResponseStatusException(HttpStatus., "status is required");
20 }
21 OrderStatus status = OrderStatus.(raw.trim().toUpperCase());
22 return ResponseEntity.ok(orderService.updateOrderStatus(id, status));
23}
24
25@DeleteMapping("/api/orders/{id}")
26public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
27 orderService.deleteOrder(id);
28 return ResponseEntity.().build();
29}
把 5 个空都填上才能检查(还差 5 个)Fill all 5 blanks to check (5 to go)
来自From 六个端点:状态码就是这道题的全部Six endpoints: the status codes are the whole task · Federation 考试Federation exam
L3写整块Write a block不看答案,自己写出全部六个端点Write all six endpoints yourself, without looking at the answer

六个端点一起写。业务逻辑全部调 orderService, 你负责选对状态码、处理可选参数、转 enum、打日志。

Write all six endpoints. Every piece of business logic goes through orderService; your job is picking the right status codes, handling the optional parameter, converting the enum, and logging.

要求Requirements
  • GET /api/orders:?userId= 传了就按用户过滤,没传返回全部;200GET /api/orders: filter by user when ?userId= is given, otherwise return everything; 200
  • GET /api/orders/{id}:200;不要 try/catch,让 404 由全局处理器给出GET /api/orders/{id}: 200; no try/catch, let the global handler produce the 404
  • GET /api/orders/user/{userId}:200GET /api/orders/user/{userId}: 200
  • POST /api/orders:201 CreatedPOST /api/orders: 201 Created
  • PATCH /api/orders/{id}/status:把 body 里的字符串转成 OrderStatus;缺失或非法值返回 400;成功 200PATCH /api/orders/{id}/status: convert the string in the body into an OrderStatus; return 400 when it is missing or invalid; 200 on success
  • DELETE /api/orders/{id}:204 No ContentDELETE /api/orders/{id}: 204 No Content
  • 六个端点都用 logger.info 打日志,并带上 MDC 里的 correlationIdAll six endpoints log with logger.info and include the correlationId from the MDC
JavaOrderController.java
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 六个端点:状态码就是这道题的全部Six endpoints: the status codes are the whole task · Federation 考试Federation exam
L3Debug LabDebug LabDebug Lab · 查一个不存在的订单,返回了 200Debug Lab · Asking for an order that does not exist returns 200

五个测试全过。但手动 curl 一个不存在的 id, 得到 200 和一个空 body。期望是 404 加一段 JSON。

All five tests pass. But curl an id that does not exist by hand and you get a 200 with an empty body. It should be a 404 with a piece of JSON.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ curl -i -s localhost:8080/api/orders/999 HTTP/1.1 200 Content-Length: 0 # 期望: # HTTP/1.1 404 # { "timestamp": "...", "status": 404, "message": "Order not found with id: 999" } # mvn test:Tests run: 5, Failures: 0 ← 测试全过!$ curl -i -s localhost:8080/api/orders/999 HTTP/1.1 200 Content-Length: 0 # Expected: # HTTP/1.1 404 # { "timestamp": "...", "status": 404, "message": "Order not found with id: 999" } # mvn test: Tests run: 5, Failures: 0 ← every test passes!
Java有问题的实现The broken implementation示意Illustrative
1@GetMapping("/api/orders/{id}")
2public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
3 logger.info("GET /api/orders/{} correlationId={}", id, correlationId());
4 try {
5 return ResponseEntity.ok(orderService.getOrderById(id));
6 } catch (EntityNotFoundException ex) {
7 return null;
8 }
9}
1@GetMapping("/api/orders/{id}")
2public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
3 logger.info("GET /api/orders/{} correlationId={}", id, correlationId());
4 try {
5 return ResponseEntity.ok(orderService.getOrderById(id));
6 } catch (EntityNotFoundException ex) {
7 return null;
8 }
9}
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this