データ取得

# フォローリスト
## http
もちろんです。Redisを使用して`GUser`テーブルのデータをキャッシュし、フォローリスト取得APIを実装する完全な例を以下に示します。この例では、`GUser`テーブルのデータをアプリケーション起動時にRedisにロードし、そのデータを使用してフォローしているユーザーのプロファイルを返します。
 
```go
package main
 
import (
"context"
"fmt"
"log"
"net/http"
 
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
 
var ctx = context.Background()
 
// GLike モデルは、ユーザー間の「いいね」関係を表します。
type GLike struct {
ID           uint `gorm:"primaryKey"`
UserID       uint
FollowUserID uint
}
 
// GUser モデルは、ユーザー情報を表します。
type GUser struct {
UserID   uint `gorm:"primaryKey"`
UserName string
}
 
// UserProfile はAPIのレスポンス型です。
type UserProfile struct {
UserID   uint   `json:"user_id"`
UserName string `json:"user_name"`
}
 
func main() {
r := gin.Default()
 
// SQLiteデータベース接続の設定
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatal("データベース接続に失敗しました:", err)
}
 
// モデルのマイグレーション
db.AutoMigrate(&GLike{}, &GUser{})
 
// Redisクライアントの設定
rdb := redis.NewClient(&redis.Options{
Addr:     "localhost:6379", // Redisサーバーのアドレス
Password: "",               // パスワードが設定されていない場合は空文字
DB:       0,                // 使用するDBを指定
})
 
// GUser テーブルからデータを読み込み、Redisに保存する処理
var users GUser
if err := db.Find(&users).Error; err != nil {
log.Fatal("ユーザーデータの読み込みに失敗しました:", err)
}
for _, user := range users {
err := rdb.Set(ctx, fmt.Sprintf("user:%d", user.UserID), user.UserName, 0).Err()
if err != nil {
log.Fatal("Redisへのデータ保存に失敗しました:", err)
}
}
 
// フォローしているユーザーリストの取得API
r.GET("/followings/:user_id", func(c *gin.Context) {
userID := c.Param("user_id")
 
// フォローしているユーザーIDのリストを取得
var likes GLike
if err := db.Where("user_id = ?", userID).Find(&likes).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
 
// Redisからユーザー名をバッチで取得
var userProfiles UserProfile
for _, like := range likes {
userName, err := rdb.Get(ctx, fmt.Sprintf("user:%d", like.FollowUserID)).Result()
if err == nil {
userProfiles = append(userProfiles, UserProfile{UserID: like.FollowUserID, UserName: userName})
}
}
 
c.JSON(http.StatusOK, gin.H{"followings": userProfiles})
})
 
// サーバー起動
if err := r.Run(); err != nil {
log.Fatal("サーバーの起動に失敗しました:", err)
}
}
```
 
このコードは、GinフレームワークとRedisクライアントを使用しています。アプリケーション起動時に、`GUser`テーブルの全データを読み込み、RedisにユーザーIDをキー、ユーザー名を値として保存します。その後、特定の
 
ユーザーがフォローしているユーザーリストを取得するAPIでは、Redisから直接ユーザー名を取得してレスポンスに組み込みます。
 
このアプローチにより、データベースへのクエリ回数を削減し、レスポンスタイムを短縮することが可能です。ただし、`GUser`テーブルのデータに変更があった場合、その変更をリアルタイムでRedisに反映させる追加のロジックが必要になります。また、大量のデータをRedisにロードする際は、Redisサーバーのメモリ容量を適切に管理することも重要です。
 
## http(2)
理解しました。パフォーマンスを考慮して、`JOIN`を使用せずに、そして可能な限りデータベースへのクエリ数を減らすために、最初に必要なすべての`GUser`のデータを一度に取得し、そのデータを使ってユーザー名を検索する方法を検討することができます。この方法は、`GUser`テーブルのデータ量が多くない場合、またはメモリに全ユーザーのデータをロードできる環境であれば効果的です。ただし、データが非常に多い場合はメモリ消費が問題になる可能性があるため、適用する環境を考慮する必要があります。
 
