Handle Multiple Events
注釈:本資料はAI技術を用いて翻訳されています。
Overview
Extend Event Handler アプリは gRPC サーバーです。AccelByte Gaming Services (AGS) は Kafka にイベントを送信し、Extend プラットフォームは gRPC メソッドを呼び出すことで各イベントをアプリに配信します。
1 つの Extend Event Handler アプリで、必要な数だけイベントを処理できます。関心のあるイベントごとに、以下を行います。
- その Protocol Buffers (Protobuf) 定義を追加する。
- proto が生成する gRPC サービスを実装する。
- そのサービスを gRPC サーバーに登録する。
設定が必要な特別なルーターはありません。すべての AGS イベントは単一の OnMessage RPC を持つ独自の gRPC サービスにマッピングされるため、サービスを追加で実装することは、単純に 1 つのアプリがより多くのイベントに応答することを意味します。このパターンは Go、Java、Python、C# のすべてで同一に機能します。異なるのは言語の仕組みだけです。
背景については、Extend Event Handler アプリテンプレートを使い始めるおよび独自の Extend Event Handler アプリを作成するを参照してください。
イベント配信の仕組み
各 AGS イベントは、1 つの RPC を持つサービスとして .proto ファイルに記述されています。メッセージ(例: UserLoggedIn)にはイベントのペイロードが含まれます。
// One service per event. The message (for example, UserLoggedIn) carries the payload.
service UserAuthenticationUserLoggedInService {
rpc OnMessage(UserLoggedIn) returns (google.protobuf.Empty);
}
service UserAuthenticationUserLoggedOutService {
rpc OnMessage(UserLoggedOut) returns (google.protobuf.Empty);
}
AccelByte のイベント仕様の proto ファイル(アカウント、認証、ユーザーイベントをはじめ多数のドメインを対象とします)は、accelbyte-api-proto リポジトリで公開されています。AGS が userLoggedIn イベントを発生させると、Extend プラットフォームはアプリの UserAuthenticationUserLoggedInService/OnMessage を呼び出します。そのサービスを実装し登録していれば、コードが実行されます。実装していない場合、そのイベントは単に処理されません。
複数のイベントを処理するための手順は、常に同じ 3 つのステップです。
- 必要なイベントごとに proto を追加し、コードを再生成する。
OnMessageハンドラーを記述して、生成されたサービスを実装する。- サービスを gRPC サーバーに登録する。
リファレンスアプリには、UserLoggedIn(および UserThirdPartyLoggedIn)が例としてすでに実装されています。追加するすべてのイベントに対して同じパターンを拡張していきます。
サービスの追加、実装、登録
以下の手順では、各言語で 3 ステップのパターンをどのように適用するかを示します。例として、既存のログインハンドラーと並んで userLoggedOut イベント用のハンドラーを追加します。
- C#
- Go
- Java
- Python
参照: extend-event-handler-csharp
-
proto を追加して参照する。 proto を
Protos/iam/account/v1/(または適切なパス)に配置し、ビルド時に生成されるように.csprojファイルで参照します。<ItemGroup>
<Protobuf Include="Protos\iam\account\v1\account.proto" GrpcServices="Server" />
</ItemGroup> -
サービスを実装する。
Services/の中で行います。生成された...ServiceBaseを拡張し、OnMessageをオーバーライドします。// Services/UserLoggedOutService.cs
public class UserLoggedOutService
: UserAuthenticationUserLoggedOutService.UserAuthenticationUserLoggedOutServiceBase
{
private readonly ILogger<UserLoggedOutService> _Logger;
public UserLoggedOutService(ILogger<UserLoggedOutService> logger,
IAccelByteServiceProvider abProvider)
{
_Logger = logger;
}
public override Task<Empty> OnMessage(UserLoggedOut request, ServerCallContext context)
{
_Logger.LogInformation("Received UserLoggedOut event: {@Request}", request);
// ... your custom logic ...
return Task.FromResult(new Empty());
}
} -
サービスを登録する。
Program.csにもう 1 つMapGrpcService<T>()呼び出しを追加します。app.MapGrpcService<UserLoggedInService>();
app.MapGrpcService<UserThirdPartyLoggedInService>();
app.MapGrpcService<UserLoggedOutService>(); // <-- your new handler
イベントを追加するたびに、新しいサービスクラス 1 つと MapGrpcService<T>() 呼び出し 1 つが必要になります。
-
proto を追加して再生成する。 イベント仕様の proto を
pkg/proto/accelbyte-asyncapi/に配置し、次のコマンドを実行します。make proto # runs proto.sh -> protoc, generating into pkg/pb/ -
サービスを実装する。
pkg/service/にハンドラーを作成します。生成されたUnimplemented...ServiceServerを埋め込み、OnMessageを実装します。// pkg/service/logoutHandler.go
type LogoutHandler struct {
pb.UnimplementedUserAuthenticationUserLoggedOutServiceServer
namespace string
}
func (o *LogoutHandler) OnMessage(ctx context.Context, msg *pb.UserLoggedOut) (*emptypb.Empty, error) {
scope := common.GetScopeFromContext(ctx, "LogoutHandler.OnMessage")
defer scope.Finish()
scope.Log.Info("received an event", "event", msg)
// ... your custom logic ...
return &emptypb.Empty{}, nil
} -
サービスを登録する。
main.goの既存のものと並べて登録します。// Existing handlers
loginHandler := service.NewLoginHandler(configRepo, tokenRepo, namespace)
pb.RegisterUserAuthenticationUserLoggedInServiceServer(s, loginHandler)
thirdPartyLoginHandler := service.NewThirdPartyLoginHandler(configRepo, tokenRepo, namespace)
pb.RegisterUserAuthenticationUserThirdPartyLoggedInServiceServer(s, thirdPartyLoginHandler)
// Your new handler — just add another Register call
logoutHandler := &service.LogoutHandler{ /* ... */ }
pb.RegisterUserAuthenticationUserLoggedOutServiceServer(s, logoutHandler)
イベントを追加するたびに、新しいハンドラーファイル 1 つと pb.Register...Server(s, handler) 呼び出し 1 つが必要になります。
このプロジェクトは grpc-spring-boot-starter プラグインを使用しているため、proto の生成とサービスの登録は自動で行われます。
-
proto を追加する。 イベント仕様の proto を
src/main/proto/accelbyte-asyncapi/に配置します。Gradle ビルド(./gradlew build)がコードを自動生成します。 -
サービスを実装し自動登録する。 生成された
...ServiceImplBaseを拡張し、クラスに@GRpcServiceアノテーションを付けます。このアノテーションが起動時に gRPC サーバーへ登録するため、手動での配線は不要です。// src/main/java/net/accelbyte/service/LogoutHandler.java
@Slf4j
@GRpcService // <-- auto-registers this service with the gRPC server
public class LogoutHandler
extends UserAuthenticationUserLoggedOutServiceGrpc.UserAuthenticationUserLoggedOutServiceImplBase {
@Override
public void onMessage(UserLoggedOut request, StreamObserver<Empty> responseObserver) {
log.info("received a message: {}", request);
// ... your custom logic ...
responseObserver.onNext(Empty.getDefaultInstance());
responseObserver.onCompleted();
}
}
より多くのイベントを処理するには、イベントごとに @GRpcService クラスを 1 つ追加します。Spring が各クラスを自動的に検出し登録します。
参照: extend-event-handler-python
-
proto を追加して再生成する。 proto を
proto/に配置し、次のコマンドを実行します。make proto # runs proto.sh -> protoc, generating *_pb2.py / *_pb2_grpc.py into src/ -
サービサーを実装する。
src/app/services/の中で行います。生成された...Servicerをサブクラス化し、非同期のOnMessageメソッドを実装します。# src/app/services/logout_handler.py
class AsyncLogoutHandlerService(UserAuthenticationUserLoggedOutServiceServicer):
full_name: str = DESCRIPTOR.services_by_name[
"UserAuthenticationUserLoggedOutService"
].full_name
def __init__(self, namespace: str, sdk=None, logger=None):
self.namespace = namespace
self.sdk = sdk
self.logger = logger
async def OnMessage(self, request: UserLoggedOut, context):
# ... your custom logic ...
return Empty() -
サービサーを登録する。
src/app/__main__.pyにもう 1 つAppGRPCServiceOptを追加します。from account_pb2_grpc import (
add_UserAuthenticationUserLoggedInServiceServicer_to_server,
add_UserAuthenticationUserLoggedOutServiceServicer_to_server,
)
# Existing handler
opts.append(
AppGRPCServiceOpt(
AsyncLoginHandlerService(namespace=namespace, sdk=sdk, logger=logger),
AsyncLoginHandlerService.full_name,
add_UserAuthenticationUserLoggedInServiceServicer_to_server,
)
)
# Your new handler — just append another opt
opts.append(
AppGRPCServiceOpt(
AsyncLogoutHandlerService(namespace=namespace, sdk=sdk, logger=logger),
AsyncLogoutHandlerService.full_name,
add_UserAuthenticationUserLoggedOutServiceServicer_to_server,
)
)
app = App(port=port, env=env, logger=logger, opts=opts)
await app.run()
イベントを追加するたびに、新しいサービサークラス 1 つと opts.append(...) 呼び出し 1 つが必要になります。
複数のイベントをローカルでテストする
すべてのリファレンスアプリは gRPC リフレクションを公開し、デフォルトでポート 6565 で動作するため、Postman のような gRPC クライアントで各ハンドラーをテストできます。
- プレーンテキストとリフレクションを有効にして
localhost:6565に接続します。 - メソッドのドロップダウンから、テストしたいサービス(例:
UserAuthenticationUserLoggedInService/OnMessageやUserAuthenticationUserLoggedOutService/OnMessage)を選択します。 namespaceやuserIdなどのフィールドを含むサンプルのイベントペイロードを JSON として送信します。- 呼び出しが成功すると空のレスポンス(
google.protobuf.Empty)が返ることを確認します。
登録済みのすべてのハンドラーが応答することを確認するために、各イベントについて繰り返します。デプロイ後は、AGS が実際のイベントを対応するサービスへ自動的にルーティングします。