Dev/Flask

Flask - Basic + Layered Architecture

sincerely10 2020. 8. 23. 22:51
반응형

안녕하세요. 이번 포스트는 Flask(플라스크)에 대해서 다뤄보겠습니다. Flask가 어떤 역할을 하는지에 대한 기본적인 이해와 특별히 Layered Architecture에 대해 이해 해보겠습니다.

1. Flask와 특징들

Flask는 Python으로 만들어진 Web Application 구현에 사용되는 Frame Work 입니다. 특징 될 만한 것으로 다음과 같은 것이 있습니다.

1.1 micro web framework

말 그대로 아주 가벼운 web framework 입니다. 제 블로그의 카테고리 중 하나인 Django도 Flask와 동일하게 Python 기반의 web freame work 이지만, Django는 다양한 기능 등을 제공하기에 상대적으로 무겁습니다.
하지만 이에 반해 Flask는 상대적으로 가볍기 때문에(제공되는 모둘이 적기에) Learning Curve(학습곡선)가 낮은 편입니다.

1.2 높은 자유도

1.1에서 micro web framework의 특징이기에 자연스럽게 따라가는 특징이지만, 조금 더 자세히 정리해보겠습니다.

이 특징은 책이나 다른 글에서가 아닌 제가 Flask를 활용하면서 느낀 점입니다. 특히 앞서 언급한 Django와 비교해서 더욱 그렇게 느끼게 되었습니다. 제공 모듈이 많은 Django의 경우도 얼마든지 원하는 형태로 개발할 수 있겠지만 App이라는 개념을 활용하고 App 생성시, MTV(Model, Template, View)를 작성하는 템플릿인 models.py와 views.py가 default로 생성되었습니다. Flask도 마찬가지로 MVC(MTV) 구조지만 Django와 같이 기본 템플릿을 제공해주지는 않았습니다,

또한, Flask는 Architecture도 본인이 원하는 방향에 따라 구성할 수 있게끔 되어 있고, 제공 모듈이 비교적 적기에 python의 설치형 모듈을 많이 활용한다는 느낌을 받았습니다.
무엇보다 Django의 경우 Django ORM이라는 것을 주요하게 활용했는데, Flask도 python Alchemy로 ORM을 쓸 수 있지만 대부분의 개발에서 오히려 지양하는 것을 보며 다르다고 느꼈습니다.

2. Layered Architecture on Flask

'1. Flask와 특징들'에서 언급한 것과 같이 Flask는 상대적으로 높은 자유도를 갖고 있습니다. Django의 경우 App별로 생성하는 형태라 큰 고민없이 프로젝트를 구성했으나 Flask는 빈 디렉토리에서 프로젝트를 시작하기 때문에 어떤 프로젝트를 하느냐에 따라 Architecture의 구조가 달라집니다.

Back End API 개발 가운데 가장 널리 적용되는 패턴 중 하나인 Layered Architecture를 Flask에 적용 해보고 고민 했던 부분을 블로깅 하겠습니다.

상세하게 소개하기 전에 Layered Architecture를 소개하겠습니다. Layered Architecture는 각 Layer 별로 구현하는 부분이 나뉘어져 있습니다.
특별히 단방향 의존성을 갖고 있는데 이는 자신의 Layer 보다 바로 아래의 레이어에만 의존한다는 것입니다. 그러므로 Presentation Layer는 Business Layer에게만 Business Layer는 Persistence Layer에게만 의존합니다. 따라서 두 단계를 건너 띈 레이어에게 영향을 주지 않고 상위 레이와 전혀 별개로 동작한다는 특징이 있습니다.

이제 위 표의 각 레이어에 대해 그리고 Flask에서는 어떻게 동작하는가를 대략적으로 설명드리겠습니다.

2.1 Presentation Layer(Flask view)

해당 Layer(레이어)는 시스템을 사용하는 Client가 시스템과 직접적으로 연결되는 부분입니다. 가장 표면에 있는 레이어로 엔드포인트를 구성하는 역할을 합니다. 즉, API 엔드포인트를 정의하고 전송된 HTTP request를 읽어오는 Logic(로직)을 구현합니다.
그 이후의 역할은 Business Layer로 넘겨주게 됩니다.

Flask에서는 보통 view라는 Directory에 정의 합니다. 역할은 동일하며 Flask의 route decorator(데코레이터)를 사용해 EndPoint를 등록합니다. 

로그인(sign-in)과 관련된 엔드포인트를 구성한다고 했을 때, 다음과 같은 코드로 생성됩니다.

 # app_name/user/user_view.py
def create_endpoints(app, service):
  user_service = service.user_service
  
  @app.route("/sign-up", methods=['POST'])
  def sign_up():
      new_user		= request.json
      new_user_id  	= user_service.create_new_user(new_user)
      new_user		= user_service.get_user(new_user_id)
      
      return jsonify(new_user)

중간에 user_service의 create_new_user function을 사용하는데 바로 이 부분이 Presentation Layer가 Business Layer를 의존한다는 것입니다. user를 생성하는 역할은 별도 정의된 함수를 통해 진행하는 것이죠.

2.2 Business Layer

해당 레이어는 비즈니스 로직이 구현되는 레이어 입니다. 실제 시스템에서 구현되는 Validation(유효성) 검사등이 이루어집니다.

Flask에서는 service라는 이름으로 사용됩니다. view에서는 API 정의만 하면 되기 때문에 비교적 코드가 짧아서 한 파일에 다 구성하거나 __init__.py에 정의할 수 있지만, service 부분은 각 기능별로(예를 들면 user, product등)에 맞춰 python 파일을 생성하는 것을 권장합니다. 2.1의 예제코드에 이어서 새로운 유저를 생성하는 로직을 구현해보겠습니다.

