Go Lang으로 간단하게 CRUD REST API 만들기 3편 - Bolt DB 연결
1. 개요
이번 포스트로 DB Bolt에 CRUD 작업을 할 수 있습니다.
1편과 2편이 존재합니다, 아래 링크를 먼저 봐주세요
이전 포스트에서는 DB를 사용하지 않고 메모리에 담아두고 진행했었습니다
그렇기에 재시작시 데이터가 유지되지 않았습니다, 이번에는 DB를 추가하여 데이터가 유지되게 수정합시다.
Bolt
이번에 사용하는 Bolt는 단순하게 Key/Value 형태로 값을 저장하는 고를 위한 내장 데이터베이스 모듈입니다.
Bolt는 GitHub에서 13.6K의 스타를 받았고 오랫동안 여러 사람들이 사용하면서 안정성이 인증된 모듈입니다.
MySQL과 같은 RDBMS도 연결할 수 있지만 간단하게 하는 것이 목적이기에 Bolt를 선택했습니다.
2. 코드
의존성 추가
아래 명령어로 Bolt를 설치해줍시다.
go get github.com/boltdb/bolt
Utils 생성
먼저 반복되는 작업은 처리할 함수들을 모아둔 utils.go를 만들어줍시다.
utils/utils.go
package utils
import (
"encoding/binary"
"log"
)
// HandleErr : 에러 발생시 Panic 으로 Log 출력
func HandleErr(err error) {
if err != nil {
log.Panic(err)
}
}
// IntToBytes : int 타입을 []byte 타입으로 변경 후 반환
func IntToBytes(integer int) []byte {
bytes := make([]byte, 8)
binary.BigEndian.PutUint64(bytes, uint64(integer))
return bytes
}
// BytesToInt : []byte 타입을 int 타입으로 변경 후 반환
func BytesToInt(bytes []byte) int {
return int(binary.BigEndian.Uint64(bytes))
}
Bolt DB 연결
프로젝트 폴더에서 db 폴더를 만드신 후 안에 db.go 파일도 생성해 줍시다.
db/db.go
package db
import (
"github.com/boltdb/bolt"
"rest-api.com/utils"
)
// dbName : DB 명으로 해당 이름으로 파일이 생성됩니다
// championBucket : Bucket 은 RDBMS 의 테이블과 같은 역할을 합니다.
const (
dbName = "champion.db"
championsBucket = "champion"
)
var db *bolt.DB
// DB : db 커넥션
func DB() *bolt.DB {
if db == nil {
// bolt.Open : dbName 으로 데이터베이스 열기, 뒤에 0600 은 파일 권한
dbPointer, err := bolt.Open(dbName, 0600, nil)
db = dbPointer
utils.HandleErr(err)
// Update : 읽기-쓰기 트랜잭션 실행
err = db.Update(func(tx *bolt.Tx) error {
// CreateBucketIfNotExists : 해당 버켓명이 없으면 버켓 생성
_, err := tx.CreateBucketIfNotExists([]byte(championsBucket))
utils.HandleErr(err)
return err
})
utils.HandleErr(err)
}
return db
}
// Close : db connection 종료
func Close() {
DB().Close()
}
이제 DB()를 실행하면 프로젝트에 champions.db가 생성됩니다.
Bolt CRUD 생성
이제 db/db.go에 마저 CRUD 로직을 추가합니다.
// SaveChampion : 챔피언을 DB에 추가
func SaveChampion(name string) {
utils.HandleErr(DB().Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(championsBucket))
// championBucket 의 다음 값 가져오기
id, _ := bucket.NextSequence()
// 저장
err := bucket.Put(utils.IntToBytes(int(id)), []byte(name))
return err
}))
}
// ReadChampions : DB에 전체 챔피언 가져오기
func ReadChampions() map[int]string {
m := make(map[int]string)
utils.HandleErr(DB().View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
bucket := tx.Bucket([]byte(championsBucket))
cursor := bucket.Cursor()
// 커서로 bucket 내부를 돌며 map 에 저장
for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
m[utils.BytesToInt(key)] = string(value)
}
return nil
}))
return m
}
// UpdateChampion : DB에 특정 아이디 이름 업데이트
func UpdateChampion(id int, name string) {
utils.HandleErr(DB().Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(championsBucket))
// id가 존재하는 값이면 값 덮어씌우기
err := bucket.Put(utils.IntToBytes(id), []byte(name))
return err
}))
}
// DeleteChampion : DB에 특정 아이디로 데이터 삭제
func DeleteChampion(id int) {
utils.HandleErr(DB().Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(championsBucket))
// id로 데이터 삭제
err := bucket.Delete(utils.IntToBytes(id))
return err
}))
}
main.go 수정
이제 champion 구조체와 슬라이스, newID 함수는 이제 사용하지 않으니 지워줍시다.
// 아래 내용들 지우기
type champion struct {
ID int `json:"id"`
Name string `json:"name"`
}
var champions []*champion
func newID() int {
if len(champions) > 0 {
return champions[len(champions)-1].ID + 1
} else {
return 1
}
}
func main()에는 가장 defer db.close()를 위에 추가합니다.
func main() {
defer db.Close()
...
}
defer는 프로세스 종료 시 실행됩니니다, 이걸로 프로세스 종료시 자동으로 bolt connection을 종료합니다.
db.go 연결
db/db.go에 만들어둔 함수들을 main.go의 getChampions, postChampions, patchChampions, deleteChampions를 각각 연결합니다.
func getChampions(rw http.ResponseWriter, r *http.Request) {
// 데이터 가져오기
json.NewEncoder(rw).Encode(db.ReadChampions())
}
func postChampions(rw http.ResponseWriter, r *http.Request) {
// request 값을 받을 구조체 변수 선언
var requestBody requestBody
utils.HandleErr(json.NewDecoder(r.Body).Decode(&requestBody))
// 데이터 생성
db.SaveChampion(requestBody.Name)
// 생성 완료 후 Http code 201 created 로 response 해더 설정
rw.WriteHeader(http.StatusCreated)
return
}
func patchChampions(rw http.ResponseWriter, r *http.Request) {
// request 값을 받을 구조체 변수 선언 및 할당
var requestBody requestBody
json.NewDecoder(r.Body).Decode(&requestBody)
// Router 에서 id로 정의 한 값은 mux 를 통해 받을 수 있음
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
// 데이터 업데이트
db.UpdateChampion(id, requestBody.Name)
rw.WriteHeader(http.StatusOK)
return
}
func deleteChampions(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
// 데이터 삭제
db.DeleteChampion(id)
rw.WriteHeader(http.StatusOK)
return
}
결과
의문점 문의 및 오타 피드백은 언제나 환영입니다.
'Go Lang > Study' 카테고리의 다른 글
[GoLang] 자료형을 초과한 큰 수를 계산해보자 (0) | 2023.09.01 |
---|---|
[GoLang] 정규식으로 URL 분석기 만들기 (0) | 2023.08.30 |
GoLang으로 느낌 있게 채팅 방 만들어보자 (0) | 2023.08.27 |
Go로 CRUD REST API 만들기 (2) (0) | 2023.07.06 |
Go로 CRUD REST API 만들기 (1) (0) | 2023.07.05 |