Deta Base (beta)
Instantly usable database with a feature-rich API.
― 기능이 풍부한 API를 이용하여 즉시 사용 가능한 데이터베이스
Deta라는 이름 자체가 Database를 노리고 지은 것 같ㄷ...
Deta Base is great for projects where configuring and maintaining a database is overkill.
― Deta Base는 데이터베이스를 구성 및 유지하기엔 지나친 프로젝트에 적합하다.
프로젝트를 기획하다보면 데이터베이스가 필요하긴 한데, 이를 위한 데이터베이스 관리 시스템을 구축하고 유지하는 건 프로젝트 규모 대비 너무 과한 것 같다고 판단되는 경우가 있을 수 있다.
Deta는 이런 경우에 Deta Base를 사용할 것을 추천하고 있다.
Deta Base의 데이터는 내부적으로는 암호화되어 AWS에 저장되며, NoSQL을 기반으로 한다.
어떤 데이터베이스 이름으로 아이템을 저장함으로써 새로운 데이터베이스를 생성할 수 있다.
한 프로젝트 내에 데이터베이스는 몇 개든지 만들 수 있다.
...프로젝트와 마찬가지로 데이터베이스 삭제 같은 건 보이지 않는다.
Deta Base는 데이터베이스를 관리하기 위해 다음과 같은 메서드를 지원한다.
put
― 데이터베이스에 아이템을 저장하되, 이미 존재한다면 이를 업데이트한다.insert
― 데이터베이스에 아이템을 저장하되, 이미 존재한다면 에러를 일으킨다.get
― key를 통해 데이터베이스로부터 아이템을 가져온다. fetch
― 필터를 통해 데이터베이스로부터 아이템들을 가져온다.delete
― 데이터베이스로부터 아이템을 삭제한다.update
― 데이터베이스 내의 아이템을 업데이트한다.insert
메서드는 put
메서드에 비해 두 배 정도 느리다는 것을 유의하자.
Deta Base를 사용하는 방법은 크게 세 가지가 있다.
그 방법에 따라 메서드를 호출하는 방법이 달라지니 하나씩 살펴 보도록 하자.
각각의 방법에 대한 예제 코드는 공식 문서에서 확인할 수 있다.
Deta Base는 Node.js, Python, Go 등의 언어에서 라이브러리를 사용할 수 있다.
각각 다음과 같은 명령어로 설치할 수 있는데
$ npm install deta
$ pip install deta
$ go get github.com/deta/deta-go
FastAPI를 다루다 왔으므로 Python을 기준으로 진행하겠다.
⚠️ 라이브러리 설치는 가상환경에서 하는 걸 잊지 말자.
주변에 Python 프로젝트 하다가 가상환경 꼬여서 운영체제 재설치 하는 친구를 봤다.$ python3 -m venv venv $ . venv/bin/activate (venv)$ pip3 install deta
Project Key project_key
와 데이터베이스 base_name
에 대하여 다음과 같이 Deta Base의 데이터베이스를 사용할 수 있다.
from deta import Deta deta = Deta("project_key") db = deta.Base("base_name")
put
메서드― 데이터베이스에 아이템을 저장하되, 이미 존재한다면 이를 업데이트한다.
put
메서드는 다음과 같다.
put(data: typing.Union[dict, list, str, int, float, bool], key:str = None)
parameter description remark data
아이템으로서 저장하고자 하는 데이터 required key
아이템을 식별하기 위한 값; 미지정 시 자동 생성 optional
Deta Base의 아이템은 dict
, list
, str
, int
, float
, bool
자료형에 해당하는 값을 가질 수 있다.
put
메서드는 성공적으로 실행 시 저장한 아이템을, 오류 발생 시 그 오류를 반환한다.
put_many
메서드만약 두 개 이상의 아이템을 동시에 저장하고자 한다면 put_many
메서드를 사용할 수 있다.
단, 한 번에 최대 25개의 아이템까지만 가능하다.
put_many
메서드는 다음과 같다.
put_many(items)
parameter description remark items
아이템을 담고 있는 list
required
put_many
메서드는 성공적으로 실행 시 해당 아이템들을 성공적으로 집어넣겠다는 약속을, 25개를 초과하는 데이터를 전달했다면 오류를 반환한다.
insert
메서드― 데이터베이스에 아이템을 저장하되, 이미 존재한다면 에러를 일으킨다.
insert
메서드는 다음과 같다.
insert(data: typing.Union[dict, list, str, int, float, bool], key:str = None)
parameter description remark data
아이템으로서 저장하고자 하는 데이터 required key
아이템을 식별하기 위한 값 required
insert
메서드는 성공적으로 실행 시 저장한 아이템을, 인자로 전달된 key
라는 key 값을 가진 아이템 존재 시 에러를 반환한다.
get
메서드― key를 통해 데이터베이스로부터 아이템을 가져온다.
get
메서드는 다음과 같다.
get(key: str)
parameter description remark key
아이템을 식별하기 위한 값 required
get
메서드는 인자로 전달된 key
라는 key 값을 가진 아이템이 있다면 그것을 dict
자료형 형태로 반환하며, 그렇지 않다면 None
을 반환한다.
fetch
메서드― 필터를 통해 데이터베이스로부터 아이템들을 가져온다.
fetch
메서드는 다음과 같다.
fetch(query=None, buffer=None, pages=10)
parameter description remark query
dict
형태의 단일 쿼리 또는 그것의 리스트; 생략 시 최대 1MB의 전체 아이템들optional buffer
페이지 당 반환할 아이템 수 optional page
반환할 페이지 수 optional
dict
내의 쿼리는 AND 관계에 있으며list
내의 각각의 쿼리는 OR 관계에 있다.
어떤 속성 attr
에 대하여 다음과 같은 쿼리를 query
의 key 값으로 사용할 수 있다.
query | key | description |
---|---|---|
equal | attr | 해당 속성이 value 값과 동일한 아이템 |
not equal | attr?ne | 해당 속성이 value 값과 동일하지 않은 아이템 |
less than | attr?lt | 해당 속성이 value 값보다 작은 아이템 |
greater than | attr?gt | 해당 속성이 value 값보다 큰 아이템 |
less than or equal | attr?lte | 해당 속성이 value 값보다 작거나 같은 아이템 |
greater than or equal | attr?gte | 해당 속성이 value 값보다 크거나 같은 아이템 |
prefix | attr?pfx | 해당 속성이 value 값으로 시작하는 아이템 |
range | attr?r | 해당 속성이 value 값에 해당하는 범위 내에 있는 아이템 |
contains | attr?contains | 해당 속성이 value 값을 포함하는 아이템 (문자열 한정) |
not contains | attr?not_contains | 해당 속성이 value 값을 포함하지 않는 아이템 (문자열 한정) |
왜 마지막 두 녀석만 안 줄여 쓰는거지?
fetch
메서드는 쿼리를 만족하는 아이템들의 제너레이터를 반환한다.
delete
메서드― 데이터베이스로부터 아이템을 삭제한다.
delete
메서드는 다음과 같다.
delete(key: str)
parameter description remark key
아이템을 식별하기 위한 값 required
delete
메서든는 인자로 전달된 key
라는 key 값을 가진 아이템의 존재 여부와 무관하게 None
을 반환한다.
update
메서드― 데이터베이스 내의 아이템을 업데이트한다.
update
메서드는 다음과 같다.
update(updates:dict, key:str)
parameter description remark updates
업데이트할 내용을 담은 dict
required key
아이템을 식별하기 위한 값 required
업데이트에는 다음과 같은 연산이 사용된다.
parameter | description | remark |
---|---|---|
set | key 값으로 사용된 속성이 있을 경우 수정; 없을 경우 추가 | 일반적인 key-value 쌍 사용 |
increment | 숫자 속성에 대하여 value 값으로 전달된 수만큼 증가 (default는 value==1 ) | base.util.increment(value) |
append | 리스트 속성에 대하여 value 값으로 전달된 값을 맨 뒤에 추가 | base.util.append(value) |
prepend | 리스트 속성에 대하여 value 값으로 전달된 값을 맨 앞에 추가 | base.util.prepend(value) |
trim | 이것을 value 로 전달한 속성 제거 | base.util.trim() |
update
메서드는 성공적으로 실행 시 None
을, 그렇지 않다면 오류를 반환한다.
Project ID project_id
와 데이터베이스 base_name
에 대하여 HTTP API를 통해 Deta Base를 사용할 땐 다음과 같은 URL을 사용한다.
https://database.deta.sh/v1/{project_id}/{base_name}
그리고 Project Key project_key
에 대하여 요청에 다음과 같은 헤더를 추가해야 한다.
'X-API-Key: project_key'
'Content-Type: application/json'
put
엔드포인트― 데이터베이스에 아이템을 저장하되, 이미 존재한다면 이를 업데이트한다.
PUT /items
payload type description remark items
array
저장할 아이템의 리스트 required
207 Multi Status
{ "processed": { "items": [ // items which were stored ] }, "failed": { "items": [ // items failed because of internal processing ] } }
insert
엔드포인트― 데이터베이스에 아이템을 저장하되, 이미 존재한다면 에러를 일으킨다.
POST /items
payload type description remark items
array
저장할 아이템의 리스트 required
201 Created
{ "key": {key}, // auto generated key if key was not provided in the request "field1": "value1", // the rest of the item }
get
엔드포인트― key를 통해 데이터베이스로부터 아이템을 가져온다.
GET /items/{key}
UML parameter type description remark key
string
아이템을 식별하기 위한 값 required
200 OK
{ "key": {key}, // the rest of the item }
query
엔드포인트― 필터를 통해 데이터베이스로부터 아이템들을 가져온다.
POST /query
payload type description remark query
list
dict
형태의 쿼리의 리스트optional limit
int
반환할 값의 개수 optional last
string
지난 응답 페이지의 마지막 key optional
200 OK
{ "paging": { "size": 5, // size of items returned "last": "adfjie" // last key seen if paginated, provide this key in the following request }, "items": [ { "key": {key}, // rest of the item }, // rest of the items ] }
delete
엔드포인트― 데이터베이스로부터 아이템을 삭제한다.
DELETE /items/{key}
UML parameter type description remark key
string
아이템을 식별하기 위한 값 required
200 OK
{ "key": {key} }
update
엔드포인트― 데이터베이스 내의 아이템을 업데이트한다.
PATCH /items/{key}
payload type description remark set
object
업데이트 또는 생성할 속성 optional increment
object
값을 증감할 속성 optional append
object
맨 뒤에 값을 추가할 리스트 속성 optional prepend
object
맨 앞에 값을 추가할 리스트 속성 optional delete
string array
제거할 속성 optional
200 OK
{ "key": {key}, "set": { // identical to the request }, "delete": ["field1", ..] // identical to the request }
마지막으로 웹 사이트 상의 UI를 사용하는 방법이 있는데, 이건 아직 정식 지원은 아니다.
이에 대한 건 관심 있다면 문서를 참고하도록 하자.
공식 Python 튜토리얼을 보며 코드를 작성해보았다.
다만, 공식 문서는 Flask를 사용하였으나 나는 FastAPI로 작성하였다.
FastAPI로 인해 Deta를 찾아보기 시작한 만큼 나는 FastAPI와 함께 사용하게 될 확률이 높기에...
Project Key는 코드 안에 포함해서 좋을 게 없기에 dotenv
를 통해 .env
파일에서 읽어오기로 하였다.
따라서 다음과 같이 필요한 라이브러리를 설치했다.
$ pip3 install deta python-dotenv fastapi uvicorn
짧은 예제이므로 단일 파일 내에서 작업해도 되지만 보다 더 확장성을 고려해 작성하였다.
프로젝트 구조는 다음과 같다.
.
├── routers/
│ ├── __init__.py
│ └── users.py
├── .env
├── database.py
└── main.py
먼저 앱을 구성하기 위한 초기 설정을 한다.
main.py
from fastapi import FastAPI app = FastAPI( title='DETA Base Test', description='Deta Base test with FastAPI')
database.py
from deta import Deta import os from dotenv import load_dotenv load_dotenv(verbose=True) PROJECT_KEY = os.getenv('PROJECT_KEY') PROJECT_ID = os.getenv('PROJECT_ID') BASE_NAME = os.getenv('BASE_NAME') deta = Deta(PROJECT_KEY) db = deta.Base(BASE_NAME)
Project ID나 Base Name까지 환경변수로 설정할 필요는 없지만 일반화를 위해 위와 같이 작성하였다.
예제에서는 다음과 같은 속성을 가진 User
데이터를 저장하는 데이터베이스를 가정한다.
{
"name": str,
"age": int,
"hometown": str
}
따라서 다음과 같은 Pydantic Class를 생성하였다.
routers/users.py
from pydantic import BaseModel from typing import Optional class User(BaseModel): name: str age: int hometown: str key: Optional[str] = None
그리고 POST 요청을 받아 put
메서드를 통해 User
를 생성하는 엔드포인트를 작성하였다.
routers/users.py
from fastapi import APIRouter, HTTPException, status from pydantic import BaseModel from typing import Optional from ..database import db class User(BaseModel): name: str age: int hometown: str key: Optional[str] = None router = APIRouter(prefix='/users', tags=['users']) @router.post('', status_code=status.HTTP_201_CREATED) def create_user(user: User): if user.key is None: del(user.key) created = db.put(user.dict()) return created
main.py
를 다음과 같이 수정하여 router를 적용한다.
main.py
from fastapi import FastAPI from .routers import users tags_metadata = [ { 'name': 'users', 'description': 'Operations with users', } ] app = FastAPI( title='DETA Base Test', description='Deta Base test with FastAPI', openapi_tags=tags_metadata) app.include_router(users.router)
FastAPI의 docs를 통해 정상적으로 작동하는 것을 확인하면,
다음으로 GET 요청을 받아 get
메서드를 통해 User
를 조회하는 엔드포인트를 작성하였다.
routers/users.py
# 생략 @router.get('', response_model=User) def get_user(key: str): user = db.get(key) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail='User not found') return user
FastAPI의 docs를 통해 정상적으로 작동하는 것을 확인하면,
PUT 요청을 받아 put
메서드를 통해 User
를 수정하는 엔드포인트를 작성하였다.
routers/users.py
# 생략 @router.put('/{key}', response_model=User) def update_user(key: str, user: User): user.key = key updated = db.put(user.dict()) return updated
FastAPI의 docs를 통해 정상적으로 작동하는 것을 확인하면,
DELETE 요청을 받아 delete
메서드를 통해 User
를 삭제하는 엔드포인트를 작성하였다.
routers/users.py
# 생략 @router.delete('/{key}') def delete_user(key: str): db.delete(key)
FastAPI의 docs를 통해 정상적으로 작동하는 것을 확인하면,
최종적으로 작성된 엔드포인트는 다음과 같다.
routers/users.py
from fastapi import APIRouter, HTTPException, status from pydantic import BaseModel from typing import Optional from ..database import db class User(BaseModel): name: str age: int hometown: str key: Optional[str] = None router = APIRouter(prefix='/users', tags=['users']) @router.post('', status_code=status.HTTP_201_CREATED) def create_user(user: User): if user.key is None: del(user.key) created = db.put(user.dict()) return created @router.get('', response_model=User) def get_user(key: str): user = db.get(key) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail='User not found') return user @router.put('/{key}', response_model=User) def update_user(key: str, user: User): user.key = key updated = db.put(user.dict()) return updated @router.delete('/{key}') def delete_user(key: str): db.delete(key)
확실히, 사람들이 PaaS를 왜 이용하는지 알 것 같다.
그리고 이거 데이터를 얼마나 쌓아놓든 무료인 거잖아?
정말 비용 걱정 없이 순수하게 프로젝트에 집중하기에 용이한 것 같다.
Deta의 다른 서비스도 빨리 훑어봐야지ㅎ
작성한 코드는 여기에서 확인할 수 있다.