카라마조프 형제들 – 고전은 힘들다.

“죄와벌” 이후에 정말 오래간만에 도스토예스키의 작품을 난데없이 읽기 시작해서 이제사 마쳤다.  책갈피 기록을 찾아보니 올해 2월 17일이니까 다 읽는데까지 무려 10달이 넘게 걸려버렸다.  하기야 대학 2학년때 읽기 시작했던 죄와벌을 대학 4학년이나 되서야 다 읽었으니, 그 시절의 독서 속도에 비해서는 그나마 읽기가 좀 더 나아졌다고 해야할까?

난데없이 얽기를 시작했다고 이야기했다.  다니던 성당의 좌파 성향 한가득이시던 신부님의 강론중에 나온 “카라마조프”의 이야기를 들고, 참 재미있겠다는 생각이 들어 덜컥 이북을 구매했다.   신부님은 참 재미있다고 읽어보라고 권하긴 했지만… 생각에는 도스토예프스키의 책이 나한테는 참 어렵다는 생각이다.  읽다보면 간간히 재미있는 부분도 있긴 한데 너무 오래 읽어서 그런가 내용이 이어지지 않는 부분들이 많았던 것 같다.  아무래도 나중에 시간을 다시 내서 한 한달쯤안에 다시 한번 읽어봐야할 것 같다.

책에서는 카라마조프 가족의 4 남자가 나온다.  지독히도 세속적인 아버지 표도르, 순정파 장교 출신 드미트리, 시대의 지식인을 상징하는 이반, 마지막으로 믿음으로 순수한 막내 알료샤.  소설이 배경을 이루는 1800년대 말 즈음의 재정 러시아를 살아가는 인간 군상의 모습을 표현한다.  시대적 배경을 알려면 당시의 러시아 상황을 알아야 할 것 같지만 짧은 지식이 거기에 미치지는 못한다.  다만 물질적 탐욕, 성욕과 순정 그리고 이성을 빙자한 인간의 이기주의 등등을 소설에서 읽을 수는 있었던 것 같다.

과연 누가 아버지의 살해범인가? 장남 드미트리인가 아니면 사생아이지 인정받지 못하고 하인의 신분으로 머물렀던 스메르자코프인가?  그리고 스스로 인텔리임을 자임하며 스메르자코프에게 불만투성이 세상으로부터의 탈출구를 은연중에 내비쳤던 둘째 아들 이반인가?  수도원에서 성직자의 길을 걷다가 장로의 유언으로 스스로 그 길을 벗어난 얄료샤는?  온 집안이 풍비박산이 난 이후 먼 곳으로 떠나는 그의 모습은 뭘 의미하는거지?  그리고 이 막장 드라마에 등장하는 그루센카와 카체리나라는 두 여성이 의미하는 시대의 자화상은 뭘까?

러시아가 격변의 소용돌이에 있는 시점이고, 자본에 의해 사회적인 구조가 급변하는 시기였던 것 같다.  커다란 회오리 바람이 불어오는 과정에서 사생아를 포함한 이 일가족 각각이 그 시대를 살아가는 사람들을 형상하는 하나의 아이콘이지 않았을까 싶은 생각이다.  그 안에 결론은 없다.  자신의 추구만을 생각하다 그 욕망의 실타래가 얽혀버렸다.  풀려고 해도 풀기에는 이성적 사고보다는 포장된 이면 아래의 감정이 그 결과로 모두를 몰아가지 않았나 싶다.  옳고 그르다라는 결론은 의미가 없는 것만 같다.  남은 절망을 뒤로하고 막내 알료샤는 희망을 이야기하며 고향을 떠나려한다.

인형조종사에 의해서 대통령이 놀아나는 2016년 겨울의 대한민국을 관통하하고 있다.  기회를 내서 한번 더 읽어봐야겠다.

Springboot에서 Exception을 활용한 오류 처리

Java를 가지고 개발하는 오류 처리는 Exception을 활용하는 것이 정석이다.  개인적으로 값을 오류 체크하고 어떻게든 값을 만들어 반환하기보다는 오류가 발생하면 “오류다!” 라고 떳떳하게 선언하는 것이 좋은 방법이라고 생각한다.

RESTful API를 구현한 경우,  오류의 상태를 알려주는 가장 정석적인 방법은 HTTP Status Code를 활용하는 방법이다.  Exception을 통해서 이 응답 코드를 반환해주는 건 아주 쉽다.

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="No candidate")
public class NotExistingCandidateException extends RuntimeException {
    public NotExistingCandidateException(String candidateEmail) {
        super(candidateEmail);
    }
}

이것과 관련해서 약간 말을 보태본다.  RESTful API를 개발하면서 응답 메시지의 Body에 상태 코드와 응답 메시지를 정의하는 경우를 왕왕본다.  RESTful 세상에서 이런 방식은 정말 안좋은 습관이다.   몇 가지 이유를 적어보면.

  1. 완전 서버 혹은 API를 만든 사람 중심적이다.  클라이언트에서 오류를 알기 위해서는 반드시 메시지를 까야한다.  호출한 쪽에서는 호출이 성공한 경우에만 처리하면 되는데 구태여 메시지를 까서 성공했는지 여부를 확인해야한다.
  2. API 클라이언트의 코드를 짜증나게 만들뿐만 아니라 일관성도 없다.  한 시스템을 만드는데 이런 자기중심적인 사람이 서넛되고, 상태 변수의 이름을 제각각 정의한다면 어떻게 되겠나?  헬이다.
  3. 표준을 따른다면 클라이언트 코드가 직관적이된다. Ajax 응답을 처리한다고 했을 때 성공은 success 루틴에서 구현하면 되고, 오류 처리는 error 구문에서 처리하면 된다.  프레임웍에서 지원해주기 때문에 코드를 작성하는 혹은 읽는 사람의 입장에서도 직관적이다.  더불어 성공/실패에 대한 또 다른 분기를 만들 필요도 없다.
  4. 이 경우이긴 하지만 Exception을 적극적으로 활용하는 좋은 습관을 가지게 된다.  코드를 작성하면서 굳이 Exception을 아끼시는 분들이 많다. 하지만 Exception은 오류 상황을 가장 명시적으로 설명해주는 좋은 도구이다.  문제 상황에서 코드의 실행을 중단시키고, 명확한 오류 복구 처리를 수행할 수 있는 일관성을 제공하기 때문에 코드의 품질을 높일 수 있다.

표준이 있으니 표준을 따르자.  이게 사려깊은 개발자의 태도다.

ResponseStatus라는 어노테이션을 사용하면 Exception이 발생했을 때, 어노테이션에 정의된 API Response Status Code로 반환된다.  이때 주의할 점은 정의한 Exception이 반드시 RuntimeException을 상속받아야 한다는 것이다.  Throwable을 상속받거나 implement 하는 경우에는 어노테이션에 의해 처리되지 않기 때문에 주의하자.

    @ExceptionHandler(UnknownAccessCodeException.class)
    public String handleUnknownAccessCodeException() {
        return "unknown";
    }

만약 Thymeleaf와 같은 UI framework을 사용해서 특정 오류 케이스가 발생했을 때 특정 뷰를 제공하고 싶다면, Controller 수준에서 ExceptionHandler 어노테이션을 활용할 수 있다.  위 예제는 Exception이 발생했을 때 unknown이라는 Thymeleaf의 뷰를 제공하라고 이야기한다.  이때도 주의할 점은 Exception은 반드시 RuntimeException을 상속해야한다는 점이다.

만약 어플리케이션 전반적으로 Exception에 대한 처리 로직을 부여하고자 한다면 ControllerAdvice 어노테이션을 활용한다.  어노테이션을 특정 클래스에 부여하면 해당 클래스에서 정의된 Exception 핸들러들이 전역으로 적용된다.

@ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.CONFLICT)  // 409
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void handleConflict() {
        // Nothing to do
    }
}

상세한 정보는 스프링 페이지에서 참고하면 된다.

외부 발표를 하다보면 좋은 것들?

