10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GoとGinをプロジェクトで使ってみて思ったこと

Last updated at Posted at 2020-02-07

はじめに

k.s.ロジャースの西谷です。

今回はGo言語とGinを使ってプロジェクトの開発を行いました。
実務利用は初めてで、このときに感じた便利な機能や躓いた箇所について書いてみようと思います。

もし、間違いやより良い実装があればコメントにて教えて頂けたらと思います。

開発環境

  • 言語: Go 1.13.3
  • フレームワーク: Gin

便利だと思った所

リクエストのバリデーション

Ginではリクエストオブジェクトの定義にbindingを書くことでバリデーションを実施できます。
特殊パターン以外はリクエストに直接書くことができるので、直感的だと思います。

以下のように書くと、reqIDの値は必ず存在し、Ageは0以上となります。

request.go
type CreateUser struct {
	ID   string `json:"id" binding:"required"`
	Name string `json:"name"`
	Age  int    `json:"age" binding:"min=0"`
}
controller.go
func (h *HogeController) Create(c *gin.Context) {
	var req request.CreateUser

    // このタイミングでバリデーションが実行される
	err := c.ShouldBindJSON(&req)
	if err != nil {
		// リクエストが間違っている時の処理
		return
	}
}

バリデーションをカスタマイズしたい場合は以下のようにします。

// 日付がyyyy-mm-ddのフォーマットになっているか
func DatetimeValidator(field validator.FieldLevel) bool {
	value := field.Field().String()

    _, err := time.Parse("2006-01-02", value)
	if err != nil {
		return false
	}

	return true
}

func main() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // is_dateのタグでDatetimeValidatorを登録
		v.RegisterValidation("is_date", DatetimeValidator)
	}
}

request.go
type CreateUser struct {
	ID   string    `json:"id" binding:"required"`
	Name string    `json:"name"`
	Age  int       `json:"age" binding:"min=0"`
	Date time.Time `json:"date" binding:"is_date"`
}

詳細はこちらを参照ください。

エラー処理周り

Goでは必ず処理直後にエラー処理を行います。
こちらはエラー処理が煩雑など賛否両論ではありますが、私の意見としては複雑なtry, catchが乱立するよりはいいかなと思いました。

err := HogeFunc()
if err != nil {
    log.Fatal(err)
}

コード品質を気にしているチームであればtry, catchが乱立することはないかなと思います。
しかし、プロジェクトによってはコードを一切管理しておらず、例外が理解できないほど複雑になることもあるので、Goを利用していれば最悪ケースを防げるかなと思います。

躓いたところ

APIリクエストでのnullの扱い

REST APIのリクエストで未入力と初期値(0や空文字など)を区別したい場合があります。
これを実装する場合は変数をポインタにしてnilを持たせる必要があります。

実際に起きたバグとしては、PATCH関連のAPIでリクエストオブジェクトを通常の型で定義したところ、未入力フィールドがnullで上書きされる問題でした。

例えば次の定義に対して、ユーザ名だけを変更しようとしてリクエストを行うとAgeが0で上書きされます。

request.go
type UpdateUser struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}
PATCH_USER
{
  "id": "xxxxxxxxx",
  "name": "hogehoge"
}

これに対して、nullが投げられる可能性のあるフィールドをポインタで定義することで解決できます。

request.go
type UpdateUser struct {
	ID   string  `json:"id"`
	Name *string `json:"name"`
	Age  *int    `json:"age"`
}

これで未入力の場合はnilとなりますので、サーバ側で保存対象を区別できます。
しかし、リクエストでnullで上書きしたい場合は、nullをリクエストで投げても更新されなくなります。
(定義済みのデータを未定義に更新出来ることが特殊だと思うので、一部の言語が特殊だと考えています)

ジェネリクスがない

型は違うが同じような処理をしたい場合はたまにあります。
現状ですと、interface{}で頑張るしかないようです。 1


func AddOne(v interface{}) interface{} {
	switch v.(type) {
	case int:
		return v.(int) + 1
	case float64:
		return v.(float64) + 1.0
	default:
		return nil
	}
}

func main() {
	a := AddOne(1.0)
	fmt.Printf("%f", a.(float64))
}

おわりに

今回はGoを実際にプロダクトで使ってみて感じたことについて説明しました。
設計や工夫次第で解消できる箇所もあるかなと思うので、これからも色々試していきたいと思います。

間違い等あればご指摘頂けたらと思います。

Wantedlyでもブログ投稿してます

Techブログに加えて会社ブログなどもやっているので、気になった方はぜひ覗いてみてください。
https://www.wantedly.com/companies/ks-rogers

  1. Go2.0でGenericsの実装される可能性があります。

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?