Skip to content

Commit

Permalink
doc: sync User guide to Kitex multi-service (#1024)
Browse files Browse the repository at this point in the history
Co-authored-by: Marina Sakai <[email protected]>
  • Loading branch information
alice-yyds and Marina-Sakai authored Mar 12, 2024
1 parent 64270fb commit 1da567f
Showing 1 changed file with 162 additions and 48 deletions.
210 changes: 162 additions & 48 deletions content/en/docs/kitex/Tutorials/advanced-feature/multi_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,36 @@ keywords: ["Kitex", "Multi Services", "gRPC", "thrift", "protobuf"]
description: Kitex supports multiple service registration on a server.
---

## Introduction

Since Kitex v0.8.0, multiple service registrations on a single server are supported.

Currently, the feature is available for:
- gRPC transport protocol
- Kitex thrift and protobuf (non-streaming)

- gRPC transport protocol, including [Thrift Streaming over HTTP2](/docs/kitex/tutorials/basic-feature/protocol/transport-streaming/thrift_streaming/) (which is also based on gRPC) (>= v0.8.0)
- Kitex thrift & protobuf (non-streaming) (>= v0.9.0)

## Usage

### Preparation
Please generate code for each service using Kitex command tool. For more details, please refer to [Code Generation Tool](/docs/kitex/tutorials/code-gen/code_generation/).
### Client Side Users

If you are client-side users and using the multi-service feature, please follow the instructions below:

1. Upgrade the client to the Kitex version >= v0.9.0
2. Use TTHeader as the transport protocol `client.WithTransportProtocol(transport.TTHeader)`
3. Add the following option on the client side: `client.WithMetaHandler(transmeta.ClientTTHeaderHandler())`

### Server Side Users

#### Preparation

Please generate code for each service using kitex command tool (>= v0.9.0). For more details, please refer to [Code Generation Tool](/docs/kitex/tutorials/code-gen/code_generation/).

(Note: For users utilizing the gRPC multi-service feature from v0.8.0 onwards, there have been some slight usage changes regarding service registration, so please upgrade your kitex command tool to v0.9.0+. For more details, please refer to the section "Create a server and register your services on the server".)

The results of code generation will be as follows:
```text

```
kitex_gen
|_ api
|_ servicea
Expand All @@ -30,26 +49,30 @@ kitex_gen
|_ server.go
|_ serviceb.go
|_ ...
```

You will see `RegisterService` func in each service's `server.go`.
```golang
You will see `RegisterService` func in each service's `server.go`.

```go
func RegisterService(svr server.Server, handler XXX, opts ...server.RegisterOption) error {
if err := svr.RegisterService(serviceInfo(), handler, opts...); err != nil {
return err
}
return nil
}

```

### Create a server and register your services on the server
#### Create a server and register your services on the server

Registering services on a server is a straightforward process.

First, create a server. Then, by calling the `RegisterService` function in your generated code, a service can be registered.

Multiple services can be called on the same server, registering as many services as needed.

```golang
```go
package main

import (
Expand All @@ -62,57 +85,57 @@ import (
func main() {
// create a server by calling server.NewServer
svr := server.NewServer(your_server_option)
// register multi-service on a server
err := servicea.RegisterService(svr, new(ServiceAImpl))
err = serviceb.RegisterService(svr, new(ServiceBImpl))
// register your multi-service on a server
err := **servicea**.RegisterService(svr, new(ServiceAImpl))
err := **serviceb**.RegisterService(svr, new(ServiceBImpl))

err = svr.Run()

if err != nil {
logs.Error("%s", err.Error())
if err := svr.Run(); err != nil {
klog.Errorf("%s", err.Error())
}
logs.Stop()
}

```

### Fallback service
#### Fallback Service

Suppose there is the same named method between services.

```thrift
// demo.thrift
namespace go api
struct Request {
1: string message
1: string message
}
struct Response {
1: string message
1: string message
}
service ServiceA {
Response sameNamedMethod(1: Request req)
Response sameNamedMethod(1: Request req)
}
service ServiceB {
Response sameNamedMethod(1: Request req)
Response sameNamedMethod(1: Request req)
}
```

In this case, **please note that you need to specify one service as a fallback service.**

Fallback service is used to maintain compatibility when the client is using an old Kitex version (< v0.9.0)
- or when `TTHeader` is not being used for transport protocol,
- or the client does not set an optional meta handler `transmeta.ClientTTHeaderHandler()`.
Fallback service is used to maintain compatibility when the client is using an old Kitex version (< v0.9.0)

- or when `TTHeader` is not being used for transport protocol
- or the client does not set an optional meta handler `transmeta.ClientTTHeaderHandler()`

If you don't specify any fallback service or if you specify multiple fallback services, an error will be returned on server startup.
**If you don't specify any fallback service or if you specify multiple fallback services, an error will be returned on server startup.**

Note that you can specify only one service as a fallback service.

`RegisterService()` in the generated code (`server.go`) has an optional argument:`server.RegisterOption`.
If `server.WithFallbackService` option is passed, the service will be registered as a fallback service.
`RegisterService()` in the generated code (`server.go`) has an optional argument: `server.RegisterOption`. If `server.WithFallbackService` option is passed, the service will be registered as a fallback service.

```golang
```go
func main() {
// create a server by calling server.NewServer
svr := server.NewServer(your_server_option)
Expand All @@ -127,6 +150,7 @@ func main() {
}
logs.Stop()
}

```

Another way to avoid an error on server startup without specifying a fallback service is to use the option `server.WithRefuseTrafficWithoutServiceName`.
Expand All @@ -135,31 +159,121 @@ With this option, no error is returned when starting up the server even when you

But when using this option, the following must be noted:

When `server.WithRefuseTrafficWithoutServiceName` option is enabled, an error will occur with a message "no service name while the server has WithRefuseTrafficWithoutServiceName option enabled"
if the server receives requests in the following cases:
- Client uses the older Kitex version (< v0.9.0), which does not support multi-service feature
- The transport protocol of a request is not TTHeader (Kitex pb's transport protocol enables TTHeader by default)
- Client option `client.WithMetaHandler(transmeta.ClientTTHeaderHandler())` is not set
When `server.WithRefuseTrafficWithoutServiceName` option is enabled, an error will occur with a message “no service name while the server has WithRefuseTrafficWithoutServiceName option enabled” if the server receives requests in the following cases:

1. Client uses the older Kitex version (< v0.9.0), which does not support multi-service feature
2. The transport protocol of a request is not TTHeader (Kitex pb’s transport protocol enables TTHeader by default)
3. Client option `client.WithMetaHandler(transmeta.ClientTTHeaderHandler())` is not set

#### How to not fallback to a fallback service (= not rely on method name to find a service)

In some cases, even though a fallback service is specified for methods with the same name between services, the client's request may be intended to call a different service than the fallback service.

In such cases, please ensure the following on the client side:

1. Upgrade the client to Kitex version that supports thrift & pb multi-service (>= v0.9.0)
2. Use TTHeader as the transport protocol
3. Add the following option on the client side:
`client.WithMetaHandler(transmeta.ClientTTHeaderHandler())`

## Obtain ServiceName and MethodName

- Service Name

```go
idlService, ok := kitexutil.GetIDLServiceName(ctx)

```

- Method Name

```go
method, ok := kitexutil.GetMethod(ctx)

```

## Middlewares

Generally, it's just the same as before, just add the option when calling `NewServer`:

```go
options = append(options, server.WithMiddleware(yourMiddleware))
svr := server.NewServer(options...)

```

You can distinguish each service/method with the usage shown before in middleware.

### Distinguish Streaming/Non-Streaming Methods

The recommended way to determine whether a request has an underlying protocol for Streaming would be to check the type of the request/response arguments:

| | **Client Middleware** | **Server Middleware** |
|-------------------------------------|--------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Bidirectional**<br/>**(gRPC)** | - request: `interface{}` = nil <br/>- response: *streaming.Result | - request: *streaming.Args<br/>- response: `interface{}` = nil |
| **Client Streaming**<br/>**(gRPC)** | - request: interface{} = nil <br/>- response: *streaming.Result | - request: *streaming.Args<br/>- response: `interface{}` = nil |
| **Server Streaming**<br/>**(gRPC)** | - request: `interface{}` = nil <br/>- response: *streaming.Result | - request: *streaming.Args<br/>- response: `interface{}` = nil |
| **Unary (gRPC)** | - request: *kitex_gen/some_pkg.${svc}${method}Args<br/>- response: *kitex_gen/some_pkg.${svc}${method}Result | - request: *streaming.Args<br/>- response: `interface{}` = nil<br/>Note: the option provided in v1.15.0 (to be released soon): `server.WithCompatibleMiddlewareForUnary()` makes it the same with PingPong API |
| **PingPong API (KitexPB)** | - request: *kitex_gen/some_pkg.${svc}${method}Args<br/>- response: *kitex_gen/some_pkg.${svc}${method}Result | - request: *kitex_gen/some_pkg.${svc}${method}Args<br/>- response: *kitex_gen/some_pkg.${svc}${method}Result |

**NOTE:**
Kitex server supports auto-detection on incoming requests, and for GRPC/Protobuf Unary methods, it accepts both GRPC requests and KitexProtobuf(TTHeader + Pure Protobuf Payload) requests, so **it may not be accurate to rely solely on the method name from RPCInfo**.

#### Client Middleware Example

**Client** middlewares should rely on the type of `resp`:

```go
func clientMWForIdentifyStreamingRequests(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, req, resp interface{}) (err error) {
if _, ok := resp.(*streaming.Result); ok {
// it's a streaming request
return next(ctx, req, resp)
} else {
// it's a non-streaming request
return next(ctx, req, resp)
}
}
}

```

#### Server Middleware Example

**Server** middlewares should rely on the type of `req`:

```go
func serverMWForIdentifyStreamingRequests(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, req, resp interface{}) (err error) {
if _, ok := req.(*streaming.Args); ok {
// it's a streaming request
return next(ctx, req, resp)
} else {
// it's a non-streaming request
return next(ctx, req, resp)
}
}
}

```

## FAQ
### 1. What's the difference between Multi-Service and Combine Service?
- Combine Service (A service formed by merging multiple services into one unified service by generating code with -combine-service flag)

## What's the difference between Multi-Service and Combine Service?

- Combine Service (A service formed by merging multiple services into one unified service by generating code with `-combine-service` flag)
- Code for all services (both combineservice and each service being combined) are generated.
- All the method names of your services must be unique.
- Only one service (= combine service) can be registered on a server.
Otherwise, you'll receive an error message saying "only one service can be registered when registering combined service".

- Multi-Service **RECOMMENDED TO USE INSTEAD OF COMBINE SERVICE**
- Only one service (= combine service) can be registered on a server. Otherwise, you'll receive an error message saying "only one service can be registered when registering combined service".
- Multi-Service **RECOMMENDED USING INSTEAD OF COMBINE SERVICE**
- Code for each service is generated.
- Method names can be the same between services. But there are some restrictions. Please choose one.
- You need to specify a fallback service for the conflicting method.
- Add `server.WithRefuseTrafficWithoutServiceName` option when creating a server,
and make sure that client uses the kitex version >=v0.9.0, TTHeader protocol,
and sets `client.WithMetaHandler(transmeta.ClientTTHeaderHandler())` client option.
- Add `server.WithRefuseTrafficWithoutServiceName` option when creating a server, and make sure that client uses Kitex version >=v0.9.0, TTHeader protocol, and sets `client.WithMetaHandler(transmeta.ClientTTHeaderHandler())` client option.

## Why does the service registration fail?

### 2. Why does the service registration fail?
There are some possible reasons:
- No fallback service is specified, despite having methods with the same name between services you register.
Please specify a fallback service.
- You are attempting to register both the combine service and other services on the server.
Combine service can only be registered on a server by itself. If you want to register other services as well, you either need to merge those services into the combine service or register each service separately without using combine service.

- No fallback service is specified, despite having methods with the same name between services you register. Please specify a fallback service.
- You are attempting to register both combine service and other services on the server. Combine service can only be registered on a server by itself. If you want to register other services as well, you either need to merge those services into the combine service or register each service separately without using combine service.

0 comments on commit 1da567f

Please sign in to comment.