올해 이런 저런 기회가 되서 몇 번 강의를 했었다.  학생 시절 과외 경험으로 가르치는건 내 적성은 아니었다.  한 두 사람도 아닌 수십명 앞에서 이야기를 한다는 것 자체가 사람을 긴장하게 만들기도 한다.

올해 이전까지 강의 경험 횟수를 합쳐보면 2~3번이 전부다.  할 때마다 몇 일 동안 시간을 가지고 준비를 했다.  전달할 내용을 충실히 전달할 수 있을지 이야기하는 강연 무대에서 심하게 떨지는 않을지…  걱정이 현실이 되지 않도록 준비하지만 항상 충분하지 못했던 것 같다.  걱정 가득히 준비하고 진행하지만 그럼에도 불구하고 강의는 할만 한 것 같다.

강의 준비를 위해 가장 필요한 건 시간이다.  자료 준비와 발표 준비를 위해 투자해야 할 시간이 만만치 않다.  자료는 단순한 텍스트 자료로 통하지 않는다.  PPT로 자료를 만드는 건 텍스트를 쓰는 것보다 더 시간이 걸린다.  부여 설명이 아닌 핵심이 되는 문장과 이해를 돕기 위한 이미지들이 있어야 한다.  맥락을 유지하기 위해 핵심 이외의 문장들을 말로 어떻게 풀어낼지도 함께 고려해야 한다.  그렇기 때문에 텍스트로 만드는 것보다 더 많은 시간이 필요하다.

발표는 과외가 아니다.  내가 앞에서 이야기하는 말을 들어주는 사람들이 한 두 명이 아닌 수십 명이다.  긴장이 될 수 밖에는 없고 말 떨리기가 일쑤다.  어떤 경우에는 주어진 시간보다 너무 빨리 혹은 너무 오래 동안 이야기를 한다.  이럴 때면 종종 “여긴 어디?, 나는 누구?“와 같은 경험을 느꼈다.

그런데 왜 이런 사서고생을 할까?  싫은 소리를 듣는 때가 태반인데??

먼저 준비 시간과 과정에서 많은 것들을 깨우치게 된다.  안다고 생각했던 것들 가운데 많은 것들이 아는 척을 했다는 것!  알고 있다고 생각을 했지만 사실 각론을 따져보다보면 제대로 알지 못했던 것들이 하나 하나씩 드러나게 된다.  시간이 길어지면 길어질수록 왜곡되고 자의적으로 해석된 지식들을 바로 잡을 수 있는 기회가 더 많아진다.  특히 발표는 자료를 읽는게 아니라 자신의 머리속에 그 내용을 정리해야  한다.  그래야지 내용을 흐름으로 이어지게 만들 수 있다.  그렇기 때문에 드문드문 알던 것들을 제대로 엮어서 온전한 지식으로 정리할 수 있게 된다.  올해 강의 혹은 발표했던 TDD, Git, TDD 내용에 대한 자료들을 만들면서 반성하고 제대로 공부할 수 있었다.

눈으로 이야기를 전달하는 글과 달리 발표(말)을 통해 전달하는 정보의 속도는 확연히 빠르다.  특히 한명이 아닌 여러 사람들에게 이야기를 전달을 위해서는 전달 방법을 계속 고민해야한다.  효과적인 방법은 하늘에서 뚝 떨어지는게 아니니까.  효과적으로 발표를 할 수 있는 사람은 그만큼 효과적인 소통을 할 수 있다.  조직화된 사회를 살아가는 인간이라는 동물의 특성상 “효과적인 소통“은 생존을 위한 중요한 수단이다.

발표는 한방향 소통이긴 하지만 세상에는 자신의 의견(주장)을 한마디도 못하는 사람들이 허다하다.  하고 싶은 것을 하지 못하는 것만큼 사람을 갑갑하게 만드는 건 없다.  자신의 의견을 적절히 말하고, 그 의견이 반영된 방향으로 사람들이 움직여준다? 사회적인 욕구가 있는 사람이라면 당연히 이걸 원한다.  그 사람의 이름값은 올라가고 다른 사람들의 지지를 받는다.  소위 말해서 “이름 값(Name Value)“를 얻는다.  열심히 노력한 결과로 얻어지는 이름 값이라면 당연한 성취라고 생각한다.  부끄러워하지 말고 되려 자랑스러워 해야한다.

마지막으로 한번 강의를 할 때마다 들어오는 금전적인 보상이 쏠쏠하다.  “돈”이라는 이야기를 꺼내니 앞서 이야기한 “사서고생”이라는 말이 영 안맞는 단어인 것처럼 보인다.  들인 시간과 대비해서 들어오는 “돈”을 생각하면 사서 고생이라는 단어가 그리 틀린 말은 아니다.  돈이 들어오면 감사한 마음으로 받아서 좋은 곳에 사용하면 된다.  시간을 만들어준 가족이나 동료와 좋은 시간을 함께 한다면 이 또한 좋지 않겠나? (개인적으로는 평소에는 나이가 패널티지만 이런 경우에는 어드밴티지로 작용한다. ^^  우리나라에서는 평가 기준이 나이순/경력순이라…)

 

Docker를 활용한 Singlepage 웹앱(WebApp) 구현 환경 구성하기

최근의 개발은 Single page 웹앱 형태로 웹 페이지를 개발하고 있다.  이전 회사에서는 웹앱이라는 개념도 제대로 몰랐는데… 장족의 발전이다.

웹앱 개발 방식이 개발자 관점에서 좋은 점은 Frontend와 Backend를 명백하게 구분할 수 있다는 점이다. 백엔드는 Business Logic을 중심으로 Restful API 방식으로 개발한다.  UI를 배제하고 로직에 집중할 수 있고, 테스트 케이스도 작성할 수 있기 때문에 제대로 개발한다라는 느낌을 준다.  근데 로직은 UI가 있어야지 표현되는 것이기 때문에 이것도 개발은 해야한다.

UI를 담당하는 Frontend는 HTML과 JS로만 구성된다.  예전처럼 PHP 혹은 JSP 같이 서버에서 상황에 따라 다른 컨텐츠를 내려줄 필요가 없다.  따라서 이런저런 복잡한 시스템은 필요없이 아파치 혹은 nginx 정도만으로도 충분히 개발할 수 있다.  딱봐도 쉬울 것 같은데 이렇게 썰을 길게 푸는건 개발 환경을 이야기하고 싶기 때문이다.  HTML과 JS로 코드를 짜면 되기 때문에 코딩을 위한 환경 자체는 쉽다.  하지만 작성한 코드를 눈으로 확인할려면 웹서버를 실행시켜야 한다.

로컬에 웹서버를 실행하는 방법이 가장 쉽게 떠오른다.  물론 가장 쉬운 방법이다.  하지만 여러 웹앱의 개발하는 경우를 생각하면 좀 귀찮아진다.

  • 테스트할 때마다 설치된 웹 서버의 Document Root를 바꿔줘야 하기 때문이다.
  • 물론 Configuration 파일 각 프로젝트별로 정의하면 된다.
  • 하지만 Configuration을 git repo에 함께 두기 애매하다. 개발하는 사람들별로 디렉토리 구성이나 이런 것들이 틀리기 때문이다.
  • 결국 이건 개발하는 사람들이 각자 잘 하는 수밖에는 없다.

이게 정답일까?  좀 더 쉽게 개발하고 배포할 수 있는 환경이 뭘까 싶어서 좀 고민을 해봤다.  최근에 Docker를 자주 사용하고 있기 때문에 이걸 활용하는 방안을 찾아봤다.  다음과 같은 접근 방법을 생각해봤다.

  • 로컬에 Docker 환경을 구성하고, Docker instance가 직접 로컬의 특정 디렉토리를 보도록 설정한다.  코딩을 하면서 변경하는 부분들은 Docker에서 바로 보고 이를 반영해줄 것이다.  HTML, CSS, JS는 따로 컴파일 할 필요가 없지 않은가?
  • 서버에 배포를 위해서는 Dockerfile을 이용해서 모든 리소스가 하나가 되도록 패키징한다.  그럼 이 안에 설정 및 컨텐츠 파일들이 모두 포함되기 때문에 이를 죽~ 배포하면 된다.

