winterjung blog


프로덕션 서버 개발을 위한 golang 패키지 추천

전 직장에서 go 언어를 처음 쓰기 시작해 현 직장에서도 계속 쓰며 약 5년간 활발히 사용하고 있다. 예전과 비교하면 go 생태계는 점점 풍부해지고, 고퍼콘 코리아를 비롯한 다양한 커뮤니티도 늘어나고, 레퍼런스도 많아지고 있다. 그럼에도 여전히 (비단 go 언어만 그런 건 아니지만) 잘 정리된 한국어 자료는 적고 시행착오를 겪어봐야 하는 부분이 많아 그동안 여러 동료와 함께 개발하며 겪었던 시행착오와 권장 사항을 프로덕션 환경에서 사용하는 golang과 gRPC, 뱅크샐러드 Go 코딩 컨벤션 두 편의 글로 정리하기도 했다. 그와 비슷한 결로 이번엔 주로 go 언어로 서버 개발을 하며 자주 사용하고 유용했던 패키지라이브러리를 정리해 봤다. go 언어에서 기본으로 제공하는 내장 패키지에도 net/http/httptest, crypto/rand 패키지처럼 믿고 쓸 수 있고 잘 만들어진 패키지는 많으나 이 글에선 서드 파티 패키지에 집중했다.

글을 쓸 때 마다 항상 드는 고민이 ‘이 정도면 “golang xxx package” 같은 키워드로 검색해도 금방 나올 텐데 괜히 적는 거 아닌가?‘라는 부분이다. 그럼에도 누군가의 의사결정 비용과 고민을 줄여주길 바라며, 또 이 글을 읽은 다양한 사람들이 얹어주는 한마디씩을 통해 이 글이 더 발전되길 바라며 소개해 본다.

stretchr/testify#

func TestSomething(t *testing.T) {
  assert.Equal(t, expected, got, "they should be equal")
  assert.NoError(t, err)
  assert.Len(t, result, 1)
  assert.JSONEq(t, `{"name": "hello"}`, msg)
}

A toolkit with common assertions and mocks that plays nicely with the standard library

rs/zerolog & sirupsen/logrus#

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
	"github.com/rs/zerolog/pkgerrors"
)

func main() {
	zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
	// ...
	log.Error().Stack().Err(err).Msg("failed to insert row to db")
}

rs/zerolog: Zero Allocation JSON Logger

import (
	log "github.com/sirupsen/logrus"
)

func main() {
	log.WithFields(log.Fields{
		"animal": "walrus",
	}).Info("A walrus appears")
}

sirupsen/logrus: Structured, pluggable logging for Go.

pkg/errors#

resp, err := userSvc.GetUser(ctx, &GetUserRequest{...})
if err != nil {
	return errors.Wrap(err, "user.GetUser")
}

Simple error handling primitives

hashicorp/go-multierror#

var errs *multierror.Error
if err := step1(); err != nil {
	errs = multierror.Append(errs, err)
}
if err := step2(); err != nil {
	errs = multierror.Append(errs, err)
}
return errs.ErrorOrNil()
// Output:
// 2 errors occurred:
//	* error 1
//	* error 2

A Go (golang) package for representing a list of errors as a single error.

samber/lo#

names := lo.Uniq([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}

A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find…)

shopspring/decimal#

func main() {
	price, err := decimal.NewFromString("136.02")
	quantity := decimal.NewFromInt(3)
	subtotal := price.Mul(quantity)

	fmt.Println("Subtotal:", subtotal) // Subtotal: 408.06
}

Arbitrary-precision fixed-point decimal numbers in Go

dgraph-io/ristretto#

func main() {
	cache, _ := ristretto.NewCache(&ristretto.Config[string,string]{
		NumCounters: 1e7,     // number of keys to track frequency of (10M).
		MaxCost:     1 << 30, // maximum cost of cache (1GB).
		BufferItems: 64,      // number of keys per Get buffer.
	})
	cache.Set("key", "value", 1)
	value, found := cache.Get("key")
	fmt.Println(value)
}

A high performance memory-bound Go cache

volatiletech/sqlboiler#

import (
	"github.com/volatiletech/sqlboiler/v4"
)
func main() {
	users, err := model.Users().All(ctx, db)
	token, err := model.Tokens(model.TokenWhere.AccessToken.EQ(accessToken)).One(ctx, db)
}
	token.Update(ctx, db, boil.Whitelist(
        model.TokenColumns.AccessToken,
        model.TokenColumns.AccessTokenExpiredAt,
    ))

Generate a Go ORM tailored to your database schema.

DATA-DOG/go-sqlmock#

func TestShouldUpdateStats(t *testing.T) {
	db, mockDB, err := sqlmock.New()
	require.NoError(t, err)
	t.Cleanup(db.Close)

	mockDB.ExpectQuery(regexp.QuoteMeta(
		"SELECT * FROM `token` WHERE (`user_id` = ?);"
	)).WithArgs("some-valid-user-id").WillReturnRows(...)
}

Sql mock driver for golang to test database interactions

그 외#


이렇게 그 동안 자주 사용하고 유용한 패키지를 정리해봤다. 위 내용은 서버 개발에 치중되어 있고, 너무 지엽적인 패키지(e.g. 레벤슈타인 거리 구하는 패키지)는 제외했는데 이 외에도 추천할만한 패키지가 있다면 계속 업데이트 할 예정이다.