-
Python 에서 몽고반점 말고 몽고디비(MongoDB) 다뤄보기💻 프로그래밍/Python 2021. 9. 26. 22:33
안녕하세요! 운동하는 개발자 Jay 입니다! 오늘은 몽고반점 ... 아 아니 몽고디비(MongoDB)에 대해서 알아보고 간단한 사용법 및 DDD(Domain Driven Development) 개념 중 Repository를 Pymongo를 사용해서 간단하게 구현해 보겠습니다!
1. What is MongoDB
저는 이번에 이직을 하면서 처음 MongoDB를 사용해봤는데요.
(아직 제대로 사용은 안해봤구 그냥 보기만 해 봤네요...ㅎㅎ 그래서 지금 블로그로 정리하면서 공부중)NoSQL 종류 중 하나로 JSON과 같은 동적 스키마형 도큐먼트들(몽고 DB는 이러한 포맷을 BSON이라 부름)을 선호함에 따라 전통적인 테이블 기반 관계형 데이터베이스 구조의 사용을 삼가합니다. 이로써 특정한 종류의 애플리케이션을 더 쉽고 더 빠르게 데이터 통합을 가능하게 합니다.
(출처: 위키백과)
쉽게 말하면 RDB 처럼 스키마를 정하고 데이터를 저장하는 방식이 아닌 document 형태(json 같은)의 데이터를 그대로 저장하는 방식입니다. 그래서 스키마도 없고 자유롭게 원하는 데이터를 추가하고 가져올 수 있습니다. (물론 저장하고자 하는 모델(?)에 따라서 스키마처럼 저장 시 제약이나 그런 로직을 애플리케이션 레벨에서 수행하겠죠?)
일단 저는 docker-compose 로 로컬에 MongoDB 를 설치해서 연결했습니다.
1234567891011mongodb:image: mongocontainer_name: "mongodb" #restart: always # container를 실행할 때 항상 이미 수행중이라면 재시작을 수행합니다.environment:MONGO_INITDB_ROOT_USERNAME: rootMONGO_INITDB_ROOT_PASSWORD: rootvolumes:- ./mongo_db/data:/data/dbports:- "27017:27017"cs 꼭 docker로 하지 않아도 편한 방법으로 하시면 됩니다!
2. MongoDB 연결해보기
python에서 쉽게 MongoDB를 사용할 수 있는 pymongo라는 라이브러리를 사용하겠습니다.
12345678from pymongo import MongoClientmongo_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 를 사용해서 파싱 해줘야 한다고 하네요.
12345678910from urllib.parse import quotefrom pymongo import MongoClientmongo_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 환경에서 하셔도 됩니다!
와우! 정말 우리가 출력해봤던 DB 들이 잘 만들어져 있네요!
3. MongoDB 사용하기
이번엔 간단히 입력을 해보고, Repository Class를 만들어서 추상화하는 작업을 해보겠습니다.
test_db라는 데이터베이스를 생성하고 member라는 이름의 collection을 추가합니다. 여기서 collection은 RDB의 Table과 같은 개념이라고 보시면 됩니다!
자 DB와 collection을 생성했으니 member라는 collection에 데이터를 넣어 보겠습니다.
pymongo 듀토리얼을 보니 insert_one이라는 함수로 데이터를 넣어주네요! 똑같이 한번 해보겠습니다.
1234567891011from urllib.parse import quotefrom pymongo import MongoClientmongo_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를 만들어 보겠습니다.1234567891011121314151617181920class MongoRepository:def __init__(self, conn=None, database_name=None, collection_name=None):self._conn = self._get_connection() if not conn else connself._database_name = database_nameself._collection_name = collection_name@staticmethoddef _get_connection():mongo_uri = "mongodb://(username):" + quote("(password)") + "@127.0.0.1:27017/"return MongoClient(mongo_uri)@propertydef database(self):return self._conn[self._database_name]@propertydef collection(self):return self.database[self._collection_name]cs MongoRepository라는 클래스를 만들었습니다.
특별한 건 없고 우리가 위해서 했던 MongoClient 접근을 클래스로 추상화시킨 것입니다.
1234mongo_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에서 제공하는 함수입니다. 튜토리얼을 참고하세요)
12345678910111213141516171819202122232425262728293031323334353637class MongoRepository:def __init__(self, conn=None, database_name=None, collection_name=None):self._conn = self._get_connection() if not conn else connself._database_name = database_nameself._collection_name = collection_name@staticmethoddef _get_connection():mongo_uri = "mongodb://(username):" + quote("(password)") + "@127.0.0.1:27017/"return MongoClient(mongo_uri)@propertydef database(self):return self._conn[self._database_name]@propertydef collection(self):return self.database[self._collection_name]def insert_one(self, data: dict):if not data:returnreturn self.collection.insert_one(data)def find_one(self, _filter: dict):if not _filter:return Nonereturn self.collection.find_one(_filter)def update_one(self, _filter: dict, update_data: dict):if not update_data:returnreturn self.collection.update_one(_filter, update_data)cs 이렇게 MongoRepository에 CRUD를 위한 함수들을 추가했습니다. 특별히 구현한 건 없고 pymongo에서 지원하는 함수들을 클래스에 정의된 database, collection과 함께 사용할 수 있도록 했습니다.
이제 이 MongoRepository를 바로 사용하는 게 아닌 Member Collection을 위한 MemberRepository를 만들어 보겠습니다. 당연히 MongoRepository를 상속받아서 사용할 것입니다!
1234567891011121314151617181920class 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 _memberdef update_job(self, member_id: ObjectId, job: str):if not job:returnreturn self.update_one({'_id': member_id}, {'$set': {'job': job}})cs
MemberRepository에서는 member를 생성하거나 member정보를 가져오는 get_or_create_member 함수와 Member의 job을 업데이트할 수 있는 update_job 함수를 추가했습니다.123456member_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 듀토리얼 보면서 만들어보니까 어느 정도 이해가 된 것 같습니다!
여러분도 다른 개발자가 만들어 놓은 아키텍처나 함수, 클래스가 있으면 한번 따라서 만들어보고 직접 해보시면 이해하기 더 쉬울 거예요!그럼 오늘도 즐거운 하루 보내세요!
'💻 프로그래밍 > Python' 카테고리의 다른 글
어느날 신입 개발자가 나에게 물었다..."python에서 staticmethod를 사용하는 것에 있어서 메모리 이슈가 없는 것 일까요?" (feat. java) (4) 2022.07.17 Context Manager 섹시하게 사용하기 😎 (0) 2022.01.22 비동기로 Third-party API 처리하기 (feat. aiohttp, asyncio) (0) 2021.04.07 filter() + lambda VS list comprehension (2) 2020.08.10 [RxPy] 디버깅, 오류 처리하기 (2) 2020.07.15