먼저 디렉토리 구조를 아래와 같이 잡았다고 가정해보자.

docker-directory-structure

이걸 바탕으로 로컬에서 간단히 Docker instance를 실행하는 방법은 아래와 같다.

$ docker run --name webapp -v $HOME/Workspace/projects/webapp/content:/usr/share/nginx/html:ro \
-v $HOME/Workspace/projects/webapp/conf:/etc/nginx:ro \
-v $HOME/Workspace/projects/webapp/logs:/var/log/webapp \
-p 5050:80 -d nginx

docker 실행에서 각 파라미터에 대해 간단히 부연한다.

  • –name : 실행할 docker instance의 이름
  • -v : docker에 마운트할 설정 정보. local-path:docker-path 형식이며 :ro를 덧붙히면 docker-path는 readonly 디렉토리임을 알리는 지시자다.
  • -p : port mapping. -v 옵션과 마찬가지로 local-port:docker-port를 나타낸다.
  • -d : docker image의 이름을 나타낸다.  nginx를 웹앱용 웹서버로 사용한다. (설정이 간단해서 아파치보다 더 좋은 것 같다. ^^;)

여기에서 가장 핵심은 -v 옵션에 따라붙은 마운트 정보이다.  로컬에서 작업하는 각 파일들을 Docker에서 잘 볼 수 있도록 해당 디렉토리를 바인딩한다.  이렇게 설정된 Docker를 통해 브라우저를 통해 확인해보자.

http://localhost:port 라고 입력하면 되겠지? 물론 포트는 앞서 설정한 local-port를 입력하면 되겠지?  라고 생각해서 입력하면 찾을 수 없다라는 어이없는 메시지만 본다.  이거 뭘까???  문제는 Docker의 nginx 서버가 특정 IP Address에 Binding되어 있다는 점이다.  Docker가 바인딩한 IP를 확인하는 방법은 다음 명령을 통해 확인할 수 있다.

$ docker-machine ip default

출력 결과로 알려준 IP와 포트로 접속해보자.  만약 문제가 없다면 짜잔~ 하고 개발하던 내용을 확인할 수 있다.

안된다고?  그럼 문제를 진단할 때다.  가장 먼저 이게 실행중인지 여부를 확인하는게 우선이다.  실행 확인은 다음 명령을 이용하면 된다.

$ docker ps

그런데 나오질 않는다고? docker ps 명령으로 목록이 나타나지 않는 이유는 docker에서 실행될 프로세스가 죽어버렸기 때문이다. nginx가 죽는 이유는 딱 하나. 바로 nginx configuration에 문제가 있기 때문이다. 이전에 설치가 제대로 되기나 한건지를 먼저 확인할려면 docker ps -a 명령을 이용하면 된다. 우리는 개발자이니까 먼저 로그를 확인해야겠지?

$ docker logs webapp

결과를 보면 시스템에서 출력해주는 웹 엑세스 로그 내용을 확인해볼 수 있다.  만약 nginx 설정에 오류가 있는 경우에는 로그 내용 잠깐 살펴보면 바로 문제를 파악할 수 있다.

개발을 다 마무리했다면 이제 배포를 준비할 때다.  배포를 위해서 Dockerfile을 하나 작성하면 된다.

FROM nginx
COPY content /usr/share/nginx/html
COPY conf /etc/nginx

설정의 구성은 간단하다.

  • nginx의 기본 설정은 Linux 기준으로 /usr/share/nginx/html을 Document Root로 지정한다. (물론 설정 파일에서 이를 변경할 수도 있지만.)  해당 디렉토리에 프로젝트에서 작업한 파일을 복사해넣으면 된다.
  • 별도의 설정 변경이 있는 경우에 해당 설정을 /etc/nginx 디렉토리에 넣으면 된다.

물론 엑세스 로그등을 별도로 봐야할 필요성이 있다면 이런 설정을 추가할 수 있다. (단순 웹앱이기 때문에 이럴 필요성이 있을까 싶긴 하지만…)

 

바쁘다.

회사를 옮겨와서 가장 바쁘게 일을 하는 시절이 아닐까 싶다.  한달 넘게 일과 삶의 균형이 무너진 상태였다.  제대로 된 개발을 포기하고 사용자를 위해 한번쯤(?) 고생하는 것이 주는 의미가 더 크다고 생각했다.  이런 결심으로 시작한 작업의 종착점이 이제 얼마 남지 않았다.  잃었던 삶을 되찾을 수 있을 것 같다는 생각도 들고, 반영되기 시작한 작업의 결과에 대한 반향도 나쁘지는 않은 듯 싶다.

동료들과 소주 한잔을 기울이면서 이번 작업에 대해 이야기를 해봤다. 작업 사이사이 지친 몸을 달래면서 소주 한잔 들이키며 나눈 이야기의 반응은 의외로 나쁘지 않다는 것이다.  대부분의 사람들이 평소와는 달리 모든 사람들이 종종 늦은 시간까지 작업을 했었다.  게임을 즐기는 시간도 상대적으로 크게 줄거나 의도적으로 아예 하지 않은 사람들도 있었다.  이런 상황을 나쁘지 않다고 평가하다니… ㅋㅋㅋ

의외다.  물론 이 “괜찮음”의 기본 전체는 “1년에 한두번”이라는 전체가 있긴했지만 의외다.

이 반응이 의외로 보는 이유는 현재의 작업 방식이 지극히 SI적(!!)이라는데 있다.  기능이 구현되어야 할 날짜는 정해져있다. 뭘 만들어야 하는 지에 대한 공감대는 있지만 세부 기능에 대한 정의는 없다.  언제든 작업하던게 뒤집어질 수도 있었다. (사실 다른 이유긴 했지만 아예 엎어질뻔하기도 했다.)  불명확한 것들 투성임에도 불구하고 일정이 정해졌으니 무조건 하라는건 개발하는 사람들이 가장 싫어하는 일 형태다.

그럼에도 불구하고 이 일에 다 같이 참여해서 해내는 의지가 있었다.  결과도 나쁘지 않았을 뿐더러 의지를 통해 함께 작업하면서 “팀” 이라는 개념이 모두에게 각인된 것처럼 보였다.  조직도에 그려진 팀이 아니라 함께 함께 논의하고 페어 프로그래밍하고 PR에 대해서 검토하는 “팀”으로써 움직였다.  개인적으로 다른 것보다 이 부분이 이번 작업의 가장 큰 결과물이라고 생각한다.

팀이 팀으로써 일을 한다는 것, 그리고 개발을 정말 제대로 된 개발 모습으로 열심히 한다는 것.  이 두가지에서 얻는 것이 개인적으로는 이번 작업의 큰 경험이었다.  물론 과정에서 들어난 잘못된 부분들과 협업에 대한 부분들도 조만간에 고쳐나가야 하긴 하지만.  바쁜 시절이 오래 가면 모둘에게 피곤함을 안겨주겠지만 그래도 좀 만 더 바빠야 할 것 같다.  그래야지 시스템의 체계도 제대로 구성시킬 수 있을 것 같고, 일의 틀도 제대로 맞출 것 같다.

미안하지만 좀만 더 고생하자.

 

logstash를 활용한 실시간 검출 시스템 구축

회사에서 DBA 분이 elastic 제품군을 가지고 나름 재미있는 기능을 개발하신 걸 공유받은 적이 있다.  그걸 보면서 WoW!!! 라는 감탄이 절로 나왔다.  주변의 오픈 소스 유틸리티들을 활용하면 쿨한 기능들을 설정만으로도 만들 수 있다라는 사실이 놀라웠다.

더욱 내가 반성했던 건 이 작품이 개발자가 아닌 DBA님의 도전이었다라는 점!  약간의 반성을 더 해보자면 뭔가를 집착적으로 코딩할 생각만 했다라는 생각이 훅~ 하고 머리를 때렸다.

언제가 기회가 된다면 elastic 제품을 함 써봐야겠다라는 다짐을 마음속에 뒀다.  근데 생각보다 일찍 기회가 찾아오는군. ㅋㅋ

