카테고리 없음

Flask 스마트한 구조 만들기

woolbro 2021. 3. 9. 11:00
반응형

Flask 스마트하게 구조 만들기!

Github Link - https://github.com/paullee714/flask-projects/tree/develop/flask-smart-structure

  • 전에는 Spring, Django를 사용해서 프로젝트를 구축했다. 개발을 시작하기 전에 세팅 해야 할 것, 그리고 환경설정과 지원하는 서비스의 파악 등, 실제로 작업을 하는데 까지 걸리는 시간이 오래 걸렸다.
  • 플라스크는 미니멀하게 프로젝트를 시작 할 수 있어서, 초기 진입 시에 세팅과 공부 할 것들이 비교적 적은 편이라고 생각했다.
  • 플라스크로 현업에서, 그리고 사이드 프로젝트로 개발을 진행 하다 보니 미니멀하게 금방 작업 하는 것에는 도움이 되었지만, Django나 Spring처럼 구조화를 하기는 조금 어렵다는 생각이 들었다.
  • '나만의 플라스크 스트럭쳐 구성'을 만들어 놓고 작업을 해 놓으면 좋겠다! 라는 생각이 들어 정리하려고한다.

가상환경

  • 호불호에 따라, 그리고 여론에 따라서 pipenv, virtualenv 로 나뉜다고 알고있다.
  • 주로 virtualenv를 사용했었지만, pipenv를 사용해 보고 싶다는 생각이 들었다. 뭔가 좀 더 직관적인 느낌이다.

install pipenv

pipenv를 사용하면 pip freeze으로 남겨주었던 requirements.txt를 남기지 않고도 pipfile으로 자동으로 남는다. 너무 편하다.

$ pip3 install pipenv # 설치가 되어있지 않다면!
$ pipenv shell # <-- 가상환경을 자동으로 만들어주고, 실행시켜준다.
(가상환경이름) $ pipenv install [package_name] # <-- 가상환경 내에 설치

내가 설치 한 요소들 - Pipfile

설치를 하면, 같은 경로 내의 Pipfile로 저장이 된다.

내가 설치한 명령어와 Pipfile이다

(가상환경이름) $ pipenv install flask flask-script python-dotenv
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "*"
flask-script = "*"
python-dotenv = "*"

[dev-packages]
autopep8 = "*"

[requires]
python_version = "3.9"

파일 구조

  • 매 프로젝트마다 이렇게 하지는 않지만, 크게 아래와 같이 잡고있다.
  • 더불어서 여러가지 고민이 있다.
    • app패키지 내에 router와 provider들을 넣어주는 방법
    • app패키지 내에 각 도메인별로 폴더를 나누어 각각 router, provider를 만드는 법
    • 그냥 app.py와 route, provider 들을 만드는 방법.

구조

.
├── Pipfile
├── Pipfile.lock
├── README.md
├── app
│   └── __init__.py
├── config
│   ├── __init__.py
│   └── flask_config.py
├── manage.py
├── model
│   ├── __init__.py
│   └── test_model.py
├── provider
│   ├── __init__.py
│   ├── common_provider.py
│   ├── first
│   │   ├── __init__.py
│   │   └── first_provider.py
│   └── second
│       ├── __init__.py
│       └── second_provider.py
└── router
    ├── __init__.py
    ├── first_router
    │   ├── __init__.py
    │   └── first.py
    └── second_router
        ├── __init__.py
        └── second.py
  • app/init.py : 플라스크 APP 전체의 구조와 설정들을 주입 해 주는 곳이다.
  • config/flask_config.py : 플라스크의 환경설정 주입을 관장한다. app/init.py에서 환경 변수를 주입 하기 전에 .env(dot-env)로 환경을 파악 해서 production과 develop 환경을 구분 해 준다.
  • model : 데이터베이스 모델 (ORM)이 있는 곳 (여기서는 작성하지 않았다)
  • provider : 각 api의 endpoint에서 사용 할 '서비스'의 영역을 작업 한다.
  • router : 각 api의 endpoint를 정해준다. 정말 말 그대로 "ROUTE"만 해 주는것이 목표이다. 최대한 깔끔하게.

각 패키지를 뜯어볼까

  • 각 패키지들 중에 내가 중요하게 생각해서 분리 한 것들을 위주로 적어 보았다.
  • 여기서 직접 작성하지 않은 패키지/모듈 들은 간단하게 설명하고 넘어가려고 한다.

app/init.py

from flask import Flask, jsonify
from config import flask_config
from router import first_router

def register_router(flask_app: Flask):
    # router들을 등록 해주는 곳
    from router.first_router.first import first
    from router.second_router.second import second

    flask_app.register_blueprint(first)
    flask_app.register_blueprint(second)

    # flask app의 request/response들과 매번 함께 실행 할 함수 정의
    @flask_app.before_request
    def before_my_request():
        print("before my request")

    @flask_app.after_request
    def after_my_request(res):
        print("after my request", res.status_code)
        return res

def create_app():
    # 앱 설정
    app = Flask(__name__)
    app.config.from_object((get_flask_env()))
    register_router(app)
    return app

