每个Express项目都会走到这一步。起初只是个小需求:在服务函数里记录一个关联ID。于是你顺手把req塞进了参数。调用它的函数需要req,再上一层的函数也需要req。几周之后,半个代码库的函数签名里都躺着这个req参数,尽管它们只读取其中一个字段。
函数签名被污染,测试要模拟req对象,服务层和Express内部耦合在一起。这是典型的技术债务雪球。
问题的根源在于参数传递的传染链。一旦某个深层函数需要请求上下文里的任何信息,整个调用栈都要跟着改。更隐蔽的伤害是测试成本——原本纯业务逻辑的单元测试,现在必须构造Express的Request对象。
Node.js从v16开始内置的AsyncLocalStorage提供了另一种思路。它能在异步调用链上创建一个存储槽,链上的任何位置都能直接读取,不需要通过参数层层传递。这个数据作用域跟随async/await、Promise、setTimeout、EventEmitter等所有异步原语自动传播。
基于这个机制,express-correlation-context封装了一个中间件。安装后只需在路由前注册一次,后续代码中通过getContext()或getCorrelationId()直接获取上下文,服务函数彻底摆脱req参数。
上下文自动包含这些信息:correlationId(自动生成UUID或从请求头读取)、startTime(请求开始时间戳)、duration()方法(计算已耗时)、ip(客户端IP,识别代理头)、method、path、userAgent。也可以通过setContext()在认证中间件等位置追加自定义字段,比如userId和tenantId,下游代码直接读取。
实现原理上,中间件为每个请求创建独立的AsyncLocalStorage实例,在next()调用时进入该存储作用域。由于Node.js的异步上下文追踪,后续所有异步操作都会继承这个作用域,无论调用嵌套多深。
这个方案的优势在于零运行时依赖,对现有代码的侵入性极低。服务层从"需要知道HTTP请求的存在"降级为"需要知道上下文的存在",测试时只需模拟上下文对象而非完整的Express请求。对于已经深陷req传递泥潭的代码库,可以逐步迁移——新旧方式在过渡期内可以共存。
热门跟贴