뭘 해야하는가 하면…

뭔가 시스템을 만들어놓으면 씨잘데기없이 뭐 없나… 하면서 주변을 서성거리는 분들이 있다.  대부분의 일반 사용자를 위한 시스템들이 그렇지만 대용량 처리를 위해 아래와 같은 시스템 형상을 가진다.   시스템의 가장 앞단에는 방화벽이 존재해서 DDoS와 같은 공격을 우선 차단하고 정상적인 요청들만 실제 서비스에 흘려 보낸다.  방화벽을 통과한 요청 건들은 각 서비스 장비들로 분산된다.  전체에 비해 분산된 개별 장비에서 인지한 의심스러운 건수들은 미미하게 간주될 수 밖에 없다.

AsIsConfiguration

나뉘어진 실체를 파악하기 위해서는 이를 다시 모아야 한다. 데이터를 모아서 함께 평가하기 위한 좋은 툴이 바로 elastic 제품군이라는 걸 앞서 링크한 슬라이드를 통해 쏠쏠히 배웠다. 배웠다면 제대로 써먹어봐야지.

우선 각 서비스 장비들에는 처리한 요청의 요구자(소스)와 처리 결과를 로그 파일로 기록한다.  이건 당연히 개발자의 상식이다. 이걸 바탕으로 활용할 수 있는 제품들을 궁리해봤다.

  • filebeat – 로그 파일을 읽어들여서 이를 수집 시스템에 전달한다.  수집 시스템은 logstash 혹은 elastic search 혹은 bigdata 처리용 hive 등등을 지정할 수 있다.
  • logstash – 수집된 로그를 regular expression을 통해 특정 필드를 추출하거나 거르는 역할을 수행한다.  정제된 값들은 elastic search를 통해 전송하거나 hive 등으로 전송할 수 있다.
  • elastic search – 수집된 데이터를 다양한 검색 조건으로 빠르게 조회할 수 있는 기능을 제공한다.
  • watcher – elastic search에 존재하는 데이터 패턴을 조회하여 지정된 조건에 도달했을 때 이를 지정된 방식으로 알린다.  알림 방식은 email 혹은 http post 등을 사용할 수 있다.

근데 이렇게 하면 정말 되나?

이 정도의 제품 셋이면 원하는 나쁜 놈들을 찾을 수 있겠다 싶었다. 하지만 급하게 기술 검토를 하다보니 몇가지 문제점이 보였다.

  1. 만들려는 시스템의 궁극적인 목표는 비정상적인 요청자들을 찾아내 엉뚱한 짓을 못하도록 방화벽(Firewall)에 도움을 요청하는 것이다.  최근 N분 동안 비정상 요청들의 소스를 찾는 것이 기본 전제이다.  하지만 watcher의 동작은 배치 방식이다. 따라서 최근 N 분이라는 전제를 달성하기 어렵다.
  2. 물론 1분 정도 단위로 계속 돌리면 될 것 같기는 하다.  하지만 인위적인 Pull 방식으로 데이터를 elastic search를 통해 처리하면 전체적인 효율성의 문제가 나타날 수 있다. filebeat, logstash를 통해 들어오는 정보를 실시간으로 판단하면 되는데, 그걸 1분 단위로 Pulling하면 중간에 있는 elastic search가 부하를 발생시키고 이후에 시스템의 bottleneck이 될 수 있다.
  3. 이 시점에서 개발자의 역량이면 이 요구 사항을 대응할 수 있는 간단한 처리 기능을 직접 만드는 편이 오히려 효율적이다.  logstash를 통해 정제된 요청들을 받아서 각 소스 단위의 최근 N분 데이터를 평가하고 오류 발생시 이를 처리하는 기능이면 족하니까.

위 설명을 정리해보면 아래와 같은 구조가 개발자의 약간의 작업을 가미했을 때 가장 적합한 구조로 보였다.

System flows

이것저것 많이 쓰는 구조에서 간결해지긴 했다. 하지만 원래 의도했던 있는 것들을 잘 활용해보자에서는 약간 멀어진 느낌이다.  설명한 Architecture 구조는 내가 다루는 시스템의 상황에 맞는 구조이다. 상황은 각자에 따라 다르기 때문에 이것이 절대 정답이 될 수 없다. 사실 정답이란 없다.  하지만 한가지 중요한 이야기를 하나 더한다.  있는 것들에 너무 의존하지 말라는 것이다.  얽매이다보면 문제를 보는 바른 시각을 잃게 되고 제대로 보지 못한다.  되려 자신의 기술 수준에서 최선을 길을 찾으려고 노력해라.  설령 그 길이 잘못된 길이라도 시도함으로써 얻는 것들이 훨씬 많다.

정리가 됐으니 이제 만들어보자

시스템을 만드는 건 우선 elastic software를 설치하는 것부터 시작이다. 데이터를 제대로 받아올 수 있는지를 확인하는게 가장 먼저일테니까.

Elastic 제품군을 깔아보자

가장 먼저 할 일이 logstash를 설치하는 일이다. 설치 방법은 링크한 사이트에 충분히 자세히 설명이 나와있으니 그걸 참고하면 되겠다.  이미 들어 알고 있는 사람들은 다들 알겠지만 설정의 핵심은 filebeat을 통해 뿜어져 들어오는 로그에서 어떤 내용을 취할 건지를 설정하는 대목이다.  일반적인 Regular expression과 아주 약간 차이가 있기에 사용할 형식을 미리 테스트해보는게 좋다.

샘플 로그를 대상으로 데이터를 미리보기식으로 살펴볼 수 있는 웹앱이 있는데 쏠쏠하다.  포맷 오류 혹은 파싱 오류로 logstash를 몇번 내렸다올렸다하는 수고를 덜어줄 것 같다.  막무가내로 정의하는 것 말고도 logstash 설치 후 공통적으로 사용할 수 있는 로그 패턴들이 있으니까 다음 링크에서 참고하는 것도 좋다.

https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns

다음으로 filebeat을 설치한다. 설치 및 설정 방법 역시 친절한 설명이 사이트에 있다.  내려 받아서 그냥 설정을 잡으면 된다………………………  하지만 Segmentation fault를 내면서 죽어버네!  뭥미???

http://pds20.egloos.com/pds/201005/25/46/c0060146_4bfb18415c1f5.gif

확인해보니 filebeat이 GO 언어로 만들어졌군.  GO 플랫폼이 리눅스 커널 2.6.23 이상 버전부터 지원하기 땜시롱 안된다.  GO 플랫폼을 까는 건 벼룩잡다가 초가삼간 태우는 격이다.  ㅠㅠ

나는 개발자다

초가삼간을 태울 수는 없지만 역할을 되집어 생각해보면 로그 파일을 읽어다가 logstash 서버로 전송하면 된다.  다행이 logstash에서 TCP로 텍스트 전송하는 입력 타입을 지원한다.  그럼 내가 해야할 일은 tail -f 와 동일한 기능을 수행하는 모듈을 하나 만들면 된다.  와중에 JRE가 설치되어 있어서 자바 기반으로 하나 만들었다!!

https://github.com/tony-riot/logreader

사용법은 간단하다.  jar 파일을 만들거나 혹은 다운로드 받은 이후에 다음 명령으로 실행하면 된다.


java -jar logreader.jar /your/logs/absolute/path.log logstash-IP logstash-listening-port

각 실행 파라미터는 다음의 의미를 갖는다.

  • path.log – 읽어들일 파일의 경로를 지정한다. 가능하면 절대 경로를 지정한다.
  • logstash-IP – Logstash 서버의 IP 주소 혹은 도메인 이름을 입력한다.
  • logstash-listening-port – Logstash 서버에서 설정한 TCP 입력의 포트를 지정한다.

실행하면 아무런 Output도 출력하지 않는다. 출력되는게 없다고 놀라지 말자. 🙂

가장 메인이 되는 logstash 설정은 아래와 같다.

input {
    tcp {
        port => "5555"
        codec => line
    }
}

