12/2/2018

WEB Framework Check Template

글에서 말하는프레임웍은 REST API 중심의프레임웍을 말한다. 해당 글의 작성 목적은프레임웍을 선택 혹은 구축하는데 있어 고려해볼사항들에 대해서 포괄적으로 언급한다. 파이썬 진형의 대표적인프레임웍 장고에서 많은 개념을 참고했다.

주요 고려 항목

  • Authentication
    • OAuth2, JWT
  • Explicit Project Structure
  • Architecture
    • Design Pattern. MVC Pattern
    • 장고의 경우 View, Model, Serializer 와 같은모듈의 명확한 역할 정의. 균일한 데이터 흐름을 갖게 하는게 매우 중요.
  • Middleware
    • Throttling
    • 필요하다면 Device 분기
    • Logging
    • 현재 서버 환경별 Request, Response Handling
      • Beta 환경인 경우. 토큰발행등으로 Beta 서버 추가 인증.
  • Dynamic and Elastic Settings Load. e.g) local, development, alpha, beta, production …
  • DDD or Adaptable MSA
    • 장고의 경우 앱을 Service 중심으로 분류. 서비스간 단일 인터페이스를 통해 의존관계 설립.
  • REST API URL일반화 가능여부.
    • 장고의 경우 function view => class view
      • class view경우 API 별 permission, authentication, throttling 관리가 매우 편해짐.
  • Exception Handling Centralization
  • Test Environment
    • Test Framework
  • Cache Backend & Engine
    • Redis, Memcached …
  • Storage Engines
    • MySQL, MSSQL, PostgreSQL …
  • ORM Engine
  • API Versioning
    • /v1/
  • API Documentation
    • Swagger
  • Web 혹은 Admin 필요시 Template Engine
  • Runtime Debugger
    • django-debbug-toolbar
  • Serialization

ON DEMAND

  • Asynchronous task queue
    • Celery
    • Email, SMS 발송 같은 서비스
  • Support DATA Team
    • DATA HOOK
  • Log Centralization
Web Django
7/2/2018

[Django] contenttypes framework

부제: django.contrib.contenttypes 은 무엇인가?

장고 프로젝트를 생성하고, settings.py 파일을 보면 기본값 설정들이 되어있고, INSTALLED_APPS 하위에 이미 어떤 장고 어플리케이션들이 선언되어있다. 해당 어플리케이션인들이 프로젝트에 판단하기 위해 역할을 알아볼 것이다. 이 포스트에서는 django.contrib.contenttypes대해 살펴볼것이다.

django.contrib.contenttypes

네이밍이 contenttypes 이여서 HTTP Header의 속성 Content-Type 에 관련된 앱인줄 알았다. 하지만 설명을 읽어보니 전혀 상관없었다.

Django includes a contenttypes application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.

설명을 보아하니, 장고 프로젝트안에서 선언된 모델을 추적하며, 높은수준의 사용하기 편한 인터페이스를 제공한단다. 목적은 파악되었다. 아직 필요한지 제거해도 되는지 판단이 서지 않아서 조금알아보겠다.

It’s generally a good idea to have the contenttypes framework installed; several of Django’s other bundled applications require it:

The admin application uses it to log the history of each object added or changed through the admin interface. Django’s authentication framework uses it to tie user permissions to specific models.

몇몇 장고의 번들 어플리케이션을 사용할때 contenttypes framework필요하단다… (몇몇!!!??? 말이여 방구여 그냥 쓰라는 의미로 받아들이자.) 장고 인증 프레임웍과 어드민에 의존 어플리케이션이란다. 더 이상 읽을 필요가 없다. 사용하기로 결정!!

PS. 장고에서 코드를 작성할때, 모델 사용을 참조 형식이 아닌, 문자열 형식으로 선언하고 런타임 환경에서 Lazy Loading 형식으로 구현하는 경우가 많다. 추축이지만 아마 contenttypes framework 때문에 가능한것 같다. 코드를 까서 살펴보면 되지만, 시간이 없다. 조사는 여기까지 그만 짜이찌엔!!

