H3 이후 중세 암흑시대와도 같던 사내 개발자 문화 좀 바꿔 보려고 진행한 기술 세미나
월: 2016 12월
생각이라는 벽돌로 만드는 집
소프트웨어에 물들다 행사.
용인 청덕 도서관에서 강연한 발표자료
dependency hell과 빌드지옥 탈출
2015년도 신입사원 교육 자료 2일차
신규 협업도구 사용자 교육
처음 사내에 jira / confluence / stash / bamboo 도입할 때 만든 교육 자료
stash 사용자 교육
DSCM 인 git 의 개념과 branch/merge 전략, fork/pull request 를 설명하는 교육자료
jira-stash 연결 가이드
개요
JIRA 이슈 – stash 리파지토리 revision 간 연결 방법을 가이드한다.
원칙 : 1기능 = 1이슈 = 1브랜치
JIRA 이슈 – stash 리비전 연결 시 사내 방침은
1개의 (기능 or 버그) = 1개의 이슈 = 1개의 (임시)브랜치 입니다.
JIRA – stash 업무 플로우
업무 플로우는 다음과 같습니다.
처음에 연결만 시켜주면 그 뒤로 변경 사항 추적 연결은 JIRA 와 stash 가 알아서 합니다.
1) 이슈 생성
2) 이슈에 해당하는 브랜치 생성
3) 생성한 브랜치에서 해당 기능 개발
4) 생성한 브랜치를 develop 브랜치에 merge
5) develop 브랜치 -> master 브랜치로 merge
이슈 생성
다들 잘 아실테니 패스…
이슈에 해당하는 브랜치 생성
Create branch 를 클릭하면 아래와 같은 화면이 뜹니다.
Repository : 현재 개발중인 repository 선택
Branch type : 이슈 타입에 맞게 bugfix / feature 중 한 가지를 선택
Branch from : master 브랜치가 원칙이나 상황에 따라 develop 브랜치 선택하여도 무방
develop 브랜치를 선택하는 것이 유리한 경우
develop 브랜치가 너무 많이 진행되어 master / develop 간 차이가 너무 벌어진 경우
현재까지 develop 브랜치에서 진행된 내용을 반영하여야 하는 경우
Branch name : 이슈 이름이 영어인 경우 알아서 자동완성되나, 한글인 경우 이슈 키와 동일하게 기재됨. 상황에 맞게 적절히 기재할 것
생성한 브랜치에서 해당기능 개발
아래와 같이 작업하시면 됩니다.
git clone -> 생성한 브랜치로 check out -> 해당 기능 개발 후 commit -> push
JIRA 이슈와 stash 리비전 간 자동 연결이 되는 원리는 다음과 같습니다.
- stash 서버가 커밋 로그에 기재된 이슈 번호를 JIRA 서버에서 찾아냄
- 해당 JIRA 이슈와 커밋 리비전을 연결시킴
헌데, 최초에 Create branch 시 브랜치 이름을 JIRA 이슈 이름으로 자동생성하였기 때문에
pull request -> merge 시 자동으로 생성되는 commit log 로 인하여 JIRA 이슈 – stash 간 연결은
머지 시점에 자동으로 알아서 됩니다.
요약하면, 최초에 연결만 잘 해놓으면 머지 시점에 알아서 JIRA 이슈 – stash 리비전 간 연결이 됩니다.
생성한 브랜치를 develop 브랜치에 merge
Create pull request 클릭
본인이 개발한 브랜치 -> develop 브랜치 로 pull request 생성되는지 확인
pull request 부가정보 기입
Description : 그동안 달아뒀던 commit log 내용으로 알아서 자동생성되나, 맘에 안들면 수정해도 무방함
Reviewers : 생성된 pull request 의 코드 리뷰어를 기입. 리뷰어가 해당 pull request 를 보면서 온라인 코드리뷰를 진행할 수 있습니다.
코드리뷰
생성된 pull request 에서 Diff 탭 선택 -> 코드 변경 라인으로 커서 이동 -> 말풍선 클릭 -> 리뷰 내용으로 댓글을 달 수 있습니다.
pull request 한 코드에 문제가 없으면 merge / 마음에 들지 않으면 decline 을 선택할 수 있습니다.
merge 후 임시 브랜치 삭제
feature / bugfix 등의 브랜치는 해당 기능을 구현하기 위한 임시 브랜치입니다.
개발이 완료되어 머지하는 시점에는 삭제하는 것이 원칙입니다.
아래와 같이 체크박스를 선택하면 stash 가 알아서 삭제해 줍니다.
develop 브랜치를 master 브랜치에 merge
develop 브랜치는 delete 하지 않습니다. 이 외에는 임시브랜치 머지하는 것과 절차가 동일합니다.
node.js 로 front 개발하기
전에 어느 신입 사원의 “node.js 로 front 개발하겠습니다.” 라는 발언이 인구에 회자되었던 적이 있다.
나또한 javascript 알못인지라 node.js 는 서버사이드 플랫폼이기에 신입 사원이 뭔가 잘못 알고 한 말이리라 생각했었다.
헌데 요즘 javascript 클라이언트 개발 환경을 좀 디벼보니 정말 node.js 로 프론트를 개발한다.
정확히 말하자면, node 로 개발된 library를 배포 시 재패키징하는 방식으로 프론트 개발 환경에서 node.js 인프라를 그대로 사용하도록 돕는다.
module bundler(e.g. webpack, browserify …) 가 이런 일이 가능하도록 해주는 녀석들인데,
webpack 의 경우 부가적으로 js 외에도 css / png / coffee script 등등등 다양한 웹 리소스를 한번에 패키징 할 수 있는 기능을 제공하기도 한다.
암튼, 신입사원의 “node.js 로 front 개발하겠습니다.” 발언은 전혀 틀린 말이 아니며, 심지어 최신의 js 개발 트렌드를 반영한 것으로 판명 되었다.
앞으로 front 개발은 node.js 로 해라.
webpack
test pyramid
출처 : http://martinfowler.com/bliki/TestPyramid.html
이 한장의 그림이 모든걸 설명한다. unit test 를 하는 이유는 비용과 효율성 이라는 현실적인 이유 때문이다.
unit test 를 생략하고 서비스 또는 UI 에 대한 테스트를 진행하는 이유가 “시간이 없어서” 라면
진지하게, 그리고 솔직하게 가슴에 손을 얹고 양심에 물어봐야 한다.
“사실 나는 테스트를 하고 싶지 않은게 아닌가?”
windows 환경에서 python/oracle 연결
Prerequisite
윈도우 환경에서 python / oracle 연결하려면 사전에 준비할 것들이 좀 된다.
- python 버전에 맞는 VC Common tools
- Oracle Instant Client Package SDK
Python version 에 맞는 VC++ 및 VC Common tools 설치
python 버전에 따라 vc++ 버전이 다르다.
아래 표를 보고 해당하는 버전의
1) VC++ 재배포가능 패키지(https://www.microsoft.com/ko-kr/download/details.aspx?id=48145) 또는
2) Visual Studio(https://www.visualstudio.com/ko-kr/downloads/download-visual-studio-vs.aspx)
를 설치한다.
VC++ version
|
Python version
|
---|---|
14 | 3.5 |
10 | 3.3, 3.4 |
9 | 2.6, 2.7, 3.0, 3.1, 3.2 |
프로그램 언어 -> Visual C++ -> Visual C++ Common Tools 를 설치한다.
Oracle Instant Client Package SDK
Instant Client Package – SDK(http://www.oracle.com/technetwork/topics/winx64soft-089540.html) 를 다운로드한다.
다운로드한 sdk의 경로를 시스템 PATH 환경변수에 등록한다.
oracle python interface 설치
상기 tool 들을 설치하면 cx_Oracle 패키지가 설치된다.
pip install cx_Oracle |
Locale 맞추기
한글을 정상적으로 입출력 하려면 system locale과 db locale 을 일치시켜야 한다.
locale 일치는 반드시 db connection 이전에 완료되어야 한다.
1) 현행 DB locale 확인
cur.execute( "select userenv('LANGUAGE') from dual" ) |
2) system locale 환경변수 등록
os.putenv( 'NLS_LANG' , 'AMERICAN_AMERICA.AL32UTF8' ) |
rabbitmq reference
prerequisite
rabbitmq 설치 : https://www.rabbitmq.com/download.html
python 설치(다른 언어로 대체 가능하나, 예제는 python3.5임)
rabbitmqctl 사용법
ack 확인되지 않은 msg list
rabbitmqctl list_queues name messages_ready messages_unacknowledged |
모든 queue 정보 삭제(=reset)
rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl start_app |
매번 queue 삭제/리스타트하기 귀찮아서 python으로 짰다.
본인 환경에 맞도록 적당히 수정해서 쓰면 될 듯
pp = pprint.PrettyPrinter(indent = 2 , compact = True ) result = [] mqctlExec = '"C:\\Program Files\\RabbitMQ Server\\rabbitmq_server-3.6.3\sbin\\rabbitmqctl" {param}' spExec = """for line in sp.Popen(mqctlExec.format(param='{param}'), shell=True, stdout=sp.PIPE, stderr=sp.PIPE).stdout: result.append(line)""" exec (spExec. format (param = 'stop_app' )) exec (spExec. format (param = 'reset' )) exec (spExec. format (param = 'start_app' )) pp.pprint(result) |
basic queue
기본적으로 mq로 msg 전송/수신하기 위한 최소 설정
1) queue
2) channel
3) message body
import pika def main(): con = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = con.channel() channel.queue_declare(queue = 'emc' ) cnt = 0 while ( True ): a = input () cnt + = 1 msg = "no {number} Hello Basic Sender" . format (number = cnt) channel.basic_publish(exchange = ' ', routing_key=' emc', body = msg) print ( str (cnt) + " send success >>" + msg) con.close() if __name__ = = "__main__" : main() |
import pika def callback(ch, method, properties, body): print (body) ch.basic_ack(delivery_tag = method.delivery_tag) def main(): con = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = con.channel() channel.queue_declare(queue = 'emc' ) channel.basic_consume(callback, queue = 'emc' , no_ack = True ) channel.start_consuming() if __name__ = = "__main__" : main() |
문제점
메세지 유실
- sender 영역 : rabbitmq 서비스가 재시작되면 메세지가 유실될 수 있다.
- receiver 영역 : receiver가 받을 준비가 되어 있지 않으면 메세지가 유실될 수 있다.
durable queue
다음과 같이 message를 유실하지 않기 위한 안전장치를 마련한다.
- queue(rabbitmq 서비스) : queue 를 durable = True 로 설정
- message(sender 설정) : delivery mode를 persistence 로 설정
- ack(receiver 설정) : message를 수신하였을 때, ack 를 송신하도록 설정
durable queue 주의점
만일 ack를 송신하지 않으면 message가 삭제되지 않으므로 주의한다.
durable = True 일 때 message가 삭제되지 않을 조건
queue의 durability 를 True로 설정하면 message 의 생명 주기는 해당 message의 delivery mode에 의하여 결정된다.
- delivery mode 1 (=non persistent) : 서비스가 종료되면 message는 삭제된다.
- delivery mode 2 (=persistent) : 서비스가 종료되더라도 삭제되지 않는다.
import pika def main(): con = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = con.channel() channel.queue_declare(queue = 'emc' , durable = True ) cnt = 0 while ( True ): a = input () cnt + = 1 msg = "no {number} Hello Basic Sender" . format (number = cnt) channel.basic_publish(exchange = ' ', routing_key=' emc', body = msg, properties = pika.BasicProperties(delivery_mode = 2 )) print ( str (cnt) + " send success >>" + msg) con.close() if __name__ = = "__main__" : main() |
import pika def callback(ch, method, properties, body): print (body) ch.basic_ack(delivery_tag = method.delivery_tag) def main(): con = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = con.channel() channel.queue_declare(queue = 'emc' , durable = True ) channel.basic_consume(callback, queue = 'emc' ) channel.start_consuming() if __name__ = = "__main__" : main() |
load balancing
아래 그림과 같이 2대의 receiver가 message를 받아 처리하는 구조를 가정해 보자.
rabbitmq의 기본 balancing 전략은 round-robin 이다.
만일 receiver 중 1대가 매우 바쁘면, 해당 receiver에 전달되어야 할 message는 제때 처리되지 못한 채 queue에 적체될 것이다.
import pika def busyFunction(): while ( True ): a = input () print ( 'i am busy. very busy.' ) def callback(ch, method, properties, body): print (body) busyFunction() ch.basic_ack(delivery_tag = method.delivery_tag) def main(): con = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = con.channel() channel.queue_declare(queue = 'emc' , durable = True ) channel.basic_consume(callback, queue = 'emc' ) channel.start_consuming() if __name__ = = "__main__" : main() |
//durableSender .py 로 message 를 10 개 발송 1 send success >>no 1 Hello Basic Sender 2 send success >>no 2 Hello Basic Sender 3 send success >>no 3 Hello Basic Sender 4 send success >>no 4 Hello Basic Sender 5 send success >>no 5 Hello Basic Sender 6 send success >>no 6 Hello Basic Sender 7 send success >>no 7 Hello Basic Sender 8 send success >>no 8 Hello Basic Sender 9 send success >>no 9 Hello Basic Sender 10 send success >>no 10 Hello Basic Sender //durableReceiver .py 는 1,3,5,7,9 번째 메세지를 전달받는다. b 'no 1 Hello Basic Sender' b 'no 3 Hello Basic Sender' b 'no 5 Hello Basic Sender' b 'no 7 Hello Basic Sender' b 'no 9 Hello Basic Sender' ...busyReceiver 가 오랜시간(default 1분)동안 heartbeat을 주지 못해 connection closed 되면 unacked message 인 2,4,6,8,10 번째 메세지를 전달 받는다. b 'no 2 Hello Basic Sender' b 'no 4 Hello Basic Sender' b 'no 6 Hello Basic Sender' b 'no 8 Hello Basic Sender' b 'no 10 Hello Basic Sender' //busyReceiver .py 는 바빠서 2번째 메세지를 받은 뒤 ack 를 보내지 못했다. b 'no 2 Hello Basic Sender' i am busy. very busy. |
qos 의 prefetch_count 를 설정하면 ack를 받지 못할 경우, 해당 receiver 로 message 를 발송하지 않는다.
import pika def busyFunction(): while ( True ): a = input () print ( 'i am busy. very busy.' ) def callback(ch, method, properties, body): print (body) busyFunction() ch.basic_ack(delivery_tag = method.delivery_tag) def main(): con = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = con.channel() channel.queue_declare(queue = 'emc' , durable = True ) channel.basic_qos(prefetch_count = 1 ) channel.basic_consume(callback, queue = 'emc' ) channel.start_consuming() if __name__ = = "__main__" : main() |
//durableSender .py 로 message 를 10 개 발송 1 send success >>no 1 Hello Basic Sender 2 send success >>no 2 Hello Basic Sender 3 send success >>no 3 Hello Basic Sender 4 send success >>no 4 Hello Basic Sender 5 send success >>no 5 Hello Basic Sender 6 send success >>no 6 Hello Basic Sender 7 send success >>no 7 Hello Basic Sender 8 send success >>no 8 Hello Basic Sender 9 send success >>no 9 Hello Basic Sender 10 send success >>no 10 Hello Basic Sender //balancedReceiver .py 는 바빠서 1번째 메세지를 받은 뒤 ack 를 보내지 못했다. //prefetch-count 를 1로 설정하였기 때문에, ack를 1번 받지 못한 balancedReceiver에게는 다시 message를 전달하지 않는다. b 'no 1 Hello Basic Sender' i am busy. very busy. //durableReceiver .py 는 2,3,4,5,6,7,8,9 번째 메세지를 전달받는다. b 'no 2 Hello Basic Sender' b 'no 3 Hello Basic Sender' b 'no 4 Hello Basic Sender' b 'no 5 Hello Basic Sender' b 'no 6 Hello Basic Sender' b 'no 7 Hello Basic Sender' b 'no 8 Hello Basic Sender' b 'no 9 Hello Basic Sender' b 'no 10 Hello Basic Sender' ...busyReceiver 가 오랜시간(default 1분)동안 heartbeat을 주지 못해 connection closed 되면 unacked message 인 1 번째 메세지를 전달 받는다. b 'no 1 Hello Basic Sender' |