以下の例では、すべての`GUser`データを最初に取得し、その後で`GLike`テーブルから取得したフォローユーザーIDに基づいてユーザー名を配列から探し出す方法を示します:
 
```go
package main
 
import (
"log"
"net/http"
 
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
 
// GLike モデルとGUser モデルは前の例と同じです。
 
func main() {
r := gin.Default()
 
// SQLiteデータベース接続の設定
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatal("データベース接続に失敗しました:", err)
}
 
// モデルのマイグレーション
db.AutoMigrate(&GLike{}, &GUser{})
 
// フォローしているユーザーリストの取得API
r.GET("/followings/:user_id", func(c *gin.Context) {
var likes GLike
var users GUser
userID := c.Param("user_id")
 
// GUser テーブルからすべてのユーザーを取得
if err := db.Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
 
// ユーザーIDをキーとして、ユーザー名をマッピング
userMap := make(map[uint]string)
for _, user := range users {
userMap[user.UserID] = user.UserName
}
 
// フォローしているユーザーIDのリストを取得
if err := db.Where("user_id = ?", userID).Find(&likes).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
 
// ユーザーIDに基づいてユーザー名を検索し、UserProfile にセット
var userProfiles UserProfile
for _, like := range likes {
if userName, exists := userMap[like.FollowUserID]; exists {
userProfiles = append(userProfiles, UserProfile{UserID: like.FollowUserID, UserName: userName})
}
}
 
c.JSON(http.StatusOK, gin.H{"followings": userProfiles})
})
 
r.Run() // localhost:8080でサーバーを起動
}
```
 
この例では、`GUser`テーブルの全データを最初に取得し、そのデータからユーザーIDをキーとしてユーザー名を格納するマップを作成します。その後、指定されたユーザーがフォローしているユーザーIDを`GLike`テーブルから取得し、先に作成したマップを使用してユーザー名を簡単に検索します。これにより、各フォローユーザーの情報を取得するためのクエリを発行する必要がな
 
くなり、データベースへの負荷を減らすことができます。
 
ただし、`GUser`テーブルのデータ量が非常に多い場合、すべてのユーザーデータをメモリにロードすることがパフォーマンスのボトルネックになる可能性があります。このアプローチは、データセットのサイズやアプリケーションの要件に応じて慎重に選択する必要があります。
 
 
 
 
## gRPC
gRPCを使用して上記の機能を実装する場合、まずはgRPCサーバーの設定とプロトコルバッファ(Protocol Buffers)の定義から始めます。gRPCはHTTP/2をベースにしたリモートプロシージャコール(RPC)システムであり、効率的な通信が可能です。ここでは、基本的なgRPCサーバーのセットアップと、フォローリスト取得機能の実装方法を示します。
 
### 1. プロトコルバッファ定義
 
まず、フォローリスト取得機能を提供するためのプロトコルバッファファイル(`.proto`ファイル)を定義します。このファイルでは、gRPCサービスと送受信されるメッセージの形式を定義します。
 
`follow_service.proto`:
```protobuf
syntax = "proto3";
 
package follow;
 
// フォローリスト取得サービスの定義
service FollowService {
  rpc GetFollowings(GetFollowingsRequest) returns (GetFollowingsResponse);
}
 
// フォローリスト取得リクエス
message GetFollowingsRequest {
  uint32 user_id = 1;
}
 
// フォローリスト取得レスポンス
message GetFollowingsResponse {
  repeated UserProfile followings = 1;
}
 
// ユーザープロファイル
message UserProfile {
  uint32 user_id = 1;
  string user_name = 2;
}
```
 
### 2. サービス実装
 
次に、上記で定義したプロトコルバッファに基づいて、サービスの実装を行います。これには、まずプロトコルバッファファイルからGoのコードを生成し、それを使用してgRPCサーバーを実装します。
 