Django Python
31/1/2018

이벤트 기반의 이해

마이크로서비스 아키텍처 구축살펴보던이벤트 기반(Event Driven)대해서 언급이 나왔다. 읽기만 해서는 이해가 되지 않아 코드를 작성해보았다. 물론 아직도 이해가안된다.

오케스트레이션(Orchestration)

중앙에서 이벤트를 모두 관리처리

class Customrer():
    def __init__(self, name, address, email):
        self.name = name
        self.address = address
        self.email = email


class CustomerService():

    def signup(self, customrer):
        # 계좌 개설
        BankService().create_account(customrer)

        # 환영 메일 발송
        MailService().send(customrer.email)

        # 가입 패키지 발송
        PostService().send(customrer.address)


class BankService():
    def create_account(self, customrer):
        print("Service Name: %s, Customrer Name: %s" %(self.__class__.__name__, customrer.name))


class MailService():
    def send(self, to):
        print("Service Name: %s, To: %s" % (self.__class__.__name__, to))


class PostService():
    def send(self, to):
        print("Service Name: %s, Address: %s" % (self.__class__.__name__, to))


if __name__ == '__main__':
    customer = Customrer(name='SELO', address='Seoul, Korea', email='rochan87@gmail.com')
    CustomerService().signup(customer)

코레오그래피(Choreography)

발행/구독 패턴을 통한 느슨한 결합

class Customrer():
    def __init__(self, name, address, email):
        self.name = name
        self.address = address
        self.email = email

EventBook = {
    # example
    'main_event': {
        'events': ['dependent_event', ],
    }
}


def register_event(pub_event_name, sub_event):
    try:
        pub_event = EventBook[pub_event]
        pub_event.append(sub_event)
    except KeyError:
        EventBook[pub_event] = [sub_event]


def pub(event_name):
    def add(func):
        def __wrapper(*args, **kwargs):
            _r = func(*args, **kwargs)
            sub_events = EventBook[event_name]
            for sub_event in sub_events:
                sub_event(*args, **kwargs)
            return _r
        
        if event_name not in EventBook:
            EventBook[event_name] = []

        return __wrapper
    return add


def sub(event_name):
    def register(func):
        def __wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        if event_name not in EventBook:
            EventBook[event_name] = []

        EventBook[event_name].append(__wrapper)
        return __wrapper
    return register


SIGNUP = 'signup'
UPDATE = 'update'


class CustomerService:
    @pub(SIGNUP)
    def signup(self, customrer):
        print("SignUp\n")

    @pub(UPDATE)
    def update(self, customrer):
        print("Update\n")


class BankService:
    @sub(SIGNUP)
    def create_account(self, customrer):
        print("Service Name: %s, Customrer Name: %s" %(self.__class__.__name__, customrer.name))


class MailService:

    # 아래와 같이 구독하고 싶은 이벤트만 추가해서 원하는 동작을 수행한다.
    @sub(SIGNUP)
    @sub(UPDATE)
    def send(self, customrer):
        print("Service Name: %s, To: %s" % (self.__class__.__name__, customrer.email))


class PostService:
    @sub(SIGNUP)
    def send(self, customrer):
        print("Service Name: %s, Address: %s" % (self.__class__.__name__, customrer.address))



if __name__ == '__main__':
    customer = Customrer(name='SELO', address='Seoul, Korea', email='rochan87@gmail.com')
    CustomerService().signup(customer)
    CustomerService().update(customer)
MSA MicroserviceArchitecture EventDriven
25/1/2018

Dynamically import django settings for multiple environment such as local, dev, beta, production

장고 개발을 하다보면환경을 구분하고, 이에 따라 변경되야 하는 부분들이 존재한다. 예를들면 임포트 되어야하는 미들웨어 혹은 장고 어플리케이션들, 데이터베이스 연결정보 등이 달라진다. 이럴때환경에 따라 코드를 수정하는 작업은 해서는 안되는 작업이며, 언젠가 한번쯤 실수를 할것이고, 비명을 지르게될것이다. 꺄야양야야야야야 ㅅㅂ

