728x90

실제로 코드를 작성하다보면, 엄청 길어지게 되고, 하나의 파일에 코드가 무한하게 길어진다.

이를 스파게티 코드라고 하는데, 스파게티 코드가 되는 것을 막기 위해 각 api 모듈별로 폴더 및 파일분리 작업을 해줘야 한다.

 

Blueprint 라이브러리로 구조 짜기

 

MVC 패턴과 blueprint에 대해서는 이미 정리를 해두었지만, 실제로 파일 분리 작업을 하려니 헷갈려서 프로젝트 api 기준으로 파일 분리 작업한 과정을 정리해보고자 한다.

 

우선 프론트구성은 별도 폴더에서 구현할 것이므로, templates와 static 폴더는 해당 구조에서 빠졌다 

 

├── app.py(메인 서버 코드)

├── view

│ └── user.py

├── controller

│ └── user.py

└── model

 

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask
from controller.user import User
from view import user
import os
from flask_cors import CORS
 
 
# https 를 쓰지 않을 경우, 보안 이슈로 에러가 남 (다음 설정을 통해 http 에서도 에러가 나지 않도록 함)
# os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
 
app = Flask(__name__)
# app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
CORS(app)
app.secret_key = 'dave_server1'
 
app.register_blueprint(blog.blog_abtest, url_prefix='/blog'
#view를 메인의 bluprint에 
cs

 

 

controller/user.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from model.user import User 
#실제 model과 통신하는 역할을 하는 controller
 
 
class User(UserMixin):
 
    def __init__(self, user_id, user_email, blog_id):
        self.id = user_id
        self.user_email = user_email
        self.blog_id = blog_id
 
    def get_id(self):
        return str(self.id)
 
    @staticmethod
    def get(user_id):
        mysql_db = conn_mysqldb()
        db_cursor = mysql_db.cursor()
        sql = "SELECT * FROM user_info WHERE USER_ID = '" + \
            str(user_id) + "'"
        db_cursor.execute(sql)
        user = db_cursor.fetchone()
        if not user:
            db_cursor.close()
            return None
 
        user = User(user_id=user[0], user_email=user[1], blog_id=user[2])
        db_cursor.close()
        return user
cs

->DB 세팅이 완료되어 model이 생긴다면 그곳에서 해당하는 DB를 불러오고 모델과 통신하는 코드를 작성

 

 

 

왼쪽의 그림에서 알 수 있듯이, controller은 모델과 view 사이를 연계해주는 역할을 한다. 이 그림을 보고 해당 코드의 구조를 보면 좀 더 이해가 쉬울 것 같다.

 

 

 

 

 

view/user.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask, Blueprint
from controller.user import User
#controller에서 전달받은 데이터를 브라우저에 보여주기 위함?
 
user = Blueprint('user', __name__)
 
# 복잡한 소스파일에서는 코드를 분리하여 MVC 패턴을 정확하게 따라서,
# 리턴할 view 데이터 생성을 위한 함수들을 control 에 넣을 수 있음
# 간단한 코드일 경우에는, 기능별로 모아놓는 것이 여러 파일을 왔디갔다하지 않아서, 더 유용함
# MVC 패턴은 케이스에 따라 사용하는 것이 합리적임
 
 
@blog_abtest.route('/auth')
@login_required
def auth_test():
    return 'auth'
 
 
@blog_abtest.route('/set_email')
def set_email():
    user_email = request.args.get('user_email')
    blog_id = request.args.get('blog_id')
    user = User.create(user_email, blog_id)
    login_user(user, remember=True, duration=datetime.timedelta(days=365))
 
    # return render_template(get_blog_page(blog_id), user_email=user_email)
    # return redirect(url_for('blog.blog', blog_id=blog_id, user_email=user_email))
    return redirect(url_for('blog.blog'))
cs

 

Blueprint 구조로 짜니 의문점은,

view 폴더(파일)이 꼭있어야 할까? 구글링해서 돌아다니는 모든 글들에 templates와 static이 있고 view는 이를 렌더링해주는 역할을 하는데, 우린 실제로 프론트와 백을 구분해서 코드를 짤 것이라서, 필요가 없기 때문이다. 그러면 view가 없어도 되지 않을까?

 

 

flask-restx 라이브러리로 구조 짜기

-> 이는 프로젝트 같은 팀원인 현정님이 구현해주신 것이고 이를 바탕으로 공부해보려 한다.

flask-restx를 이용한 MVC 패턴
├── app
│ ├── init.py
│ ├── main
│ │ ├── controller: 모델과 관련된 모든 수신, HTTP요청을 처리
│ │ │ └── init.py
│ │ │ └── user_controller.py
│ │ ├── model: 모델 정의
│ │ │ └── init.py
│ │ │ └── user.py
│ │ └── service: DB 테이블 관련 쿼리문 작성
│ │ │ └── init.py
│ │ │ └── user_service.py
│ │ ├── util( 해당 파일의 역할이 아직 헷갈린다,)
│ │ │ └── decorator.py
│ │ │ └── dto.py
│ │ ├── init.py(create.app 실행)
│ │ ├── config.py (환경변수 설정)
│ └── migrations
│ └── manage.py(main 서버파일)
└── .gitignore

 

 

app/__init__.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask_restx import Api
from flask import Blueprint
 
from .main.controller.user_controller import api as users_ns
-> 각 controller에서 만든 api 함수를 가져와서 아래 blueprint에 등록해서 사용가능하도록
 
blueprint = Blueprint('api', __name__)
 
api = Api(blueprint,
          title='FLASK RESTPLUS(RESTX) API BOILER-PLATE WITH JWT',
          version='1.0',
          description='a boilerplate for flask restplus (restx) web service'
          )
 
api.add_namespace(users_ns, path='/user')
cs

 

app/main/__init__.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
 
from .config import config_by_name
 
db = SQLAlchemy()
flask_bcrypt = Bcrypt()
 
def create_app(config_name):
-> 실제 실행
    app = Flask(__name__)
    app.config.from_object(config_by_name[config_name])
    db.init_app(app)
    flask_bcrypt.init_app(app)
 
    return app 
cs

 

app/manage.py

-> 실제로 서버가 실행되는 파일

let’s create our application entry point. In the root directory of the project
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import os
import unittest
 
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
 
from app import blueprint
from app.main import create_app, db
 
app = create_app(os.getenv('BOILERPLATE_ENV'or 'dev'
# 환경 변수에서 필요한 매개 변수를 사용하여 응용 프로그램 인스턴스를 생성 초기에 만들어진 기능 - dev, prod, test. 환경 변수에 아무것도 설정되지 않은 경우 기본값 dev이 사용됩니다.
app.register_blueprint(blueprint)
 
app.app_context().push()
 
manager = Manager(app) #Flask-Script를 통해 모든 데이터베이스 마이그레이션 명령을 노출. dbMigrateCommandadd_commandmanager
 
migrate = Migrate(app, db)
 
manager.add_command('db', MigrateCommand)
 
 
@manager.command
def run():
    app.run()
 
 
@manager.command
def test():
    """Runs the unit tests."""
    tests = unittest.TestLoader().discover('app/test', pattern='test*.py')
    result = unittest.TextTestRunner(verbosity=2).run(tests)
    if result.wasSuccessful():
        return 0
    return 1
 
if __name__ == '__main__':
    manager.run()
cs

 

 

app/main/controller/user_controller.py

->http 통신을 보여주며, namespace.model에 대해 설명하는 파일인 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# user 모델과 관련된 모든 수신, HTTP요청을 처리.
 
 
from flask import request
from flask_restx import Resource
 
from ..util.dto import UserDto
from ..service.user_service import save_new_user, get_all_users, get_a_user
 
api = UserDto.api -> 모델 자체를 호출
_user = UserDto.user -> 모델의 각 칼럼을 호출
 
 
 
@api.route('/')
class UserList(Resource):
    @api.doc('list_of_registered_users')
    @api.marshal_list_with(_user, envelope='data')
    def get(self):
        """List all registered users"""
        return get_all_users()
 
    @api.expect(_user, validate=True)
    @api.response(201'User successfully created.')
    @api.doc('create a new user')
    def post(self):
        """Creates a new User """
        data = request.json
        return save_new_user(data=data)
 
 
@api.route('/<id>')
@api.param('id''The User identifier')
@api.response(404'User not found.')
class User(Resource):
    @api.doc('get a user')
    @api.marshal_with(_user)
    def get(self, id):
        """get a user given its identifier"""
        user = get_a_user(id)
        if not user:
            api.abort(404)
        else:
            return user
cs

 

위의 코드에서 api 부분(util에서 import해온 것)을 namespace로 보고 각 메소드의 관계를 이해하면 된다.

Namespace.doc() 데코레이터를 이용하여, Status Code 마다 설명을 추가하거나, 쿼리 파라미터에 대한 설명을 추가 할 수 있습니다.

>
params: dict 객체를 받으며, 키로는 파라미터 변수명, 값으로는 설명을 적을 수 있습니다.
>responses: dict 객체를 받으며, 키로는 Status Code, 값으로는 설멍을 적을 수 있습니다.

Namespace.expect() : 말 그대로, 특정 스키마가 들어 올것을 기대 한다. Namespace.Model 객체를 등록하면 된다

Namespace.response() : 말 그대로, 특정 스키마가 반환 된다. 라는 것을 알려 준다고 보면 됩니다.
첫 번째 파라미터로 Status Code, 두 번째 파라미터로 설명, 세 번째 파라미터로 Namespace.Model 객체가 들어 갑니다.

 

 

app/main/service/user_service.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#user 모델에 관련된 쿼리문 작성 파일
from app.main import db
from app.main.model.user import User
 
 
#회원가입한 회원 정볼를 user모델(즉, user테이블에 넣기)
def save_new_user(data): 
    user = User.query.filter_by(email=data['email']).first()
    if not user:
        new_user = User(
            email=data['email'],
            username=data['username'],
            password=data['password'],
        )
        save_changes(new_user)
        response_object = {
            'status''success',
            'message''회원가입 되었습니다. '
        }
        return response_object, 201
    else:
        response_object = {
            'status''fail',
            'message''이미 가입된 회원입니다.',
        }# 체를 JSON으로 포맷 하기 위해 jsonify 를 사용할 필요가 없습니다 . Flask-restplus가 자동으로 수행합니다.
        return response_object, 409
 
 
 
def get_all_users():
    return User.query.all()
 
 
def get_a_user(id):
    return User.query.filter_by(id=id).first()
 
 
def save_changes(data):
    db.session.add(data)
    db.session.commit() 
cs

 

app/main/util/dto.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask_restx import Namespace, fields
 
 
class UserDto:
    api = Namespace('user', description='user related operations')
    user = api.model('user', {
        'email': fields.String(required=True, description='user email address'),
        'username': fields.String(required=True, description='user username'),
        'password': fields.String(required=True, description='user password'),
    })
 
 
 
cs

 

 

다시 서버구조의 종류와 특징들에 대해서 알아보았다.

 

server architecture 종류

1. MVC 패턴

 : 블로그 내에 정리해두었으니 참고하면 될 것 같다.

 

2. layered architecture

www.quora.com/Is-MVC-different-from-a-3-layered-architecture

 

Is MVC different from a 3 layered architecture?

Answer (1 of 4): In 3-layer architecture * 3-layer architecture separates the application into 3 components which consists of Presentation Layer Business Layer and Data Access Layer. * In 3-layer architecture, user interacts with the Presentation layer. *

www.quora.com

4ngeunlee.tistory.com/m/222

 

MVC 패턴과 Layerd Architecture

MVC 패턴이란? MVC는 Model View Controller의 약자이다. Model은 어플리케이션이 무엇을 할 것인지 정의하며 비즈니스 로직을 가지고 있다. - 처리되는 알고리즘, DB, 데이터 등 Contoller는 사용자로 부터 어

4ngeunlee.tistory.com

 

-> 엄연히 MVC 패턴과 layered architecture은 다르지만 어떻게 보면 그 뿌리는 비슷한 것 같다.

위의 그림에서 보면 알 수 있듯이, MVC 패턴은 좀더 유기적으로 역할을 서로 공유하는 것이고 layered는 이러한 MVC 패턴에 체계화와 분리성을 준 개념인 듯하다. 

 

 

server architecture 라이브러리 종류

 

1. blueprint

위에서 실제로 구현해본 라이브러리이다. 

 

2. flask-restx 라이브러리 활용하기

justkode.kr/python/flask-restapi-2

 

Flask로 REST API 구현하기 - 2. 파일 분리, 문서화

저번 시간에는 Flask-RESTX 에 대한 기본적인 사용 법을 알아보고, 이를 이용하여 간단한 API Server를 만들어 보았습니다. 모두가 스파게티 코드를 원하지 않잖아요. 여러분은 당신의 코드가 스파게

justkode.kr

->  두 라이브러리를 구분하려 했는데, 이 두개는 경쟁하는 개념이 아니라 별개의 라이브러리이며 실제로 혼용돼서 사용되기도 하는것 같다.

flask-restx.readthedocs.io/en/latest/scaling.html

 

Scaling your project — Flask-RESTX 0.2.1.dev documentation

Scaling your project This page covers building a slightly more complex Flask-RESTX app that will cover out some best practices when setting up a real-world Flask-RESTX-based API. The Quick start section is great for getting started with your first Flask-RE

flask-restx.readthedocs.io

-> blueprint의 주 역할은

registering the blueprint with the app takes care of setting up the routing for the application
Using a blueprint will allow you to mount your API on any url prefix and/or subdomain in you application:

 

즉, 라우팅의 역할에 있는 것이고,

 

-> flask-restx의 주 역할은

Sometimes you need to maintain multiple versions of an API. If you built your API using namespaces composition, it’s quite simple to scale it to multiple APIs.

즉, 다중 api의 사용 시에 유용하게 쓰일 수 있다.

 

 

 

www.youtube.com/watch?v=pjVhrIJFUEs

exploreflask.com/en/latest/blueprints.html

 

Blueprints — Explore Flask 1.0 documentation

Using a dynamic URL prefix Continuing with the Facebook example, notice how all of the profile routes start with the portion and pass that value to the view. We want users to be able to access a profile by going to a URL like https://facebo-ok.com/john.doe

exploreflask.com

flask-docs-kr.readthedocs.io/ko/latest/blueprints.html

 

블루프린트를 가진 모듈화된 어플리케이션 — Flask 0.11-dev documentation

블루프린트는 리소스 또한 제공할 수 있다. 때때로 여러분은 단지 리소스만을 제공하기 위해 블루프린트를 사용하고 싶을 수도 있다. 블루프린트 리소스 폴더 보통 어플리케이션처럼, 블루프린

flask-docs-kr.readthedocs.io

www.freecodecamp.org/news/structuring-a-flask-restplus-web-service-for-production-builds-c2ec676de563/

 

How to structure a Flask-RESTPlus web service for production builds

by Greg Obinna How to structure a Flask-RESTPlus web service for production buildsImage credit - frsjobs.co.ukIn this guide I’ll show you a step by step approach for structuring a Flask RESTPlus web application for testing, development and production env

www.freecodecamp.org

 

728x90

+ Recent posts