前言
虽然domain模块目前处于弃用(Deprecated)状态。但经过我们多年来对domain模块的使用,并没有什么问题。同时node8.0之后,出现了async_hook模块,domain模块已经完全使用async_hook模块重写了,因此再也不用担心domain模块被移除后无法使用。
简单介绍domain模块
一个domain对象,可以将多个异步操作归为一组。当这些操作抛出Error事件时,domain对象可以捕捉到这些Error事件,并且不丢失上下文。
举个例子!
一般我们会用try-catch来捕捉异常,但如果在try里头有异步操作并且异步操作里抛出异常,那么这时候catch里面并不能捕捉到这个异常,比如:
这种情况在一个HTTP Server中比较常见。比如用户请求一个数据,node端接收到请求发起一个异步操作读取数据库。如果异步操作里面代码写得不够完善,抛出了异常,这时候因为无法捕捉到这个异常,所以没法拿到上下文(这里指用户的request与response)及时给用户返回错误信息。这样体验是不能接受的。
那么怎么解决这个问题呢? 可以引入domain模块。
就像图片代码所示,每一个HTTP请求来的时候创建一个domain对象。后续的操作全部在domain对象中执行,这样即使有异步操作出现异常,也能捕捉到这个异常并且不丢失上下文。
彩蛋!利用domain模块实现HTTP请求生命周期的全局变量
有仔细阅读过domain模块文档的同学可能会发现:
process.domain 这个全局变量会自动指向当前作用中的domain对象。
利用这一个特性,我们可以把一个HTTP请求生命周期内需要共享的变量挂载到domain对象上。这样在同一个请求里面的所有操作都可以通过domain对象获取到共享的变量,而再不需要通过函数参数的方式透传。
如代码所示,handle函数不需要通过外部传入res对象,也能获取到当前请求的res对象。这在代码结构非常复杂的时候非常实用!
window对象的诞生
就像浏览器环境有一个全局的window对象,TSW框架根据domain模块的特性也创建一个全局的window对象。
这里很关键的一点是利用了Object.defineProperty来创建window对象。这样window的值永远返回的是当前domain对象上的window对象。通过这种方式,在TSW框架内的编写的代码可以直接使用window变量获取到request和response对象,非常方便。
全息日志
有了一个HTTP请求声明周期的全局变量,我们实现按HTTP请求这一维度聚合日志就变得可能并且是一件很简单的事。这里直接上一张日志图:
(部分信息因安全问题做了隐藏处理)
全息日志功能支持通过用户uid快速查询用户的日志。当前这一功能也集成到tsw开放平台(tswjs.org)中,欢迎大家试用。
关于domain模块被弃用的一些思考
按nodejs的文档所说,使用domain模块之后,大家很容易就会忽略异常了。但是出现异常如果不作处理是很容易有内存泄露,这样就导致nodejs服务的不稳定。不过根据我们的实际使用, 只要每次请求结束后(包括出现异常)及时清除掉domain的引用。内存泄露的问题是不存在的。
另外根据官方文档所说,现在也没有其他方案可以完全代替domain模块,因此也不会贸然废弃domain模块。同时我们发现,从node8.0版本之后,node出现了async_hook模块,并且将domain模块用async_hook模块重写了。这意味着,以后即使官方将domain模块废弃了,第三方也可以使用async_hook模块实现一个domain模块继续使用。
所以不用担心domain会被废除,大胆的使用domain模块吧!