def get_flask_env():
    # 환경변수에 따라 config나누기
    if(flask_config.Config.ENV == "prod"):
        return 'config.flask_config.prodConfig'
    elif (flask_config.Config.ENV == "dev"):
        return 'config.flask_config.devConfig'
  • register_router(flask_app: Flask) - Flask를 매개변수로 받는 함수를 만들어, blueprint들을 관리한다. 각각 분리한 router들을 등록하는 작업들을 해주었고, 서버에 들어온 request를 처리하기 전 / response를 되돌려 주기 전에 공통적으로 실행 할 친구들을 모아두었다.

말 그대로 "before_request" 와 "after_request" 이다.

  • create_app : app을 선언하고, config파일을 불러오고 앱을 반환한다.
  • get_flask_env : Config의 환경변수 파악 로직에 따라 production, develop으로 나뉘어 환경 설정을 받게 한다.

manage.py

from flask_script import Server, Manager
from app import create_app

app = create_app()
manager = Manager(app)
manager.add_command(
    "runserver",
    Server(host='0.0.0.0', port=5000, use_debugger=True)
)

if __name__ == "__main__":
    manager.run()
  • manage.py 는 flask-srcipt라는 친구를 알게되어서 사용 해 보게 되었다.
  • flask-script는 차후에 더 정리 해 보려고 한다.
  • 서버 실행과 실행 요소 설정들을 작업 해 준다. 이외의 설정 요소들은 app/init.py에서 모두 하고, manage.py 는 완료된 app을 불러오기만 하면 된다.

config/flask_config.py

import os
from dotenv import load_dotenv

load_dotenv(verbose=True)

class Config(object):
    ENV = os.getenv('ENV')
    CSRF_ENABLED = True
    SECRET_KEY = os.getenv('SECRET_KEY')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class devConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.environ["DB_USERNAME"] + ":" \
                              + os.environ["DB_PASSWORD"] + "@" \
                              + os.environ["DB_HOST"] + ":" \
                              + os.environ["DB_PORT"] + "/" \
                              + os.environ["DB_DATABASE"]

class prodConfig(Config):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.environ["DB_USERNAME"] + ":" \
                              + os.environ["DB_PASSWORD"] + "@" \
                              + os.environ["DB_HOST"] + ":" \
                              + os.environ["DB_PORT"] + "/" \
                              + os.environ["DB_DATABASE"]
  • dotenv를 사용해서, .env파일을 추적하고 관련된 환경변수들을 받아온다.
  • 지금은 기본 클래스인 Config과, Config을 상속받는 devConfig / prodConfig이 있다.
  • 배포 서버가 나뉘는 것에 따라 여러가지를 추가 해도 될 것 같다.

router/first_router (second_router도 비슷하다)

from flask import jsonify, request, Blueprint

first = Blueprint('first_route', __name__)

@first.route("/first", methods=['GET'])
def first_route():
    msg = {
        "page": "first",
        "method": "GET"
    }
    return jsonify(msg)
  • 사실 라우터는 Blueprint를 잘 사용하면 될 것 같다.
  • Blueprint로 잘 나누어서 api의 endpoint를 잡아주면 될 것 같다.
  • 내가 중요하게 생각 하는 요소 중 하나인데, router파일에는 정말 route와 로직을 실행 해 주는 provider 몇개를 만들어서 사용 해야 한다고 생각한다.
  • 귀찮아서 그냥 router 내부에서 여러 함수들을 선언 할 수 있지만, 나중에 코드 정리하지뭐~ 하다가 치킨집을 먼저 차릴 수 도 있을...읍읍

provider

  • provider는 위의 router 에서 사용 될 '서비스'의 영역이다.
  • 각 도메인 별로 나뉘기도 하고, 공통으로 사용 할 내용으로 나뉘기도 한다. 개발자 맘이지 뭐...
  • api endpoint가 있는 router에 전달할 '정리된 데이터'를 만드는 역할을 한다고 생각하면 좋을 것 같다.

그래서 꼭 이대로 해야해?

절대 아니다. 프로젝트에 따라서 자유롭게 적용 하면 좋을 것 같다.

내가 이렇게 하면 좋겠다~ 해서 작성한 구조가 절대 정답이 아니다. 그냥 정리를 좋아한다.

좀더 컴팩트하게 정리하고 싶은 생각이든다.....!!!!!!

디비 커넥션

  • 디비 커넥션은 Config에 넣었는데, 따로 빼는 분들도 있던 것 같다.
  • 디비 모델을 불러오는 공간은 나는 model 로 두었다. 각 테이블 별로 파일을 만드는 것을 선호한다.

외부 pipeline

  • 외부 파이프라인은 여러가지로 생각 할 것이 있는 것 같다.
  • pipeline 환경설정 및 커넥션과 같은 것들은 Config에 몰아두거나 extConfig을 하나 더 선언하면 좋을 것 같다.
  • pipeline을 사용해서 실제로 로직을 처리 하는 것은 provider안에 두어 작업 하는것이 좋을 것 같다.
  • 괜히 rotuer쪽에 꼽아서 복잡하게 만들고 싶지 않다..