본문 바로가기

Golang

[Golang] MongoDB Wrapper - 2

728x90

2022.08.15 - [Golang] - [Golang] MongoDB wrapper

 

[Golang] MongoDB wrapper

go-mongo go.mongodb.org/mongo-driver 를 기반으로 실제 프로젝트에서 유용하게 사용될 수 있는 wrapper 어떻게 하다 시작했나? 회사에 입사하고 나서, golang과 몽고 DB를 주로 사용하게 되었다. mongo-driver..

juna-dev.tistory.com

https://github.com/kjh03160/go-mongo/pull/25/files

 

generic 디코딩 & not modified 삭제 & 리팩토링 by kjh03160 · Pull Request #25 · kjh03160/go-mongo

 

github.com

이전에 고민했던 부분을 어느정도 정리를 했다.

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