어떻게 환경을 인식을 할 것인가?”

어플리케이션이 작동하는 환경을 나누어 보자. 아래처럼 환경을 구분하는 것이 정답은 아니며, 개발프로세스나 회사 업무에 따라 다를것이다.

  • local: 개발자 개인PC
  • development: 사내 네트워크 망에서 공유되는 서버를 통해 작동하는 어플리케이션 환경
  • staing: 외부 네트워크 망에서 작동하는 서버에서 제공되는 어플리케이션 환경. 릴리즈1차 외부 테스트 환경.
  • beta: staging동일하지만 production최대한 일치되는 환경으로 production 릴리즈 이전에 최종테스트가 일어나는 환경.
  • production: 어플리케이션이 실제 서비스 되는 환경.

위와 같이 다양한 환경에서 동일한 코드베이스의 장고 어플리케이션이 동작하게 되며, 각 환경에서 로드되어야하는 모듈이나 설정정보들이 다를것이다. 이렇게 다른점을 적용하기 위해서는 현재 어플리케이션이 작동하는 환경이 production 인지 local 인지 인식을 해야한다.

현재 작동하는 환경을 인식하기 위한 방법은 다양하지만, 필자가 가장 선호하는 방법은 환경변수 셋팅이다.

export SELO_ENV=local

TIP) 개인 개발PC .bash_profile같은부트스트래핑 파일에 구문을 추가해 놓으면 편하다.

import os
CURRENT_APP_ENV = os.getenv('SELO_ENV')

환경별 설정 모듈화디렉토리 구조잡기

유연하며, 명시적인 구조를 만들어보자. Here we go. Let’s get it. Put your hands up!!!”

시작이 반: 장고 프로젝트 초기 구조

##### 장고의 초기 디렉토리 구조 #####
$ tree
.
|____bar
| |______init__.py
| |____settings.py
| |____urls.py
| |____wsgi.py
|____manage.py

버전 1.11 기준 장고의 프로젝트를 처음 생성하면 위와 같은 디렉토리 구조로 프로젝트가 생성된다. 아주 심플하다. 구조화 없이 프로젝트가 진행되다보면 알아보기 무서운 괴물“이 될수있다.

결과물: 아주 이쁘게 모듈화된 최종 구조

먼저 우리가 갖게최종 구조를 훓어보고그림을 그려보자. 최종구조는 다음과 같다.

$ tree
.
|____meta
| |______init__.py
| |____unittest.py
| |____ckeditor.py
| |____ ...
|______init__.py
|____default.py
|____development.py
|____staging.py
|____beta.py
|____prod.py

# 한눈에 보기 편하도록 출력결과물의 순서를 조정했다.

설명이 필요없는 한눈에 이해되는 명확한 구조가 아닌가!! 아름답다!! 나만 그런가…?!!”

최종구조에서 각 모듈(파일)의 역활을 정리해보자.

  • settings/default.py: 환경에 상관없는 공통적인 설정을 작성한다.
    • 기본 앱. e.g) 'django.contrib.admin', 'django.contrib.auth'
    • 공통 미들웨어
    • TIMEZONE서비스에서 통용되는 상수
  • settings/<env_name>.py: prod.py같이환경에 의존적인 설정을 작성한다. <env_name>.py.
    • 개발환경에서의 디버깅 툴. e.g) Django Debug Toolbar
    • 데이터베이스 커넥션 정보
    • 환경별 미들웨어. e.g) 베타유저 인증 미들웨어
    • TOKEN같은 환경별 상수
    • 모니터링 툴. e.g) NewRelic
  • settings/meta/<*.py>: meta 폴더 하위에는 특정 라이브러리의 설정이나 특별한 실행 환경에서 실행되는 설정에 대한 부분을 작성한다.
    • $ python manage.py test 처럼 테스트 실행과 같은 특별한 환경에서 필요한 설정 혹은 라이브러리 monkey patch같은 작업을 진행한다.
    • 많은 라인(15줄 이상)의 설정이 필요한 라이브러리(django-ckeditor 등). settings/meta/ckeditor.py
    • default.py읽기 힘들정도로 길어질모듈로 분리해야하는 경우

