08 Mar 2015
- 개발 생활 - 1 : PDF 문서 생성 서버
- 개발 생활 - 2 : 연산된 데이터 수집 작업자
- 개발 생활 - 3 : 전자우편 알림 서버
개발 생활 - 4
: Python과 Django 강의
- 개발 생활 - 5 : 공부 자료
- 개발 생활 - 6 : 앞으로 계획
3. Python과 Django 강의
개요
2014년 12월 4일부터 2015년 1월 31일까지 패스트캠퍼스에서 Python과 Django를 이용해 웹서비스를 개발하는 커리큘럼으로 강의를 했다. 나는 이론 부분을, 함께 강의한 고빈섭님은 실습 부분을 담당했고, 김구영님은 조교로 강의를 도와주셨다.
패스트캠퍼스에는 웹 프로그래밍 입문 과정이 있는데, 내가 강의를 맡은 웹 서비스 개발
과정은 입문 과정의 후속 과정이다. 그래서 초기 교육 과정 이름은 가칭 웹 프로그래밍 중급이었다가 실전 웹 서비스 개발에 초점을 맞추어 교육 과정 이름이 웹 서비스 개발로 바뀌었다.
패스트캠퍼스에서 내게 제안한 중급 과정 강의를 수락한 동기는 집필이었다. 내가 프로그래머로 전업을 결심하기 전에 한 출판사로부터 집필 제안을 받아서, 몇 몇 개발자에게 의견을 구하고 Django 입문 강좌의 후속 강좌인 날로 먹는 Django 웹프레임워크 강좌 연재를 시작하면서 집필을 준비하고 있었다. 하지만 목차를 구상하는 게 쉽지 않아 좀처럼 진도를 나가지 못하고 있던 차에 패스트캠퍼스로부터 제안을 받은 것이다.
교육 재료, 환경
- 프로그래밍 재료 : Python 2.7, Django 1.7
- 교재 재료 : 수강생 교재는 IPython으로 작성, 강사 발표는 Apple Keynote로 작성
- 소통 공간 : 페이스북 비공개 그룹
강의 준비 과정
교육 과정이 시작되기 전에 나부터 교육할 필요가 있었다. 경험이나 직관으로 알던 것에서 그치지 않고, 제대로 설명하여 정확히 이해시켜야 하기 때문이다. 더구나 나는 입문 과정을 강의하는 게 아니기 때문에 명확하게 이해시키는 걸 넘어 더 깊은 내용을 설명할 줄 알아야 했다.
곧바로 여러 Python 책과 공식 문서로 공부했다. 한창 편의점 프로젝트를 진행하던 시기였기 때문에 내 서재 책상엔 개발 관련 책 대여섯 권이 늘 펼쳐져 있었고, 웹브라우저엔 관련 자료에 대한 탭이 스무 개 이상 열려 있었다. 한 달 동안 책 다섯 권을 정독하고, 모든 예제를 실습하였다.
그 다음 공부 주제는 Django로 잡았다. 내가 Django를 얼마만큼 제대로 알고 있는 지 파악해야 해서 Django를 이용해 인스타그램 같은 웹 서비스를 서로 다른 구현 방법과 구조로 세 번 만들었다. 그 중 한 버전은 블로그에 연재 중인 강좌에 사용하고, 다른 한 버전은 강의 실습용으로 정했다. 그러고나서 Django 공식 문서를 보며 하나 하나 뜯어보고 다듬었는데, 각 기능마다 다른 사람이 구현한 방법을 웹에서 찾아다녔고, 구현 방법이 다른 경우 Django 소스를 열어서 Django의 작동 방식이나 의도를 분석했다. 이 과정에 약 4주가 걸렸고, Django 소스 코드 중 많은 부분에 대해 리뷰 아닌 리뷰를 두 차례 했다.
마지막으로 Django 릴리즈 노트와 Django의 master 브랜치에 쌓인 commit들을 훑으며 주목할만한 변화를 찾아 보았다. Django 1.0 이후로 Django를 사용하지 않아서 주요 변동 이력을 알지 못했기 때문이다.
학습을 일단락하고 강의를 준비했다. 나는 markdown 문서 형식을 애용하지만, IPython으로 교재를 만들어 보기로 했다. IPython은 2014년 초에 회사 개발 프로젝트에 도입하면서 처음 접했는데, 상상력을 일으키는 재밌는 도구라 여겨왔다. 프로젝터에 띄울 강의 자료도 IPython으로 작성하려 했지만, 가독성이 좋지 않아 Apple Keynote로 작성하기로 했다.
강의 과정
첫 수업에서 나는 무척 당황했다. 여러 수강자가 예상보다 내 강의를 어려워했기 때문이다. 입문 단계는 뗀 사람을 대상으로 하는 커리큘럼이니 입문서나 자료에 나오는 내용에서 그치지 않고 좀 더 이론 부분을 다뤘는데, 일부 수강자를 보니 무척 혼란스러워 하는 표정이었다. 가령, Python에서 모든 데이터는 객체라는 내용을 설명할 때, Python에서 객체가 무엇인지 설명했다.
- 모든 데이터는 객체(object)
- 객체 구조 : 신원(identity), 타입(type, class), 값(value)
number = 1
에서 number
는 신원, 1
은 값, 이 값이 할당된(assigned) 객체의 타입은 int
- 모든 객체의 조상은
object
이며, type
은 모든 타입의 조상. object
는 type
의 인스턴스이며, type
은 object
의 하위 클래스. type
은 자기 자신에 대한 type
.
- 동적 형(type) 변환 : 객체의 타입이 변하는 게 아니라 객체의 신원이 가리키는(reference) 대상(값)이 바뀌는 것.
- 변수, 즉 신원 자체는 객체나 값이 아니다.
del
문은 객체를 지우는 것이 아니라 객체 참고 관계를 끊는 것.
1 == True
와 1 is True
의 차이
True
의 값은 1
이고, False
의 값은 0
.
- 값 비교는
==
연산자로, 신원 비교는 is
연산자로 평가.
하지만, 일부 수강자가 “객체”라는 것 자체를 생소해 하거나 잘 모르는 상황이었다. 그들 입장에선 내가 어렵게 강의를 준비하고 설명한 것이었고, 결국 미리 준비한 강의 자료와 교재를 전면 수정해야 했다.
커리큘럼은 이론 부분과 실습 부분으로 구성했고, 나는 이론을 담당했다. 그러다보니 실습할 내용에 대해 이론 부분을 여러 방면으로 준비했다.
가령, Django의 모델 필드형 중 TextField
를 Django 공식 문서에서는 큰(large) 텍스트를 담는 필드라고 설명하는데, 나는 SQLite 3는 text
형으로 약 1기가 바이트, PostgreSQL은 길이 제한이 없는 text
, MySQL은 longtext
형으로 약 4기가 바이트, Oracle은 8~12테라 바이트를 담는 NCLOB
형이라고 설명하는 것이다. 수강자가 얼마나 긴 문자열을 담느냐는 질문을 하는데 CharField
보다 긴, 게시판의 글 본문에 쓸만큼 긴 문자열이라고 대답하고 싶진 않았다.
또는, 흥미롭게 구현된 Django 기능이 있는 경우, 그 기능이 작성된 Django 소스를 직접 설명하기도 했다. 예를 들어, Django의 settings
는 수강자들이 재밌다고 느낄만한 Python 기법이 잘 녹아있는 모듈이라서 가볍게 훑고 지나가지 않고 동작하는 방식에 대해 설명했다. Django 자체보다는 Python 기법에 대한 내용이다보니 다른 Django 자료에서는 잘 다루지 않는 내용이고, 내 강의를 듣기 때문에 접하는 내용이었다. 가능하면 이런 내용을 다루려 했다.
수업은 4회차부터 강의 목표와 계획을 완전히 수정했다. 수강자들이 수업 당시에는 수업 내용을 완전히 이해하지 못하더라도 교재를 보며 복습하여 언젠가는 이해하는 것으로 목표를 바꿨다. 일명 “교재라도 남기자”. 그래서 교재도 최대한 서술형으로 풀어서 문장을 작성했다. 마치 내가 교재를 강의 원고로 삼아 그대로 읽는 것처럼 작성했고, 발표용 슬라이드는 목차처럼 활용하도록 작성했다. 그러다보니 매 수업에 사용하는 교재는 100여 장을 넘기곤 했다. 한 수강자는 우스개소리로 100만원이 넘는 Python + Django 책이라고 말하기도 했다.
서술형 교재는 IPython으로 작성했다. 의도와 목표는 교재를 IPython으로 열어서 코드를 교재 안에서 직접 실행(run)하고 실습하는 상호작용 교재로 활용하도록 하는 것이었지만, 의도대로 활용되는 것 같진 않았다. 그래서 소스 문법 강조가(highlighting) 잘 된 문서로라도 활용하였고, 실제로 교재도 IPython 파일을 HTML 파일로 내보낸(export) 문서도 함께 배포하였다.
실습을 본격 시작한 수업부터는 매 수업에 진행한 전체 소스 코드도 배포하였다. 그러니까, 4회 수업까지 진행한 소스 코드, 5회 수업까지 진행한 소스 코드를 구분하여 매 수업 때 마다 배포한 것이다. 처음엔 git
으로 관리하여 각 수업에 진행한 소스 이력에 tag를 달아서 접근하도록 하려 했다. 하지만, 이 교육 과정으로 입문한 것이나 마찬가지인 수강자는 새로운 개념이 등장할 때마다 힘들어해서 git을 수업에 도입하진 않았다.
이렇게 교재를 만들어 배포하다보니 일단 수업엔 참여해서 최대한 듣고 따라오고, 수업 이후엔 내가 작성한 소스 코드를 기반으로 실습하거나 복습하고, 설명은 교재를 읽는 사례가 나타났다. 수강자 나름대로 적응하고 수업과 교재를 활용하는 것이다.
점차 강의에 익숙해지다보니 분량 조절이 맞아서 수업 시간 운용도 안정되었다. 교재를 만드는 건 여전히 힘들었지만, 강의 진행은 한결 편안해졌다. 수강자 개개인을 알아가면서 강의와 교재에 예제나 보충 설명을 반영하기도 했다. 글로 강좌를 연재하는 것과는 다른 매력이었다.
Bootstrap을 적용하고, Heroku에 개발한 프로젝트를 배치(deployment)하는 것으로 전체 강의를 마쳤다. Heroku 무료판은 무척 느린데다 국내에서 사용 사례가 많지 않아서 Heroku에 서비스를 배치하는 것에 주제를 맞추지 않고, 프로젝트를 배치하고 배포하는 과정을 이해하는 데 맞추었다. 즉, Heroku가 자동으로 처리해주는 부분은 무엇이고, Amazon Web Service 등을 이용할 경우 어느 부분까지 사람이 해야 하며, 어떻게 하는 것이 좋은 지 설명하였다.
우여곡절
초반 3주는 무척 힘든 시간을 보냈다. 강의 자료와 교재를 전면 재작성 하다보니 매주 시간에 쫓겼고, 회사 업무도 마감에 임박한 상황이라 정신 없는 나날이었다. 무엇보다도 내가 과의욕 상태이다보니 난이도나 분량을 조절하지 못하여 스트레스를 자초한 탓이 가장 컸다.
개인 사정으로 수업을 목요일과 토요일로 잡은 것도 좋지 않았다. 수업 간격이 좁다보니 수강자는 복습할 시간 여력이 없었고, 매 수업마다 100여 장이 넘는 교재를 작성하는 나 역시 수업 자료를 만들 시간이 부족했다. 특히 초반 2주는 주말에도 내 수업이 잡혀있다 보니 밤샘하기 일쑤였다.
실습 환경을 맞추어 예외 상황을 피하려는 목적으로 VirtualBox에 Ubuntu를 가상으로 설치한 건 득보다는 실이 컸다. 대다수가 VirtualBox에 Ubuntu를 설치하는 것에서부터 난관에 부딪혔고, 너무 느리게 동작하여 답답했다. 또, 시간이 부족하여 Linux 사용법에 대해서는 전혀 다루지 않다보니 디렉터리 이동과 같은 쉘 사용 기초를 어려워하는 수강자도 있었다. 그에 반해 한 수강자는 끝까지 Windows에서 개발과 실습을 했는데, 자잘한 문제에 부딪혔지만 그래도 실습 전 과정을 무사히 치러내 다행스러우면서도 다소 허무하기도 했다. 난 Debian 계열 Linux를 좋아하지만, 실제로는 개발은 Mac OS X에서 하고 실 서비스는 Redhat 계열 Linux에서 하다보니 나 조차도 강의 진행에 시행착오를 겪기도 했다.
강의에 대해서도 고민이 많았다. 나는 하용호님의 발표나 강의를 좋아하여 그의 강의 방식을 분석하고 좇으려 했지만, 교재 작성하는 데 시간에 쫓겨서 흉내조차 내지 못 했다. “교재라도 남기자”로 강의 계획을 변경한 요인엔 내 강의 운영이 부족한 탓도 있다.
수강자들마다 프로그래밍 소양이나 수준이 상당히 다르다보니 수강자가 할 만한 질문이나 겪을만한 오류 상황을 최대한 폭넓게 예측하고 준비하는 것도 힘들었다. 그런 강의 준비 과정에서 number format의 구분자 문자열이 뒤집히는 Django의 버그를 발견해 Django 프로젝트에 소스를 제출하는 즐거운 경험을 했지만, 강의 준비 자체는 무척 고되었다. 실제로 준비한 강의 자료 대부분은 수업 시간이 부족하기도 하고 수강자가 질문하지 않아서 활용되지 못하였다.
정리
초반 혼란기를 극복한 60% 수강자는 마지막 수업까지 남아 함께 했다. VirtualBox에 Ubuntu Linux를 설치하면서 Linux를 제대로 접한 한 수강자는 아예 자신의 랩탑에 Linux를 설치하여 다루었고, 굉장히 사소한 오타나 실수로 Python 인터프리터 오류를 일으켜 어쩔 줄 몰라하던 수강자는 내가 힌트를 주는 것만으로도 문제 원인을 파악해 스스로 해결할만큼 성장했다. 놀랍고 기뻤다.
잔존율 50%를 목표로 두었는데 예상보다 높은 잔존율을 달성한 점도, 질문 수준이 나날이 높아진 점도 보람찼다. 질문과 답변이 왕성하게 오간 교육 과정이라는 패스트캠퍼스측 피드백도 기분 좋았다. 누군가 성장하는 데 기여하는 건 정말 뿌듯하다. 그리고, 다른 이가 성장하도록 돕는 과정에서 나 역시 성장하는 것도 좋은 경험이다.
곧 웹 서비스 개발 2기를 시작한다. 1기 때 구축한 수업 자료도 있고 내 강의량을 좀 줄였기에 1기 때에 비해 좀 더 수월하길 바라본다.
아참, 애초 강의의 목적이었던 집필에 대해 조금 언급하자면, 목차 작업을 얼추 마쳐가고 있다. :)
04 Mar 2015
- 개발 생활 - 1 : PDF 문서 생성 서버
- 개발 생활 - 2 : 연산된 데이터 수집 작업자
개발 생활 - 3
: 전자우편 알림 서버
- 개발 생활 - 4 : Python과 Django 강의
- 개발 생활 - 5 : 공부 자료
- 개발 생활 - 6 : 앞으로 계획
2. 개발 프로젝트
2-3. 전자우편 알림 서버
개요
전자우편 알림 서버는 특정 사건(event)이 발생하면 관련된 사람에게 그 사건에 대해 알리는 역할을 한다. 가령, K와 C영업인이 한날이라는 고객을 담당하고 있는데, C담당자가 고객과 영업 관련 일정을 잡을 경우, K담당자에게도 이에 대한 내용을 전자우편으로 안내하는 것이다.
- 요구사항
- 사건이나 상황(event)이 발생하면, 이와 관련된 담당자나 팀에 즉시 전자우편으로 그 내용을 보낸다.
- 비밀번호 찾기 등 고객 홈페이지에서 발생하는 안내나 통지 행위도 처리한다.
- 여러 상황에 처한 고객에게 상황에 맞는 전자우편을 자동으로 보낸다.
- 예) 특정 기간 이상 접속하지 않은 고객에게 서비스 이용 안내와 매물을 추천
- 내가 정한 추가 목표치
- Python 3로 작성한다.
- 프로그래밍 언어나 라이브러리, 프레임워크를 이전(migration)할 가능성을 염두에 두고 구조를 짠다.
- PEP 8을 최대한 지킨다.
- 병렬로 작업(알림 처리)을 수행한다.
- Unittest를 작성한다.
- 오류 내역을 효율성 있게 관리한다.
개발 환경
- 언어 : Python 3.4, Go 1.3
- 사용 라이브러리, 프레임워크
개발 과정
먼저 이름부터 붙였다. Mailer라는 비공식 이름이 통용되었지만 좀 더 일상과 대중에 친숙한 단어인 postman이라 프로젝트 이름을 붙였다. 보이지 않는 곳에서 나대지 않고 조용히 일하는 느낌이 들도록 머릿글자도 소문자로 표기했다.
작동 흐름은 간단했다.
- 알림 전자우편
- 전자우편 발송 요청(request)을 API Server가 받으면 각 요청을 발송 대기함에 쌓음.
- 발송 작업자(worker)가 대기함에 있는 발송 대상을 가지고 와서 전자우편 발송 서비스에 전달.
- 개인 또는 그룹을 대상으로 하는 소식지(newsletter)
- 일정 주기 마다 소식지 주제 별 수신자를 수집(build).
- 예) 가입 후 일정 기간 동안 로그인을 안 한 이용자들에게 서비스 안내 전자우편 발송.
- 수신자 그룹을 전자우편 발송 서비스에 생성.
- 생성한 수신자 그룹으로 전자우편 발송.
SMTP 서버를 직접 구축하진 않기로 했다. 개발팀에서 프로그래머는 개발과 운영, 관리를 수행하고 있는데다 여러 개 제품이 이미 운영되고 있었기 때문에 되도록 관리할 대상을 줄여야 했기 때문이다. 그래서 전자우편 발송 서비스를 이용하기로 하고, 몇 개 업체를 검토한 끝에 Mailjet을 이용하기로 결정했다. API도 잘 만들어져 있고, SMTP를 제공하며, 이미 쓰고 있는 지인이 소개해주어 무료 서비스를 사용해봤는데 기대만큼 만족스러웠다.
또한, 고객 지원도 아주 빠르고 친절했다. 예를 들어, 무료 서비스는 한 달에 발송 가능한 전자우편 개수가 무척 적게 제한되어 있는데, 이 개수를 모르던 나는 몇 백 개째부터는 전자우편이 발송이 안 되고 대기 상태에 머물러있자 이것 저것 설정을 변경하며 방황하고 있었다. 그러자 놀랍게도 몇 십 분 후에 대시보드 페이지에 도움이 필요하느냐는 안내 버튼이 출력되었고, 이 버튼을 눌러 실시간 대화를 나누어 문제를 파악하고 처리할 수 있었다. 그때가 한국 시간으로 14~15시 경이었기에 꽤 놀랐다. 비용도 한 달에 30,000건 발송하는 정도는 한 달에 약 9 USD이면 되는 수준이라 바로 유료 전환했다.
postman 프로젝트는 여러 차례 구조를 세웠다 무너뜨리며 설계를 되풀이했다. 그렇다고 해서 처음부터 크게 구조를 잡은 건 아니고, 1차 버전은 특정 상황(event)이 발생하면 담당자에게 이에 대해 개별 전자우편을 보내어 알리는 기능만 개발하는 범위여서 확장하거나 변경될 여지를 두고 구조를 잡았다.
- 발송부(sender)
- 발송 서버에 연결하는 API
- 일정 시간 마다 발송 대기함에서 대기 작업물 처리자(periodic worker)
- 각 전자우편에 지정된 템플릿으로 전자우편 제목과 본문을 만드는 서식부
- 발송 대기함에 쌓는 기능부
- 수신자 구성부(builder)
Mailjet에서 REST API
로 개별 전자우편을 발송하는 API를 제공했지만, 발송 서버에 연결하는 부분은 SMTP로 처리하였다. 이 방식이 HTTP로 연결하여 요청하는 것보다 좀 더 빨랐다. 쌓인 전자우편을 여러 개 보내야 하는 경우가 생기는데, HTTP/1.x는 매 요청마다 Mailjet 서버에 연결하고 끊기를 되풀이한다. 그에 반해 SMTP는 연결을 유지한다.
Python으로 SMTP는 smtplib
모듈을 이용하여 간단하게 다룬다. 다만, 웹에 있는 대부분 예제는 Python 2용이어서 Python 3.4에서는 문제가 발생하는데, 원인은 패키지나 모듈 배치가 바뀐 탓이다. 다행히 Python 공식 문서에 email: Examples라는 문서에 아주 자세한 예제가 나와있다.
편의점 프로젝트과는 달리 postman 프로젝트는 Celery
를 이용하여 일정 시간마다 지정한 작업이 진행되도록 하였다. Periodic Tasks를 이용했는데, periodic_task
라는 장식자(decorator
)를 사용했다.
from datetime import timedelta
from celery.decorators import periodic_task
notify_staff_settings = {
'notify_staff': {
'run_every': timedelta(minutes=1),
},
}
@periodic_task(**notify_staff_settings)
def notify_staff():
pass
템플릿 엔진인 Jinja2
는 Django의 템플릿 문법과 유사하고, 제안서를 PDF로 만드는 문서 서버를 만들면서 다뤄서 친숙했다. 다만, 템플릿 전체가 아니라 블록 단위로 내용을 가져오려고 하는 부분은 문서에 예제가 자세히 나오지 않아서 조금 애먹었다.
{% block subject %}
[공실] {{ building.name }}에 새로운 공실
{% endblock %}
{% block body %}
{{ building.id }} - {{ product.id }}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
{% endblock %}
이런 템플릿이 있는 경우, 전자우편 제목은 subject
블록에 있는 내용을, 본문은 body
블로에 있는 내용을 사용하는 것이다. 템플릿에서 전자우편 제목과 본문을 만들면 알림 전자우편 제목이나 본문 형식(format)이 바뀌어도 Python 애플리케이션 코드는 변동되지 않으므로 애플리케이션 서버를 재가동하지 않아도 되고, 템플릿이 읽히는 다음 작업 시기에 곧바로 변동 내역이 반영된다는 장점이 있다.
Jinja에서 템플릿을 렌더링하면 템플릿 컨텍스트(변수나 필터 등)가 반영된 최종 결과물 문자열이 반환된다. 나는 이걸 subject
블록 따로, body
블록 따로 다루고 싶었고, 당연히(?) Jinja로 이런 처리가 가능하다.
from jinja2 import (
Environment,
FileSystemLoader,
)
_template_engine = Environment(
loader=FileSystemLoader(settings.TEMPLATE_PATH),
trim_blocks=True,
)
_template = _template_engine.get_template('test.html')
_context = _template.new_context({
'title': 'Hello world',
})
email_subject = ''.join(_template.blocks['subject'](_context))
email_body = ''.join(_template.blocks['body'](_context))
오류 내역은 벼르고 별렀던 Sentry
를 도입하여 관리했다. 그동안은 로그를 쌓거나 출력하여 문제를 추적했는데, 로그가 쌓이면 문제를 추적하기 불편했고, traceback
정보가 제대로 나오지 않아 문제 파악도 힘들었다. Sentry 역시 전자우편 발송 서버와 마찬가지로 실 서버는 Sentry 회사가 제공하는 서비스를 이용하고, 개발 중에는 내 작업 PC에 직접 설치하여 관리했다. 사용법도 간단하다.
from raven import Client as RavenClient
raven_client = RavenClient(SENTRY_APIKEY)
@raven_client.capture_exceptions
def notify_staff():
pass
우여곡절
Mailjet에서 제공하는 REST API
로 개별 전자우편을 발송하는 과정에서 적지않은 시행착오를 겪었다. REST API를 다루려고 Requests
라이브러리를 사용했는데 자꾸 요청이 Mailjet 서버로부터 거절되었다. Curl
을 이용하면 잘 작동하였다.
curl -X POST --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \
https://api.mailjet.com/v3/send/message \
-F from='Miss Mailjet <[email protected]>' \
-F [email protected] \
-F subject='Hello World!' \
-F text='Greetings from Mailjet.'
이는 HTTP에서 Post
방식으로 데이터를 보낼 때 컨텐트 타입을 multipart/form-data
로 보내는 RFC 2388를 따르는 것이며, 이를 Requests 라이브러리를 이용하여 다음과 같이 처리한다.
_res = requests.post(
'https://api.mailjet.com/v3/send/message',
auth=(
'$MJ_APIKEY_PUBLIC',
'$MJ_APIKEY_PRIVATE'
),
data={
'from': 'Miss Mailjet <[email protected]>',
'to': '[email protected]',
'subject': 'Hello World!',
'text': 'Greetings from Mailjet.',
},
)
하지만, 무슨 이유에서인지 Mailjet쪽에서 발송을 거절했다. Postman이라는 HTTP 클라이언트로 보내도 잘 작동했는데, 유독 Requests로는 실패했다. 그래서 HTTP Header를 하나씩 까보니 Requests는 파일을 첨부하면 Curl 등 다른 소프트웨어와는 미묘하게 다른 HTTP Header를 만든다는 걸 발견했고, Mailjet은 이런 요청은 거부하는 민감한 동작을 했다. 어차피 SMTP를 이용해 보낼 계획이어서 SMTP로 직접 발송하여 문제는 해결했지만, 찝찝한 마음이 남았다. 현재는(2015년 3월 기준) 문제없이 발송된다.
Celery를 사용하는 인터페이스 부분을 추상화하는 과정도 뜻대로 되지 않은 부분이 많았다. 언제든지 Celery를 걷어내고 다른 라이브러리를 사용해도 문제가 없도록 패키지와 모듈 구성을 구성하였는데, 문제가 발생했을 때 문제를 추적하기 불편하였고 추상화 한 것에 비해 실제 Celery를 사용하는 부분 인터페이스가 많지 않았다. 결국은 Celery 인터페이스 이름만 바꾼 것에 가까운 반쪽짜리 추상화가 되고 말았다. 어설픈 추상화는 안 하느니만 못 하다.
Python 3를 사용하는 데엔 별다른 시행착오를 겪지 않았다. Python 3 기능을 그다지 깊게 사용하지 않기도 했지만, 지난 프로젝트부터 Python 3를 대비하고 준비한 게 도움이 됐다. 그리고, postman 프로젝트에 사용한 외부 라이브러리도 모두 Python 3를 지원했다.
PEP 8을 도입하는 것도 무난했다. PEP 8 검사에 사용한 Flake8의 Sublime Text용 부가기능(plugin)이 Sublime Text 2에서 잘 작동하지 않아 엉겁결에 Sublime Text 3로 이전한 것 빼고는 별달리 어렵거나 힘든 상황은 맞이하지 않았다. 한 줄을 80열 미만으로 코드를 작성하는 것을 제외하면.
Unittest는 만족스럽게 사용하진 못 했다. 딸 출산이 임박한 시기에 이르자 마음이 급해져 다시 원래 개발하던 방식으로 돌아가고 말았던 것이다. 결국 Unittest를 쓰지 않아 겪는 불편을 개발 막바지에 그대로 다시 겪었다.
Linux에서 프로세스를 관리하는 upstart
용 프로세스 구동 스크립트를 작성하는 데 꽤 고생했다. 그동안 Linux의 init을 쓰거나 Python용 프로세스 관리 도구인 Supervisor를 써왔는데, 서버 프로세스를 관리하는 올바른 방법에 대한 글을 읽고 upstart를 사용하기로 했다. 그런데 AWS AMI에서 돌아가는 upstart는 상당히 오래된 버전이어서 웹에서 참고한 자료가 별 도움이 되지 않았다. 가령, uid
나 gid
, chdir
같은 명령어가 동작하지 않았다. 워낙 간단한 스크립트여서 무작정 시도했는데, 동작하지 않아 결국 로그를 찍으며 문제를 해결했다.
script
exec >/tmp/postman_sender.log 2>&1
exec sudo -u ec2-user /bin/sh -c "/.../postman_run.sh"
end script
정리
postman 프로젝트는 코딩보다는 설계와 추상화, 그리고 외부 도구 연계에 시간을 많이 썼다. 그동안 Celery나 SQLAlchemy 등 도구의 단편만 다뤘는데, 이번 프로젝트를 진행하면서 좀 더 깊게 들여다보고 시험해 보았다.
이번 프로젝트는 여러 모로 무척 바빠서 공부를 많이 하지 못 했다. 이 프로젝트에 사용한 기술의 하부 영역을 더 이해하려고 리눅스 시스템 프로그래밍(C언어)과 Python twisted를 공부하였지만, 다소 지지부진하게 진도가 나갔다.
Go 언어로 작성한 코드는 Go 언어에 좀 더 친숙해진 정도 성과를 거두었다. 워낙 간단한 코드이기 때문이다. 나중에 시간을 내어 발송할 전자우편을 구성(build)하는 부분과 발송 부분을 분리하여 발송 부분을 Go 언어로 재작성하면 좋을 것 같다.