[cookbook] Domain API (또다른 try ~ catch)

Domain module 소개

Domains provide a way to handle multiple different IO operations as a single group. If any of the event emitters or callbacks registered to a domain emit an error event, or throw an error, then the domain object will be notified, rather than losing the context of the error in the process.on('uncaughtException') handler, or causing the program to exit with an error code.

node v0.8 새로운 기능 domain api, 뭐라 설명하기가 굉장히 애매한 API입니다. 그냥 에러처리기라고 하기도 그렇고... 위 정의가 굉장히 멋있는데, 실무에서 사용한 결과 제 입장에서는 try ~ catch 의 또다른 형태라고 정의하였습니다. 아래 소스설명 보시고 나름 이해하시고 정의 내리시면 됩니다. 그리고, 저와 다른 정의를 내린 분은 의견 공유 부탁드려요.

배경

nodejs의 callback에서 발생하는 throw를 전체 프로세스를 묶은 try로 에러를 제어 할 수 없습니다. 예를 들어봅시다.


자 이리하여 등장한 domain api 이런 경우에 대해 조금이나마 해결 혹은 아이디어를 제공해줍니다. 돌이켜보면 처음에 try~catch 구문을 어떻게서든지 사용할라고 노력했으나, 지금은 process.on('uncaughtException')에 기대고 있네요.

사용순서

domain api 에 기타 함수설명들이 있습니다.

사용패턴

보통 아래 3가지 경우로 요약할 수 있습니다.

crash 되지 않도록 하는 일반적인 소스

a.js

var foomodule = require('./foomodule');
//
// 모든 throw 를 처리
// 무조건 필수!!!!!!!!!!!!!!
//
process.on('uncaughtException', function(err) {
  console.log('uncaughtException ===');
  console.log(err);
});
//
// 모듈호출
// recursive/directory 전달하여 fs.mkdir에서 처리될 수 없도록 에러발생
//
foomodule.foo('recursive/directory', function() {
  console.log('result ===');
  console.log(arguments);
});

foomodule.js

var fs = require('fs');

//
// @param String dirname
// @paran Function cb
//
function foo(dirname, cb) {
  fs.mkdir(dirname, function(err, data) {
    if (err) {
      // 에러발생
      throw err;
    }

    cb(null, data);
  });
}

module.exports = {
  foo: foo
};

실행결과

uncaughtException ===
{ [Error: ENOENT, mkdir 'recursive/directory'] errno: 34, code: 'ENOENT', path: 'recursive/directory' }

이 경우의 단점은 process.on('uncaughtException', function() {})으로 모든 에러가 처리되기 때문에, 모듈을 호출시 해당 컨텍스트안에서 무엇을 처리할 요구사항이 생긴다면 멘붕이 다가올 수 있습니다.

crash 되지 않도록 domain api를 사용하는 소스

초점은 모듈 context 안에서 무언가를 처리하자는 목적입니다.

a.js

var foomodule = require('./foomodule');
//
// 모든 throw 를 처리
// 사용안함.
//
process.on('xxxxx___uncaughtException', function(err) {
  console.log('uncaughtException ===');
  console.log(err);
});
//
// 모듈호출
// recursive/directory 전달하여 fs.mkdir에서 처리될 수 없도록 에러발생
//
foomodule.foo('recursive/directory', function() {
  console.log('result ===');
  console.log(arguments);
});

foomodule.js

var fs = require('fs');

function foo(dirname, cb) {
  //
  // 도메인 require / 객체 생성
  //
  var domain = require('domain').create();

  //
  // domain에서 exception 체크
  //
  domain.on('error', function(err) {
    console.log('domain ===');
    console.log(err);

    // 현재 모듈의 context에서 처리
    cb('wow');
  });

  //
  // callback에 domain을 binding
  //
  fs.mkdir(dirname, domain.bind(function(err, data) {
    //
    // context안에서 에러나면 위 에러핸들러에서 처리됩니다.
    //
    if (err) {
      throw err;
    }

    cb(null, data);
  }));

  //
  // 밑에 혹은 위 callback 안에서 여러개의 함수조작을 하고, 안에 안에 안에 ... context에서 throw가 발생하더라도
  // 모두 domain error handling으로 컨트롤 할 수 있습니다.
  //
}

module.exports = {
  foo: foo
};

실행결과

domain ===
{ [Error: ENOENT, mkdir 'recursive/directory']
  errno: 34,
  code: 'ENOENT',
  path: 'recursive/directory',
  domain_thrown: true,
  domain: { members: [], _events: { error: [Function] } } }
result ===
{ '0': 'wow' }

crash 되지 않도록 domain api를 global 로 사용하는 소스

a.js

//
// try ~ catch 하나의 흐름으로 시도
//
global.domain = require('domain').create();

//
// domain에서 exception 체크
//
domain.on('error', function(err) {
  console.log('domain ===');
  console.log(err);
});

var foomodule = require('./foomodule');

//
// 모듈호출
// recursive/directory 전달하여 fs.mkdir에서 처리될 수 없도록 에러발생
//
foomodule.foo('recursive/directory', function() {
  console.log('result ===');
  console.log(arguments);
});

foomodule.js

var fs = require('fs');

function foo(dirname, cb) {
  //
  // callback에 domain을 binding
  //
  fs.mkdir(dirname, domain.bind(function(err, data) {
    //
    // context안에서 에러나면 위 에러핸들러에서 처리됩니다.
    //
    if (err) {
      throw err;
    }

    cb(null, data);
  }));

  //
  // 한번더! callback에 domain을 binding
  //
  fs.mkdir(dirname, domain.bind(function(err, data) {
    //
    // context안에서 에러나면 위 에러핸들러에서 처리됩니다.
    //
    if (err) {
      throw err;
    }

    cb(null, data);
  }));
}

module.exports = {
  foo: foo
};

실행결과

domain ===
{ [Error: ENOENT, mkdir 'recursive/directory']
  errno: 34,
  code: 'ENOENT',
  path: 'recursive/directory',
  domain_thrown: true,
  domain: { members: [], _events: { error: [Function] } } }

wow

결론

domain api에 대해서 설명을 드렸습니다. 저는 몇가지 아이디어가 떠오르네요. nodejs app을 crash 하지 않기 위해 process.on('uncaughtException', function() {})을 애용했는데, domain api도 적절히 포함시켜야 겠습니다.

추천 관련글