以下のコマンドを使用して、プロトコルバッファからGoのコードを生成します(事前に`protoc`コンパイラと`protoc-gen-go`、`protoc-gen-go-grpc`プラグインが必要です):
 
```bash
protoc --go_out=. --go-grpc_out=. follow_service.proto
```
 
これにより、gRPCのインターフェースと必要なメッセージ型が含まれたGoのコードが生成されます。
 
次に、このインターフェースを実装するサービスのコードを記述します。
 
```go
package main
 
import (
"context"
"fmt"
"log"
"net"
 
"github.com/go-redis/redis/v8"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"google.golang.org/grpc"
 
follow "path/to/your/protobuf/package" // 生成されたprotobufパッケージのインポート
)
 
var db *gorm.DB
var rdb *redis.Client
var ctx = context.Background()
 
type server struct {
follow.UnimplementedFollowServiceServer
}
 
func (s *server) GetFollowings(ctx context.Context, req *follow.GetFollowingsRequest) (*follow.GetFollowingsResponse, error) {
// 省略: フォローリスト取得の実装
}
 
func main() {
// GORMとRedisの初期化
// ...
 
// gRPCサーバーのセットアップ
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
follow.RegisterFollowServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
```
 
### 3. gRPCクライアント
 
クライアント側でも、生成されたGoのコードを使用して、gRPCサーバーにリクエストを送信できます。サーバー側で定義した`GetFollowings`メソッドを呼び出すクライアントの例を以下
 
に示します。
 
```go
package main
 
import (
"context"
"log"
 
"google.golang.org/grpc"
follow "path/to/your/protobuf/package" // 生成されたprotobufパッケージのインポート
)
 
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := follow.NewFollowServiceClient(conn)
 
// GetFollowings メソッドを呼び出し
req := &follow.GetFollowingsRequest{UserId: 1}
res, err := c.GetFollowings(context.Background(), req)
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Followings: %v", res)
}
```
 
この例では、`GetFollowings` RPCを呼び出して、指定したユーザーIDのフォローリストを取得しています。サーバー側で`GetFollowings`メソッドの実装を完了させ、Redisからユーザー名を取得するロジックを追加することで、gRPCを介してフォローリストを取得できるようになります。
 
 
以下は、gRPCサーバー側で`GetFollowings` RPCメソッドを実装し、指定されたユーザーIDに基づいてフォローリストを取得し、それをクライアントに返す例です。この例では、Redisを使用してユーザー名をキャッシュし、それに基づいてレスポンスを構築しています。
 
```go
package main
 
import (
"context"
"fmt"
"log"
"net"
 
"github.com/go-redis/redis/v8"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"google.golang.org/grpc"
 
pb "path/to/your/protobuf/package" // protobufパッケージのインポート
)
 
var db *gorm.DB
var rdb *redis.Client
var ctx = context.Background()
 
type server struct {
pb.UnimplementedFollowServiceServer
}
 
// GLike と GUser のモデル定義は前述のものを使用
 
func (s *server) GetFollowings(ctx context.Context, req *pb.GetFollowingsRequest) (*pb.GetFollowingsResponse, error) {
var likes GLike
var response pb.GetFollowingsResponse
 
// フォローしているユーザーIDのリストを取得
if err := db.Where("user_id = ?", req.UserId).Find(&likes).Error; err != nil {
return nil, err
}
 
// Redisからユーザー名を取得
for _, like := range likes {
userName, err := rdb.Get(ctx, fmt.Sprintf("user:%d", like.FollowUserID)).Result()
if err != nil {
log.Printf("Redisからのユーザー名の取得に失敗しました: %v", err)
continue // Redisからユーザー名を取得できなかった場合はスキップ
}
response.Followings = append(response.Followings, &pb.UserProfile{
UserId:   uint32(like.FollowUserID),
UserName: userName,
})
}
 
return &response, nil
}
 
func main() {
var err error
// GORMとRedisの初期化
db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatalf("データベース接続に失敗しました: %v", err)
}
rdb = redis.NewClient(&redis.Options{
Addr:     "localhost:6379",
Password: "", // パスワードが設定されていない場合は空文字
DB:       0,  // 使用するDBを指定
})
 
// gRPCサーバーのセットアップ
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterFollowServiceServer(s, &server{})
log.Printf("サーバーがポート50051で起動しています")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
```
 