#app_name/service/user_service.py
class  UserService:

	def __init__(self, user_dao):
    	self.user_dao = user_dao
        
    def create_new_user(self, new_user):
        new_user['password'] = bcrypt.hashpw(
            new_user['password'].encode('UTF-8'),
            bcyrpt.gensalt())
        
        new_user_id = self.user_dao.insert_user(new_user)
        
        return new_user_id

2.1에서 호출 됐던 create_new_user라는 함수가 정의되고 사용되었습니다. 여기서 전혀 상위 레이어에 대한 의존성은 없습니다. 그리고 마찬가지로 단방향으로 아래에 있는 Persistance Layer가 호출 되고 있습니다.(user_dao 부분)

2.3 Persistance Layer

DataBase와 직접적으로 연결되는 로직이 구현됩니다. Data의 CRUD(CREATE, READ, UPDATE, DELETE)가 실제적으로 이루어지는 곳 입니다.

Flask에서는 model이라는 디렉토리에 생성됩니다. service와 마찬가지로 각 엔드포인트에 맞게 두 개로 나눠 주시면 좋습니다. 파일 이름은 조금 전에 보았듯이 DAO(Database Access Object)라는 이름을 포함시킵니다.

참고할 점은 python에서 mysql을 연동하는 모듈이 mysql-connector-python과 pymysql이 있는데 저는 pymysql이 더 편해서 이를 사용하도록 하겠습니다. 아래는 user_dao.py의 코드 가운데 일부입니다.

#app_name/model/user_dao.py
class UserDao:
  def __init__(self, database):
    self.db = database
    
  def insert_user(self, user):
    return self.db.execute(text("""
    	INSERT INTO users (
        	name,
            email,
            profile,
            hashed_password
        ) VALUES (
        	:name,
            :email,
            :profile,
            :password
        )
    """), user).lastrowid

 

Flask의 tree 구조를 확인하면 다음과 같습니다.

api
|_ view
|_ service
|_ model

 

3. Blueprints를 활용한 프로젝트의 Layered Architecture 적용

Flask에서의 Layered Architecture에 대해 소개했습니다. 3에서는 제목과 같이 Blueprints라는 URL EndPoint를 bind 해서 구성하는 과정을 소개하겠습니다.

3.1 프로젝트 안내

프로젝트를 소개하는 포스트는 아니지만, 기본적으로 이 구조를 고민하게 된 이유를 소개하기 위해서는 프로젝트에 대한 최소한의 소개가 필요하다고 생각합니다.

진행하는 프로젝트는 프론트 엔드 개발자 둘, 백엔드 개발자 셋으로 구성이 되어 다양한 브랜드를 모아 놓은 여성 쇼핑몰 브랜디의 고객 사이트와 관리자 사이트를 클론하는 프로젝트 입니다.

3.2 Blueprint 소개

Blue Print에 대해 소개 하도록 하겠습니다. 

Blueprint를 간단하게 정의하면 엔드포인트를 Components(Web Page에 제공되는 API를 붙이는 부분) 별로 구성하게 하는 역할을 합니다.

가장 간단하게 예를 들자면 user와 관련된 EndPoint 구성을 하기 위한 EndPoint는 'user/ '로 부터 시작이 될 것입니다.(user/sign-in, user/sign-up, user/follow 등..) 그리고 user라는 endpoint를 묶는다면 정의가 더 깔끔해질 것 입니다.

view에 UserView라는 클래스를 정의하고 그 이하에 있는 함수는 각 EndPoint로 이어지는 설정을 해보겠습니다.

#brandi/view/user_view.py

import requests

from flask import (
    request,
    Blueprint,
    jsonify,
)

from service.user_service import UserService

class UserView:
    user_app = Blueprint('user_app', __name__, url_prefix='/user')

    @user_app.route('/sign-in', methods=['POST'])
    def signin():
        user_service = UserService()
        data = request.json
        sign_in_user = user_service.sign_in(data)

        if sign_in_user:
            #access_token 생성
            access_token = user_service.generate_access_token(sign_in_user)

            return jsonify({'access_token' : access_token}), 200
            
        return jsonify({'message' : 'UNAUTHORIZED'}), 401

이어서 app.py의 설정을 확인해보겠습니다.

# brandi/app.py

from flask      import Flask
from flask.json import JSONEncoder
from flask_cors import CORS

from view.user_view import UserView

from model import UserDao

from service import UserService

from view import UserView

def create_app():
    app = Flask(__name__)
    app.json_encoder = CustomJSONEncoder

    #CORS 설정
    CORS(app)

    #config 설정
    app.config.from_pyfile("config.py")

    # DataBase Connection 생성
    database = get_connection()
    
    # DAO 생성
    user_dao = UserDao(database)

    # Service 생성
    user_service = UserService(user_dao)

    # view blueprint 등록
    app.register_blueprint(UserView.user_app)

    return app

user_view.py에서 정의 해주었던 Blueprint를 Flask 프로젝트의 app.py에 정의해줍니다.

이 과정을 거치면 user_view.py에 있는 UserView class의 route의 decorator로 생성되는 end point에 대해 user/라는 prefix가 기본으로 붙은채로 정의할 수 있습니다.

즉 Blueprint를 활용하면
1) 가독성이 좋아진다. 2) 유지보수가 쉬워진다. 3) RESTful 한 URL 생성이 간편해진다.
라는 장점이 있습니다.

더 좋은 포스트로 찾아뵙겠습니다.

감사합니다.

반응형

'Dev > Flask' 카테고리의 다른 글

Flask(python) - Image 사이즈 별 S3 저장 및 URL Link 저장하기  (0) 2020.08.31