출처 : 깔끔한 파이썬 탄탄한 백엔드 (송은우 저)
백엔드 API 코드에 가장 널리 적용되는 패턴 중 하나는 레이어드 아키텍처(Multi-tier) 패턴이다. 레이어드 아키텍처는 코드를 논리적인 부분 혹은 역할에 따라 독립된 모듈로 나누어서 구성하는 패턴이다. 각 모듈은 서로의 레이어에 의존성을 가지고 전체의 시스템을 구성한다.
이렇게 레이어별로 영역을 명확히 구분해놓으면, 코드의 확장성과 테스트의 효율성이 높아진다.
일반적으로 다음과 같은 3개의 레이어가 존재한다
- Presentation Layer (View) : 사용자와 직접적으로 연결되는 부분이다. 웹사이트에서는 UI 부분에 해당, 백엔드에서는 엔드포인트 부분에 해당한다. 호출될 API의 엔드포인트를 정의하고 HTTP request들을 읽어 들이는 로직을 구현한다.
- Buisness Layer (Service) : 로직을 구현하는 부분이다. 예를들어 사용자에 대한 권한 확인, 글자 수 제한등의 비즈니스 로직을 담당한다
- Persistence Layer (Model) : 데이터베이스와 관련된 로직을 구현하는 부분이다. Buisness layer에서 필요한 데이터 생성, 수정, 읽기등을 처리한다.
레이어드 아키텍처의 핵심 요소는 단방향 의존성이다. 각각의 레이어는 자신의 레이어보다 하위에 있는 레이어에만 의존한다. 반대로 상위에 있는 레이어에서는 완전히 독립적이라고 할 수 있다.
Presentation Layer -> Buisness Layer -> Persistence Layer 순
Presentation Layer에서는 로직을 처리하기 위해 Buisness Layer가 필요하고, Buisness Layer에서는 데이터베이스에서 데이터를 읽어들이기 위해 Persistence Layer가 필요하다.
미니 트위터 api의 회원가입 과정에 레이어드 아키텍처를 적용해보자. 여기서는 pymysql 모듈을 사용했다. 우선 디렉터리 구조는 각 레이어별로 다음과 같이 구성해준다.
$ mkdir {view,service,model}
├── model
├── service
└── view
가장 하위 레이어인 Persistence Layer 부터 시작해보자. 데이터베이스에 직접 접근을 해야 하므로 DAO 클래스를 구성해준다. 참고로 DAO는 (Data Access Object)의 줄임말로써, DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 Object를 뜻한다. 회원(User)에 대한 기능을 담당하는 DAO 이므로 UserDao 클래스를 생성해준다.
class UserDao:
def __init__(self, database):
self.db = database
def insert_user(self, user):
cursor = self.db.cursor()
sql = """
insert into users(
email,
password
) values (
%s,
%s
)
"""
cursor.execute(sql,(user['email'],user['password']))
self.db.commit()
self.db.close()
로직을 담당하는 service 코드를 작성해보자. 회원(User)에 대한 로직을 담당하는 UserService 클래스를 생성해준다. 그 안에서 사용자 회원가입 로직을 처리하는 create_new_user 메소드를 구현한다.
import bcrypt
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'),bcrypt.gensalt())
new_user_id = self.user_dao.insert_user(new_user)
return new_user_id
간단하게 사용자의 비밀번호를 bcrypt를 사용해 단방향 해쉬하는 로직만 추가했다. 그 이후에 앞서 생성했던 UserDao 클래스의 insert_user 메소드를 사용해준다. 인자는 상위 레이어인 Presentation Layer(view)에서 건네받은 회원가입할 사용자의 email과 password가 담긴 request 딕셔너리이다.
마지막으로 엔드포인트를 정의해주는 view 레이어로 올라온다. @app.route 데코레이터를 통해 엔드포인트의 url을 설정해주고 http method를 정의해준다. 위에서 구현한 UserService 클래스의 create_new_user 메소드를 호출하고 인자로 http request를 json화 해서 넘겨준다.
from flask import jsonify, request
def create_endpoints(app, services):
user_service = services.user_service
@app.route('/sign-up', methods=['POST'])
def sign_up():
new_user = request.json
new_user = user_service.create_new_user(new_user)
return jsonify({'message' : 'ok'}), 200
3가지 레이어에 대한 코드 구현을 다 끝냈다고 해서 회원가입 엔드포인트가 호출되는것은 아니다. 각 레이어를 모두 연결해줘야 하는데 이 작업을 app.py에서 해주면 된다. app.py는 각 레이어의 클래스나 모듈들을 import해 연결시켜주고, Flask 애플리케이션을 생성하는 역할을 한다.
import pymysql
import pymysql.cursors
from flask import Flask
from flask_cors import CORS
from config import db
from model import UserDao
from service import UserService
from view import create_endpoints
class Services:
pass
def create_app(test_config = None):
app = Flask(__name__)
app.debug = True
CORS(app)
if test_config is None:
app.config.from_pyfile('config.py')
else:
app.config.update(test_config)
database = pymysql.connect(
host = db['host'],
user = db['user'],
password = db['password'],
db = db['database']
)
user_dao = UserDao(database)
services = Services
services.user_service = UserService(user_dao, app.config)
create_endpoints(app, services)
return app