2011년 6월 21일 화요일

[node.js] 비동기 코딩스타일 이라는 암초

node.js 는 비동기(asynchronous) 모델의 코딩을 하기 쉽도록 디자인 되어 있다. 하지만 그렇다고 무작정 쉽게 코딩이 가능한건 아니다. 기존의 동기화 기반의 멀티스레드 모델을 코딩 했거나 함수나 메서드 등의 리턴값을 이용해 로직을 작성하던 때에 비해서 달라지는 점이 있는데, 익숙하지 않아서 문제가 발생해도 쉽게 해결되지 않을 수 있다.

여기서는 대표적인 예로 HTTP 서버를 하나 만드는 Pseudo Code (가상 코드)를 작성해 본다.
http.createServer(function (req, res) {
    var result = somefunc(req);
    res.setHeader('200');
    res.end(result);
});

function somefunc(req) {
    db.query('SELECT * FROM sometable', function (err, results) {
        // blah blah
        return output;
    });
}
로직 설명을 위한 pseudo code 이니 에러를 걸고 넘어지면 지는거다 -_-;;;

http.createServer를 통해 HTTP 서버가 기동되고 접속이 들어왔을 때 somefunc() 라는 함수에 requestObject를 넘겨줘서 여기의 결과를 받아서 responseObject로 전달하는 코드가 보인다. somefunc가 request를 처리해서 response 할 내용을 돌려주기 위한 의도를 가진 코드다.

불행히도 문제는 somefunc() 함수 자체에 있다.

return output 이라는 부분이 있어서 얼핏 보면 잘 동작할 것 같은 코드다. 하지만, somefunc 내의 db.query 라는 비동기 함수 덕분에 somefunc는 무조건 null을 리턴하게 된다. db.query 문은 비동기 코드이기 때문에 쿼리 진행 상황에 관계 없이 바로 끝나버리기 때문이다. (주. somefunc는 리턴문이 없기 때문에 null을 반환하는 것으로 취급된다) 따라서 위 코드의 somefunc() 는 무조건 null을 리턴하게 되어서 원하는 동작을 하지 못 하게 된다.

정확하게 동작하게 하려면 다음 식으로 바꿔야 한다.
http.createServer(function (req, res) {
    somefunc(req, res);
});

function somefunc(req, res) {
    db.query('SELECT * FROM sometable', function (err, results) {
        // blah blah
        res.setHeader('200');
        res.end(output);
    });
}
결국 somefunc() 자체에 responseObject 를 넘겨줘서 직접 응답을 처리할 수 있도록 바꾸었다.

결과적으로 함수의 리턴 코드가 전부 사라져 버렸고, 왠지 억울하고 마음에 안드는 코드 모형이 된 듯 하다. 함수가 리턴을 하는 건 일반적인 것이니까 매우 이상하다.

하지만 node.js 식으로 서버를 구축한다면 비동기로 호출되는 콜백의 가장 마지막이 결과를 처리해야 한다. 비동기 모델에서는 당연한 것이다.

이것이 아마도 node.js 로 서버 프로그래밍을 시작하려는 프로그래머들에게 암초가 아닐까. 코딩 스타일 자체를 바꿔야 하니 말이다.

댓글 2개 :

익명 :

저도 아직 배우는 입장이지만,
아래와 같은 방식도 좋지 않나 싶습니다.
정말 비동기처리 때문에 머리 깨질거 같음. ㅠㅠ


http.createServer(function (req, res) {
somefunc(req,function(result){
res.setHeader('200');
res.end(result);
});
});

function somefunc(req,callback) {
db.query('SELECT * FROM sometable', function (err, results) {
// blah blah
callback(results);
});
}

Seorenn :

이제는 node.js를 멀리하고 있기에 뭔가 더 좋은 방법이 있는지도 잘 모르겠네요.

그래도 방법이야 얼마든지 있다고 생각됩니다. 예를 들어 sequencial한 작업을 처리하기 위한 직렬큐(serial queue)를 만들고, 앞의 결과를 다음 작업의 입력으로 돌려주는 일반적인 방식을 만들던가 등등;;;