라우팅(Routing)
당연하게도 라우터는 라우팅을 하는 녀석입니다. 라우팅(Routing)이란 뭘까요?
위키나 사전등에서 대략
어떤 네트워크 안에서 통신 데이터를 보낼 경로를 선택하는 과정이다
정도로 설명하고 있습니다. 뭐 의미가 저렇긴하지만 그 중에 집중하고 싶은 부분은 경로를 선택하는 과정 입니다. 거기서 보다 잘라내고 핵심만 추리면
선택하는 과정
라고 줄일 수 있습니다. 선택하는 과정이라고 한글로 쓰니 좀 이상하네요. 영어로 하자면 과정은 process가 되니까선택이 일어나는 일련의 처리과정이라고 이해할 수 있겠습니다.
선택(Condition?)
프로그래밍에서 선택이란 사실 select가 아닙니다. 아무것도 없이 골라잡을 게 주어지지는 않습니다.
프로그래밍에서 선택이란 정확하게 말하자면 어떤 상태를 선택하는 것이고, 이 중 주인공은 선택이 아니라 상태입니다. 따라서 프로그래밍에서의 선택이란 condition 이라고 할 수 있습니다.
이는 마치 잠을 잘까 밥을 먹을까의 선택을 하는 것이 아니라, 배고프면 밥을 먹고, 졸리면 잠을 잔다는 것이 프로그래밍이란 의미입니다.
그렇다면 프로그래밍에서의 선택이 상태고 이를 라우팅과 결합해보면 상태에 따라 일어나는 일련의 처리과정이라고 이해할 수 있습니다.
조건문(Conditional Statement)
판단문이라는 잘못된 이름으로도 알려진 if, switch 등의 익숙한 문들이 왜 조건문이라 불리는지 이제 알 수 있습니다. 조건이란 주어진 상태라는 뜻입니다.
주어진 상태에 따라 다른 처리를 선택하기 때문에 조건에 따라 달리 선택되는 문이라는 의미로 조건문입니다.
이거 뭔가 위에 얘기하고 있던 프로그래밍의 라우팅과 무지하게 비슷하지 않나요?
조건문의 제거
프로그래밍의 가장 큰 문제는 복잡성입니다. 컴터는 아무리 복잡한 로직과 조건에 따른 분기도 문제없이 처리합니다만, 이 알고리즘 자체를 인간이 이해하는데는 한계가 명확합니다. 보통 2단 중첩 조건문이 한계로 3단 중첩 조건문은 이미 버그라고 확정지어도 무관할 정도로 안정성이 떨어지게 됩니다.
하지만 알고리즘에서 조건문은 결코 제거되지 않습니다. 반대로 조건문을 제거하지 않으면 복잡성은 제거되지 않습니다.
이 모순을 어떻게 해결할까를 수많은 개발자들이 고민해왔습니다. 디자인패턴이나 전통적인 알고리즘 분야에서도 열심히 노력하고 있습니다만 수학적 연산을 통해 일부 제거할 수 있을 뿐 근본적인 해결책을 얻지는 못했습니다.
그나마 한가지 발견한게 있는데 바로 조건에 따른 문을 분산시키는 기술입니다. 간단히 js코드로 살펴보죠.
switch ( a ){ |
case 'print' : //statement1; break; |
case 'action' : //statement2; break; |
case 'run' : //statement3; break; |
case 'stop' : //statement4; break; |
} |
위에 switch문이 있습니다. 이를 어떻게 하면 제거할 수 있을까요?
당연히 제거할 수 없습니다. 하지만 다음과 같이 미리 조건에 해당되는 모든 데이터를 구비해두면 코드를 변화시킬 수 있습니다.
var actor = { |
print : function (){ //statement1; }, |
action : function (){ //statement2; }, |
run : function (){ //statement3; }, |
stop : function (){ //statement4; } |
}; |
위의 actor는 switch문에 있던 모든 조건에 대응하는 키에 함수를 할당하여 조건별 처리되던 문을 각각의 함수 내부로 옮겼습니다. 이렇게 되면 switch문은 제거되고 아래와 같이 변합니다.
var runner = actor[a]; |
runner(); |
switch문을 없애는데 성공했습니다! 이 방법이 유일하게 조건문을 제거하는데 찾아낸 방법입니다. 이 방법은 디자인패턴이 되면 전략패턴이나 상태패턴1으로 나타나고 방법론으로는 데이터드리븐(Data Driven)2으로 나타납니다.
게다가 이렇게 되면 큰 차이점이 생기는데 기존의 switch문에서는 조건이 늘어나면 매번 코드를 건드려야하지만 경우의 수만큼 데이터화한 경우는 새로운 경우에 대한 데이터를 추가함으로서 처리됩니다. 즉 아래와 같은 차이가 생깁니다.
//switch |
switch (a){ |
... |
case 'handle' : ....; break ; |
} |
//data |
actor.handle = function (){...}; |
따라서 조건문을 조건별 처리기를 각각 제작하여 데이터화하는 것은 미래의 확장이나 수정이 일어나면 대처할 수 있는 범위나 수준이 완전히 달라집니다.3
라우터
사실 조건문을 데이터드리븐형태로 제거할 때 이미 라우터가 등장했습니다. 매우 심플하지만 아래 코드가 바로 라우터입니다.
var runner = actor[a]; |
상태에 따라 일어나는 일련의 처리과정을 훌륭하게 커버하고 있습니다. 이때 상태는 a 이고 일련의 처리과정에 해당되는 녀석은 runner입니다.
프로그래밍에서 라우터가 등장하는 이유는 복잡성을 제거하고 데이터드리븐으로 변경되면서 기존의 조건문을 일관된 처리객체와의 매핑으로 바꾸는 과정에서 만들어지는 산출물인 셈입니다.
라우터와 조건문의 차이
데이터 드리븐으로 구축해도 라우팅하는 과정은 다시 조건문으로 회귀할수도 있습니다. 즉 아래와 같죠.
var runner; |
switch ( a ){ |
case 'print' : runner = actor.print; break ; |
case 'action' : runner = actor.action; break ; |
... |
} |
runner(); |
그렇다면 라우터와 조건문은 차이가 없는건가요?
.
.
넵 없습니다. 문법 상으로는 차이가 없습니다. 차이가 있는 부분은 매핑규칙(Mapping Rule)이 존재하는가 입니다.
매핑규칙(Mapping Rule)
매핑규칙은 조건문의 조건식에 들어갈 내용을 정형화하여 간단한 규칙으로 만든 것입니다. 규칙이 될 수 있는 것은 함수, 값 등 제약이 없습니다만 제약이 많을 수록 복잡성이 줄어들어 관리하기 간단해지고, 복잡해질 수록 다시 조건문과 동일한 복잡성에 수렴합니다.
위의 라우터의 매핑규칙을 보죠.
var runner = actor[a]; |
이는 a라는 값이 뭐가 되었든 actor에 정의만 되어있으면 된다는 엄청나게 간단한 규칙입니다. 이 때의 문제는 a에 해당되는 키가 actor에 없으면 안되다는 것이지만, 안정성은 둘째치고 규칙은 엄청나게 간단하고 확장도 무한입니다.4
그에 비해 두 번째의 조건문에 가깝던 규칙을 보죠.
var runner; |
switch ( a ){ |
case 'print' : runner = actor.print; break ; |
case 'action' : runner = actor.action; break ; |
... |
} |
이것은 라우터이기도 하지만 거의 조건문에 가깝고, 조건의 확장도 코드의 수정없이는 불가능합니다.
매핑규칙과 조건식의 근본적인 차이점은 조건의 확장성에 달려있습니다. 조건식 즉 식이 되면 확장성이 거의 없어지게 됩니다. 하지만 규칙이 되면 규칙을 따르는 이상 전부 허용하게 됩니다.
어떻게 보면
- 조건식은 블랙리스트로
- 매핑룰은 화이트리스트로
이해할 수 있습니다. 이는 전부 상대적이면서 개념적이기 때문에 어떤게 규칙인지 식인지 명확히 나눌 수 있는 성격의 것이 아닙니다.
반대로 판단할 수 있는 것은
그 식보다는 이 식이 보다 규칙에 가깝다
라고는 할 수 있겠죠.
트리거, 테이블, 타겟, 규칙
라우팅에는 기본적인 네 가지 요소가 필요합니다. 물론 추가적으로 더욱 많은 요소야 추가될 수 있습니다만 이 네 가지는 반드시 있어야 합니다.
여태 설명한 것은 규칙(Rule)입니다. 그리고 당연히 코드 상의 a는 라우팅 타겟(Target)입니다. 즉 규칙은 타겟을 기반으로 판단하는 것입니다.
규칙이 타겟을 판단한 뒤는 매핑할 대상이 필요합니다. 이게 바로 라우팅 테이블(Table)로 코드 상에서는 actor에 해당됩니다.
라우팅 테이블의 경우 많은 웹애플리케이션 프레임웍에서는 xml등으로 관리합니다. Spring을 비롯한 많은 프레임웍에서 라우팅 테이블을 기술할 때 보통 그 테이블 상의 정보로 연결되는 규칙을 동시에 정의합니다.
즉 라우팅 테이블 안에 규칙을 포함하고 타겟을 넘겨주는 게 일반적인 구조입니다.
마지막으로 라우팅 트리거(Trigger)는 이러한 라우터의 작동이 언제 일어날 지를 정의합니다. 많은 웹프레임웍에서는 서버에 request가 올 때 작동됩니다만 js용 프레임웍에서는 hash가 바뀔 때를 감시하고 있다가 작동하는 경우도 있습니다.
라우팅 트리거라는 개념을 갖고 있다면, 모든 것을 트리거 대상으로 인식할 수 있습니다.
쿠키의 변화, 디비값의 변화, 세션의 변화, 날씨의 변화, 센서의 변화, 위치의 변화.. 무엇이든 라우팅의 트리거가 될 수 있습니다.
또한 트리거의 대상이 꼭 라우팅 타겟일 필요도 없습니다. 라우팅 발동 자체는 순방문자가 늘어날 때 실행되었지만, 그 때의 타겟은 디비에 쌓인 글이 몇 개라면.. 으로 할 수도 있는 거죠.
결론
네트웍 장비로부터 생겨난 라우팅의 개념은 프로그래밍 구조 전반에 영향을 끼치고 있습니다. 프로그래밍시 라우팅의 개념을 이해하고 기존의 한정적이고 복잡한 구조를 매핑테이블과 라우팅 시스템으로 이전함으로서 유연성과 확장성을 확보해갈 수 있습니다.
…안되면 걍 전략패턴이나 상태패턴 좀 많이 쓰는 것 정도라도..=.=