このコードは、指定されたユーザーIDに基づいてフォローしているユーザーのIDリストをデータベースから取得し、そのIDを使用してRedisからユーザー名を取得します。取得した情報をもとに`GetFollowingsResponse`メッセージを構築し、クライアントに返します。
 
実装の前提として、RedisにはユーザーのIDをキーとし、ユーザー名を値としてデータが保存されている必要があります。また、`pb "path/to/your/protobuf/package"`は、あなたが生成したプロトコルバッファのGoコードを指すパスに置き換えてください。このコードを動かす前に、必要なパッケージのインストールとプロトコルバッファファイルからのコード生成が完了していることを確認してください。
 
## ctx context.Context
func (s *server) GetFollowings(ctx context.Context, req *pb.GetFollowingsRequest) (*pb.GetFollowingsResponse, error) {
のctx context.Context
 
`ctx context.Context`は、Goの`context.Context`型で、関数やプロセス間でのデータの伝達、キャンセル信号、デッドラインなどを管理するためのメカニズムを提供します。`context.Context`は、API呼び出しや他の操作が実行される"コンテキスト"を表し、実行中の処理のスコープを定義するために使用されます。
 
gRPCでの使用例を挙げると、サーバーやクライアントはリクエスト処理を開始する際に`Context`を受け取り、この`Context`を介して、リクエストのキャンセルやタイムアウトの管理、メタデータの伝達などを行います。以下に、その主な使用目的をいくつか示します:
 
### リクエストのキャンセル
クライアントがリクエストをキャンセルした場合(例えば、ユーザーがUI上で操作をキャンセルした場合)、そのキャンセル信号は`Context`を介してサーバーに伝達され、サーバー側で進行中の処理を適切に中断することができます。
 
### デッドラインの管理
リクエストには、完了が期待される最終期限(デッドライン)が設定されることがあります。`Context`を使用すると、このデッドラインを管理し、期限を超えた場合に処理を中断することができます。
 
### メタデータの伝達
`Context`は、リクエストに関連するメタデータ(認証情報、リクエストIDなど)をサーバーに伝達する手段としても使用されます。このメタデータは、リクエストの処理に必要な追加情報を提供することがあります。
 
### トレーシングとロギング
`Context`は、分散トレーシングやロギングの情報を伝達するためにも使用されることがあり、システム全体でのリクエストの流れを追跡しやすくします。
 
`context.Context`は、APIの第一引数として頻繁に登場し、Goのコンカレントな処理やネットワーク通信において広く使われています。これにより、開発者はリクエストのライフサイクルを効果的に管理し、より信頼性の高いアプリケーションを構築することができます。
 
# フォロワーリスト
もちろんです。まず、gRPCを使用してフォロワーリストを取得する機能を実装するために、プロトコルバッファ定義を更新し、サービス実装を行います。ここでは、新しいgRPCメソッド`GetFollowers`を定義し、それをサーバーで実装する例を示します。
 
### Step 1: プロトコルバッファ定義の更新
 
`follower_service.proto`ファイルを作成または更新し、新しいサービス定義を追加します。
 
```protobuf
syntax = "proto3";
 
package follower;
 
// フォロワーリスト取得サービスの定義
service FollowerService {
  rpc GetFollowers(GetFollowersRequest) returns (GetFollowersResponse);
}
 
// フォロワーリスト取得リクエス
message GetFollowersRequest {
  uint32 user_id = 1;
}
 
// フォロワーリスト取得レスポンス
message GetFollowersResponse {
  repeated UserProfile followers = 1;
}
 
// ユーザープロファイル
message UserProfile {
  uint32 user_id = 1;
  string user_name = 2;
}
```
 
### Step 2: Goコードの生成
 
`protoc`コマンドを使って、上記の`.proto`ファイルからGoのコードを生成します。以下のコマンドを実行する前に、`protoc-gen-go`と`protoc-gen-go-grpc`プラグインがインストールされていることを確認してください。
 
```sh
protoc --go_out=. --go-grpc_out=. path/to/your/follower_service.proto
```
 
このコマンドは、指定したプロトコルバッファファイルからGoのソースコードを生成し、gRPCサーバーとクライアントで使用するための型とメソッドを提供します。
 
### Step 3: gRPCサーバーの実装
 
次に、生成されたインターフェイスに基づいて`GetFollowers`メソッドを実装します。以下は、gRPCサーバーでのフォロワーリスト取得機能の実装例です。
 
```go
package main
 
import (
"context"
"fmt"
"log"
"net"
 
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"google.golang.org/grpc"
 
pb "path/to/your/generated/follower" // 生成されたprotobufコードのパス
)
 
type server struct {
pb.UnimplementedFollowerServiceServer
}
 
func (s *server) GetFollowers(ctx context.Context, req *pb.GetFollowersRequest) (*pb.GetFollowersResponse, error) {
var likes GLike
var response pb.GetFollowersResponse
 
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("failed to connect database: %v", err)
}
 
// フォローされているユーザーIDのリストを取得
if err := db.Where("follow_user_id = ?", req.UserId).Find(&likes).Error; err != nil {
return nil, err
}
 
for _, like := range likes {
var user GUser
if err := db.Where("user_id = ?", like.UserID).First(&user).Error; err == nil {
response.Followers = append(response.Followers, &pb.UserProfile{
UserId:   uint32(user.UserID),
UserName: user.UserName,
})
}
}
 
return &response, nil
}
 
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterFollowerServiceServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
```
 
このコードは、指定された`user_id`に基づいてフォローされて
 
いるユーザーのリストをデータベースから取得し、その結果を`GetFollowersResponse`メッセージに格納してクライアントに返します。
 
### 注意点
 
- 上記のコードは例示的なものであり、実際にはデータベースの接続情報(`gorm.Open`の引数など)や、protobufファイルのパス(`pb "path/to/your/generated/follower"`)を適切に設定する必要があります。
- データベーススキーマ(`GLike`と`GUser`の定義)は前述のHTTP APIの実装で使用されたものと同様です。
- このコードを実行する前に、`follower_service.proto`からGoのコードを生成し、必要なパッケージがインストールされていることを確認してください。
 
## 余談(コンテキストにユーザセット)
ログイン時にユーザーIDを必ず取得し、それをリクエストパラメータとして必要としない場合、gRPCのコンテキスト(`context.Context`)を活用してメタデータとしてユーザーIDを渡すことができます。このメタデータは、リクエストの送信時にクライアントからサーバーへと伝達されます。このアプローチにより、リクエストメッセージ自体からユーザーIDを削除することができます。
 
### gRPCメタデータを使用したユーザーIDの伝達
 
以下の手順で、メタデータを使ってgRPCサーバー側でユーザーIDを取得する方法を実装します。
 
#### 1. `GetFollowersRequest`メッセージから`user_id`フィールドを削除
 
`follower_service.proto`で`GetFollowersRequest`メッセージを更新します。
 
```protobuf
message GetFollowersRequest {
  // リクエストパラメータなし
}
```
 
#### 2. クライアント側でメタデータにユーザーIDをセット
 
クライアントからリクエストを送信する際に、メタデータを使用してユーザーIDを含めます。
 
```go
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
pb "path/to/your/generated/follower" // 更新されたprotobufパッケージのインポート
)
 
// 例としてユーザーIDを設定
userID := "1234"
 
// メタデータを作成してユーザーIDをセット
md := metadata.New(map[string]string{
"user-id": userID,
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
 
// gRPCサーバーにリクエストを送信
resp, err := followerClient.GetFollowers(ctx, &pb.GetFollowersRequest{})
if err != nil {
log.Fatalf("Could not get followers: %v", err)
}
// 処理...
```
 
#### 3. サーバー側でメタデータからユーザーIDを取得
 
サーバー側では、リクエストのコンテキストからメタデータを取り出し、ユーザーIDを取得します。
 
```go
import (
"context"
"google.golang.org/grpc/metadata"
)
 
func (s *server) GetFollowers(ctx context.Context, req *pb.GetFollowersRequest) (*pb.GetFollowersResponse, error) {
// コンテキストからメタデータを取得
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, fmt.Errorf("could not retrieve metadata from context")
}
 
// メタデータからユーザーIDを取得
userIDs := md.Get("user-id")
if len(userIDs) == 0 {
return nil, fmt.Errorf("user-id is required in metadata")
}
userID := userIDs[0]
 
// userIDを使用して処理...
}
```
 
このようにメタデータを通じてユーザーIDを伝達することで、リクエストメッセージ自体にはユーザーIDを含めずに済みます。これは、特定の認証情報やセッション情報をAPIの各リクエストとともに透過的に扱いたい場合に有用な手法です。
 
### gRPCで
はい、パフォーマンスを意識して、`GUser`テーブルのデータをあらかじめ配列(またはマップ)に読み込み、フォローされているユーザーのリストを取得する際に、その配列から一致するユーザー名を検索してセットする方法は可能です。このアプローチにより、各フォロワーごとにデータベースクエリを発行する回数を減らし、全体のレスポンスタイムを改善することができます。
 
以下は、そのような実装を示す疑似コードです。この例では、`GetFollowers`メソッドの実装をgRPCサービスとして提供していますが、同じロジックはHTTP APIや他のプロトコルで提供されるサービスにも適用できます。
 
### Step 1: ユーザーデータのプリロード
 
サーバー起動時または適宜、`GUser`テーブルの全データを読み込んで、ユーザーIDをキーとするマップに格納します。このマップは、ユーザーIDからユーザー名を効率的に検索するために使用します。
 
### Step 2: フォロワーリストの取得とユーザー名のセット
 
フォロワーリスト取得時には、まずフォローされているユーザーIDのリストを`GLike`テーブルから取得し、その後、プリロードしたユーザーデータのマップを使用してユーザー名を検索します。
 
```go
func (s *server) GetFollowers(ctx context.Context, req *pb.GetFollowersRequest) (*pb.GetFollowersResponse, error) {
    var likes GLike
    var response pb.GetFollowersResponse
 
    // userMapの初期化
    userMap := make(map[uint]string)
    
    // GUserテーブルから全ユーザーのデータを読み込む
    var users GUser
    db.Find(&users)
    for _, user := range users {
        userMap[user.UserID] = user.UserName
    }
 
    // フォローされているユーザーIDのリストを取得
    if err := db.Where("follow_user_id = ?", userIDFromContext(ctx)).Find(&likes).Error; err != nil {
        return nil, err
    }
 
    // プリロードしたユーザーデータのマップからユーザー名を検索
    for _, like := range likes {
        if userName, ok := userMap[like.UserID]; ok {
            response.Followers = append(response.Followers, &pb.UserProfile{
                UserId:   uint32(like.UserID),
                UserName: userName,
            })
        }
    }
 
    return &response, nil
}
```
 
### 注意点
 
- `userIDFromContext(ctx)`は、リクエストのコンテキストからユーザーIDを取得する架空の関数です。実際には、メタデータからユーザーIDを取得するロジックを実装する必要があります。
- `userMap`は、サーバー起動時に`GUser`テーブルから読み込んだユーザーデータを格納するマップです。キーはユーザーID、値はユーザー名です。
- この方法は、`GUser`テーブルのデータが頻繁に変更されない、または適宜マップを更新するロジックが実装されている場合に適しています。
 
このアプローチにより、フォローされているユーザーのリスト取得処理のパフォーマンスを向上させることができます。ただし、ユーザーデータのマップをメモリに保持するため、アプリケーションのメモリ使用量が増加する可能性がある点には注意が必要です。