728x90
2022.08.15 - [Golang] - [Golang] MongoDB wrapper
https://github.com/kjh03160/go-mongo/pull/25/files
이전에 고민했던 부분을 어느정도 정리를 했다.
error check하는 것을 reflect 방식 vs Is~Err() 방식으로 할지
우선 결론을 말하자면 Is~Err() 방식을 취하기로 했다.
- reflect 방식은 1:1 매핑밖에 되지 않는다는 큰 단점이 있었다.
- 만약 내가 duplicated key, timeout 등을 구분하지 않고 모두 Internal Error로 간주하려고 한다면, reflect 방식은 핸들링 할 다른 error 타입을 먼저 모두 선행하고 나서, else로 처리하거나 각각을 체크할 수 밖에 없다.
- 하지만 Is~Err() 방식은 IsInternalErr() 에서 위의 에러들을 type switch로 같이 잡아낼 수 있었다.
func IsDBInternalErr(err error) bool {
switch err.(type) {
case *internalError,
*timeoutError,
*mongoClientError:
return true
default:
return false
}
}
not matched, not modified error 정리
이것은 not matched는 남겨두기로 했고, not modified는 삭제하기로 했다.
- not matched는 대부분의 update/delete 쿼리를 날릴 때, 사용자는 해당 값이 있는 것을 기대하고 사용하기 때문에, not found의 경우에 대한 에러 핸들링이 필요할 수 있기 때문에 남겨두었다. (delete count가 0인 경우도 not found로 간주)
- 하지만 not modified는 이미 해당 값으로 db에 저장되어있다는 것이기 때문에, 사용자의 기대 혹은 의도와 일치하고, 해당 에러를 핸들링할 경우가 없다고 생각하여 삭제했다.
cursor decode 방식
cursor.All() / reflect 방식을 고민했었는데, go 버전이 1.18로 되면서 generic이 추가되었고, generic을 사용하면 앞의 2가지 방식을 고민할 필요가 없었다.
- Collection 객체를 생성할 때, 해당 컬렉션에서 사용되는 struct 정보를 같이 넘겨주고, decode할 때 해당 struct로 디코딩을 하면 아주 간단히 되었다.
// mongo/operation.go
type Collection[T any] struct {
*mongo.Collection
}
func MakeCollection[T any](mongoManager *Client, databaseName, collectionName string) *Collection[T] {
collection := mongoManager.GetCollection(databaseName, collectionName)
return &Collection[T]{Collection: collection}
}
// mongo/decode.go
func DecodeCursor[T any](cursor *mongo.Cursor) ([]T, error) {
defer cursor.Close(context.Background())
var slice []T
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
}
// mongo/wrapper.go
func (col *Collection[T]) FindAll(filter interface{}, opts ...*options.FindOptions) ([]T, error) {
ctx, ctxCancel := context.WithTimeout(context.Background(), DB_TIMEOUT)
defer ctxCancel()
cursor, err := col.findAll(ctx, filter, opts...)
if err != nil {
return nil, errorType.ParseAndReturnDBError(err, col.Name(), filter, nil, nil)
}
resultSlice, err := DecodeCursor[T](cursor)
if err != nil {
return nil, errorType.DecodeError(col.Name(), filter, nil, nil, err)
}
return resultSlice, nil
}
마지막으로 남은 건 범용적으로 사용할 수 있게, logger interface 를 선언하고, 해당 interface를 모든 operation 함수 파라미터로 받아 db slow 쿼리를 잡아낼 수 있게 하는 작업이 있을 것 같다.
728x90
'Golang' 카테고리의 다른 글
Golang, 그대들은 어떻게 할 것인가 - Error Wrapping, Handling (0) | 2024.04.04 |
---|---|
[Golang] Golang에서 error 처리를 어떻게 해야할까 (0) | 2023.01.08 |
[Golang] MongoDB wrapper (0) | 2022.08.15 |
[Golang] Memory Leak 예방하기 (1) | 2022.06.27 |
[Golang] golang validator (0) | 2022.06.27 |