예를 들어 아래와 같은 소스가 있습니다.
app.get('/', function(req, res) {
db.open(function(err, db) {
db.authenticate(USERID, PASS, function() {
db.collection("", function(err, collection) {
collection.find({},{}, function(err, cur) {
res.render('index.jade', {});
});
});
});
});
});
이 부분은 express에서 / 로 요청이 왔을때 MongoDB에 쿼리를 날려서 데이터를 가져온뒤 그 데이터로 응답은 해주는 소스입니다.(각 함수의 기능같은 것은 여기서 중요치 않습니다.) 처음에는 이런 구조의 소스가 무척 이상해보이지만 계속 보자보면 익숙해지고 node.js의 구조가 그렇게 되어 있는 것이니 머 그리 이상할 것도 없습니다.
하지만 문제는 리팩토링을 하기 시작했을 때 발생합니다. app.get()은 express에서 라우팅을 담당하고 있는 함수이므로 이곳에서 디비처리까지 해버리는 것은 별로 좋지 않기 때문에 디비만 따로 모으기 위해서 디비관련부분을 메소드 추출하면 아래와 같이 됩니다.
app.get('/', function(req, res) {
readData(req, res);
});
var readData = function(req, res) {
db.open(function(err, db) {
db.authenticate(USERID, PASS, function() {
db.collection("", function(err, collection) {
collection.find({},{}, function(err, cur) {
res.render('index.jade', {});
});
});
});
});
}
이전의 소스에서는 JavaScript가 클로저를 지원하기 때문에 콜백함수 내에서 res를 그냥 사용할 수 있지만 이렇게 메서드를 분리하면 req와 res를 파라미터로 넘겨주어야 합니다. 왜냐하면 응답을 만들어주는 부분이 최종콜백 안에서 처리되어야 하기 때문입니다.(물론 이 소스에서는 req까지 넘겨줄 필요는 없습니다.) 여기서는 메서드를 하나로만 분리하였지만 복잡한 로직이나 I/O접근을 여러개의 메서드로 분리되어 있을경우에는 중간단계의 메서드에서는 필요도 없는 req와 res를 계속 물고 다녀야 하는 문제가 발생합니다.(메서드는 한가지 기능만 하는게 좋기 때문에 조금만 복잡한 걸 처리하더라도 금새 메서드는 여러가지로 분리되게 됩니다.)
그냥 파라미터 2개 추가하는 것 자체는 큰 문제가 아니라고 생각할 수도 있지만 더 큰 문제는 테스트에서 발생합니다. 클라이언트측 자바스크립트에서는 UI와 밀접하기도 하고 그래서 라이브러리개발이 아닌 다음에야 TDD나 유닛테스트가 아직 일반화되어 있지는 않은것 같지만(최소한 국내에서는...) node.js는 서버사이드이기 때문에 다른 서버언어들 처럼 유닛테스트를 작성할수 있는데(비동기는 접근이 다르기는 합니다.) 이렇게 분리된 모듈이 웹서버에 대한 의존성을 깊게 가지기 때문에 테스트를 할 수 없게 되어버립니다. 아주 불가능 하지는 않겠지만 req와 res에 대한 값을 만들어서 테스트를 실행하고 결과 응답을 확인해야 합니다. readData()는 단순히 디비를 읽는 함수이지만 테스트까지 express 웹프레임웍에 종속되어서 테스트가 어려워지는 문제가 발생합니다.
이런 문제에 대해서 오랫동안 고민했지만 마땅한 해결책은 못 찾고 있었는데 얼마전 발표준비를 하면서 본 Introduction to Node.js with Ryan Dahl에서 그에 대한 해결책을 찾을 수 있었습니다.
app.get('/', function(req, res) {
readData(function() {
res.render('index.jade', {});
});
});
var readData = function(callback) {
db.open(function(err, db) {
db.authenticate(USERID, PASS, function() {
db.collection("", function(err, collection) {
collection.find({},{}, function(err, cur) {
callback();
});
});
});
});
}
다른 메서드를 호출할 때 req와 res를 넘겨주는 대신에 콜백함수를 넘겨주고 해당처리가 다 끝난 최종 콜백함수 내에서 파라미터로 박은 콜백함수를 다시 실행시켜줍니다. 이렇게 하면 불필요하게 req, res를 물고다닐 필요가 없이 callback하나만 물고 다니면 되게 바뀌고 분리된 메서드가 콜백에만 의존하기 때문에 express에 가지고 있는 의존성도 사라지고 테스트도 훨씬 간단히 할 수 있게 됩니다.(이런 콜백패턴은 아주 흔하게 사용하는 방식인데 이런걸 왜 스스로는 생각해 내지 못하는지 모르겠군요 ㅠㅠ)
좋은 정보 고맙습니다
예.. 댓글 감사드립니다. ㅎ
리턴이 필요 있는 의존성을 가진 녀석은 어떻게 해결 하실지!!
다음 회를 기대하겠습니다 ㅋㅋ
사실 session을 다루는 녀석들은 또 req랑 완전 의존 적이기도 하고 ..;;
리턴이라.. 다음회는 아직 없는데 ㅋㅋㅋㅋㅋ
세션같이 특수한건 별수 없는듯 ㅎㅎ
이 포스트를 한달전까지만 해도 문제 자체를 이해를 하지 못했는데;;
막상 조금씩 제가 원하는 로직을 만들다 보니
구구절절히 동감되고 정말 필요로 하던 해답이네요^^
비단 node뿐만아니라 js로 코딩을 한다는것이
항상 이런류의 비동기 때문에 일어나는 의존성 문제를 해결하려고
고민을 엄청 했었는데^^
좋은 정보 포스팅 감사의 마음을 전합니다.
댓글 감사드립니다.
콜백을 이용한 방법외에도 이벤트를 이용해서 의존성을 분리하는 방법도 있으니 한번 참고해 보셔도 좋을듯 합니다.
return 은 callback 이 또다시 handler() 를 호출하게 하면 어떨지..
그러고보면 형식에따라 handler 메서드가 너무 많이 필요해지려나...
그걸 규격화하면 되지않을까 싶은..
그런식으로 사용하면 호출될 핸들러가 고정되기 때문에 재사용이 쉽지 않을것 같습니다.