Create a service extension app
Overview
An Extend Service Extension app is a RESTful web service created using a stack that includes a gRPC Server and the gRPC Gateway.
In general, the steps to build a RESTful web service from scratch using this stack are as follows:
- Define the gRPC server using a protobuf (
*.proto
) file. - Generate stubs from the
*.proto
file and implement the gRPC Server. - Generate the gRPC Gateway code from the
*.proto
file and write an entry point for it. - Generate the OpenAPI 2.0 specification from the
*.proto
file.
However, with the Service Extension app template, you only need to perform steps 1 and 2. The remaining steps have been packaged to be performed automatically at build time. This app template also comes with a gRPC server interceptor which helps you create RESTful endpoints that require some authorization. Additionally, it comes with built-in instrumentation for observability, ensuring that metrics and logs are available upon deployment.
This article walks you through how to modify the Extend Service Extension app template and transform it into your own app that fits your requirements.
Prerequisites
You have cloned the Extend Service Extension app template.
- Go
- C#
- Java
- Python
git clone https://github.com/AccelByte/extend-service-extension-go
git clone https://github.com/AccelByte/extend-service-extension-csharp
git clone https://github.com/AccelByte/extend-service-extension-java
git clone https://github.com/AccelByte/extend-service-extension-python
Project structure
- Go
- C#
- Java
- Python
Customizing your Extend Service Extension app involves modifying the service.proto
and myService.go
files. The app initializes key components, such as the gRPC server, in main.go
. When a request is made to the RESTful endpoint, the gRPC gateway handles it and forwards it to the corresponding gRPC method. Before myService.go
executes any custom logic based on the request, the authServerInterceptor.go
first verifies that the request has the necessary access token and authorization. No other files need to be modified unless you require further customization.
.
├── main.go # App starts here
├── pkg
│ ├── common
│ │ ├── authServerInterceptor.go # gRPC server interceptor for access token authentication and authorization
│ │ ├── ...
│ ├── pb # gRPC stubs generated from gRPC server protobuf
│ │ └── ...
│ ├── proto
│ │ ├── service.proto # gRPC server protobuf with additional options for exposing as RESTful web service
│ │ └── ...
│ ├── service
│ │ ├── myService.go # gRPC server implementation containing the custom logic
│ │ └── ...
│ └── ...
└── ...
Customizing your Extend Service Extension app involves modifying the service.proto
and MyService.cs
files. The app initializes key components, such as the gRPC server, in Program.cs
. When a request is made to the RESTful endpoint, the gRPC gateway handles it and forwards it to the corresponding gRPC method. Before myService.cs
executes any custom logic based on the request, the authServerInterceptor.cs
first verifies that the request has the necessary access token and authorization. No other files need to be modified unless you require further customization.
.
├── src
│ ├── AccelByte.Extend.ServiceExtension.Server
│ │ ├── AccelByte.Extend.ServiceExtension.Server.csproj
│ │ ├── Classes
│ │ │ ├── AuthorizationInterceptor.cs # gRPC server interceptor for access token authentication and authorization
│ │ │ └── ...
│ │ ├── Program.cs # App starts here
│ │ ├── Protos
│ │ │ ├── service.proto # gRPC server protobuf with additional options for exposing as RESTful web service
│ │ │ └── ...
│ │ ├── Services
│ │ │ └── MyService.cs # gRPC server implementation containing the custom logic
│ └── extend-service-extension-server.sln
└── ...
Customizing your Extend Service Extension app involves modifying theservice.proto
and MyService.java
files. The app initializes key components, such as the gRPC server, in Application.java
. When a request is made to the RESTful endpoint, the gRPC gateway handles it and forwards it to the corresponding gRPC method. Before MyService.java
executes any custom logic based on the request, the AuthServerInterceptor.java
first verifies that the request has the necessary access token and authorization. No other files need to be modified unless you require further customization.
.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── net
│ │ │ └── accelbyte
│ │ │ └── extend
│ │ │ └── serviceextension
│ │ │ ├── Application.java # App starts here
│ │ │ ├── grpc
│ │ │ │ ├── AuthServerInterceptor.java # gRPC server interceptor for access token authentication and authorization
│ │ │ │ └── ...
│ │ │ ├── service
│ │ │ │ └── MyService.java # gRPC server implementation containing the custom logic
│ │ │ └── ...
│ │ ├── proto
│ │ │ ├── service.proto # gRPC server protobuf with additional options for exposing as RESTful web service
│ │ │ └── ...
│ │ └── ...
│ └── ...
└── ...
Customizing your Extend Service Extension app involves modifying theservice.proto
and my_service.py
files. The app initializes key components, such as the gRPC server, in __main__.py
. When a request is made to the RESTful endpoint, the gRPC gateway handles it and forwards it to the corresponding gRPC method. Before my_service.py
executes any custom logic based on the request, the authorization.py
first verifies that the request has the necessary access token and authorization. No other files need to be modified unless you require further customization.
.
├── proto
│ ├── app
│ │ ├── service.proto # gRPC server protobuf with additional options for exposing as RESTful web service
│ │ └── ...
│ ├── ...
├── src
│ ├── accelbyte_grpc_plugin
│ │ ├── interceptors
│ │ │ ├── authorization.py # gRPC server interceptor for access token authentication and authorization
│ │ │ └── ...
│ │ └── ...
│ ├── app
│ │ ├── __main__.py # App starts here
│ │ ├── services
│ │ │ ├── my_service.py # gRPC server implementation containing the custom logic
│ │ │ └── ...
│ │ └── ...
│ └── ...
└── ...
Modify the protobuf
- Go
- C#
- Java
- Python
In the app template, the file can be found in pkg/proto/service.proto
.
In the app template, the file can be found in src/AccelByte.Extend.ServiceExtension.Server/Protos/service.proto
.
In the app template, the file can be found in src/main/proto/service.proto
.
In the app template, the file can be found in proto/app/service.proto
.
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "permission.proto";
service Service {
rpc CreateOrUpdateGuildProgress (CreateOrUpdateGuildProgressRequest) returns (CreateOrUpdateGuildProgressResponse) {
option (permission.action) = CREATE;
option (permission.resource) = "ADMIN:NAMESPACE:{namespace}:CLOUDSAVE:RECORD";
option (google.api.http) = {
post: "/v1/admin/namespace/{namespace}/progress"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Update Guild progression"
description: "Update Guild progression if not existed yet will create a new one"
security: {
security_requirement: {
key: "Bearer"
value: {}
}
}
};
}
message CreateOrUpdateGuildProgressRequest {
string namespace = 1;
GuildProgress guild_progress = 2;
}
message CreateOrUpdateGuildProgressResponse {
GuildProgress guild_progress = 1;
}
}
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "Service API";
version: "1.0";
};
schemes: HTTP;
schemes: HTTPS;
base_path: "/service";
security_definitions: {
security: {
key: "Bearer";
value: {
type: TYPE_API_KEY;
in: IN_HEADER;
name: "Authorization";
}
}
};
};
The Extend Service Extension service.proto
file is essentially a regular gRPC server definition with additional options:
option (google.api.http)
Describes the relationship between gRPC methods and RESTful endpoints. For more details, refer to the gRPC-Gateway documentation.
option (permission.resource)
andoption (permission.action)
Describes the required permission resource and action to be able to invoke each RESTful endpoint. With this, you can create an endpoint that requires a valid AGS access token and permission.
The permission resource and action values are used by the included gRPC server interceptor to perform authorization.
option (permission.resource)
: You can create your own permission resource string using the AGS format.option (permission.action)
: Valid values for this option areCREATE
,READ
,UPDATE
, orDELETE
. For more details, refer to AGS permission actions.
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger)
andoption (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation)
Provides information for generating OpenAPI 2.0 specification. For more details, refer to the gRPC-Gateway documentation.
Generate stubs from protobuf
- Go
- C#
- Java
- Python
Run this command to generate stubs from the proto file:
make proto # Generate protobuf code, gateway code, and swagger JSON
Run this command to generate stubs from the proto file:
make proto # Generate gateway code and swagger JSON
make build # Some protobuf code is generated on-the-fly during build
Run this command to generate stubs from the proto file:
make proto # Generate gateway code, and swagger JSON
make build # Some protobuf code is generated on-the-fly during build
Run this command to generate stubs from the proto file:
make proto # Generate protobuf code, gateway code, and swagger JSON
Always run the above commands after modifying the service.proto
file to regenerate the stubs.
Implement request handlers
- Go
- C#
- Java
- Python
In the app project, the following can be found in src/master/pkg/service/myService.go
.
To set up the service, create a struct that embeds the UnimplementedServiceServer
.
import pb "extend-custom-guild-service/pkg/pb"
type MyServiceServerImpl struct {
pb.UnimplementedServiceServer
// Other fields
}
The CreateOrUpdateGuildProgress
function is implemented as follows.
func (g MyServiceServerImpl) CreateOrUpdateGuildProgress(
ctx context.Context, req *pb.CreateOrUpdateGuildProgressRequest,
) (*pb.CreateOrUpdateGuildProgressResponse, error) {
// Your implementation
}
Similarly for the GetGuildProgress
function.
func (g MyServiceServerImpl) GetGuildProgress(
ctx context.Context, req *pb.GetGuildProgressRequest,
) (*pb.GetGuildProgressResponse, error) {
// Your implementation
}
In the app project, the following can be found in src/AccelByte.Extend.ServiceExtension.Server/Services/MyService.cs
.
To set up the service, create a class derived from Service.ServiceBase
. This class will act as the service implementation.
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Grpc.Core;
using AccelByte.Sdk.Api;
using AccelByte.Extend.ServiceExtension.Server.Model;
namespace AccelByte.Extend.ServiceExtension.Server.Services
{
public class MyService : Service.ServiceBase
{
public MyService()
{
}
// Implement your service logic in here
}
}
To implement the CreateOrUpdateGuildProgress
function, you can override the method as follows.
public override Task<CreateOrUpdateGuildProgressResponse> CreateOrUpdateGuildProgress(CreateOrUpdateGuildProgressRequest request, ServerCallContext context)
{
// Implementation goes here
}
And similarly for the GetGuildProgress
function.
public override Task<GetGuildProgressResponse> GetGuildProgress(GetGuildProgressRequest request, ServerCallContext context)
{
// Implementation goes here
}
In the app project, the following can be found in src/main/java/net/accelbyte/extend/serviceextension/service/MyService.java
.
To set up the service, create a class derived from ServiceGrpc.ServiceImplBase
. This class will act as the service implementation.
import lombok.extern.slf4j.Slf4j;
import net.accelbyte.extend.serviceextension.*;
import net.accelbyte.sdk.core.AccelByteSDK;
import org.lognet.springboot.grpc.GRpcService;
@GRpcService
@Slf4j
public class MyService extends ServiceGrpc.ServiceImplBase {
// Implementation goes here
}
To implement the CreateOrUpdateGuildProgress
function, you can override the method as follows.
public void createOrUpdateGuildProgress(
CreateOrUpdateGuildProgressRequest request, StreamObserver<CreateOrUpdateGuildProgressResponse> responseObserver
) {
// Implementation goes here
}
You can do the same for the `GetGuildProgress`` function.
public void getGuildProgress(
GetGuildProgressRequest request, StreamObserver<GetGuildProgressResponse> responseObserver
) {
// Implementation goes here
}
In the app project, the following can be found in src/master/src/app/services/my_service.py
.
To set up our service, we'll first create a class derived from ServiceServicer
.
This class will act as our service implementation.
from ..proto.service_pb2 import (
CreateOrUpdateGuildProgressRequest,
CreateOrUpdateGuildProgressResponse,
GetGuildProgressRequest,
GetGuildProgressResponse,
DESCRIPTOR,
)
from ..proto.service_pb2_grpc import ServiceServicer
class AsyncService(ServiceServicer):
full_name: str = DESCRIPTOR.services_by_name["Service"].full_name
# Implement your service logic in here.
To implement the CreateOrUpdateGuildProgress
function, you can override the method as follows.
async def CreateOrUpdateGuildProgress(
self, request: CreateOrUpdateGuildProgressRequest, context: Any
) -> CreateOrUpdateGuildProgressResponse:
...
And similarly for the GetGuildProgress
function.
async def GetGuildProgress(
self, request: GetGuildProgressRequest, context: Any
) -> GetGuildProgressResponse:
...