Import: 즐거운 코딩하는 시간

manage.py
...
# setdefault 함수의 2번째 인자 부분을 알맞게 변경해주자
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "foo.settings.default")
...
foo/settings/default.py

환경변수를 읽어, 셋팅을 동적으로 로드하자.

import os


try:
    SELO_ENV = os.environ['SELO_ENV']
except KeyError:
    print "SELO_ENV must be set to OS Variable."
    exit(1)
    
if SELO_ENV == 'development':
    from foo.settings.development import *
elif SELO_ENV == 'staging':
    from foo.settings.staging import *
elif SELO_ENV == 'beta':
    from foo.settings.beta import *
elif SELO_ENV == 'prod':
    from foo.settings.beta import *
else:
    print "Given Value is %s. It's invalid environment name." % SELO_ENV
    exit(1)

코드가 드럽다. 안이쁘다. 구려보인다. 이쁘게 바꿔보자. 코드는 역시 외모지상주의!!

try:
    SELO_ENV = os.environ['SELO_ENV']
except KeyError:
    print "SELO_ENV must be set to OS Variable."
    exit(1)
    
    
def import_module_asterisk(module_name):
    module = __import__(module_name, globals=globals(), fromlist=['*'])
    for v in dir(module):
        if v in {'__name__', '__builtins__', '__doc__', '__file__', '__package__'}:
            continue
        globals()[v] = getattr(module, v)


try:
    import_module_asterisk(SELO_ENV)
except ImportError:
    print "Given Value is %s. It's invalid environment name." % SELO_ENV
    exit(1)
else:
    print("Successfully Loaded %s settings." % SELO_ENV)

코드를 간단히 설명하겠다. __import__빌트인 함수이며, 동적으로 모듈을 임포트할있게해준다. 즉, 오직 실행환경에서만 임포트 대상을 알 수 있는 경우 사용한다. 코드로 풀어서 설명하자면, __import__('development', fromlist=['*'])from development import *동등한 기능을 수행한다. 결론적으로 development.py 모듈의 네임스페이스를 통째로 임포트한다. 설명은 파이썬2 기준이며, 파이썬3에서는 사용해보지 않았다.

정리

필자가 참여한 장고 프로젝트에는 신규 프로젝트도 있었고, 몇 년동안 개발 운영 되고 있는 서비스도 있었다. 프로젝트에서 이러한 환경에 따른 설정은 매우 치명적인 부분임에도 불구하고 기능개발에 집중하다보면 간과되는 경우가 많다. 문제가 발생하지 않으면 다행이지만최근에 오픈한 모바일게임 듀랑고 클라이언트에서 서버 로그가 출력되는 안타까운 문제가 발생했고, 논란의 중심에 있다. 듀랑고 사태의 정확한 맥락은 알수 없지만 기본을 간과하는 순간 문제가 발생하게 되는것 같다.

같은 맥락으로 이어서 말하자면 코드를 공유하는 개발자들 사이에서 설정을 변경푸쉬하는 경우로 문제가 발생했던 적이 몇 번 있었다. 특히, 디비커넥션 정보, 디버거 임포트 등과 같이 개발중인 기능에 의존적인 설정들이 예가것이다. 위 방식과 구조를 취한다면 이러한 문제들을 해결할있을 것이다.

PS. 언젠가 정리해야지 막연히 생각하던걸 새로운 회사에서 또 한번(ㅠ?) 장고프로젝트를 진행하게 됬고, 이 참에 정리했다개운하구만!! 그럼 이만 뿅!!

오타나 잘못된 부분에 대한 언급은 언제나 감사드립니다.

Reference

python django pythonic
22/1/2018

Python 함수 내부에서의 Null Validation 혹은 검증 로직에 대한 생각

def handle(data=None):
    """ 
   :param data: user's information, the type is dict
   :return: user's info added connection history
   """    
   if data is None:
        return {}
   ...
   return data

위와 같이 handle 함수에서 None 확인은 적절한 것일까요?

