Flask에서 MongoDB를 어떻게 사용하면 좋을지 고민해보았습니다.

d3fau1t·2021년 11월 1일
1

개발환경구성

목록 보기
3/5
post-thumbnail
post-custom-banner

Flask를 사용하면서 프로젝트 디렉터리를 구성하고 데이터베이스 코드는 어떻게 관리해야 코드가 좀 더 보기 좋을까 라는 의구심이 들면서 이 주제가 시작되었다.

PyMongo 패키지를 설치해놓고 별도의 DB드라이버 클래스를 하나 만들어서 사용해보고.. 이게 맞는건가 싶어서 Flask-PyMongo도 써보다가 결국엔 Flask-MongoEngine으로 넘어오게되었다.

각 방법마다 장단점이 있겠지만 ORM을 사용하는 방법이 꽤 괜찮았다.

PyMongo

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를 조작하는 방식으로 사용하였다.

database.py

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)

apps/app_name.py

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 조작이 필요한 작업이 늘어날 수록 커넥션 수가 늘어날 것 같다.
정확한 내용은 잘 모르겠지만 좋은 방법은 아닐 것 같다.

Flask-PyMongo

의존성

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를 그대로 사용하려는 목적이라면 괜찮을지도.

Flask-MongoEngine

기존의 날것의 방식과 다르게 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이라고 부르나요..?

profile
웹 백엔드 합니다.
post-custom-banner

0개의 댓글