🌱 Flask setup with MongoEngine

yeeun lee·2020년 8월 18일
0

flask를 기반으로 Mongodb를 사용할 때, mongoengine을 사용하면 NoSQL DB임에도 모델링을 하고(데이터 타입이나 관계를 정의), view를 작성할 때 Django ORM 비스무리하게 쿼리할 수 있다.

1. 필요한 것들

사용할 stack, package manager는 다음과 같다.

  • Flask-classful - view
  • mashmallow - validation
  • MongoEngine - modeling, orm
  • Poetry - package, dependency manager

2. 개발 순서

간단하게 순서를 보면 다음과 같다. (참고 링크)

  1. 서버에서 MongoEngine을 초기화하기 전에, MongoDB의 정보를 Flask에 설정한다.
  2. MongoEngine을 사용해 데이터 모델을 만든다.
  3. model에서 만든 class를 기반으로 쿼리하고, 로직을 짠다.

3. 프로젝트 구조

poetry로 파이썬 프로젝트를 만들었고, root directory에 run.py를 넣었다.

디자인 패턴은 크게 models / views / serializers로 나누었고 각 디렉토리 안에 기능에 따라 파일을 추가할 예정이다.

작동하는 방식은 다음과 같이 이해했다.

  • poetry run python run.py를 하게 되면 view init 파일에 있는 create_app을 호출
  • create_app 함수에서 DB연결, CORS, api 확인
  • request route에 맞는 api를 호출해서 response
❯ tree
.
├── app
│   ├── __init__.py
│   ├── config.py
│   ├── database.py
│   ├── models
│   │   └── user.py
│   ├── serializers
│   │   └── user.py
│   └── views
│       ├── __init__.py
│       └── user.py
├── README.rst
├── poetry.lock
├── pyproject.toml
├── run.py
└── tests
    └── __init__.py

4. 개념

아래 내용들은 MongoEngine 공식 문서를 참고했다.

Document

관계형 데이터베이스의 row와 비슷한 개념. 관계형 데이터베이스에서 row들은 테이블에 저장되지만, MongoDB는 document를 collections에 저장한다.

여기서 원칙적으로 다는 점은 데이터베이스 레벨에서 스키마가 필요하지 않다는 점이다.

- Document 스키마 정의하기

스키마를 정의하기 위해서, Document를 상속하는 class를 만든다. Field들은 Field object를 document class에 속성으로서 추가해서 지정된다. BSON은 순서에 의존적이기 때문에, document들은 field의 순서에 따라 serialize된다.

from mongoengine import *
import datetime

class Page(Document):
    title = StringField(max_length=200, required=True)
    date_modified = DateTimeField(default=datetime.datetime.utcnow)

- Document 생성 시 특징

document class로 만들어진 collection의 이름은 소문자로 생성된다.

  • MongoEngine의 Document class를 상속하는 document class들은 데이터베이스에서 자신만의 collection을 가지게 된다. collection의 이름은 디폴트로 class의 일므이 되고, 다만 주의할 점은 lower case로 변경된다는 점이다. (공식 문서 참고)

이걸 왜 알게됐냐면, get test method를 만드는데 분명 User collection에 내용이 있는데도 Nonetype을 읽어올 수 없다는 오류가 떴다. 그래서 post method를 만들어보니까 User가 아니라 user collection이 생성되면서 데이터가 들어갔다.

Dynamic document

MongoDB의 장점 중 하나가 dynamic document다. 데이터가 계획되고 정리되는 것, 즉 명확한게 좋긴 하지만 동적이고 확장 가능한 document 스타일이 가능한 시나리오가 있다.

동작하는 것은 Document와 똑같지만 어떤 데이터나 속성도 저장될 수 있다는 특징이 있다. 주의할 점이 하나 있는데, field는 언더바(_)로 시작할 수 없다.

from mongoengine import *

class Page(DynamicDocument):
    title = StringField(max_length=200, required=True)

# Create a new page and add tags
>>> page = Page(title='Using MongoEngine')
>>> page.tags = ['mongodb', 'mongoengine']
>>> page.save()

>>> Page.objects(tags='mongoengine').count()
>>> 1

Fields

  • NoSQL이기 때문에 fields는 기본적으로 불필요
  • 필수적으로 만들기 위해서는 required keyword argument를 True로 설정
  • Validation constraints(최대 길이 같은)를 가짐
  • Field argument는 required, default, unique 등

예시: BinaryField, BooleanField, DateTimeField, DictField, ListField, EmailField...etc.

- Embedded document

다른 Document 안에 document를 끼워넣는 것이 가능하다.

class Comment(EmbeddedDocument):
    content = StringField()
    
class Page(Document):
    comments = ListField(EmbeddedDocumentField(Comment))

comment1 = Comment(content='Good work!')
comment2 = Comment(content='Nice article!')
page = Page(comments=[comment1, comment2])

- Dictionary Fields

embedded coument는 dictionary 대신 쓰인다. 일반적으로 embedded document들은 validation을 지원하지 않는 딕셔너리나 custom type으로 쓰일 때 권장된다. 하지만 때때로 저장하고자 하는 구조가 무엇인지 모르기 때문에, 이럴 때 DictField를 사용하게 된다.

Dictionary는 복잡한 데이터, 다른 dictionary들, 리스트, 다른 객체를 참조할 수 있기 때문에 field type 중에 가장 유연하다.

- Reference Fields

특정 document에서 ReferenceField를 사용하면 다른 document에 저장될 수 있다. constructor에서 첫 번째 인자로 다른 document class를 넣으면 field에 Document 객체들을 할당한다.

class User(Document):
    name = StringField()

class Page(Document):
    content = StringField()
    author = ReferenceField(User)

john = User(name="John Smith")
john.save()

post = Page(content="Test Page")
post.author = john
post.save()

이 때 User 객체는 자동적으로 참조가 되고, Page 객체를 검색했을 때는 역참조된다. 정의 중인 document를 참조하는 ReferenceField를 추가하기 위해서는 'self'를 사용한다.

class Employee(Document):
    name = StringField()
    boss = ReferenceField('self')
    profile_page = ReferenceField('ProfilePage')

class ProfilePage(Document):
    content = StringField()

One to Many with ListFields

일대다 관계를 list를 통해 실행한다면, 참조들은 DBRefs에 저장되고 객체의 instance들을 통과시켜야 한다.

class User(Document):
    name = StringField()

class Page(Document):
    content = StringField()
    authors = ListField(ReferenceField(User))

bob = User(name="Bob Jones").save()
john = User(name="John Smith").save()

Page(content="Test Page", authors=[bob, john]).save()
Page(content="Another Page", authors=[john]).save()

- Generic reference fields

참조 필드 중 두 번째 종류는 GenericReferenceField인데, 어떤 종류의 document도 다 참조할 수 있게끔 해준다. 그래서 constructor argument로서 document subclass가 필요하지 않다.

class Link(Document):
    url = StringField()

class Post(Document):
    title = StringField()

class Bookmark(Document):
    bookmark_object = GenericReferenceField()

link = Link(url='http://hmarr.com/mongoengine/')
link.save()

post = Post(title='Using MongoEngine')
post.save()

Bookmark(bookmark_object=link).save()
Bookmark(bookmark_object=post).save()
profile
이사간 블로그: yenilee.github.io

0개의 댓글