こんにちは、KOUKIです。
GolangのWebフレームワークであるfiberを使ってAPIを開発しています。
前回は、Redisを導入しました。
今回は、Redisにキャッシュデータを保存する処理を実装したいと思います。
尚、この記事に出てくるソースコードは、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
<目次>
前回
作るもの
Ambassdor機能を作りたいと思います。エンドポイントは、次の通りです。
- GET /api/ambassador/products/frontend
- GET /api/ambassador/products/backend
- POST /api/ambassador/links
- GET /api/ambassador/stats
- GET /api/ambassador/rankings
今回は、「/api/ambassador/products/frontend」への処理を追加します。
Productデータのキャッシング
RedisにProductデータをキャッシングして、処理速度の向上に勤めましょう。本APIのように小規模APIの場合は、キャッシングがなくても問題なく動くと思いますが、より実践を見据えてチャレンジしましょう。
ルートの追加
「/api/ambassador/products/frontend」のルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// routes/routes.go ... func Setup(app *fiber.App) { // Group api := app.Group("api") // admin ... // Ambassador ... ambassador.Get("products/frontend", controllers.ProductFrontend) // 追加 } |
コントローラー
「/api/ambassador/products/frontend」のコントローラーを追加します。
1 2 3 4 5 6 7 8 |
// productController.go func ProductFrontend(ctx *fiber.Ctx) error { var products []models.Product database.DB.Find(&products) return ctx.JSON(products) } |
これで、Productデータを全て取得し、呼び出し元に返すことができるようになりました。
検証
以下のパラメーターで検証してみましょう。
- URL: http://localhost:8000/api/ambassador/products/frontend
- 形式: GET