결론 부터 말하자면 NO 입니다. 함수내부에서 파라미터검사를 하는것이 틀린 방법은 아닙니다. 하지만 부득이한 경우가 아니라면 함수에 정상적인 값이 들어온다는 전제하에 함수를 작성하는 것이 효율적입니다. 위와 같은 코드의 단점은 무엇일까요?

  1. 함수 호출시 항상 None 체크 로직을 통과하게 된다.
    • 함수에는 일반적으로 대부분 정상적인 데이터가 들어오고, 아주 드물게 잘못된 데이터가 전달될것으로 예상됩니다. 그렇다면 매개변수의 검증은 호출하는 쪽에서 다루는게 맞을것입니다. 만약 항상 매개변수에 대한 검증이 필요하다면, 함수에 포함시키는 것보다는 decorator분리하거나, 검증 함수를 두는 것이 좋은 방법이 될수있습니다.
  2. 함수의 목적이 희석되다.
    • 일반적으로 함수가 단일 목적을 갖고 작성되면, 수많은 장점을 얻게 됩니다.
      1. 함수가 수행하려는 목적에 맞는 코드만 존재함으로서 처리 로직에 집중할있게됩니다.
      2. 가독성 향상. 무엇보다 코드가 날씬하고 이뻐집니다. 데헷. (파이썬 코드는 언제나 이뻐야한다!!!)
  3. 검증 로직의 추가가 필요하다면?
    • 해당 함수를 호출하는 곳이 많아지고, 매개변수 종류가 다양해 짐에 따라 검증로직을 계속 추가할수 없습니다.
    • 결국 특정 매개변수의 검증은 호출하는 곳에서 이루어 지는것이 좋은 경우도 있습니다.

일반적으로 말하는 함수에서의 Null Validation같은 검증 로직에 대한 생각을 간단하게 정리해 보았습니다. 위 내용이 정답은 아니지만 코드 작성시 자주 접하는 고민 사항임으로 같이 생각해보고 싶어서 글을 쓰게되었습니다. 읽어주셔서 감사합니다. 피드백은 언제나 환영합니다^^.

python pep pythonic convention
21/1/2018

50줄로 만들어 보는 tiny 블록체인

Blockchain

비트코인이나 암호화폐의 트랜잭션을 공개적이며 순차적으로 기록하는 디지털 분산 원장 입니다. - Google Search

보다 일반적으로 설명하자면, 블록체인은 새로운 데이터가 블록이라는 컨테이너에 저장되고, 이전 블록과 체인으로 연결된 변경불가능한 공용 데이터베이스입니다. 비트코인이나 다른 암호화폐에서 이러한 데이터는 트랜젝션(거래)의 묶음 이지만, 다른 타입의 데이터가수도 있습니다.

블록체인 기술은 비트코인 그리고 라이트코인과 같이 중앙기관에서 통제되지 않는 디지털 화폐를 탄생시켰습니다. 블록체인은 오늘날 금융시스템이 사기 또는 실폐의 대상이라고 믿는 개인에게 새로운 자유를 가져다 줍니다. 또한, 블록체인은 스마트 계약과 같은 흥미로운 개념을 도입한 이더리움과 같은 분산 컴퓨팅 기술을 혁신시켰습니다.

먼저 블록이 무엇인지 정의해봅시다. 블록체인에서블록은 타임스탬프인덱스를 저장합니다. 각 블록은 무결성을 위한 자체 해시를 갖습니다. 비트코인과 같이블록의 해시는 블록의 인덱스, 타임 스탬프, 데이터이전 블록 해시를 암호화 하는 해시입니다.

import hashlib


class Block:
    def __init__(self, idx, ts, data, pre_hash):
        self.idx = idx
        self.ts = ts
        self.data = data
        self.pre_hash = pre_has
        self.hash = self.hash_block()

    def hash_block(self):
        sha256 = hashlib.sha256():
        sha256.update(
            str(self.idx) +
            str(self.ts) +
            str(self.data) +
            str(self.pre_hash)
        )
        return sha256.hexdigest()

