题目重点:DoorDash Code Craft 可追溯题:用 userId 查 customerId,再并发取支付卡和地址,明确 500、缺字段、超时与部分失败策略。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 可见题面规定 UserService(userId) 先返回 customerId,随后 PaymentService(customerId) 和 AddressService(customerId) 独立调用。示例代码接收三个已给定的 response map,模拟可执行编辑器,不假装它负责真正发起网络请求。
- 示例采用明确的训练策略:用户身份请求失败则报错;卡或地址失败时返回 null,并在 Errors 列出原因。题目把 fallback/default 作为面试讨论点,没有指定唯一正确策略。
- 三个 response map 形如 {statusCode, data}。示例假设成功码为 200、customerId 为非空字符串、card 为对象、address 为非空字符串;具体字段名以现场代码为准。
- 的 Java starter 是更宽的同题版本:用户响应含 roles,支付响应含 giftCards,期望输出还有 availableGiftCardCredits 与 formattedAddress;本 Python map 示例按 的较窄公开契约,不把那些字段强加给所有版本。
解题路线
- 先画依赖图:UserService 是串行屏障;拿到 customerId 后,支付和地址两条调用可并发。真实服务封装要传同一 customerId,并用一次请求 deadline 限制尾延迟。
- 把 HTTP 失败与成功响应缺字段分开处理。身份缺失不能安全拼接其他用户数据;可选字段失败按产品确认的部分成功契约返回,并让调用方能识别缺失原因。
- 仅对幂等读请求的可重试失败做有上限的退避重试,限定总超时与并发数。不要把每个 500 无限制重试;缓存兜底要检查用户绑定、数据新鲜度和隐私要求。
手动走一遍
UserService 对 userId=u7 返回 customerId=c1。PaymentService 返回卡尾号 4242,AddressService 返回 Main St,则聚合为 CustomerId=c1、DefaultCard={card_last_four:4242}、Address=Main St。若地址 500,示例返回 Address=null、Errors=[address_unavailable];若 UserService 500,直接报错,不拼出假身份。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Training contract for the visible DoorDash Bootstrap API prompt.
The editor's three supplied maps stand in for upstream service responses.
"""
from collections.abc import Mapping
class BootstrapError(ValueError):
pass
def _payload(response: Mapping, service: str) -> Mapping | None:
if response.get("statusCode") != 200:
return None
data = response.get("data")
if not isinstance(data, Mapping):
raise BootstrapError(f"{service}: missing data map")
return data
def bootstrap_api(
user_response: Mapping,
payment_response: Mapping,
address_response: Mapping,
) -> dict:
"""Fail closed on identity; return explicit nulls for optional failures.
The caller must ensure payment/address maps were fetched for the returned
customerId. This function cannot prove upstream request provenance.
"""
user = _payload(user_response, "user")
if user is None:
raise BootstrapError("user service unavailable")
customer_id = user.get("customerId")
if not isinstance(customer_id, str) or not customer_id:
raise BootstrapError("user response has no customerId")
errors: list[str] = []
payment = _payload(payment_response, "payment")
if payment is None:
errors.append("payment_unavailable")
elif not isinstance(payment.get("defaultCard"), Mapping):
errors.append("payment_card_missing")
card = dict(payment["defaultCard"]) if payment and isinstance(payment.get("defaultCard"), Mapping) else None
address_data = _payload(address_response, "address")
if address_data is None:
errors.append("address_unavailable")
address = address_data.get("address") if address_data else None
if address_data is not None and (not isinstance(address, str) or not address):
errors.append("address_missing")
address = None
return {
"CustomerId": customer_id,
"DefaultCard": card,
"Address": address,
"Errors": errors,
}
复杂度
设响应数据总大小为 S,映射与卡字段复制 O(S) 时间、O(S) 输出空间。真实服务的关键是延迟:Tuser + max(Tpayment,Taddress) 加有界重试时间;若顺序调用则为三者之和。
易错点与追问
容易失分的边界
- 把三个服务同时发出会在 customerId 尚未确定时请求支付/地址,违反可见依赖;但拿到 customerId 后继续串行又无谓增加延迟。
- 默认空卡或空地址会被前端误当成真实值。部分失败需要显式错误标志;如果产品要求原子全量响应,则应改为整体失败,不能悄悄混用契约。
- 本示例的 map 接口无法验证 payment/address 响应属于哪个 customerId,真实客户端必须在请求层保证参数绑定,缓存 key 也应含 customerId。
继续深挖
- 若下游服务可能超时,在整体 deadline 内分配各调用预算,并区分超时、5xx 与永久性 4xx;重试加 jitter 且尊重预算。
- 若需要服务结果逐步显示,可定义每块独立 loading/error 状态;若必须一次返回全量,明确哪些字段是关键字段,再写相应失败测试。
- 对生产测试覆盖 8 种上游成功/失败组合、缺 customerId、缺 card/address、不同 customerId 的缓存隔离和重复请求。
- 若采用 Java starter 变体,另确认 gift card credits 的数值/货币格式、roles 来源与 formattedAddress 中姓名拼接规则;不要直接从示例输出反推完整规范。
