Golang, ๊ทธ๋๋ค์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ - 2. MongoDB Go Driver ์ถ์ํ
Golang, ๊ทธ๋๋ค์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ - 2. MongoDB Go Driver ์ถ์ํ ๊ด๋ จ
ํด๋ก๋ฐ๋
ธํธ V1์ ์ฃผ์ ์๋ฒ๋ค์ Golang(v1.14)์ผ๋ก ๊ฐ๋ฐ๋์๊ณ MongoDB๋ฅผ ๋ฉ์ธ DB๋ก ์ฌ์ฉํ๊ณ ์์ต๋๋ค. MongoDB Go Driver (mongodb/mongo-go-driver
)๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, ์ด๋ DB ์ฟผ๋ฆฌ(raw query)๋ฅผ ๊ฐํธํ๊ฒ ์์ฑํ ์ ์๊ฒ ํ๊ณ struct์ ๋งคํ์ ๋์์ฃผ๋ ๋งคํผ ํ์์ DB ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๊ณ ์๊ฐํ์๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ ๊ธ์์ ์ด์ผ๊ธฐํ error์ ๋ํ ๊ณ ๋ฏผ ์ค DB ๋ ์ด์ด์์ ๊ณ ๋ฏผ์ด ํ์ํ ๋ถ๋ถ์ ๋ค์๊ณผ ๊ฐ์์ต๋๋ค.
- ๋ก๊ทธ๋ ์ด๋์ ๋จ๊ฒจ์ผ ํ๋๊ฐ? ๋จ๊ธด๋ค๋ฉด ์ด๋ค ๋ ๋ฒจ๋ก ๋จ๊ฒจ์ผ ํ๋๊ฐ?
์ด๋ค document์ ๋ํ find๋ฅผ ์คํํ๋๋ฐ ํด๋น document๊ฐ ์๋ ๊ฒฝ์ฐ, ์ด๊ฒ์ด ๋ฐ์ํ๋ฉด ์ ๋๋ ์ํฉ์ธ์ง ์ ์์ ์ธ ์ํฉ์ธ์ง๋ ์์ ๋ ์ด์ด์์ ๊ฒฐ์ ๋ฉ๋๋ค. ๊ทธ๋ ๋ค๋ฉด DB ๋ ์ด์ด์์๋ ๋ก๊ทธ๋ฅผ Info, Warn ์ค ์ด๋ค ๋ ๋ฒจ๋ก ๋จ๊ฒจ์ผ ํ ๊น์? DB ๋ ์ด์ด์์๋ ๋ก๊ทธ ๋ ๋ฒจ์ ๊ฒฐ์ ํ ์๊ฐ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด DB ๋ ์ด์ด์์๋ Info ๋ ๋ฒจ๋ก ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ์์ ํจ์์์ error ์ฌ๋ถ๋ฅผ ํ๋จํ์ฌ Error ๋ ๋ฒจ๋ก ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ผ ํ ๊น์? ํ์ง๋ง ๊ทธ๋ฌ๋ฉด ๋ก๊ทธ๊ฐ ์ค๋ณต๋๊ณ ํจ์จ์ ์ด์ง ๋ชปํ ๊ฒ ๊ฐ์์ต๋๋ค.
์ด ๋ฐ์๋ DB์์ ๋ค์ํ error(์ฐพ์ ์ ์์, ์ค๋ณต๋ ํค, ์๊ฐ ์ด๊ณผ, ๋์ฝ๋ฉ ์ค๋ฅ ๋ฑ)๊ฐ ๋ฐ์ํ ์ ์๋๋ฐ ์์ ๋ ์ด์ด์์๋ ์ด๋ฅผ ์ด๋ป๊ฒ ํ๋ณํ ์ง์ ๋ํ ๊ณ ๋ฏผ๋ ํ์ํ์ต๋๋ค.
๊ธฐ์กด ๋ฌธ์ ์
V1 ์๋ฒ์์ DB ๋ ์ด์ด ๊ตฌ์กฐ๋ง ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
ํด๋ก๋ฐ๋ ธํธ V1์ ๋ง๋ค ๋๋ ์๋น์ค๋ฅผ ๋ง๋ค์ด๋ด๋ ์๋๊ฐ ์ค์ํ ์ํฉ์ด์ด์ ์ผ๊ด์ฑ ์์ด ๊ฐ ์๋ฒ์์ ๊ฐ์์ ๋ฐฉ์์ผ๋ก MongoDB Go Driver๋ฅผ ์ฌ์ฉํ๊ณ ์์๋๋ฐ, ์ด๋ฌํ ๊ตฌ์กฐ๋ฅผ ์์ ๊ณ ๋ชจ๋ ์๋ฒ๊ฐ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ผ๊ด์ฑ ์๊ฒ ๋์ํ๊ฒ ๊ฐ์ ํ๊ณ ์ถ์์ต๋๋ค.
document๋ฅผ ๊ฐ์ ธ์ค๋ ์ฝ๋์ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์์ต๋๋ค.
- DB collection ๊ฐ์ฒด ์์ฑ
- ์๊ฐ ์ด๊ณผ(timeout) ์ค์
- ์ฟผ๋ฆฌ ์์ฑ(filter, update ๋ฑ)
- slow ์ฟผ๋ฆฌ ๋ก๊น
- document ๋์ฝ๋ฉ
3๋ฒ์ ์ ์ธํ ์ฝ๋๋ ๋๋ถ๋ถ ๊ฐ ์๋ฒ์์ ์์ฑํด ์ฝ๋๊ฐ ์ค๋ณต๋์ด ์์๊ณ , ๊ทธ์ ๋ฐ๋ผ ์ฝ๋์ ์์ด ๋ง์์ง๊ณ ๋์์ด ๋ค๋ฅด๊ฑฐ๋ ๋๋ฝ๋ ๊ฒฝ์ฐ๋ ๋ง์ ๊ณตํตํ์ ๋ํ ๊ณ ๋ฏผ๋ ํ์ต๋๋ค.
์ฝ๋ ์
type MyCollectionManager struct {
authSource string
collection string
client *mongoDB.Client
}
// 1. collection ๊ฐ์ฒด ์์ฑ
func MyCollection(client *mongo.Client) *MyCollectionManager {
manager := &MyCollectionManager{}
dbConfig := config.GetDatabaseConfig()
manager.authSource = dbConfig.DatabaseName
manager.collection = "myCollection"
manager.client = client
return manager
}
// ์ค์ ์ฟผ๋ฆฌ ์กฐ๊ฑด ์ธํ
๋ฐ ๋์ฝ๋ฉ
func (manager *MyCollectionManager) GetDocument() (*MyDocument, int, error) {
document := &MyDocument{}
// 3. ์ฟผ๋ฆฌ ์กฐ๊ฑด ์ธํ
filter := bson.M{}
startTime := time.Now()
singleResult := manager.client.FindOne(manager.authSource, manager.collection, &filter)
// 4. slow ์ฟผ๋ฆฌ ๋ก๊น
if time.Since(startTime) > slowQueryLimit {
log.Error(...)
}
if singleResult == nil {
msg := fmt.Sprintf("FindOne document is failed")
log.Error(msg)
return document, ERROR_INTERNAL_SERVER, errors.New(msg)
}
// 5. ๋์ฝ๋ฉ
var document MyDocument
if err := singleResult.Decode(&document); err != nil {
log.Error(err)
if err == mongo.ErrNoDocuments {
return document, ERROR_NOT_FOUND, err
}
return &document, ERROR_INTERNAL_SERVER, err
}
return &document, notecommon.SUCCESS, nil
}
// 2. ์๊ฐ ์ด๊ณผ ์ค์ ๋ฐ ์ฟผ๋ฆฌ ์ํ
func (client *mongo.Client) FindOne(databaseName string, collectionName string, filter *bson.M) *mongo.SingleResult {
collection := client.getCollection(databaseName, collectionName)
if collection != nil {
ctx, ctxCancel := context.WithTimeout(context.Background(), timeoutLimit)
defer ctxCancel()
res := collection.FindOne(ctx, filter)
return res
}
return nil
}
๊ฐ์ ๋ฐฉํฅ
์ ๋ ๊ฐ์ ๋ฐฉํฅ์ ๋ค์๊ณผ ๊ฐ์ด ์ ํ์ต๋๋ค.
- DB ๋ ์ด์ด์์๋ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ์ง ์๊ณ DB ์ฟผ๋ฆฌ ์คํ ์ ๋ณด์ error๋ฅผ ๋ํํ์ฌ ๋ฐํํ๋ค.
error
๊ฐ์ดnil
(=Null)์ด๋ฉด, ๋ฐํ๋๋ ๊ฐ(document)์nil
์ด ์๋๋ฉฐ ์ฟผ๋ฆฌ ์ฑ๊ณต์ ๋ณด์ฅํ๋ค.- singleResult, Cursor ๋ฑ์ ๋์ฝ๋ฉํ๋ ์ค๋ณต ์ฝ๋๋ฅผ ๊ณตํตํํ๋ค.
- ์์ ๋ ์ด์ด์์๋ MongoDB Go Driver์ error๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์๋๋ผ DB ๋ ์ด์ด ๋ด๋ถ์์ ์ ์ํ error๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ก๊ทธ ๋ ๋ฒจ์ ํ๋จํด ๋ก๊ทธ๋ฅผ ๋จ๊ธด๋ค.
๋ฐฉํฅ์ ์ด๋ ๊ฒ ์ ํ ์ด์ ์ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํ๋์ฉ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
DB ์ฟผ๋ฆฌ ์คํ ์ ๋ณด์ error๋ฅผ ๋ํํ์ฌ ๋ฐํ
๊ธ์ ์ด๋ฐ์์ ์ธ๊ธํ ๋๋ก, DB ๋ ์ด์ด์์๋ error์ ์ฌ๊ฐ์ฑ์ ํ๋จํ ์ ์์ต๋๋ค. ์ค์ ๋ก ์ด๋ค ๋ ๋ฒจ์ error์ธ์ง๋ ๋น์ฆ๋์ค ๋ก์ง์ ํฌํจํ ์์ ๋ ์ด์ด๊น์ง ์ฌ๋ผ๊ฐ์ผ๋ง ์ ์ ์๊ธฐ ๋๋ฌธ์, DB ๋ ์ด์ด๋ ๋ก๊ทธ๋ฅผ ๋จ๊ฐ์ง ์๋๋ก ์ค๊ณ ๋ฐฉํฅ์ ์ก์์ต๋๋ค.
DB ๋ ์ด์ด์์ ๋ก๊ทธ๋ฅผ ๊ธฐ๋กํ์ง ์๊ธฐ๋ก ๊ฒฐ์ ํ์ผ๋ฏ๋ก, DB ๋ ์ด์ด๋ ์์ ๋ ์ด์ด์ error ์ ๋ณด๋ฅผ ์ ํํ๊ฒ ์ ๋ฌํด์ผ ํ์ต๋๋ค. ๊ทธ๋ฌ๋ฉด error์ ์ด๋ค ์ ๋ณด๋ฅผ ํฌํจํด์ผ ํ ์ง ์๊ฐํด๋ณด์์ต๋๋ค.
์ฐ์ , DB์์ ๋ฐ์ํ ์ ์๋ error๋ฅผ ํ์ ํ์ต๋๋ค.
- ์๊ฐ ์ด๊ณผ(timeout)
- ์ฐพ์ ์ ์์(not found)
- ์ค๋ณต๋ ํค(duplicated key)
- ๋คํธ์ํฌ ์ค๋ฅ(network)
- ์ฐ๊ฒฐ ๋๊น(disconnect)
MongoDB Go Driver์์ ์ ์ํ error๋ ์์ 5๊ฐ ์ธ์๋ ๋ ์์ง๋ง ํฌ๊ฒ ์์ ๊ฐ์ด ๋ถ๋ฅํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋์ฝ๋ฉํ๋ ์ค ๋ฐ์ํ ์ค๋ฅ๋ ๊ณ ๋ คํ์ฌ ๋์ฝ๋ฉ error๊น์ง ์ด 6๊ฐ์ error๋ฅผ ์ถ๋ ธ์ต๋๋ค.
๊ทธ ๋ค์์๋ ์ด๋ค ์ ๋ณด๋ฅผ error์ ํฌํจํ ์ง ์ ํ์ต๋๋ค. DB ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ๋๋ฒ๊น ์ ์ด๋ค ์ ๋ณด๊ฐ ํ์ํ ์ง ๊ณ ๋ฏผํ์ต๋๋ค.
- ํํฐ๋ง ์กฐ๊ฑด
- update/insert ๋ด์ฉ
- ๋์ collection
- Mongo ๋ด๋ถ ์ค๋ฅ ๋ฉ์์ง
์์ ๊ฐ์ ์ ๋ณด๊ฐ ํ์ํ๋ค๊ณ ์๊ฐํ๊ณ , ํด๋น ๋ด์ฉ์ ๋ด์ ์ ์๋ struct
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ์ต๋๋ค.
package errorUtils
type basicQueryInfo struct {
collection string
filter interface{}
update interface{}
doc interface{}
}
type notFoundError struct {
basicQueryInfo
}
type duplicatedKeyError struct {
basicQueryInfo
error
}
/* ... ์๋ต ... */
๋ถ๊ฐ ํจ์
func NotFoundError(col string, filter, update, doc interface{}) error {
err := ¬FoundError{}
err.setBasicError(col, filter, update, doc)
return err
}
/* ... ์๋ต ... */
func (err *basicQueryInfo) setBasicError(col string, filter, update, doc interface{}) {
err.filter = filter
err.collection = col
err.update = update
err.doc = doc
}
๊ทธ๋ฆฌ๊ณ ๋ก๊ทธ๋ฅผ ๋จ๊ธธ ๋ ์์์ ์ ์ํ struct์ ์ ๋ณด๊ฐ ๋ณด์ด๋๋ก ๋ฌธ์์ด์ ์์ฑํ์ต๋๋ค.
func (e *notFoundError) Error() string {
return fmt.Sprintf("%s not found. ", e.collection) + getBasicInfoErrorMsg(e.basicQueryInfo)
}
/* ... ์๋ต ... */
func getBasicInfoErrorMsg(e basicQueryInfo) string {
msg := "| {query info: "
if e.filter != nil {
msg += fmt.Sprintf(" filter: %+v", e.filter)
}
if e.update != nil {
msg += fmt.Sprintf(", update: %+v", e.update)
}
if e.doc != nil {
msg += fmt.Sprintf(", doc: %+v", e.doc)
}
msg += "}"
return msg
}
์ด ์ฝ๋์ error์ ๋ก๊ทธ๋ ๋ค์๊ณผ ๊ฐ์ด error ์ข
๋ฅ, Mongo ์ค๋ฅ ๋ด์ฉ, ์ฟผ๋ฆฌ ๋ด์ฉ
์ ๊ตฌ์กฐ๋ก ๋จ๊ฒจ์ง๋๋ค.
// accounts collection์์ ํน์ document๋ฅผ ์ฐพ์ง ๋ชปํจ
"accounts not found. | {query info: filter: map[account_id: 123]}"
error
๊ฐ์ด nil
์ด๋ฉด, ๋ฐํ๋๋ ๊ฐ(document ๋ฐ์ดํฐ)์ nil
์ด ์๋๋ฉฐ ์ฟผ๋ฆฌ ์ฑ๊ณต์ ๋ณด์ฅ
Golang์ ์ฌ์ฉํ๋ฉด์ ์ธ๋ถ API๋ฅผ ํธ์ถํ ๋ net/http ํจํค์ง๋ฅผ ์์ฃผ ์ฌ์ฉํ๋๋ฐ, error
๊ฐ์ด nil
์ด๋ฉด ๋ฐํ ๊ฐ์ด ํญ์ non-nil
์ด๋ผ๋ ์ ์ด ๊ด์ฐฎ๋ค๊ณ ์๊ฐํ์ต๋๋ค. ๊ฐ๋ฐ์๋ error
๊ฐ์ด nil
์ด๋ฉด ๋ฐํ ๊ฐ์ ๊ฒ์ฆ ์์ด ์ฌ์ฉํ ์ ์์์ต๋๋ค.
net/http/client.go
// net/http/client.go
// If the returned error is nil, the Response will contain a non-nil
// Body which the user is expected to close.
// ...
func (c *Client) Do(req *Request) (*Response, error) {
return c.do(req)
}
๊ทธ๋์ ์ ๋ ์ด๋ฌํ ๊ฐ๋
์ ๋์
ํ๊ธฐ๋ก ํ์ต๋๋ค. ๋ฐํ๋ error
๊ฐ์ด nil
์ด๋ฉด, ๋ฐํ๋ document ๋๋ ๊ฒฐ๊ณผ๋ nil
์ด ์๋๋ฉฐ ๋์์ ์ฟผ๋ฆฌ ์ฑ๊ณต์ ๋ณด์ฅํ๋๋ก ํ์ต๋๋ค.
์ด๋ฌํ ๊ฒฐ์ ์ ํ ์ฃผ์ํ ์ด์ ๋ document๋ฅผ ์ฐพ์ ์ ์๋ ์ํฉ ๋๋ฌธ์ด์์ต๋๋ค. find์ ๊ฒฐ๊ณผ๋ก document๋ฅผ ์ฐพ์ ์ ์๋ ๊ฒฝ์ฐ, ์ฟผ๋ฆฌ๊ฐ ์คํจํ์ง๋ ์์๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ๊ฒฐ๊ณผ๋ฌผ์ nil
๋ก, error
๋ nil
๋ก ๋ฐํํ ์ ์์ต๋๋ค.
์ฝ๋ ์
func FindSomething() (*MyDocument, error) {
if not found {
return nil, nil
}
}
ํ์ง๋ง ์ ๋ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์ด ๋ฐฉ์์ ์ ํธํ์ง ์์์ต๋๋ค.
- ๋ง์ฝ
nil
์ด ๋ฐํ๋๋ค๋ฉด ์์ ํจ์์์ document๊ฐnil
์ธ์ง ํ์ธํด์ผ ํ๋ค. - MongoDB Go Driver์์๋ document๋ฅผ ์ฐพ์ ์ ์๋ ์ํฉ์ error(
ErrNoDocuments
)๋ก ์ ์ํ๋ค.
mongo/single_result.go
// single_result.go
// ErrNoDocuments is returned by SingleResult methods when the operation that created the SingleResult did not return
// any documents.
var ErrNoDocuments = errors.New("mongo: no documents in result")
๋ฌผ๋ก document๋ฅผ ์ฐพ์ ์ ์๋ ์ํฉ์ error๋ก ๋ฐํํ๋ ๊ฒฝ์ฐ๋ ํ์ธํด์ผ ํฉ๋๋ค.
ํ์ง๋ง document๋ฅผ ์ฐพ์ ์ ์๋ ์ํฉ์ error๋ก ์ทจ๊ธํ๋ค๋ฉด, ๊ตณ์ด error ๋ด์ฉ์ ํ์ธํ ํ์ ์์ด ์์ ๋ ์ด์ด๋ก ์ฌ๋ฆฌ๊ฑฐ๋ error ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ธฐ๋ง ํ๋ฉด ๋๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค.
๊ทธ๋์ ์ ๋ not found
๋ error๋ก ๋ณด๊ธฐ๋ก ํ๊ณ , error
๊ฐ์ด nil
์ธ ๊ฒฝ์ฐ์๋ ๊ฒฐ๊ณผ ๊ฐ์ nil
์ด ์๋๋ฉฐ ์ฟผ๋ฆฌ ์ฑ๊ณต์ ๋ณด์ฅํ๋ ๋ฐฉ์์ ๋์
ํ์ต๋๋ค.
singleResult
, Cursor
๋ฑ์ ๋์ฝ๋ฉํ๋ ์ค๋ณต ์ฝ๋๋ฅผ ๊ณตํตํ
MongoDB Go Driver๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ถํธํ ์ ์ ๋งค๋ฒ ๋์ฝ๋ฉ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค๋ ๊ฒ์ด์์ต๋๋ค.
์ฝ๋ ์
// find all 1
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
/* ... ์๋ต ... */
}
defer cursor.Close(ctx)
var results []MyDocument
for cursor.Next(ctx) {
var doc MyDocument
if err = cursor.Decode(&doc); err != nil {
/* ... ์๋ต ... */
}
results = append(result, doc)
}
// find all 2
var results []MyDocument
if err = cursor.All(context.TODO(), &results); err != nil {
/* ... ์๋ต ... */
}
// find one
singleResult := collection.FindOne(ctx, bson.M{})
var doc MyDocument
if err := singleResult.Decode(&doc); err != nil {
/* ... ์๋ต ... */
}
fmt.Println(doc)
์์ ๊ฐ์ ์ค๋ณต ์ฝ๋๋ฅผ ์ ๊ฑฐํ๊ณ ๊ณตํต ํจ์์์ ๋์ฝ๋ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํ์ต๋๋ค.
๋จผ์ , singleResult
๋์ฝ๋ฉ์ ๋์ฝ๋ฉ ํจ์๋ฅผ ํ ๋ฒ ๋ํํ๋ ๊ฒ์ผ๋ก ๊ฐ๋จํ ํด๊ฒฐํ ์ ์์์ต๋๋ค.
package mongo
var SingleResultErr = errors.New("single result is nil")
func EvaluateAndDecodeSingleResult(result *mongo.SingleResult, v interface{}) error {
if result == nil {
return SingleResultErr
}
if err := result.Decode(v); err != nil {
return err
}
return nil
}
ํ์ง๋ง ๋ฌธ์ ๋ Cursor ๋์ฝ๋ฉ์ด์์ต๋๋ค. ์ด๋ค ํ์ ์ ๋์ฝ๋ฉํด์ผ ํ๋์ง๋ ๋ฐํ์์ ๊ฒฐ์ ๋์ด, ๋์ ์ผ๋ก ์ถ๋ก ํ๋ ๋ฐฉ๋ฒ์ reflect ๋ฐ์ ์์๊ณ ๊ทธ๊ฒ๋ ์๋ฒฝํ์ง ์์์ต๋๋ค.
reflect๋ฅผ ์ฌ์ฉํ์ฌ ๋์ฝ๋ฉํ๋ ์
func (col *Collection) FindAll(requiredExample interface{}, filter interface{}, opts ...*options.FindOptions) (interface{}, error) {
/* ... ์๋ต ... */
cursor, err := col.findAll(ctx, filter, opts...)
if err != nil {
/* ... ์๋ต ... */
}
return DecodeCursor(cursor, GetInterfaceType(requiredExample)), nil
}
func DecodeCursor(cursor *mongo.Cursor, t reflect.Type) interface{} {
// ํ์
์ ๋ง์ถฐ slice ์์ฑ
slice := reflect.MakeSlice(reflect.SliceOf(t), 0, 10)
for cursor.Next(context.Background()) {
// struct ์ด๊ธฐํ
doc := reflect.New(t).Interface()
// ๋์ฝ๋ฉ
if err := cursor.Decode(doc); err != nil {
/* ... ์๋ต ... */
}
// ๋์ฝ๋ฉ ๊ฒฐ๊ณผ slice append
slice = reflect.Append(slice, reflect.ValueOf(doc).Elem())
}
// slice return
return slice.Interface()
}
func GetInterfaceType(v interface{}) reflect.Type {
var t reflect.Type
if xt, ok := v.(reflect.Type); ok {
t = xt
} else {
t = reflect.TypeOf(v)
}
return t
}
์ด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ ๋จ๊ณ๋ฅผ ๊ฑฐ์นฉ๋๋ค.
FindAll()
ํจ์์ slice๋ก ๋ฐํ๋ฐ์ ์์ struct ๊ฐ์ฒด๋ฅผ ๋ฃ์ผ๋ฉด ํด๋น ๊ฐ์ฒด๊ฐ ๋ด๊ธดinterface{}
๋ฅผ ๋ฐํ- ์์ ํจ์์์๋ ์ด๋ฅผ ํ ๋ฒ ๋ type assertion
reflect๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ ์ฝ๋๋ฅผ ๋ฐ๋ก ์ด์ฉํ๊ธฐ๊ฐ ์ฝ์ง ์์๊ณ , interface{}
๋ก ๋ฐํ๋๋ฏ๋ก ๋ค์ ํ์
์ ๋ณํํด์ผ ํ๋ ๋ถํธํจ์ด ์์์ต๋๋ค.
reflect ๋ฐฉ์ ์ฌ์ฉ ์
func find() {
// FindAll(Account ํ์
, ์ฟผ๋ฆฌ ์กฐ๊ฑด)
all, _ := m.FindAll(types.Account{}, bson.M{})
result := all.([]types.Account)
}
DecodeCursor()
ํจ์ ๋ด๋ถ์์ slice๋ฅผ ๋ง๋ค์ง ์๊ณ ๊ธฐ์กด์ ์ปค์ ๋์ฝ๋ฉ ๋ฐฉ์์ฒ๋ผ ์ธ๋ถ์์ slice๋ฅผ ๋ฐ์์ appendํ๋ ๋ฐฉ์์ ์๋ํด๋ณด์์ผ๋ ์ฝ์ง ์์๊ณ ๊ตฌ๊ธ๋ง์ผ๋ก๋ ํด๊ฒฐ์ฑ
์ ์ฐพ์ง ๋ชปํ์ต๋๋ค.
๊ทธ๋์ ๊ธฐ์กด MongoDB Go Driver์์๋ cursor.All()
์ ์ด๋ป๊ฒ ๊ตฌํ๋์ด ์๋์ง ํ์ธํด๋ณด์์ต๋๋ค.
mongo/
cursor.go
func (c *Cursor) All(ctx context.Context, results interface{}) error {
resultsVal := reflect.ValueOf(results)
if resultsVal.Kind() != reflect.Ptr {
return fmt.Errorf("results argument must be a pointer to a slice, but was a %s", resultsVal.Kind())
}
sliceVal := resultsVal.Elem()
if sliceVal.Kind() == reflect.Interface {
sliceVal = sliceVal.Elem()
}
if sliceVal.Kind() != reflect.Slice {
return fmt.Errorf("results argument must be a pointer to a slice, but was a pointer to %s", sliceVal.Kind())
}
elementType := sliceVal.Type().Elem()
var index int
var err error
defer c.Close(ctx)
batch := c.batch // exhaust the current batch before iterating the batch cursor
for {
sliceVal, index, err = c.addFromBatch(sliceVal, elementType, batch, index)
if err != nil {
return err
}
if !c.bc.Next(ctx) {
break
}
batch = c.bc.Batch()
}
if err = replaceErrors(c.bc.Err()); err != nil {
return err
}
resultsVal.Elem().Set(sliceVal.Slice(0, index))
return nil
}
:::
์ ๊ฐ ๊ตฌํํ ๋ฐฉ์๊ณผ ๋น์ทํ๊ฒ reflect๋ฅผ ์ฌ์ฉํ๊ณ ์์ง๋ง, ๋ค๋ฅธ ์ ์ ๋ง์ง๋ง ์ค์ด์์ต๋๋ค.
resultsVal.Elem().Set(sliceVal.Slice(0, index))
๋ด๋ถ์์ ์์ฑํ slice์ ๋ฐ์ดํฐ๋ฅผ ์ธ๋ถ์์ ๋ฐ์ slice์ ๋ด๋ ์์
์ธ๋ฐ, ์ด๋ ๊ฒ ์์ฑํ๋ฉด All()
๋ด๋ถ์ 2๊ฐ์ slice๊ฐ ์กด์ฌํ๊ฒ ๋ฉ๋๋ค. ์ด์ ๋ํด ๊ณต์ ๋ฌธ์์์๋ cursor.All()
์ ๋ฉ๋ชจ๋ฆฌ ์ด์ ๊ฐ๋ฅ์ฑ์ ์ค๋ช
ํ๊ณ ์์ต๋๋ค.
Memory
If the number and size of documents returned by your query exceeds available application memory, your program will crash. If you except a large result set, you should consume your cursor iteratively.
์ถ์ฒ: Retrieve All Documents
๊ทธ๋์ ๊ฒฐ๊ตญ cursor.All()
์ ์ฌ์ฉํ๋ ๋ฐฉ์๊ณผ ์ ๊ฐ ์ง์ ๋ง๋ reflect ํจ์๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์, ์ด๋ ๊ฒ ๋ ๊ฐ์ง๋ฅผ ๋ง๋ค์ด, ๋ง์ฝ ์กฐํํ ๋ฐ์ดํฐ๊ฐ ํฌ์ง ์๋ค๋ฉด ์ ์์ ํจ์๋ฅผ, ํฌ๋ค๋ฉด ๋ถํธํจ์ ์์ง๋ง ํ์์ ํจ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ผ๋ก ๋ง๋ฌด๋ฆฌํ๋ ค๊ณ ํ์์ต๋๋ค.
ํ์ง๋ง ์ด๋ฌํ ๊ณ ๋ฏผ์ ํด๊ฒฐํด์ค Go 1.18 ๋ฒ์ ์ด 2022๋ 3์ 15์ผ์ ๊ณต๊ฐ๋์๊ณ Generic์ด ๋์ ๋์์ต๋๋ค. Generic์ ์ด์ฉํ ํด๊ฒฐ์ ์๋ํด๋ณด์๋๋ฐ, ๋์ฝ๋ฉํ ๋ ํจ์์ Generic ํ์ ์ ๋๊ฒจ์ฃผ๋ฉด ์์ฃผ ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์์์ต๋๋ค.
// cursor
func DecodeCursor[T any](cursor *mongo.Cursor) ([]T, error) {
defer cursor.Close(context.Background())
slice := make([]T, 0) // nil์ด ์๋์ ๋ณด์ฅํ๊ธฐ ์ํด slice intialize
for cursor.Next(context.Background()) {
var doc T
if err := cursor.Decode(&doc); err != nil {
return nil, err
}
slice = append(slice, doc)
}
return slice, nil
}
// single result
func EvaluateAndDecodeSingleResult[T any](result *mongo.SingleResult) (*T, error) {
if result == nil {
return nil, errorType.SingleResultErr
}
var v T
if err := result.Decode(&v); err != nil {
return nil, err
}
return &v, nil
}
์ด ๋ฐฉ๋ฒ์ผ๋ก, ์ด์ ๋ reflect๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ caller์์ type assertion์ ํ ํ์๊ฐ ์์์ผ๋ฉฐ, cursor.All()
์ ๋ฉ๋ชจ๋ฆฌ ์ด์๋ ํด๊ฒฐํ ์ ์์์ต๋๋ค.
์์ ๋ ์ด์ด์์๋ DB ๋ ์ด์ด ๋ด๋ถ์์ ์ ์ํ error๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ก๊ทธ ๋ ๋ฒจ์ ํ๋จํด ๋ก๊น
๋ง์ง๋ง์ผ๋ก, ์์ ๋ ์ด์ด์์ MongoDB Go Driver์ error๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์๋๋ผ ์์ DB ๋ ์ด์ด๊ฐ ์ฟผ๋ฆฌ ์คํ ์ ๋ณด์ ํจ๊ป ๋ํํ error๋ฅผ ์ฒ๋ฆฌํ๊ฒ ํ๋ ์ผ์ด ๋จ์์ต๋๋ค.
์ด๋ฅผ ์ํด์๋ ์์ ๋ ์ด์ด์์ error๋ฅผ ๊ตฌ๋ณํ ๋ฐฉ๋ฒ์ด ํ์ํ๊ณ , ๋ค์์ ์ด 3๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์์ต๋๋ค.
errors.As()
- reflect
- type swtich
1. errors.As()
github.com/pkg/errors
๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ๊ตฌ์กฐ์ฒด์ error๊ฐ ๋ฐ์ธ๋ฉ๋ ์ ์๋์ง ํ์ธํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
func IsErrorOf(err error, target interface{}) bool {
if errors.As(err, target) {
return true
}
return false
}
ํ์ง๋ง ์ด ๋ฐฉ์์ ๋งค๋ฒ error๋ฅผ ํ์ธํ ๋๋ง๋ค target error์ ๋ณ์๋ฅผ ์ ์ธํด์ ๋๊ฒจ์ผ ํ๋ ๋ถํธํจ์ด ์์ต๋๋ค.
์ฝ๋ ์
func service(err error) {
var notFoundErr notFoundError
if IsErrorOf(err, ¬FoundErr) {
// handle error
}
var dupKeyErr duplicatedKeyError
if IsErrorOf(err, &dupKeyErr) {
// handle error
}
/* ... ์๋ต ... */
}
2. reflect
Golang์ reflect
๋ฅผ ์ฌ์ฉํ์ฌ error๊ฐ ํด๋น struct
์ ํ์
๊ณผ ๋์ผํ์ง ํ๋ณํ๋ ๊ฒ์
๋๋ค.
func GetInterfaceType(v interface{}) reflect.Type {
var t reflect.Type
if xt, ok := v.(reflect.Type); ok {
t = xt
} else {
t = reflect.TypeOf(v)
}
return t
}
func IsErrorTypeOf(err error, v interface{}) bool {
t := GetInterfaceType(v)
errorType := reflect.TypeOf(err)
if t == errorType {
return true
}
return false
}
// IsErrorTypeOf(err, duplicatedKeyError{})
ํ์ง๋ง ์ด ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ error struct๋ฅผ public์ผ๋ก ๊ณต๊ฐํด์ผ ํ๋ค๋ ์กฐ๊ฑด์ด ์์ต๋๋ค. ๋ํ, ํ์ธํ๋ ค๋ struct์ ๊ฐ์ฒด๋ฅผ ์์ฑํด์ผ ํ๋ฏ๋ก ์ด๋ก ์ธํ ๋ถํธํจ๋ ์กด์ฌํฉ๋๋ค.
3. type switch
์ด ๋ฐฉ์์ Golang์์ ํ์ ์ ํ๋ณํ ๋ ๊ฐ์ฅ ์์ฃผ ์ฌ์ฉ๋๋ ๋ฐฉ์์ ๋๋ค.
func IsDBInternalErr(err error) bool {
for err != nil {
switch err.(type) {
case *internalError,
*timeoutError,
*dbClientError:
return true
}
err = errors.Unwrap(err)
}
return false
}
(Golang์์ error๋ ์ฌ๋ฌ ๊ฒน์ผ๋ก ๋ํ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ํ ๊ป์ง์ ๊น๋ฏ์ด ํ์ธํ๋๋ก ๋ง๋ค์์ต๋๋ค. ๋ํ์ ๊ดํ ๋ด์ฉ์ ๋ค์ ๊ธ์์ ์์ธํ ์ค๋ช ํ๊ฒ ์ต๋๋ค.)
์ ๋ฐฉ์์ ๋ชจ๋ error struct๋ง๋ค ํ์
switch ํจ์๋ฅผ ๋ง๋ค์ด์ผ ํ๋ ๋ถํธํจ์ ์์ง๋ง ๊ฐ์ฅ ์ง๊ด์ ์ด๊ณ , DB์์ ๋ฐ์ํ๋ error์ ์ข
๋ฅ๊ฐ ๋์ด๋ ๊ฐ๋ฅ์ฑ์ด ๊ฑฐ์ ์๊ธฐ ๋๋ฌธ์ ์ด ๋ฐฉ๋ฒ๋ ๊ด์ฐฎ์ ๋ณด์์ต๋๋ค. ๋ํ, IsDBInternalErr()
์ ๊ฐ์ด ์ฌ๋ฌ error๋ฅผ 1๊ฐ๋ก ์ฒ๋ฆฌ๋๋๋ก ํ ์ ์์์ต๋๋ค.
์์ ํจ์์์๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
์ฌ์ฉ ์
func service(err error) {
if IsNotFoundErr(err) {
// handle error
}
if IsDBInternalErr(err) {
// handle error
}
/* ... ์๋ต ... */
}
์์ ์ํฉ์ ์ ์ฒด์ ์ผ๋ก ๊ณ ๋ คํ ๋, ํ ๋ด์์๋ ์ธ ๋ฒ์งธ ๋ฐฉ์์ด ๊ฐ์ฅ ์ข์ ๊ฒ์ผ๋ก ๊ฒฐ๋ก ์ ๋ด๋ ธ์ผ๋ฉฐ, ํ์ switch ๋ฐฉ์์ผ๋ก ์์ ํจ์์์ ์ฒ๋ฆฌํ๋๋ก ์ค์ ํ์ต๋๋ค.
๊ณตํตํ
error๋ฅผ ์ ์ํ๊ณ ์ฒ๋ฆฌํ๊ณ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋์ฝ๋ฉํ๋ ๊ฒ๊น์ง ์์ฑํ๊ณ , V1์์ ์ค๋ณต๋์ด ์๋ ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ๊ณตํตํํ๋ ์์ ์ ์งํํ์ต๋๋ค.
- collection ๊ฐ์ฒด singleton
- slow ์ฟผ๋ฆฌ ๋ก๊น
- ์๊ฐ ์ด๊ณผ
- ๋์ฝ๋ฉ
- error ํ์ ์์ฑ
collection ๊ฐ์ฒด singleton
๊ธฐ์กด V1์์๋ MongoDB client
๊ฐ์ฒด์์ ๋งค๋ฒ Collection
๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์์์ต๋๋ค.
์ฝ๋ ์
func (client *mongo.Client) getCollection(database string, collection string) *mongo.Collection {
return client.Database(database).Collection(collection)
}
func (manager *mongo.Client) FindOne(databaseName string, collectionName string, filter *bson.M) *mongo.SingleResult {
collection := manager.getCollection(databaseName, collectionName)
/* ... ์๋ต ... */
}
์ฟผ๋ฆฌ๋ง๋ค ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋น์ฉ์ด ์๊ธฐ ๋๋ฌธ์, ์ด๋ฅผ ์ค์ด๊ธฐ ์ํด ๊ฐ์ฒด๋ฅผ ๊ณต์ ํด์ ์ฌ์ฉํด๋ ๋๋์ง MongoDB Go Driver ๊ณต์ ๋ฌธ์๋ฅผ ์ฐพ์๋ณด์์ต๋๋ค. ํ์ธ ๊ฒฐ๊ณผ, Collection
๊ฐ์ฒด๋ goroutine safeํ์ฌ singleton์ผ๋ก ์ฌ์ฉํด๋ ๋ฌด๋ฐฉํ๋ค๋ ๋ด์ฉ์ ์ฐพ์ ์ ์์์ต๋๋ค.
mongo/collection.go
// Collection is a handle to a MongoDB collection. It is safe for concurrent use by multiple goroutines.
type Collection struct {
/* ... ์๋ต ... */
}
๊ทธ๋์ V2์์๋ Collection
๊ฐ์ฒด๋ singleton์ผ๋ก ์ฌ์ฉํ๊ธฐ๋ก ํ๊ณ , Collection
๊ฐ์ฒด์ ๋์ฝ๋ฉํ ๋ ์ฌ์ฉํ struct
ํ์
์ Generic ํ์
์ผ๋ก ๋ฐ์, ์์์ ์ธ๊ธํ ๋์ฝ๋ฉ ํจ์์ ๋๊ธฐ๋๋ก ํ์ต๋๋ค.
package mongo
type Collection[T any] struct {
*mongo.Collection
}
func MakeCollection[T any](mongoManager *MongoDBClient, databaseName, collectionName string) *Collection[T] {
collection := mongoManager.GetCollection(databaseName, collectionName)
return &Collection[T]{Collection: collection}
}
slow ์ฟผ๋ฆฌ ๋ก๊น
์ด์ ์ ์์ฑํ Collection
๊ฐ์ฒด์ ๋ฉ์๋๋ก ๊ฐ ์ฟผ๋ฆฌ ํจ์๋ฅผ ๋ํํ์ฌ, ์ฌ๊ธฐ์ slow ์ฟผ๋ฆฌ ๋ก๊น
์ ํ ์ ์๋๋ก ํ์ต๋๋ค(์ดํด๋ฅผ ๋๊ธฐ ์ํด ๋ฐ๋ก ํจ์ ์ถ์ถ์ ํ์ง ์์).
const slowQueryLimit = 1 * time.Second // slow ์ฟผ๋ฆฌ ๊ธฐ์ค ๊ฐ
func (col *Collection[T]) findAll(ctx context.Context, filter interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) {
startTime := time.Now()
cursor, err := col.Collection.Find(ctx, filter, opts...)
// slow ์ฟผ๋ฆฌ ๋ก๊น
if time.Since(startTime) > slowQueryLimit {
log.Errorf("%s, filter: %+v", "findAll", filter)
}
return singleResult
}
์๊ฐ ์ด๊ณผ ์ฒ๋ฆฌ
๋ํ, DB ์๋ต์ด ์์ผ๋ฉด ์๊ฐ ์ด๊ณผ error๋ฅผ ๋ด๋ฉด์ ํจ์๋ฅผ ์ข ๋ฃํ๋๋ก ํด์ผ ํ์ต๋๋ค.
const timeoutLimit = 15 * time.Second // ์๊ฐ ์ด๊ณผ ๊ธฐ์ค ๊ฐ
func (col *Collection[T]) FindAll(filter interface{}, opts ...*options.FindOptions) (*T, error) {
ctx, ctxCancel := context.WithTimeout(context.Background(), timeoutLimit)
defer ctxCancel()
cursor, err := col.findAll(ctx, filter, opts...)
/* ... ์๋ต ... */
}
Golang์ ๋ด์ฅ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ context
๋ฅผ ํ์ฉํ์ฌ , ๊ธฐ์ค ์๊ฐ ๋์ findAll
์ ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด err
๋ณ์์ context Deadline exceed
๋ผ๋ error๊ฐ ๋์ค๋๋ก ํ์ต๋๋ค(MongoDB Golang ํด๋ผ์ด์ธํธ ์ค์ ์ผ๋ก ๊ธ๋ก๋ฒํ๊ฒ ์ค์ ํ ์๋ ์์ต๋๋ค). ์๊ฐ ์ด๊ณผ ์ค์ ์ ๋ํ ๋ ์์ธํ ๋ด์ฉ์ MongoDB Go Driver ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
๋์ฝ๋ฉ ๋ฐ error ํ์ ์์ฑ
๋ชจ๋ error์ ๋ํ ์ ์๋ ๋๋ฌ์ผ๋, ์ด์ ๋ error ํ์ ์ ๋งคํํด์ผ ํ์ต๋๋ค.
func ParseAndReturnDBError(err error, collection string, filter, update, doc interface{}) error {
if errors.Is(err, mongo.ErrNoDocuments) || errors.Is(err, NotMatchedAnyErr) {
return NotFoundError(collection, filter, update, doc)
}
if mongo.IsDuplicateKeyError(err) {
return DuplicatedKeyError(collection, filter, update, doc, err)
}
if mongo.IsTimeout(err) || errors.Is(err, context.DeadlineExceeded) {
return TimeoutError(collection, filter, update, doc, err)
}
return InternalError(collection, filter, update, doc, err)
}
err
๋ณ์๋ฅผ ๋ฐ์์ ๊ฐ๊ฐ ์ ์ํ error ๊ฐ์ฒด๋ฅผ ๋งคํํ์ฌ ๋ฐํํ๋๋ก ํจ์๋ฅผ ๋ง๋ค๊ณ , ๋ค์๊ณผ ๊ฐ์ด ์ค์ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ํจ์์ ์ ์ฉํ์ต๋๋ค.
func (col *Collection[T]) FindAll(filter interface{}, opts ...*options.FindOptions) (*T, error) {
ctx, ctxCancel := context.WithTimeout(context.Background(), timeoutLimit)
defer ctxCancel()
cursor, err1 := col.findAll(ctx, filter, opts...)
if err != nil {
return nil, ParseAndReturnDBError(err, col.Name(), filter, nil, nil)
}
resultSlice, err2 := DecodeCursor[T](cursor)
if err != nil {
return nil, DecodeError(col.Name(), filter, nil, nil, err)
}
return resultSlice, nil
}
์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์์ ๋์จ err1
๋ณ์์๋ ์ฟผ๋ฆฌ์์ ๋ฐ์ํ error๊ฐ ๋ด๊ธฐ๋ฉฐ, ์ด๋ฅผ ๋๊ฒจ์ ๋ถ๋ฅ์ ๋ฐ๋ฅธ error๊ฐ ๋ฐํ๋๊ฒ ํ์ต๋๋ค.
์ฟผ๋ฆฌ์์ ๋ฐ์ํ error๊ฐ ์๋ค๋ฉด ์ปค์๋ฅผ ๋ฐ์์ ๋์ฝ๋ฉํ๊ณ , ๋์ฝ๋ฉ์์ ๋์จ err2
๋ DecodeError
๋ก ๋งคํํ์ต๋๋ค. ์ปค์์์ DecodeError
๋ struct์ ํ์
์ด ์ ์์ ์ด๋ผ๋ฉด ๊ฑฐ์ ๋์ค์ง ์์ต๋๋ค.
ํ์ง๋ง singleResult
์์๋ ๋์ฝ๋ฉํ ๋ ์ด 3๊ฐ์ง error๋ฅผ ๋ฐ๋ก ๋ถ๋ฅํด์ผ ํ์ต๋๋ค.
- no document found
- deadline exceed
- duplicated key error(replace, update ๋ฑ์์ ๋ฐ์)
์ error๋ ๋ฐ๋ก ์ฒ๋ฆฌํ์ฌ DecodeError
๋ก ๋ถ๋ฅ๋์ง ์๋๋ก ํ์ต๋๋ค.
์ ์ฉ ์ฝ๋
func (col *Collection[T]) FindOneAndModify(filter interface{}, update interface{}, opts ...*options.FindOneAndUpdateOptions) (*T, error) {
/* ... ์๋ต ... */
singleResult := col.findOneAndModify(commonHead, ctx, filter, update, opts...)
doc, err := EvaluateAndDecodeSingleResult[T](singleResult)
if err != nil {
if errors.Is(err,mongo.ErrNoDocuments) || errors.Is(err, context.DeadlineExceeded) || mongo.IsDuplicateKeyError(err) {
return nil, errorType.ParseAndReturnDBError(err, col.Name(), filter, nil, nil)
}
return nil, errorType.DecodeError(col.Name(), filter, nil, nil, err)
}
return doc, nil
}
๋๋ถ๋ถ์ DB CRUD์์์ ์๊ฐ ์ด๊ณผ, slow ์ฟผ๋ฆฌ ๋ก๊น , error ๋ถ๋ฅ๊น์ง ๋ชจ๋ ๋ง์ณค์ง๋ง, ํ ๊ฐ์ง ๋จ์ ๊ณ ๋ฏผ์ด ์์์ต๋๋ค.
error ๋ถ๋ฅ์ ๋ํ ๊ณ ๋ฏผ
update, delete์ ๊ฒฐ๊ณผ MatchedCount
/ModifiedCount
๊ฐ์ด 0์ด๋ฉด error๋ก ๋ด์ผํ๋์ง ๊ณ ๋ฏผํด๋ณผ ํ์๊ฐ ์์์ต๋๋ค.
๋ฉฑ๋ฑ์ฑ ๊ด์ ์์๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณผ ์ ์์ต๋๋ค.
- delete์ ๊ฒฝ์ฐ, ์ง์ฐ๋ ค๋ ๋์์ด ์๋ค๋ ๊ฒ์(
MatchedCount == 0
) ํด๋น ๊ฐ์ด DB์ ์๋ ์ ์์ ์ธ ์ํฉ์ด๋ค. - update์ ๊ฒฝ์ฐ, ์์ ๋ ๋์์ด ์๋ค๋ ๊ฒ์(
MatchedCount โ 0 & ModifiedCount == 0
) ํด๋น ๊ฐ์ด ์ด๋ฏธ update ์์ฒญํ ๊ฐ์ด๋ค.
ํ์ง๋ง ๋น์ฆ๋์ค ๋ก์ง๋ง๋ค ๊ด์ ์ด ๋ค๋ฅด๋ฏ๋ก, ๋ฉฑ๋ฑ์ฑ ๊ด์ ๋ง ๊ณ ๋ คํ์ฌ error๋ก ๋ณด์ง ์๊ธฐ๋ ์ด๋ ค์ธ ๊ฒ ๊ฐ์์ต๋๋ค.
๊ทธ๋์ ํ ๋ด ๋ฆฌ๋ทฐ ์๊ฐ์ ์ด์ ๊ฐ์ ๊ณ ๋ฏผ์ ๋๋ด๊ณ , ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก not matched
๋ not found
๋ก ๋จ๊ฒจ๋๊ณ , not modified
๋ error๋ ๋ณด์ง ์๊ธฐ๋ก ๊ฒฐ๋ก ๋ด๋ ธ์ต๋๋ค.
not matched
์ ๊ฒฝ์ฐ, ๋๋ถ๋ถ์ ์ฌ์ฉ์(๊ฐ๋ฐ์)๋ update/delete ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๋ ํด๋น ๊ฐ์ด ์๋ ๊ฒ์ ๊ธฐ๋ํ๊ณ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์,not found
์ ๋ํ error ์ฒ๋ฆฌ๊ฐ ํ์ํ ์๋ ์๋ค(์ญ์ ๋ ๊ฐ์๊ฐ 0์ธ ๊ฒฝ์ฐ๋not found
๋ก ๊ฐ์ฃผ).not modified
์ ๊ฒฝ์ฐ, ์ด๋ฏธ ํด๋น ๊ฐ์ผ๋ก db์ ์ ์ฅ๋์ด ์๋ค๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, ์ฌ์ฉ์์ ๊ธฐ๋ ํน์ ์๋์ ์ผ์นํ๋ฏ๋ก error ์ฒ๋ฆฌ๊ฐ ํ์ํ์ง ์๋ค.
๋ฐ์๋ ์ฝ๋
var NotMatchedAnyErr = errors.New("no documents have been matched")
func (col *Collection[T]) UpdateOne(filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) {
/* ... ์๋ต ... */
updateResult, err := col.updateOne(ctx, filter, update, opts...)
if err != nil {
return nil, errorType.ParseAndReturnDBError(err, col.Name(), filter, update, nil)
}
if updateResult.MatchedCount == 0 & updateResult.UpsertedCount == 0 {
return updateResult, errorType.ParseAndReturnDBError(errorType.NotMatchedAnyErr, col.Name(), filter, update, nil)
}
return updateResult, nil
}
func (col *Collection[T]) DeleteOne(filter interface{}, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) {
/* ... ์๋ต ... */
deleteResult, err := col.deleteOne(ctx, filter, opts...)
if err != nil {
return nil, errorType.ParseAndReturnDBError(err, col.Name(), filter, nil, nil)
}
if deleteResult.DeletedCount == 0 {
return deleteResult, errorType.ParseAndReturnDBError(errorType.NotMatchedAnyErr, col.Name(), filter, nil, nil)
}
return deleteResult, nil
}
์ฌ๊ธฐ์ ์กฐ์ฌํด์ผ ํ๋ ๋ถ๋ถ์, update์์ upsert๊ฐ ์ํ๋ ๊ฒฝ์ฐ์๋ MatchedCount๊ฐ 0์ด ๋๋ค๋ ๊ฒ์
๋๋ค. ์ด๋ UpsertedCount
๋ ์์์ธ๋ฐ ์ด ๊ฐ๊น์ง ํ์ธํ์ง ์์ผ๋ฉด not found error๋ก ๋ถ๋ฅ๋ ์ ์์ผ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํฉ๋๋ค.
๋ง์น๋ฉฐ
๋ก๊ทธ๋ ์ด๋์ ๋จ๊ฒจ์ผ ํ๋๊ฐ, ์ด๋ ๋ ๋ฒจ๋ก ๋จ๊ฒจ์ผ ํ๋๊ฐ ํ๋ ๊ณ ๋ฏผ์์ ์์๋, ๊ฐ์ฅ ํ์ ๋ ์ด์ด์ธ DB ๋ ์ด์ด๋ฅผ ๊ฐํธํ ๊ณผ์ ์ด์์ต๋๋ค.
๊ฒฐ๊ตญ ์์ ๋ํ ๊ณ ๋ฏผ์ ๋ํ ๋ต์, ์ฌ๊ธฐ์๋ ํ๋จ์ด ๋ถ๊ฐํ๊ธฐ ๋๋ฌธ์ 'DB ๋ ์ด์ด์์๋ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ์ง ์๋๋ค'์ ๋๋ค. ๋์ ๋ค์๊ณผ ๊ฐ์ ์ฅ์น๋ฅผ ๋ง๋ จํ์ฌ, ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ์ง ์๋๋ผ๋ ์ถฉ๋ถํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ก ํ์ต๋๋ค.
- DB ๋ ์ด์ด์์๋ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ์ง ์๊ณ DB ์ฟผ๋ฆฌ ์คํ ์ ๋ณด์ error๋ฅผ ๋ํํ์ฌ ๋ฐํํ๋ค.
error
๊ฐ์ดnil
์ด๋ฉด, ๋ฐํ๋๋ ๊ฐ(document)์nil
์ด ์๋๋ฉฐ ์ฟผ๋ฆฌ ์ฑ๊ณต์ ๋ณด์ฅํ๋ค.- DB ๋ ์ด์ด ๋ด๋ถ์์ ์ ์ํ error๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ก๊ทธ ๋ ๋ฒจ์ ํ๋จํด ๋ก๊ทธ๋ฅผ ๋จ๊ธธ ์ ์๋๋ก error ํ์ ์ ์ ์ํ๊ณ ํ๋ณ ํจ์๋ฅผ ์ ๊ณตํ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ ์๋ฒ๋ง๋ค ๊ตฌํํ๋ ๋ค์๊ณผ ๊ฐ์ ๋ถ๋ถ์ ๊ณตํต ์ ์ฅ์๋ก ์ฎ๊ฒจ ์ผ๊ด๋๊ฒ ๋ก์ง์ ์ํํ๊ณ error๋ฅผ ๋ถ๋ฅํ๊ฒ ํ์ต๋๋ค.
- collection ๊ฐ์ฒด singleton ์ ์ง
- slow ์ฟผ๋ฆฌ ๋ก๊น
- ์๊ฐ ์ด๊ณผ ์ฒ๋ฆฌ
- singleResult, Cursor ๋ฑ ๋์ฝ๋ฉ
๊ทธ ๊ฒฐ๊ณผ V2 ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์ค์ด๋ค์์ต๋๋ค.
type MyCollectionManager struct {
collection *commonMongoDB.Collection[MyDocument]
}
func MyCollection(client *mongo.Client) *MyCollectionManager {
manager := &MyCollectionManager{}
dbConfig := config.GetDatabaseConfig()
manager.collection = commonMongoDB.MakeCollection[MyDocument](client, dbConfig.DatabaseName, "myCollection")
return manager
}
func (manager *MyCollectionManager) GetDocument() (*MyDocument, error) {
filter := bson.M{}
doc, err := manager.collection.FindOne(&filter)
if err != nil {
return nil, err
}
return doc, nil
}
๋ค์ ๊ธ์์๋ ์ฌ๊ธฐ์ ๋ฐ์ํ error๋ฅผ ์์์ ์ด๋ป๊ฒ ์ ๋ฌํ ์ ์์์ง์ ๋ํ ๊ณ ๋ฏผ์ ์ด์ผ๊ธฐํด๋ณด๋ ค ํฉ๋๋ค.
์ฐธ๊ณ
slow ์ฟผ๋ฆฌ ๋ก๊น ๊ณผ ๊ฐ์ ๋ถ๋ถ์ monitor ํน์ logger๋ฅผ ์ด์ฉํ๋ฉด ์ข ๋ ์ ํํ๊ณ ์ค์ MongoDB raw query๋ฅผ ๋ฐ์ ๋ณผ ์๋ ์์ผ๋, ๊ด์ฌ์ด ์๋ค๋ฉด ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.