filter {
    grok {
        match => { "message" => "\AINFO\s{2}\|\s%{DATE_US:date} %{TIME:time}\s\|\s[a-zA-Z0-9._-]+\s\|\s[a-zA-Z0-9._\(\)]+ \|\stransaction_code=error\: (?[a-zA-Z\s]+),userName=%{USERNAME:username}\,ip=%{IP:sourceIp},source=%{USERNAME:source}" }
    }
    if "_grokparsefailure" in [tags] {
        drop { }
    }
}

output {
    http {
        url => "http://127.0.0.1:8080/api/v1/log-receiver"
        http_method => "post"
        format => "json"
        content_type => "application/json"
        mapping => ["date", "%{date}", "time", "%{time}", "cause", "%{cause}", "username", "%{username}", "sourceIp", "%{sourceIp}", "source", "%{source}" ]
    }
}

이런 설정으로 이제 수집에 관련된 부분은 마무리가 됐다. 

2017/08/01 첨언

logstash 5.5.1 버전으로 업하면서 이전과 설정이 변경된 사실을 완전 삽질 가운데 알았다.  이전 버전에서는 grok의 match 설정만으로도 매치되지 않는 다른 패턴의 경우에는 데이터가 output으로 전달되지 않았다.  근데 버전업을 해보니 매치가 되던 안되던 죄다 output으로 데이터를 전송해버려서 과도한 Stacktrace로 인해 시스템이 맛탱이가 가버렸다. ㅠㅠ

이런 현상을 막기 위해서는 성공 여부를 파악할 수 있는 _grokparsefailure 값을 tags에서 검색해서 존재하는 경우, 이를 drop filter를 사용해서 걸려내야한다. 버전업에 항상 좋은 일은 아니라는거… 덕분에 반나절 가까이를 날려버렸다.

 

자, 그럼 Analyzer라는 걸 이야기해볼까?

앞선 설정에서 볼 수 있는 것처럼 logstash에서 정제된 결과는 POST 방식으로 지정된 API 서버에 전달된다.  이때 POST의 Payload 값에는 match를 통해 추출된 값들이 JSON format을 통해 전달된다.

이제 Source 단위로 정리해서 추출하는 Business Logic을 구현하면 되겠다.  이건 뭐 누구나 할 수 있는 웹 어플리케이션 개발이다.  구질구질한 설명은 생략하겠다. 🙂

이렇게 만들어졌다.

지금까지의 설명을 한장의 그림으로 설명하면 이렇다.  원래는 많은 것들을 빌어다가 손쉽게 시스템을 만들 계획이었지만 생각만큼 녹록하지는 않았던 것 같다.  결국 1개 시스템만 활용하고 말았으니 말이다.

LogstashBasedSystem

의도한 전체 시스템을 검증된 오프 소스 제품들을 이용해서 구축해본 몇 가지 소감을 정리해본다.

  • 전체적인 시스템 구축 비용이 확실히 절감된다.  다른 개발자들을 통해 이미 검증되었고, 사용에 대한 다양한 레퍼런스들이 많아서 Troubleshooting이 쉽다.
  • logstash라는 중심축을 통해 필요한 부분만을 구현하기 때문에 개발에 낭비가 없다.  딱 그 부분 혹은 그 기능까지만 개발하면 된다.
  • 시스템적인 제약 사항이나 성능적인 부분은 쓰기 전에 충분히 검토해봐야 뒤늦은 후회를 안한다.
  • 이름에 현혹되지 말아야겠다. 명성이 자자하더라도 쓰고자 하는 현실에 맞질 않으면 폭망이다.

 

에필로그

이 작업을 하면서 30대의 젊음을 불사르면 만들었던 Open Manager 라는 제품 생각이 났다.  로그 처리 하나는 기막히게 했던 물건이었다.  과거의 추억이지만 간만에 로그 다루는 작업을 하다보니 예전 추억이 새록새록하다.

몇 날의 밤세움이후에도 지치지 않았던 건 그만한 열정이 가슴 안에 가득했기 때문이겠다.

아직은 그 열정의 불꽃을 꺼뜨리고 싶지 않다.

 

칼의 노래

어랍쇼!!!!

독후감은 어데로가고, 엉뚱한 워드프레스 PP 내용이 버틴거지?

서비스를 이전할 때 어데론가 날라간 모양이다.

JWT(JSON Web Token): 웹에서 안전한 개발을 하기 위한 도구

우리가 사용하는 웹은 공개된 세상이다. 프로토콜이 공개되어 있고, 자유롭게 접근할 수 있는 데이터들이 있다.  개발자들은 공개된 프로토콜과 데이터를 활용해 이를 공개된 정보를 사용자들에게 제공한다.  웹이 지향하는 이 개방성은 모두에게 방대한 정보를 제공하는 기회를 제공한다.  이것이 최초의 웹이 현재의 웹이 된 이유일 것이다.

웹 세상에서의 통신

하지만 모든 정보가 모두에게 공개될 수 있는 것은 아니다.  특정 정보는 개인의 사적 정보를 담고 있기 때문에 그 사람에게만 제공되어야 한다.  마찬가지로 개인의 사적 정보 역시 안전하게 입력받아야 한다.  특히 사용자가 입력하는 정보는 사용자 개인을 특징지을 수 있는 정보를 포함하기 때문에 무조건 이런 처리를 해야한다.

그럼 웹에서 통신은 어떻게 이뤄질까?  개인 관점에서 정의해보면 아래와 같은 큰 덩어리로 정리될 수 있다고 생각한다. (OSI 7 Layer를 참고하긴 했지만 정확하게 일치하지 않는다. 순전히 개인적인 관점이다. ^^)

CommunicationLayers

각 계층(Layer)가 가지는 의미를 정의해보면 아래와 같다.

  • Physical Layer – 실질적인 통신선을 나타낸다.  무선이든 유선이든 일단 선을 깔아야지 통신이 되겠지?
  • Transport Layer – 선이 깔린 상태에서 이제 보내는 쪽과 받는 쪽이 일련이 규격을 통해 정보를 주고 받는다.
  • Application Layer – 쌍방간에 데이터를 주고 받을 때 어떤 형식(Protocol)을 사용할지 어플리케이션 사이에 정의한다.

이런 환경에서 웹 서버와 클라이언트(웹 브라우저)가 통신을 하고 있다. 당신이 PC에서 이 글을 보고 있다면 아마도 유선 랜으로 연결되어 있을 것이고 스마트폰에서 보고 있다면 무선으로 연결되어 있다.(Physical Layer)  접속한 각 기기는 IP를 가지고 TCP 기반에서 패킷을 통해 이 글을 요청했고, 웹 서버는 이에 응답해서 글을 내려보낸 것이다.(Transport Layer)  마지막으로 서버에서 HTML이라는 프로토콜로 ASCII 데이터를 내려보내면 브라우저가 이를 해석해서 보기 좋은 형태의 텍스트로 표시했다.(Application Layer)  그렇기 때문에 당신은 이 글을 볼 수 있다.

개인적으로 이 글에 내 신상에 관련된 아주 특별한 정보가 있는 것도 아니고 원래 의도가 불특정 다수에게 읽혀지길 원하기 때문에 특별히 보안을 요구하지는 않는다.  다만 내 의지가 아닌 남의 의지에 의해 내가 작성한 글들이 싸그리 날라가는 일만 없길 바랄 뿐이다.

불안함에 대응할려면?

불안함에 대응하기 위한 어지간한 방법들은 이미 널리 알려져있다.

Physical Layer 차원에서의 대응

전용선을 쓰면 된다.  하지만 구리선을 깐다는건 돈이 억수로 든다는 것을 의미한다.  그리고 당신의 프로그램(혹은 시스템)은 특정 고객을 위한 것이어야만 한다는 전제가 깔여 있어야 한다.  전용선을 여기저기 이어준다는 것은 전용선이 전용선이 아니라는 것이니 말이다.

돈이 걱정이라면 VPN(Virtual Private Network)을 이용할 수도 있다.  하지만 이 역시 소프트웨어를 이용한 가상적인 망이라는 것. 그리고 엄청나게까지는 아니겠지만 상당히 느릴 것이라는 것을 감수해야 한다.

