Flask를 사용하면서 프로젝트 디렉터리를 구성하고 데이터베이스 코드는 어떻게 관리해야 코드가 좀 더 보기 좋을까
라는 의구심이 들면서 이 주제가 시작되었다.
PyMongo
패키지를 설치해놓고 별도의 DB드라이버 클래스를 하나 만들어서 사용해보고.. 이게 맞는건가 싶어서 Flask-PyMongo
도 써보다가 결국엔 Flask-MongoEngine
으로 넘어오게되었다.
각 방법마다 장단점이 있겠지만 ORM을 사용하는 방법이 꽤 괜찮았다.
database.py
파일을 생성하고 각 서비스 레이어에서 DB를 조작할 때마다 초기화 하는 방식으로 사용했다.
pip install pymongo
pip install "pymongo[srv]"
.
├── app.py
├── apps
│ ├── __init__.py
│ └── app_name.py
├── settings.py
└── database.py
apps/app_name.py
파일에서 database
모듈에 있는 mongo
를 받아와서 DB를 조작하는 방식으로 사용하였다.
from urllib.parse import quote_plus
from pymongo import MongoClient
from settings import DATABASES
class Mongo:
def __init__(self, user=None, password=None, host=None, port=None, database=None):
host = host and host or DATABASES.get("host")
port = port and port or int(DATABASES.get("port"))
database = database and database or DATABASES.get("database")
user = user and user or DATABASES.get("user")
password = (
password
and quote_plus(str(password))
or quote_plus(str(DATABASES.get("password")))
)
self.client = MongoClient(
f"mongodb+srv://{user}:{password}@{host}?retryWrites=true&w=majority",
int(port),
)
self.db = self.client.get_database(database)
from database import mongo
mongo = Mongo()
@app.route("/something")
def get(self):
result = mongo.db.collection.find_one({}, {"_id": False})
return {"result": result}, 200
이 방법은 독립적인 데이터베이스 연결이 가능하지만 매번 사용할 때마다 초기화해야한다는 단점이 있기도하고.. DB 조작이 필요한 작업이 늘어날 수록 커넥션 수가 늘어날 것 같다.
정확한 내용은 잘 모르겠지만 좋은 방법은 아닐 것 같다.
pip install Flask-PyMongo
일단 패키지를 설치하고..
.
├── app.py
├── apps
│ ├── __init__.py
│ └── user.py
├── database
│ ├── __init__.py
│ └── user.py
└── settings.py
app.py
from flask_pymongo import PyMongo
app = Flask(__name__)
app.config["MONGO_URI"] = get_database_uri(DATABASES)
mongo = PyMongo(app)
...
app을 초기화하는 부분 PyMongo를 초기화한다.
database/__init.py
from apps import mongo
from settings import DATABASES
db = mongo.db.client.get_database(DATABASES.get("name"))
분리된 database 모듈에서 데이터베이스 객체를 생성한 뒤, 사용하려는 코드에서 받아서 쓰면 된다.
database/user.py
from database import db
from utils.auth import generate_key
user = db.get_collection("user")
def set_user(provider: str, info: dict) -> bool:
# provider like google, facebook, kakao.. etc..
auth = generate_key(64)
result = user.insert_one({"provider": provider, "auth": auth, **info})
return auth
이런식으로 DB를 조작하는 부분을 함수로 정의한 뒤, 서비스코드에서 사용할 수 있지만..
apps/user.py
@user.route("/")
def get_home():
from database.user import get_user_by_user_auth
if "auth" in session:
user = get_user_by_user_auth(auth=session["auth"])
return render_template("home.html", hello="Greeting", user=user)
return render_template(
"home.html",
hello="How'bout login?",
)
약간의 문제가 있다.
이 구조에서는 view 로직 안에서 함수를 import 해서 사용하지 않으면 순환참조 이슈가 발생한다.
lazy init하려고 했는데 잘 안되었다.
그래도 주의해서 사용한다면 꽤 괜찮아보이지만 같은 로직을 사용하려면 각 뷰마다 함수를 import 해야되서 좋은 방법은 아닌것 같다.
잘 몰라서 그런건지 몰라도 구조만 바꾸면 괜찮을 것 같은데..
Flask에서 PyMongo를 그대로 사용하려는 목적이라면 괜찮을지도.
기존의 날것의 방식과 다르게 ORM을 사용할 수 있다.
pip install flask-mongoengine
.
├── app.py
├── apps
│ ├── __init__.py
│ └── user.py
├── config.dev.json
├── database
│ ├── __init__.py
│ ├── interfaces
│ └── user.py
├── settings.py
└── utils
├── __init__.py
└── env.py
database/__init__.py
from flask_mongoengine import MongoEngine
db = MongoEngine()
database 모듈을 초기화해두고 app 초기화 하는 과정에서 같이 초기화해주면 된다.
database/interfaces/User.py
from database import db
class User(db.Document):
provider = db.StringField(required=True)
auth = db.StringField(required=True)
_id = db.StringField(required=True)
email = db.StringField(required=True)
verified_email = db.BooleanField(required=True)
picture = db.StringField()
def __repr__(self):
return "<User %r>" % self.email
mongoengine은 WTF를 지원한다.
인터페이스로 사용할 유저 모델을 정의할 수 있다.
database/user.py
from typing import Union
from database.interfaces.User import User
from utils.env import generate_key
def set_user(provider: str, info: dict) -> bool:
# provider like google, facebook, kakao.. etc..
auth = generate_key(64)
user = User(
provider=provider,
auth=auth,
_id=info["id"],
email=info["email"],
verified_email=info["verified_email"],
picture=info["picture"],
)
user.save()
return auth
def get_user_by_user_auth(auth: str) -> Union[dict, None]:
try:
result = User.objects.get(auth=auth)
return result
except User.DoesNotExist:
return None
def get_users_by_email(email: str) -> list:
result = User.objects(email__regex=f".*{email}.*")
return result
정의해둔 모델을 가져다가 ORM 처럼 쓸 수 있다.
데이터베이스에 데이터를 넣고, 넣은걸 찾는 등 뭔가 할 때 더 간단해보인다.
코드를 작성하는데 더 집중할 수 있는 장점이 있다.
apps/__init__.py
from flask import Flask
from utils.env import get_database_uri
from utils.env import generate_key
from settings import *
from database import db
from . import user
app = Flask(__name__)
app.secret_key = generate_key(16)
app.config["MONGODB_SETTINGS"] = {
"db": DATABASES.get("name"),
"host": get_database_uri(DATABASES),
"port": int(DATABASES.get("port")),
"username": DATABASES.get("username"),
"password": DATABASES.get("password"),
}
db.init_app(app)
app.register_blueprint(user.user)
apps에서 database 모듈에 정의해둔 db를 가져와서 초기화하고 사용하면 된다.
이런걸 Factory pattern이라고 부르나요..?