ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Python 에서 몽고반점 말고 몽고디비(MongoDB) 다뤄보기
    💻 프로그래밍/Python 2021. 9. 26. 22:33

     

    안녕하세요! 운동하는 개발자 Jay 입니다! 오늘은 몽고반점 ... 아 아니 몽고디비(MongoDB)에 대해서 알아보고 간단한 사용법 및 DDD(Domain Driven Development) 개념 중 RepositoryPymongo를 사용해서 간단하게 구현해 보겠습니다!

     

     

    1.  What is MongoDB


    저는 이번에 이직을 하면서 처음 MongoDB를 사용해봤는데요.
    (아직 제대로 사용은 안해봤구 그냥 보기만 해 봤네요...ㅎㅎ 그래서 지금 블로그로 정리하면서 공부중)

    NoSQL 종류 중 하나로  JSON과 같은 동적 스키마형 도큐먼트들(몽고 DB는 이러한 포맷을 BSON이라 부름)을 선호함에 따라 전통적인 테이블 기반 관계형 데이터베이스 구조의 사용을 삼가합니다. 이로써 특정한 종류의 애플리케이션을 더 쉽고 더 빠르게 데이터 통합을 가능하게 합니다.

    (출처: 위키백과)

     

    쉽게 말하면 RDB 처럼 스키마를 정하고 데이터를 저장하는 방식이 아닌 document 형태(json 같은)의 데이터를 그대로 저장하는 방식입니다. 그래서 스키마도 없고 자유롭게 원하는 데이터를 추가하고 가져올 수 있습니다. (물론 저장하고자 하는 모델(?)에 따라서 스키마처럼 저장 시 제약이나 그런 로직을 애플리케이션 레벨에서 수행하겠죠?)

     

    일단 저는 docker-compose 로 로컬에 MongoDB 를 설치해서 연결했습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      mongodb:          
        image: mongo    
        container_name: "mongodb" #
        restart: always # container를 실행할 때 항상 이미 수행중이라면 재시작을 수행합니다.
        environment:
          MONGO_INITDB_ROOT_USERNAME: root
         MONGO_INITDB_ROOT_PASSWORD: root
        volumes:
          - ./mongo_db/data:/data/db
        ports:                
          - "27017:27017"  
    cs

     

    꼭 docker로 하지 않아도 편한 방법으로 하시면 됩니다! 

     

    2.  MongoDB 연결해보기


    python에서 쉽게 MongoDB를 사용할 수 있는 pymongo라는 라이브러리를 사용하겠습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    from pymongo import MongoClient
     
     
    mongo_uri = "mongodb://(username):(password)@127.0.0.1:27017/"
    client = MongoClient(mongo_uri)
     
    print(client.list_database_names())
     
    cs

     

    듀토리얼을 보면 요런식으로 연결하여 list_database_names() 를 실행하면 데이터 베이스 리스트를 확인할 수 있습니다. 기본적으로 생성된 database인 admin, config, local 이 출력돼야 하며 한번 실행해보겠습니다!

    홀리몰리~

    InvalidURI 오류가 떠서 검색해 보니까 username, password quote 를 사용해서 파싱 해줘야 한다고 하네요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from urllib.parse import quote
     
    from pymongo import MongoClient
     
     
    mongo_uri = "mongodb://(username):" + quote("(password)"+ "@127.0.0.1:27017/"
    client = MongoClient(mongo_uri)
     
    print(client.list_database_names())
     
    cs

     

    요렇게! 다시 수정하여 실행해보니

    오호! 잘 나온다!

    정말 기본적으로 생성되는 DB 리스트가 출력되었습니다! 그럼 실제로 우리가 MongoDB에 접속해서 한번 확인해볼까요?!

    일단 저는 CLI 환경이 아닌, 좀 더 편하고 빠르게 보기 위해서 MongoDB Compass 라는 GUI 툴을 사용하기로 했습니다. CLI가 편하신 분은 CLI 환경에서 하셔도 됩니다!

     

    MongoDB Compass 롤 접속해서 확인한 모습

    와우! 정말 우리가 출력해봤던 DB 들이 잘 만들어져 있네요!

     

     

    3. MongoDB 사용하기


    이번엔 간단히 입력을 해보고, Repository Class를 만들어서 추상화하는 작업을 해보겠습니다.

    test_db 생성 및 member collection 생성

    test_db라는 데이터베이스를 생성하고 member라는 이름의 collection을 추가합니다. 여기서 collection은 RDB의 Table과 같은 개념이라고 보시면 됩니다! 

    자 DB와 collection을 생성했으니 member라는 collection에 데이터를 넣어 보겠습니다.

    insert_one 함수 설명

    pymongo 듀토리얼을 보니 insert_one이라는 함수로 데이터를 넣어주네요! 똑같이 한번 해보겠습니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from urllib.parse import quote
     
    from pymongo import MongoClient
     
     
    mongo_uri = "mongodb://(username):" + quote("(password)"+ "@127.0.0.1:27017/"
    client = MongoClient(mongo_uri)
    database = client['test_db']
    result = database['member'].insert_one({"name""제이""job""백엔드 개발자"})
    print(result.inserted_id)
     
    cs

     

    DB, Collection 접근은 MongoClient가 정상적으로 커넥션 되었다면

    1. client['database 이름']['collection 이름']
    2. client.database_이름.collection_이름

     

    이렇게 두 가지 방식으로 접근이 가능합니다. 자 그럼 데이터가 정상적으로 들어갔는지 확인해보겠습니다.

    와우! 정상적으로 추가된 데이터

    정상적으로 우리가 넣은 데이터가 저장되었습니다. 여기서 _id라는 필드에 있는 ObjectID는 RDS Primary key와 같은 역할을 하며 자동으로 생성해줍니다. ObjectID에 대한 자세한 설명은 여기를 확인해주세요.

    자! 우리는 그럼 DDD의 개념 중 하나인 데이터베이스 접근을 위한(CRUD) Repository를 만들어 보겠습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class MongoRepository:
     
        def __init__(self, conn=None, database_name=None, collection_name=None):
            self._conn = self._get_connection() if not conn else conn
            self._database_name = database_name
            self._collection_name = collection_name
     
        @staticmethod
        def _get_connection():
            mongo_uri = "mongodb://(username):" + quote("(password)"+ "@127.0.0.1:27017/"
            return MongoClient(mongo_uri)
     
        @property
        def database(self):
            return self._conn[self._database_name]
     
        @property
        def collection(self):
            return self.database[self._collection_name]
     
    cs

     

    MongoRepository라는 클래스를 만들었습니다.

    특별한 건 없고 우리가 위해서 했던 MongoClient 접근을 클래스로 추상화시킨 것입니다.

    1
    2
    3
    4
    mongo_repo = MongoRepository(database_name='test_db', collection_name='member')
    print(mongo_repo.database)
    print(mongo_repo.collection)
     
    cs

     

    database, collection을 print 해보면

     

    요렇게 Database, Collection 접근이 정상적으로 이루어진 걸 볼 수 있습니다.

    이제 여기에 insert_one, find_one, update_one 등 기본적인 CRUD 함수를 추가해 보겠습니다. 

    (위 함수들은 pymongo에서 제공하는 함수입니다. 튜토리얼을 참고하세요)

    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
    class MongoRepository:
     
        def __init__(self, conn=None, database_name=None, collection_name=None):
            self._conn = self._get_connection() if not conn else conn
            self._database_name = database_name
            self._collection_name = collection_name
     
        @staticmethod
        def _get_connection():
            mongo_uri = "mongodb://(username):" + quote("(password)"+ "@127.0.0.1:27017/"
            return MongoClient(mongo_uri)
     
        @property
        def database(self):
            return self._conn[self._database_name]
     
        @property
        def collection(self):
            return self.database[self._collection_name]
     
        def insert_one(self, data: dict):
            if not data:
                return
            return self.collection.insert_one(data)
     
        def find_one(self, _filter: dict):
            if not _filter:
                return None
     
            return self.collection.find_one(_filter)
     
        def update_one(self, _filter: dict, update_data: dict):
            if not update_data:
                return
     
            return self.collection.update_one(_filter, update_data)
     
    cs

     

    이렇게 MongoRepository에 CRUD를 위한 함수들을 추가했습니다. 특별히 구현한 건 없고 pymongo에서 지원하는 함수들을 클래스에 정의된 database, collection과 함께 사용할 수 있도록 했습니다.

     

    이제 이 MongoRepository를 바로 사용하는 게 아닌 Member Collection을 위한 MemberRepository를 만들어 보겠습니다. 당연히 MongoRepository를 상속받아서 사용할 것입니다!

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class MemberRepository(MongoRepository):
     
        def __init__(self):
            super().__init__(database_name='test_db', collection_name='member')
     
        def get_or_create_member(self, name: str):
            _member = self.find_one({'name': name})
     
            if not _member:
                self.insert_one({'name': name})
                _member = self.find_one({'name': name})
     
            return _member
     
        def update_job(self, member_id: ObjectId, job: str):
     
            if not job:
                return
     
            return self.update_one({'_id': member_id}, {'$set': {'job': job}})
    cs


    MemberRepository
    에서는 member를 생성하거나 member정보를 가져오는 get_or_create_member 함수와 Member의 job을 업데이트할 수 있는 update_job 함수를 추가했습니다.

    1
    2
    3
    4
    5
    6
    member_repo = MemberRepository()
    member = member_repo.get_or_create_member(name='제이')
    print(member)
    member = member_repo.update_job(member.get('_id'), '프론트엔드 개발자')
    print(member_repo.get_or_create_member(name='제이'))
     
    cs

     

    위 코드는 '제이'라는 member를 찾거나 생성해서 정보를 가져오고, update_job을 통해 job 데이터를 업데이트 하는 로직입니다.

    아까 위에서 '제이' 라는 member를 추가했으니 새로 생성하진 않고, 찾아와서 업데이트하겠죠?

     

    백엔드 -> 프론트엔드 개발자가 되버린 제이

    와우! 매우 잘 되었습니다!!!



    4. 마치며


    위에서 크게 MongoDB의 장단점에 대해서는 설명하지 않았습니다. 이 부분은 여러분들이 한번 찾아보시길 권장드립니다.
    (글 쓰기 귀찮아요 ~ 라고 할 뻔)
    이번에 팀을 옮기면서 FastAPI와 DDD를 도입한 프로젝트를 보고 있는데, 처음에 MongoDB에 대한 개념을 모르니까 뭐가 뭔지 모르고 더 어렵더라고요! 그리고 pymongo라는 라이브러리에 대해서도 잘 몰랐고! ㅎㅎ 암튼 이번에 저희 팀 팀장님께서 설계하신 부분에 대해 설명 듣고 지금 한번 비슷하게 pymongo 듀토리얼 보면서 만들어보니까 어느 정도 이해가 된 것 같습니다!
    여러분도 다른 개발자가 만들어 놓은 아키텍처나 함수, 클래스가 있으면 한번 따라서 만들어보고 직접 해보시면 이해하기 더 쉬울 거예요!

    그럼 오늘도 즐거운 하루 보내세요!

    댓글

운동하는 개발자 JAY-JI