Transport Layer 차원의 대응

일반적인 OSI 7 레이어에서 Transport 계층의 상위 계층은 TCP/IP이다. 그냥 쓰면 여러분이 보내는 정보를 그냥 까볼 수 있다. 이게 안되게 할려면 오고 가는 정겨운 메시지들을 암호화하면 된다. 주로 SSL 등이 TCP/IP 계층 위에서 연결된 양 단말 사이에 오고 가는 데이터들을 암호화한다.

이 방식은 대표적인 예가 HTTPS 프로토콜이다.  일반적인 HTTP는 TCP/IP 스택 위에서 암호화를 적용한다.  따라서 만약 이 데이터를 누군가가 중간에 가로채서 데이터를 변경했다고 했을 때 변경됐는지 아닌지 알 도리가 없다.  HTTPS는 중간 경로를 통한 위변조를 막기 위해

  1. “중립 기관(Certificate Authority)”를 통해 서명된 인증서(Certification)를 이용해 데이터를 암호화하고
  2. 브라우저에 암호화된 데이터와 인증서를 전달한다.
  3. 브라우저는 인증서에 적용된 서명이 올바른지를 다시 중립 기관을 통해 확인하고
  4. 실제 데이터를 보낸 서버로부터 받은 경우에만 인증서를 통해 데이터를 복호화하여 실행(표시)한다.

 