멋지네요! 블록체인 자료구조를 만들었고, 블록체인만들것이며, 실제 체인에 블록을 추가해야 합니다. 앞에서 말했듯이, 각 블록체인은 이전 블록정보가 필요합니다. 그런데

번째 블록에는 어떻게 이전 블록의 정보를 담을 수 있을까요?

첫번째 블록은 특별하며, 이는 Genesis Block 라고 부릅니다. 대부분 Genesis Block수동으로 추가되거나, 추가되게 하는 고유한 로직을 갖습니다.

우리는 Genesis Block반환하는 함수를 만들 것이며, Genesis Block인덱스 0, 임의의 데이터, 임의의 이전 블럭 해시 값을 갖습니다.

from datetime import datetime


def create_genesis_block():
    return Block(0, datetime.utcnow(), "Genesis Block", "0")

이제 우리는 Genesis Block만들있게 되었으므로, 후속 블록들을 생성하는 함수가 필요합니다. 이 함수는 블록체인의 이전 블록을 매개변수로 받고, 생성될 블록에 필요한 데이터를 만든알맞는 데이터와 함께 블록을 반환합니다. 새로운 블록이 이전 블록 정보를 해시할때, 새로운 블록과 블록체인의 무결성증가합니다. 이러한 작업이 없다면, 외부자가 과거의 정보를 바꾸거나 그들 소유의 새로운 정보로 이전의 블록체인을 위변하기 쉬워 집니다. 이러한 해시 체인은 암호화 증명을 수행하고, 블록체인에 블록이 추가될때 블록이 교체되거나 제거 되는 것을 보증합니다.

from datetime import datetime


def next_block(pre_block):
    idx = pre_block.idx + 1
    ts = datetime.utcnow()
    data = "Hey! I'm %d block " % idx
    hash_ = pre_block.hash
    return block(idx, ts, data, hash_)

이제 우리의 블록체인을 만들수 있습니다. 여기서 블록체인은 파이썬 리스트 입니다. 리스트의 첫번째 요소는 Genesis Block 이며, 후속 블록 추가가 필요합니다. for loop사용하여 20개의 새로운 블록을 추가해 봅시다.

blockchain = [create_genesis_block()]
pre_block = blockchain[0]


num_of_blocks_to_add = 20


for _ in range(0, num_of_blocks_to_add):
    block_to_add = create_next_block(pre_block)
    blockchain.append(block_to_add)
    pre_block = block_to_add

    print("Block #{} has been added to the blockchain!".format(block_to_add.idx))
    print("Hash: {}\n".format(block_to_add.hash))

우리가 지금까지 만들 것을 테스트봅시다.

$ python blockchain.py
Block #1 has been added to the blockchain!
Hash: efb894d339ab1e272641676758e1a5b5cbc5b82fd3c069c98a7d7338f8aa5944

Block #2 has been added to the blockchain!
Hash: 1d49888f46aafeaeb56738f115b0572592089316895736f43e254c7906bf6a50

Block #3 has been added to the blockchain!
Hash: b20b257dbd350c2097ebcfca70e7890c6552b79857e6306b3d549576d833e2fa

...(중략)

Block #19 has been added to the blockchain!
Hash: 11643f989f71121cdba4a9cc180890da75bd875505f736119cf6a32e81a9dba2

Block #20 has been added to the blockchain!
Hash: 43380ce50d045f4f30d126ff3a50ee3b1d4d039c7c55c4c9305ea165c48392fa

블록체인이 작동합니다! 더 많은 정보를 콘솔에 확인해보고 싶다면, 완성된 코드수정하고 출력해보세요.

우리의 블록체인을 실제 상용 블로체인들 처럼 확장하기 위해서는 서버 계층과 같은 기능들을 추가해야만 합니다. 이는 체인의 변경 사항을 추적하고, 한정된 시간에서 추가될 블록의 수를 제한하는 작업증명 역할을 합니다.

기술적으로자세히 알고 싶다면, Bitcoin Whitepaper참고하세요.

blockchain bitcoin cryptocurrency python