三处埋雷:怎么系统地找出来The three planted bugs: how to find them systematically
README 只说「有 integration issues」。这一节教你怎么把它们挖出来。The README only says there are integration issues. This lesson shows you how to find them.
这一页有什么On this page8
- 01 排查方法:三张对照表The method: three reference tables
- 02 埋雷 1 · getOrderById 不存在Planted bug 1 · getOrderById does not exist
- 03 埋雷 2 · orderAPI 不存在,而且签名也错了Planted bug 2 · orderAPI does not exist, and the signature is wrong too
- 04 埋雷 3 · catch 把 INVALID_INPUT 吞成了 SERVICE_ERRORPlanted bug 3 · catch turns INVALID_INPUT into SERVICE_ERROR
- 05 Mutation.createOrder 的完整修复版The fully fixed Mutation.createOrder
- 06 为什么这三个错都「看起来很合理」Why all three bugs look reasonable
- 练习 · 动手做Practice
- 迁移模式Transfer
- 掌握一套「核对而非猜测」的排查流程Learn a debugging routine based on checking, not guessing
- 独立找出并修复三处埋雷Find and fix the three planted bugs on your own
- 把 Mutation.createOrder 改到测试通过Get Mutation.createOrder to pass its tests
- 解释为什么这三个错误都「看起来很合理」Explain why all three bugs look reasonable at first
三处埋雷各挂一个测试。而且它们的错法很典型 —— 名字对不上、签名对不上、错误被吞掉。这三类问题在任何后端代码里都会遇到。Each planted bug fails one test. All three are common kinds of mistake: a name that does not match, a signature that does not match, and an error that gets swallowed. You meet these three in any backend code.
graphql-federation-practice/node-subgraph/src/resolvers/orderResolvers.js三处埋雷都在这里All three planted bugs are in here
提醒:源项目在磁盘上是做完的版本 —— 下面就是答案。想自己先写一遍的话,现在关上。Heads up: on disk this project is the finished version — what follows is the answer. Close this if you want to write it yourself first.
graphql-federation-practice/node-subgraph/src/resolvers/orderResolvers.jsgraphql-federation-practice/node-subgraph/src/dataSources/orderDataSource.js核对方法名与签名的依据(PROVIDED,别改)What you check method names and signatures against (PROVIDED, do not change)
graphql-federation-practice/node-subgraph/src/dataSources/orderDataSource.js排查方法:三张对照表The method: three reference tables
不要靠读代码「感觉哪里怪」。逐项核对。Do not read the code looking for something that feels off. Check item by item.
README 说有 integration issues,但不说在哪。 系统的做法是核对三件事:
- 每一处
context.xxx的键名, 对照index.js里 context 函数的 return。 - 每一处数据源方法调用的名字和参数, 对照
dataSources/orderDataSource.js里的类定义。 - 每一处
throw和catch的配对, 看有没有「自己抛的错被自己吞掉」。
这三项核对能找出全部三处埋雷。而且这套方法在任何项目里都管用 —— 「跨模块的名字和签名」是所有集成 bug 的高发区。
更快的办法:先跑测试,看报错指向哪一行。三处埋雷各自挂一个测试,报错信息都很直接。 但你得能看懂报错说的是什么。
The README says there are integration issues but not where they are. The systematic approach is to cross-check three things:
- Every key name in a
context.xxxaccess, against what the context function inindex.jsreturns. - Every data source method call, name and arguments, against the class definitions in
dataSources/orderDataSource.js. - Every pairing of
throwandcatch, looking for an error you threw being swallowed by your own handler.
Those three checks find all three planted bugs. And the method works in any project — names and signatures that cross module boundaries are where integration bugs live.
The faster route: run the tests first and see which line the errors point at. Each planted bug takes down one test, and the messages are direct. But you have to be able to read what they are saying.
埋雷 1 · getOrderById 不存在Planted bug 1 · getOrderById does not exist
报错:TypeError: orderDataSource.getOrderById is not a function
位置:createOrderLoader 的 batch 函数。
核对:OrderDataSource 上只有getOrder、getOrdersByUserId、createOrder。
修法:把调用改成 getOrder(id)。不是给数据源加方法 —— 那个文件是 PROVIDED。
为什么容易犯:getOrderById 是个再自然不过的名字。 很多项目就叫这个。靠直觉写就中招。
The error: TypeError: orderDataSource.getOrderById is not a function
Where: the batch function of createOrderLoader.
Cross-check: OrderDataSource only has getOrder, getOrdersByUserId and createOrder.
The fix: change the call to getOrder(id). Not adding a method to the data source — that file is PROVIDED.
Why it is easy to fall for: getOrderById is about as natural a name as there is. Plenty of projects call it exactly that. Write on instinct and you are caught.
埋雷 2 · orderAPI 不存在,而且签名也错了Planted bug 2 · orderAPI does not exist, and the signature is wrong too
这一处其实是三个错叠在一起。This one is really three mistakes stacked on top of each other.
报错:Cannot read properties of undefined (reading 'createOrder')
原始代码:await dataSources.orderAPI.createOrder({ userId, items })
三处问题:
- 键名错。context 里是
orderDataSource,没有orderAPI。 所以dataSources.orderAPI是undefined, 在它上面取.createOrder就抛了。 - 签名错。真实签名是
createOrder(userId, items)——两个位置参数,不是一个对象。 传对象进去,userId会是那个对象,items会是 undefined。 - 缺一步。
OrderItemInput里没有price, 而数据源要用item.price算总价。resolver 必须先去查价格。
第 3 点是最隐蔽的 —— 前两点报错很直接, 第 3 点即使前两点修好了,也只会表现为totalAmount 是 NaN、items[0].price 是 undefined。 测试用expect(order.items[0].price).toBeDefined()和 expect(order.totalAmount).toBeGreaterThan(0)两条断言抓它。
The error: Cannot read properties of undefined (reading 'createOrder')
The original code: await dataSources.orderAPI.createOrder({ userId, items })
Three problems in one line:
- Wrong key name. context has
orderDataSource, there is noorderAPI. SodataSources.orderAPIisundefined, and reading.createOrderoff it throws. - Wrong signature. The real signature is
createOrder(userId, items)— two positional arguments, not one object. Pass an object anduserIdbecomes that object whileitemsbecomes undefined. - A missing step.
OrderItemInputhas noprice, and the data source needsitem.priceto compute the total. The resolver has to look the price up first.
Number 3 is the sneaky one — the first two throw loudly, while the third, even after the other two are fixed, only shows up as totalAmount being NaN and items[0].price being undefined. The tests catch it with two assertions, expect(order.items[0].price).toBeDefined() and expect(order.totalAmount).toBeGreaterThan(0).
graphql-federation-practice/node-subgraph/src/dataSources/orderDataSource.js埋雷 3 · catch 把 INVALID_INPUT 吞成了 SERVICE_ERRORPlanted bug 3 · catch turns INVALID_INPUT into SERVICE_ERROR
这一处不报错,只是错误码不对。Nothing crashes here. Only the error code is wrong.
测试报错:
病因:try 块里先抛了GraphQLError(code: INVALID_INPUT), 紧接着自己的 catch 把它接住, 重新包成 code: SERVICE_ERROR。
修法:catch 第一行加if (error instanceof GraphQLError) throw error;
为什么这是最重要的一处:前两处是「打错字」级别的错误,报错很直接。 这一处是设计缺陷 —— 代码能跑、不抛异常、只是给客户端的信号是错的。这类 bug 在生产环境里能藏几个月: 客户端一直在重试「输入不合法」的请求, 监控看到的是「服务错误率高」, 实际是校验失败被误报成了系统故障。
What the test reports:
The cause: the try block throws GraphQLError(code: INVALID_INPUT), and its own catch immediately grabs it and rewraps it as code: SERVICE_ERROR.
The fix: make the first line of the catch if (error instanceof GraphQLError) throw error;
Why this is the most important one of the three: the first two are typo-grade mistakes with direct error messages. This one is a design flaw — the code runs, throws nothing, and merely sends the client the wrong signal. This class of bug can hide in production for months: clients keep retrying requests that were invalid input, monitoring shows a high service error rate, and the real story is validation failures misreported as system faults.
Mutation.createOrder 的完整修复版The fully fixed Mutation.createOrder
三处埋雷有两处在这个函数里。Two of the three planted bugs are inside this one function.
Mutation.createOrder 的注释写着 「provided for reference - candidates focus on Query resolvers」,但它是坏的。 「给你参考」不等于「它是对的」—— 这也是这个项目的一个小陷阱。
审计实测:这样改完之后 10 个测试全部通过。
The comment on Mutation.createOrder reads “provided for reference - candidates focus on Query resolvers”, and yet it is broken. “Here for reference” does not mean “this is correct” — another small trap in this project.
Measured in the audit: with these changes all ten tests pass.
为什么这三个错都「看起来很合理」Why all three bugs look reasonable
出题人选这三处不是随机的。它们的共同点是「读代码时不会觉得奇怪」:
getOrderById—— 比getOrder更符合常见命名习惯。orderAPI—— Apollo 老版本的 DataSource 就常叫xxxAPI。createOrder({ userId, items })—— 「参数打包成对象」是现代 JS 的流行风格。- catch 里统一包装错误 —— 这是好实践, 只是漏了一个例外情况。
所以「读一遍觉得没问题」是不够的。必须核对。这也是为什么本门课反复强调 「写代码前先抄一张方法名对照表」—— 那五分钟能省下半小时的困惑。
The examiner did not pick these three spots at random. What they share is that nothing looks odd while you are reading:
getOrderById— a closer fit to common naming habits thangetOrder.orderAPI— older Apollo DataSources were often namedxxxAPI.createOrder({ userId, items })— packing arguments into an object is a popular modern JS style.- wrapping every error in the catch — that is good practice, it just misses one exception.
So “I read it and it seemed fine” is not enough. You have to cross-check. Which is why this course keeps repeating “copy out a table of method names before you write code” — those five minutes save half an hour of confusion.
动手做Get your hands on it
填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.
Mutation.createOrder 的测试挂了。 报错说在读一个 undefined 的属性。自己分诊。
The Mutation.createOrder test fails. The error says something read a property of undefined. Diagnose it yourself.
代码跑得通,没有异常。但测试说错误码不对。 这是三处埋雷里最值得理解的一处。
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.
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- 三处埋雷:getOrderById 不存在、orderAPI 不存在且签名错且缺 price、catch 吞掉 INVALID_INPUT。The three planted bugs: getOrderById does not exist; orderAPI does not exist, its signature is wrong and price is missing; catch swallows INVALID_INPUT.
- 排查靠核对三张表,不靠「读一遍感觉哪里怪」—— 这三个错都看起来很合理。Find them by checking the three tables, not by reading once and looking for something odd. All three look reasonable.
- 只改 EDIT THIS 的文件;给数据源加方法是错的修法。Only change files marked EDIT THIS. Adding a method to the data source is the wrong fix.
- 自己包装错误时保留 originalError,否则真实原因彻底丢失。Keep originalError when you wrap an error, otherwise the real cause is lost for good.
- catch 里统一包装错误时,第一行必须先放行已结构化的错误。When a catch block wraps every error, its first line must let already structured errors through.