kth 개발자 세미나 1회

H3 이후 중세 암흑시대와도 같던 사내 개발자 문화 좀 바꿔 보려고 진행한 기술 세미나

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 연결하려면 사전에 준비할 것들이 좀 된다.

  1. python 버전에 맞는 VC Common tools
  2. 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 확인

locale
cur.execute("select userenv('LANGUAGE') from dual")

2) system locale 환경변수 등록

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

basicSender
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()
basicReceiver
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) : 서비스가 종료되더라도 삭제되지 않는다.
durableSender.py
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()
durableReceiver.py
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에 적체될 것이다.

 

busyReceiver.py
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 를 발송하지 않는다.

busyReceiver.py
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'