OKですね。
キャッシングの意義
先ほどの検証で、全てのProductデータを取得しました。しかし、何度も取得し直すことはパフォーマンスの劣化を招きます。
なぜなら、MySQLのようなDBへのアクセスは“コスト”がかかるからです。
そこで、前回導入したRedisにProductデータをキャッシュとして持たせることで、パフォーマンスを改善します。
先ほど実装した、ProductFrontend関数に、Redisのキャッシング処理を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
unc ProductFrontend(ctx *fiber.Ctx) error { var products []models.Product var c = context.Background() redisKey := "products_frontend" expiredTime := 30 * time.Minute // products_frontend keyでRedisからデータを取得 _, err := database.Cache.Get(c, redisKey).Result() if err != nil { log.Println("---DB Serch---") database.DB.Find(&products) // Redisにデータを格納するため、エンコードする productBytes, err := json.Marshal(&products) if err != nil { panic(err) } // products_fronend keyでRedisにデータを格納 err = database.Cache.Set(c, redisKey, productBytes, expiredTime).Err() if err != nil { panic(err) } } else { // 検証用 後で消す。 log.Println("---Redis Serch---") } return ctx.JSON(products) } |
検証2
以下のパラメーターで検証してみましょう。
- URL: http://localhost:8000/api/ambassador/products/frontend
- 形式: GET
一度目のリクエスト時には、以下のログが出力されます。
1 |
backend_1 | 2021/06/15 22:34:40 ---DB Serch--- |
この際に、RedisにProductデータが格納されました。
そのため、次回のリクエストからは、Redisからデータを取得します。
ただし、現状だと戻り値がNullになるので、修正が必要です。

1 2 3 4 5 6 7 8 9 10 11 |
func ProductFrontend(ctx *fiber.Ctx) error { ... if err != nil { ... } } else { // デコードする json.Unmarshal([]byte(result), &products) } return ctx.JSON(products) } |
一度、Dockerコンテナを立ち上げ直してから、再度リクエストを送ってみてください。

OKですね。
補足
キャッシュしてから定期的にデータを更新する必要があります。そうしないと、古いデータがキャッシュとして残り続けてしまうので、正しいデータが取得できなくなります。
現場では、バッチなどを用いて定期的にキャッシュの更新を行いますが、本APIにはそのような機能はありませんので、あしからず^^
その内、キャッシュし直す処理を追加するとは思いますが…
次回
次回は、プロダクトの検索機能を実装しましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
productController.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
// productController.go package controllers import ( "admin/src/database" "admin/src/models" "context" "encoding/json" "strconv" "time" "github.com/gofiber/fiber/v2" ) func Products(ctx *fiber.Ctx) error { var products []models.Product // 全てのプロダクトを取得 database.DB.Find(&products) return ctx.JSON(products) } func CreateProducts(ctx *fiber.Ctx) error { var product models.Product // リクエストデータをパースする if err := ctx.BodyParser(&product); err != nil { return err } // プロダクト取得 database.DB.Create(&product) return ctx.JSON(product) } func GetProduct(ctx *fiber.Ctx) error { // リクエストからIDを取得 id, _ := strconv.Atoi(ctx.Params("id")) var product models.Product product.ID = uint(id) // プロダクト検索 database.DB.Find(&product) return ctx.JSON(product) } func UpdateProduct(ctx *fiber.Ctx) error { // リクエストからIDを取得 id, _ := strconv.Atoi(ctx.Params("id")) product := models.Product{} product.ID = uint(id) if err := ctx.BodyParser(&product); err != nil { return err } // プロダクト更新 database.DB.Model(&product).Updates(&product) return ctx.JSON(product) } func DeleteProduct(ctx *fiber.Ctx) error { // リクエストからIDを取得 id, _ := strconv.Atoi(ctx.Params("id")) product := models.Product{} product.ID = uint(id) // プロダクト削除 database.DB.Delete(&product) return nil } func ProductFrontend(ctx *fiber.Ctx) error { var products []models.Product var c = context.Background() redisKey := "products_frontend" expiredTime := 30 * time.Minute // products_frontend keyでRedisからデータを取得 result, err := database.Cache.Get(c, redisKey).Result() if err != nil { database.DB.Find(&products) // Redisにデータを格納するため、エンコードする productBytes, err := json.Marshal(&products) if err != nil { panic(err) } // products_fronend keyでRedisにデータを格納 err = database.Cache.Set(c, redisKey, productBytes, expiredTime).Err() if err != nil { panic(err) } } else { // デコードする json.Unmarshal([]byte(result), &products) } return ctx.JSON(products) } |
routes.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// routes/routes.go package routes import ( "admin/src/controllers" "admin/src/middleware" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // Group api := app.Group("api") // admin admin := api.Group("admin") admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) adminAuthenticated := admin.Use(middleware.IsAuthenticate) adminAuthenticated.Get("user", controllers.User) adminAuthenticated.Post("logout", controllers.Logout) adminAuthenticated.Put("info", controllers.UpdateInfo) adminAuthenticated.Put("password", controllers.UpdatePassword) adminAuthenticated.Get("ambassadors", controllers.Ambassadors) adminAuthenticated.Get("products", controllers.Products) adminAuthenticated.Post("products", controllers.CreateProducts) adminAuthenticated.Get("products/:id", controllers.GetProduct) adminAuthenticated.Put("products/:id", controllers.UpdateProduct) adminAuthenticated.Delete("products/:id", controllers.DeleteProduct) adminAuthenticated.Get("users/:id/links", controllers.Link) adminAuthenticated.Get("orders", controllers.Orders) // Ambassador ambassador := api.Group("ambassador") ambassador.Post("register", controllers.Register) ambassador.Post("login", controllers.Login) ambassador.Get("products/frontend", controllers.ProductFrontend) ambassadorAuthentication := ambassador.Use(middleware.IsAuthenticate) ambassadorAuthentication.Get("user", controllers.User) ambassadorAuthentication.Post("logout", controllers.Logout) ambassadorAuthentication.Put("users/info", controllers.UpdateInfo) ambassadorAuthentication.Put("users/password", controllers.UpdatePassword) } |
コメントを残す
コメントを投稿するにはログインしてください。