(http://www.webstepbook.com/supplements-2ed/slides/lecture27-security.shtml#slide8)

이 과정에서 오류가 발생되면 브라우저는 이를 사용자에게 알리거나 아예 접근을 차단한다.  이것이 우리가 구버전의 IE를 사용하면 안되는 이유이다.  IE 구버전은 사용의 편이성(?)을 위해 HTTPS의 증명이 틀리더라도 내려받은 코드(특히 자바스크립트 혹은 ActiveX)를 실행하기 때문이다.

Application Layer 차원의 대응

이정도면 충분한 준비를 갖춘것 아닐까?  기본은 충족을 한 것 같지만 그래도 좀 부족하다.

HTTPS 방식에서 인증 기관을 해커가 사칭한다면 어떻게 될까?  이 상황에서 서버에서 내려오는 정보를 마찬가지로 동일 해커가 위변조시킨다면 영락없이 당할 소중한 정보를 빼앗기거나 사용자 컴퓨터의 이러저러한 정보들이 탈탈 털리는 상황이 나온다.  서버와 클라이언트 사이에 전달되는 정보를 누구나 다 볼 수 있는 쿠키에 암호화되지 않은 형태로 주고받는 환경이라면 더욱 심각한 상황이 되버린다.

이런 상황을 가급적 최소화하기 위해서는 개발자의 힘이 필요하다.

JWT(JSON Web Token)이란?

앞서 이야기한 것처럼 시스템을 통해 정보를 보호하는 건 한계가 있다.  일반적인 보안 시스템이 할 수 있는 수준은 정성들인 해커에게는 대부분 무용지물인 경우가 많다. 쿠키에 많은 정보를 주고받는 것이 그냥 손에 익어버린 경우라면 더욱 더 문제다.

이런 걸 하지 말라는 차원에서 등장한 개념이 암호화된 토큰을 이용하는 방법이다.  우리가 다루는 모든 정보를 암호화할 필요는 없다.  외부에 노출되면 곤란한 개인 정보 혹은 중요 정보들을 그 대상으로 한다.  그리고 이 정보들을 시스템 사이에 주고 받을 때 이놈이 그놈이 맞는지를 확인할 수 있으면 된다.

JWT는 토큰 방식으로 이를 처리하기위해 제시된 방식이다. 이름이 의미하는 것처럼 JSON String 형태이기 때문에 웹 환경에서 쓰기에 좋다.  특히 RFC로 정의된 표준이다. JSON의 특성상 다양한 형태의 정보를 정의할 수 있으며, 정보의 출처를 확인하기 위한 검증 방식을 제공한다.

source from https://jwt.io/introduction/

그림에서 보는 것처럼 JWT은 일련의 암호화된 문자열이며, 점(.)을 기준으로 3가지 영역으로 구분된 구조를 갖는다.

  • Header – 해당 토큰이 어떤 암호화 알고리즘을 통해 암호화되었는지를 정의한다.  alg 라는 세부 필드로 표현된 알고리즘이 암호화에 적용된 알고리즘을 나타낸다.
  • Payload – 실제 전달하고자 하는 메시지 Body를 나타낸다.
  • Signature – 전달된 메시지에 대한 Signature 값을 나타낸다.  말 그대로 signature 값이 포함되어 있으며, 해당 값을 인식하기 위해서는 보낸쪽과 받는쪽에서 합의된 키 값을 가지고 Header에서 정의된 암호화 알고리즘을 통해 검증할 수 있다.

구조에서 Header와 Payload에는 암호화가 적용된 것이 아니라 Base64 URL Encoding되었을 뿐이다. 실제 암호화는 Signature에만 적용되어 있으며, 전달된 메시지를 서로 신뢰할 수 있는지를 평가하기 위한 구조로 사용된다.  이 말이 의미하는 바를 풀어 설명하면 이렇다.

전체 데이터에 대한 외부 노출 차단은 HTTPS와 같은 시스템 수준의 암호화를 통해 담보하고, 전달받은 데이터의 신뢰성을 평가를 암호화 토큰을 통해 보장한다.  따라서 암호화 알고리즘과 이에 대한 암호화 키 정의는 어플리케이션 개발자가 시스템의 구조와 토큰의 역할에 의해 결정된다.

그렇기 때문에 개발자의 역할이 중요해진다.

Just do it with code

그럼 이걸 어케 개발자가 프로그램으로 작성할 수 있을까?  앞서 링크한 jwt.io 사이트에서 관련된 이를 지원하는 여러 언어별 라이브러리들이 있다.  Javascript를 이용한 예제는 여기를 참조하자.  소개할 예제는 Java를 활용한 예제 코드로 nimbus 라이브러리를 활용했다.  nimbus를 사용하기 위해서는 다음의 메이븐 dependency를 설정한다.

    <dependency>
      <groupId>com.nimbusds</groupId>
      <artifactId>nimbus-jose-jwt</artifactId>
      <version>4.12</version>
    </dependency>

JWT를 생성하는 방식은 아래 코드를 참조한다.

JWSSigner signer = new MACSigner(key);
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .claim("cid", "this-is-key")
                .claim("name", "chidoo")
                .claim("admin", true)
                .build();
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
signedJWT.sign(signer);

String jwtString = signedJWT.serialize();
System.out.println(jwtString);

암호화 알고리즘으로 HS256을 사용하기 때문에 key값은 32바이트 이상의 문자열을 사용한다. 출력된 코드가 정상적인지는 간단하게는 jwt.io 사이트에서 Verification할 수 있다.  혹은 다음 코드와 같이 입력된 JWT 값을 동일한 키 값을 통해 확인해볼 수 있다.

JWT signedJWT = (SignedJWT)SignedJWT.parse(jwtString);

// check payload and applied algorithm in header
System.out.println(signedJWT.getPayload().toJSONObject());
System.out.println(signedJWT.getHeader().getAlgorithm());

// verification
JWSVerifier verifier = new MACVerifier(key);
assertThat(signedJWT.verify(verifier), is(true));

실제 코드를 통해 이걸 사용하는 방법은 위의 몇 줄에서 보는 바와 같이 쉽다. ^^;

이렇게 활용할 수 있다.

그럼 이걸 어떤 경우에 써먹어야지 제대로 써먹었다고 이야기를 할 수 있을까?  가장 대표적으로 특정 시스템에 접근하기 위한 권한을 이 사용자가 제대로 가지고 있는지를 확인하는 경우이다.  아래 그림의 예제를 통해 살펴보면 다음과 같은 절차로 처리가 이뤄진다.

  1. 사용자가 로그인을 하면 보통 세션을 생성한다.
  2. 생성된 세션을 위한 사용자의 Key를 발급하고, 이를 가지고 사용자 식별자(보통 User ID)를 포함한 JWT를 생성해서 이를 Response로 보낸다.
  3. 이 JWT는 세션이 유지되는 동안 사용자가 쿠키 혹은 내부 변수의 형태로 가지고 있는다.
  4. 사용자가 민감한 정보를 가지는 시스템에 접근하는 경우 JWT를 그 시스템에 전송한다.
  5. 해당 시스템은 전달된 JWT 정보를 Decode해서 사용자를 알아낸다.
  6. 사용자에게 발급된 키를 통해 전달된 JWT 값이 위변조된 값인지 아닌지를 확인한다.
  7. 정상적인 토큰으로 인식하면 사용자에게 부여된 권한 등등에 부합할 때 데이터에 대한 접근을 허용하면 된다.

 

JWTUsageExample

 

위의 경우처럼 일반적인 사용자 인증 체계의 신뢰성을 확보하기 위한 용도가 가장 대표적일 수 있다.  이 이외에도 마이크로서비스 아키텍처 환경에서 각 서비스 시스템간 Collaboration을 위한 권한 관리 용도로도 활용이 가능하다.  마이크로서비스 환경에서 각 서비스 시스템들은 본인들이 제공하는 정보의 규격을 정의한다.  하지만 누구나 정보를 임의대로 허용할 수는 없다.  자신과 협업할 수 있는 시스템들의 제한이 필요하며 이를 위한 신뢰성있는 토큰으로 JWT를 활용할 수 있다.

ServiceCollaboration

Service A에 접근하기 위해서는 사전에 시스템간 협의에 의해 key를 발급한다.  연동 서비스에서 Service A에 접근하기 위해서는 본인에게 발급된 키를 가지고 JWT 생성하고 이를 원하는 Operation과 함께 시스템에 전송한다. A 서비스는 전달된 값을 검증해서 허용된 시스템에 대해서만 요청된 기능을 실행한다.  만약 협업이 허용되지 않은 시스템으로부터의 요청에 대해서는 당연히 이를 거부한다.

JWT를 사용하는데 있어서 JWT 값의 Lifetime을 명확하게 관리해야한다.  생성된 JWT가 반복적으로 쓰여도 별 문제가 안된다면 이건 하나마나한 결과를 초래하기 때문이다. 따라서 이 값이 언제 생성된 값인지 그리고 언제까지 유통 가능한지를 JWT 안에 포함시켜 관리하는 것이 보다 안전한 시스템 구성을 유지하는 길이다.

마음만 먹으면 제대로 할 수 있다.

뭔가를 보호하는 일은 솔직히 많이 사람을 귀찮게 한다. 특히나 재미있는 기능을 만들고 싶은 마음이 굴뚝인데, 기능 그 자체와는 동떨어진 일에 얽매여 있다보면 짜증이 날 수도 있다.  하지만 귀중한 정보를 가지고 있다면 그 귀중함을 잘 지켜야 할 의무 또한 존재한다.

방법이 있다면 그 방법을 적용해야 한다.

매번 소잃고 외양간만 고치고 있을 수는 없지 않을까?

– 끝 –

 

Pair Programming을 위한 준비 사항

간만에 짝 프로그래밍(Pair Programming)을 해보고 있다. (이후부터는 그냥 페어 프로그래밍)

Pair Programming.jpg - Wikimedia Commons

이 방법을 에자일과 XP 책들을 읽으면서 “아, 이런 방법도 있구나~” 하고 배웠다.  그러고 보면 이 시절에 페어 프로그래밍과 TDD를 포함해 새로운 지식들이 넘쳐나던 시절이었다.  어줍잖은 자신감으로 진행했던 프로젝트의 실패와 더불어 회사의 자금 사정 악화로 경제적으로 빈궁한 시절이었다. 하지만 실패를 곱씹는 과정에서 챙긴 지적 호기심은 이후에 참 많은 도움이 됐다.

사설이 길었지만 페어 프로그래밍을 실제로 시작한 건 네이버에서 일하기 시작한 이후다.  그것도 시작은 하다가 아예 공중 분해된 프로젝트를 진행할 때!!! (우연일지 필연일지 모르겠지만 망한 프로젝트에서 개인적으로 건져지는게 많은 듯…)  당시의 “나”라는 사람은 에자일(Agile)과 TDD라는 선진 문물에 신기해하면서 제대로 개발자의 궁색을 갖추는 중이었다.  아무래도 역량 부족.  페어의 정당한 짝꿍이라기에는 부족한 관전자일 수 밖에 없었다.  (이런 찌질함이었음에도 불구하고  2009년에 이따구 글을 적었다라는게 참… )

그 시절 이후로 시간이 많이 지났다. 제대로 개발하기 위한 몇 년의 시간이 있었고 때아닌 질풍같은 시간이 휩쓸고 지난 이후 현재의 자리에 있게 됐다.  잘 하는건 아니지만 나름 백엔드 개발에 대해서는 나름 코딩을 할 수 있다는 생각도 있고.  ^^;

최근에 시작한 새로운 프로젝트가 있어서 함께 하는 친구와 페어로 진행하고 있다.  진행하면서 느낀 “제대로 하기 위해 이런 것들이 꼭 필요하겠구나?” 하는 생각이 들어 정리해본다.

1. 서로를 존중해야 한다.

한 컴퓨터를 가지고 두 사람이 붙어 작업하는 것을 페어의 원칙이다.  세상에는 동일한 인간이 존재할 수 없다.  일란성 쌍둥이라고 하더라도 인격은 다를 수 밖에 없다. 특히나 프로그래머들의 경우에는 자신만의 이고(Ego)를 가진 사람들이 흔하다. 그렇기 때문에 긱스(Geeks)들이 널리고 널린 분야다.

둘이 하는 작업이다. 서로 양보하지 않고 부디친다면 충돌은 피할 수 없다.  기술에 대한 논쟁의 충돌은 환영할만한 일이다. 그러나 말이 전도되어 감정적인 언어가 이야기 사이에 끼어들면 폭망한다.

특히나 대화를 매개로 한 두 사람의 공동 작업에 나이나 직위가 끼어들지 않도록 해야한다. 권위주의가 대화에 개입하면 제대로 된 말이 이뤄지지 못한다.

2. 적극적으로 대화를 한다.

같이 작업을 하는 친구가 부끄럼쟁이지만 술만 먹으면 이야기를 또 나름 하는 친구다.  물론 실력도 출중하다. 그래서 페어를 할 때 이야기를 많이 한다. 말빨에 관련해서는 나도 밀리지는 않는 사람이기 때문에 또한 이야기를 많이 한다.

코드가 생긴 모양새나 왜 이렇게 코딩을 해야하는지에 대해 이야기하는 그 시간은 개인적으로 행복한 시간이다. (되려 게임하는 것보다도 이 시간이 더 재미있는 것 같기도 하다.)  특히 어떤 방식의 코드의 구조가 좋은지, 잘 읽히는 코드를 작성하기 위한 서로의 생각을 이야기는 정말 좋다. 너가 옳다 내가 옳다 이야기를 많이 하지만 결론적으로 각자 방식에서 장점을 취한다.  애도 아닌데 뭐가 결국 올바른지 몇 마디 말을 주고 받다보면 자연스럽게 알게 된다.  이런식의 대화가 길어지다보면 결국 쌓이는 건 지식이다.

3. 대등한 기량이면 더욱 좋다.

개발 능력이 되도록이면 비슷한 편이 좀 더 페어를 하기에 이점이 있다는게 개인적인 생각이다. 이전에 주니어 친구와 페어를 해봤다. 하지만 페어가 아니라 한 사람은 훈계를 하고 있고, 한 사람은 그 이야기를 주눅든 상태로 듣고 있었다. 아무래도 성질이 드러웠던 모양이다. -_-;;  이 시간이 물론 의미가 없는 건 아니다.  그렇다고 “두 사람 모두에게 발전적인 시간이었냐?” 라는 질문에는 “글쎄???” 라는 답변이 나올 것이다.

페어 프로그래밍도 일하는 방법 가운데 하나이다. 일을 하는 누구라도 일이 생산적이길 희망한다.  앞서 이야기한 것처럼 페어 과정의 토론은 서로의 성장에 큰 도움이 된다.  하지만 이것이 어느 일방의 가르침이라면 어떨까?  가르침을 받는 입장에서는 1:1 과외를 받는 특전일 수 있다.  일방적인 강의를 대학때에도 많이 들었다. 하지만 금방 잊혀지더라는 것이 개인적인 진리라고 본다.

4. 코드는 돌아가면서 작성한다.

내가 작성하는 코드를 누가 옆에서 혹은 뒤에서 쳐다보는 경험은 해보지 않은 사람에게는 매우 낯선 광경이다.  하지만 관찰 대상의 입장과 관찰자의 입장에서 이 광경은 모두 흥미롭다.  관찰 대상의 관점에서 본다면 코드 작성 과정과 툴 사용 과정들을 보여줘야한다.  그만큼 숙달된 조교의 솜씨를 강제한다. 관찰자의 관점에서 역시 다른 사람의 코딩하는경쾌한 키보드 소리를 듣거나 겁내 빠른 툴 사용을 위한 단축키 신공을 눈으로 볼 수 있다.

서로가 보여주고 보는 과정은 코드를 작성하는 본인의 자세를 그대로 까발린다.  얼마나 직관적으로 코드를 작성해나가는지? 술술술 코드를 적어나가는지를 볼 수 있다.  물론 이 과정에서 본인의 코딩 습관도 방청객에게 생얼굴로 보여준다.

관찰자는 민낯에서 얻는 것이 많다.  내가 작성하는 코드 대비 어떤 방식으로 다른 친구는 코드를 작성하는지 혹은 코딩 스타일은 어떤지를 배울 수 있다.  따라서 누구 하나가 독점적으로 키보드를 독차지하는 건 불합리하다.  종종 의자에 붙히는 엉덩이의 주인을 바꾸자.

5. 표준을 정하고 지킨다.

개발자는 코드를 작성한다. 하지만 인간인 이상 개성이 있고, 그 개성이 코드에 묻어나기 마련이다.  지금 작업하는 친구와 의미있는 페어를 하지만 이후에는 다른 친구와도 이런 의미있는 시간을 가져야 한다.  다른 친구의 만남에서 어떻게 상호 절충을 해야할까?  그리고 이런 상호 절충을 계속해야 하는 건가?

이것보다는 “표준안“이라는게 있는게 좋다. 왜 공통의 표준안을 따라야 하는가?  배려심이다.  자신이 작성한 코드를 다른 사람이 편안하게 읽을 수 있도록 혹은 다른 친구가 편안하게 수정할 수 있도록 맞춰주면 정말 좋아하지 않을까?

다른 이야기지만 내부 공유 시간에 나온 JS Framework에 대한 코딩 컨벤션을 무조건 체크하기 위해 lint를 사용했고, 하자는 의견이 있었다.  좋은 움직임이고 자바에 대해서도 이런 툴을 적용해보기로 했다.  표준안이 없다면 정립된 Defacto를 따르고, 그걸 팀의 현실에 맞춰 조정해나가는게 좋지 않을까 싶다.

6. 시간을 맞춘다(혹은 정한다)

이번 페어를 하면서 같이 하는 친구에게 미안한 점은 함께 진행할 수 있는 시간이 길지 않다는 것이다.  상대적으로 참석해야할 미팅이 많다보니 하루에 길게 진행하더라도 2시간 이상은 진행이 어려웠던 것 같다.  서로 시간을 할애해주지 않는건 내가 저지른 실수이긴 하지만 민폐다.

제대로 한다면 하루에 2시간 정도의 시간을 정해두고 페어를 하는게 좋다고 생각한다. 페어를 통해 공통적으로 해야할 일들, 예를 들어 핵심적인 내부 구조를 잡는다던지 서로 알고 있어야 할 업무 로직을 작성해야하는 일을 함께 하는게 좋다. 그리고 이외의 시간에 각자 작업한 부분을 코드 작성하고 이후에 각자 리뷰하면 일도 나름 효과적으로 할 수 있지 않을까 생각한다.

codingconfidence

여기까지 정리해봤다. 아마도 가장 중요한 건 “서로에 대한 존중과 이해“일 것이다.  만약 이걸 갖추고 있지 않으면 절대로 페어를 하지 않는게 좋다.  무시하고 시기하고 결국에는 쌈난다.  본인이 다른 사람을 존중할 줄 안다면 그럼 바로 페어를 해봐라.  존중과 이해 다음에 본인이 갖춰야 할 점이 바로 “실행“이다.

오늘 하루도 좋은 일이 있으시길~~~~

코드 리뷰의 시작에 임하는 자세

코드 리뷰를 어떻게 시작할 때 뭘 생각해야 하는지에 대한 재미있는 글이 있어서 짧게 기록해둔다.

Building a better code review process

글의 제목은 더 좋은 코드 리뷰에 대해 이야기를 하지만 것보다는 리뷰를 할 때 코드보다 더 신경써야 할 것들이 뭔지에 대한 이야기다.  코드 리뷰에서 우리가 주로 신경쓰는 부분이 “코드의 품질”이다.  사실 나도 다른 사람의 코드를 리뷰할 때 주로 이 부분을 본다. 얼마나 읽기 편하게 작성했는지, 재사용이라는 관점을 얼마나 반영했는지, 적절한 테스트 코드로 보장이 되는지 등등을 확인할려고 한다.

하지만 이 글에서는 “리뷰의 시작은 코드가 아니다!” 라고 이야기한다.  먼저 확인해야 할 부분은 코드가 아니라 “사용자, 제품“라고 말한다.  리뷰 대상 코드가 실 환경에 배포되면 이로 인해 변경되는 기능이 있다. 변경된 기능이 사용자에게 미치는 영향은 어떤 것이고, 그리고 인한 사이드 이펙트가 없는지를 먼저 따져봐야 한다.

작성된 코드가 사용성의 어떤 부분을 개선하기 위해 목적을 가지고 있는지를 알아야 한다. 이걸 알아야 작성된 것이 제대로 작성된 것인지, 그게 최선인지를 따질 수 있기 때문이다.

이 글을 읽으면서 지금까지 해온 코드 리뷰가 기능적 혹은 형식적이지 않았나 하는 생각이 들었다.  리뷰를 위한 리뷰를 했을 뿐이라는 생각이다. 리뷰의 결과로 반영될 제품에 이 코드가 어떤 변화를 일으키고, 그 변화가 사용자에게는 어떤 도움을 줄지에 대해 생각이 모자랐다.

리뷰 요청 메시지를 작성할 때 “사용자 기능 XXX를 YYY로 변경해서 사용자가 ZZZ 라는 이점을 얻을 수 있다” 라는 형식이 되면 좋을 것 같다.  메시지를 작성하면서 사용자에게 도움이 되는 코드를 작성하고 있는지 한번 더 돌이켜 볼 수 있지 않을까?  “장인(Craftsman)”이라는 관점에서 작성하는 코드의 매력도 물론 있기 하지만 결국 우리가 작성하는 대부분의 코드는 사람을 지향한다. 사람에게 도움이 되는 코드를 작성하는게 이성적 관점에서 옳다.

글을 작성한 분이 코드 리뷰를 진행하기전에 스스로 반문해보면 좋을 질문 리스트가 있어서 옮겨 적어본다.

  • Does this change solve a real problem for the customer?
  • Is the solution robust, or merely adequate?
  • How do we know that this change is a good investment, relative to other things we could be doing instead?
  • What new problems might arise as a result of this change, and how will we mitigate them?
  • Can the change be easily reversed and/or gracefully degraded if something goes wrong?
  • Are there ongoing maintenance costs associated with this change? What are they?

리뷰 진행전에 이 질문들에 대한 명확한 답을 본인이 할 수 있다면 통과할 가능성이 높다.  그만큼 사용자, 제품에 한발짝 도움이 되는 코드를 본인이 작성하거나 리뷰를 해주고 있다는 의미이기 때문이다.

크게 동감한다.