Advanced Tutorial

Advanced Tutorial for Kitex

Before starting this section, make sure you have an understanding of the Pre-knowledge and have completed the Environment Preparation.

In this tutorial, we will simulate a simple e-commerce scenario that includes a product service, inventory service, and an API service. The product service calls the inventory service to query the stock, and the API service calls the product service to query product information. It exposes HTTP interfaces for front-end or user access.

Complete Code

Creating the Project Structure

Create a directory to store the code for the project and navigate into it:

mkdir example_shop

cd example_shop

Writing the IDL

Following the development process, the first step is to write the IDL. In this example, we will use thrift IDL.

Create an idl directory to store the IDL files for the project:

mkdir idl

cd idl

Typically, different services use different IDLs. So, here we will create item.thrift and stock.thrift to define the interfaces for the product service and inventory service, respectively. We will also create base.thrift to define common data structures.

base.thrift

namespace go example.shop.base

struct BaseResp {
    1: string code
    2: string msg
}

item.thrift

namespace go example.shop.item

include "base.thrift"

struct Item {
    1: i64 id
    2: string title
    3: string description
    4: i64 stock
}

struct GetItemReq {
    1: required i64 id
}

struct GetItemResp {
    1: Item item

    255: base.BaseResp baseResp
}

service ItemService {
    GetItemResp GetItem(1: GetItemReq req)
}

stock.thrift

namespace go example.shop.stock

include "base.thrift"

struct GetItemStockReq {
    1: required i64 item_id
}

struct GetItemStockResp {
    1: i64 stock

    255: base.BaseResp BaseResp
}

service StockService {
    GetItemStockResp GetItemStock(1: GetItemStockReq req)
}

Code Generation

Once we have the IDL, we can use the Kitex tool to generate the project code. Let’s navigate back to the root directory of the project, which is example_shop. Since we have two IDL files defining the services, we will execute the Kitex command twice:

kitex -module example_shop idl/item.thrift

kitex -module example_shop idl/stock.thrift

The generated code consists of two parts. The first part is the serialization and deserialization code for the structures, which is generated by the IDL compiler. The second part is the stub code generated by the Kitex tool, which is built on top of the previous artifacts and is used to create and make RPC calls. By default, these artifacts are generated in the kitex_gen directory.

The generated code cannot be run directly; you need to complete the construction of NewClient and NewServer yourself. The Kitex command-line tool provides the -service parameter to generate code with scaffolding. Let’s generate the scaffolding code for the product service and inventory service.

First, create separate directories for the two RPC services:

mkdir -p rpc/item rpc/stock

Then, navigate to each respective directory and execute the following commands to generate the code:

// Execute in the 'item' directory
kitex -module example_shop -service example.shop.item -use example_shop/kitex_gen ../../idl/item.thrift

// Execute in the 'stock' directory
kitex -module example_shop -service example.shop.stock -use example_shop/kitex_gen ../../idl/stock.thrift

By default, Kitex will generate the code in the directory where the command is executed. The Kitex command includes the following options:

  • The -module parameter specifies the module name in the go mod of the generated code. In this example, it is example_shop.
  • The -service parameter indicates that we want to generate scaffolding code, followed by the name of the service, either example.shop.item or example.shop.stock.
  • The -use parameter prevents Kitex from generating the kitex_gen directory and uses the provided import path. In this case, since the kitex_gen directory has already been generated in the first command, we can reuse it.
  • The last parameter is the IDL file for the respective service.

After generating the code, the project structure will look as follows:

.
├── go.mod // go module file
├── go.sum
├── idl   // Directory for the example IDL files
│   ├── base.thrift
│   ├── item.thrift
│   └── stock.thrift
├── kitex_gen
│   └── example
│       └── shop
│           ├── base
│           │   ├── base.go // Serialization and deserialization code generated by the IDL compiler
│           │   ├── k-base.go // Kitex-specific extensions
│           │   └── k-consts.go
│           ├── item
│           │   ├── item.go // Serialization and deserialization code generated by the IDL compiler
│           │   ├── itemservice // Kitex encapsulated code mainly resides here
│           │   │   ├── client.go
│           │   │   ├── invoker.go
│           │   │   ├── itemservice.go
│           │   │   └── server.go
│           │   ├── k-consts.go
│           │   └── k-item.go // Kitex-specific extensions
│           └── stock
│               ├── k-consts.go
│               ├── k-stock.go // Kitex-specific extensions
│               ├── stock.go // Serialization and deserialization code generated by the IDL compiler
│               └── stockservice // Kitex encapsulated code mainly resides here
│                   ├── client.go
│                   ├── invoker.go
│                   ├── server.go
│                   └── stockservice.go
└── rpc
    ├── item
    │   ├── build.sh   // Script for building the project (generally not to be changed)
    │   ├── handler.go // Server-side business logic resides here (this is the file we need to modify and write)
    │   ├── kitex_info.yaml`
    │   ├── main.go
    │   └── script
    │       └── bootstrap.sh
    └── stock
        ├── build.sh 	 // Script for building the project (generally not to be changed)
        ├── handler.go // Server-side business logic resides here (this is the file we need to modify and write)
        ├── kitex_info.yaml
        ├── main.go    // Server startup function, typically used for resource initialization (can be modified)
        └── script
            └── bootstrap.sh

Dependency Retrieval

After generating the code, let’s go back to the project root directory, example_shop. Use the go mod tidy command to retrieve project dependencies.

If you encounter one of the following errors:

github.com/apache/thrift/lib/go/thrift: ambiguous import: found package github.com/apache/thrift/lib/go/thrift in multiple modules

github.com/cloudwego/kitex@v0.X.X/pkg/utils/thrift.go: not enough arguments in call to t.tProt.WriteMessageBegin

Execute the following commands before proceeding:

go mod edit -droprequire=github.com/apache/thrift/lib/go/thrift
go mod edit -replace=github.com/apache/thrift=github.com/apache/thrift@v0.13.0

This is because the Thrift official library introduced a breaking change in version 0.14, which makes the generated code incompatible.

If you want to upgrade the Kitex version, execute go get -v github.com/cloudwego/kitex@latest.

Writing the Item Service Logic

The server-side logic we need to write is located in the handler.go file. Currently, we have two services, each with its own handler.go file. The structure of both files is similar. Let’s take a look at the server-side logic for the Item service in rpc/item/handler.go:

package main

import (
	"context"
	item "example_shop/kitex_gen/example/shop/item"
)

// ItemServiceImpl implements the last service interface defined in the IDL.
type ItemServiceImpl struct{}

// GetItem implements the ItemServiceImpl interface.
func (s *ItemServiceImpl) GetItem(ctx context.Context, req *item.GetItemReq) (resp *item.GetItemResp, err error) {
	// TODO: Your code here...
	return
}

The GetItem function corresponds to the GetItem method defined in the item.thrift IDL file.

Now let’s modify the server-side logic. Since this project is primarily focused on demonstrating usage, we will keep it simple and return a predefined response.

package main

import (
	"context"
	item "example_shop/kitex_gen/example/shop/item"
)

// ItemServiceImpl implements the last service interface defined in the IDL.
type ItemServiceImpl struct{}

// GetItem implements the ItemServiceImpl interface.
func (s *ItemServiceImpl) GetItem(ctx context.Context, req *item.GetItemReq) (resp *item.GetItemResp, err error) {
	resp = item.NewGetItemResp()
	resp.Item = item.NewItem()
	resp.Item.Id = req.GetId()
	resp.Item.Title = "Kitex"
	resp.Item.Description = "Kitex is an excellent framework!"
	return
}

In addition to handler.go, we also need to pay attention to the main.go file. Let’s take a look at what main.go does:

package main

import (
	item "example_shop/kitex_gen/example/shop/item/itemservice"
	"log"
)

func main() {
	svr := item.NewServer(new(ItemServiceImpl))

	err := svr.Run()

	if err != nil {
		log.Println(err.Error())
	}
}

The code in main.go is simple. It uses the code generated by Kitex to create a server and calls its Run method to start the server. Usually, main.go is used for project initialization, such as loading configurations.

Running the Item Service

Now, we can start running the item service. Kitex has generated a build script for us, called build.sh:

#!/usr/bin/env bash
RUN_NAME="example.shop.item"

mkdir -p output/bin
cp script/* output/
chmod +x output/bootstrap.sh

if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
    go build -o output/bin/${RUN_NAME}
else
    go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
fi

The build.sh script performs the following actions:

  1. It defines a variable RUN_NAME which specifies the name of the generated executable file. In this example, it is set to the namespace we specified in the IDL, which is example.shop.item.
  2. It creates the output directory to store the compiled binary files. The project startup script from the script directory is also copied into the output directory.
  3. It compiles the regular executable file or test executable file based on the value of the environment variable IS_SYSTEM_TEST_ENV. If the value is 1, it generates a test file using go test -c. Otherwise, it uses the go build command for regular compilation.

You can compile the project by executing sh build.sh.

After successful compilation, the output directory will be generated:

output
├── bin // Contains the binary executable file
│   └── example.shop.item
└── bootstrap.sh // Script to run the file

You can start the compiled binary file by executing sh output/bootstrap.sh.

If everything runs successfully, the output will be similar to the following log:

2024/01/19 22:12:18.758245 server.go:83: [Info] KITEX: server listen at addr=[::]:8888

In the above log output, addr=[::]:8888 indicates that our service is running on port 8888 locally. You can modify this parameter by passing a server configuration option when creating the server. For more server configuration options, refer to the Server Option documentation.

Running API Service

After having the item service, let’s now write an API service to invoke the item service we just started and expose an HTTP interface.

First, as before, let’s go back to the root directory of the project and create a directory to store our code:

mkdir api

Enter the directory:

cd api

Then create a main.go file and start writing the code.

Creating a client

In the generated code, under the kitex_gen directory, Kitex has already provided us with the code to create a client, so we just need to use it:

import (
	"example_shop/kitex_gen/example/shop/item/itemservice"
	"github.com/cloudwego/kitex/client"
	...
)
...
c, err := itemservice.NewClient("example.shop.item", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
	log.Fatal(err)
}

In the above code, itemservice.NewClient is used to create the client. The first parameter is the service name to invoke, and the second parameter is the options to pass in, where client.WithHostPorts is used to specify the address of the server. We can see that the item service is listening on port 8888 locally when running, so we specify port 8888. For more parameters, you can refer to the Client Option section.

Invoking the service

Next, let’s write the code to make the invocation:

import "example_shop/kitex_gen/example/shop/item"
...
req := item.NewGetItemReq()
req.Id = 1024
resp, err := cli.GetItem(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
if err != nil {
  log.Fatal(err)
}

In the above code, we first create a request req, and then make the invocation using cli.GetItem. Click here for code detail.

The first parameter is context.Context, which is typically used to pass information or control some behaviors of the invocation. You can find how to use it in the following sections.

The second parameter is the request parameter for this invocation.

The third parameter is the options for this invocation. Kitex provides a mechanism called callopt, which stands for call options. It is different from the options passed in when creating the client. The options passed here only take effect for this invocation. In this case, callopt.WithRPCTimeout is used to specify the timeout for this invocation (usually not necessary, it’s just for demonstration purposes). Similarly, you can find more parameters in the Basic Features section.

Exposing an HTTP interface

You can use net/http or other frameworks to expose an HTTP interface. Here, we’ll demonstrate a simple example using Hertz. You can get usage for Hertz in Hertz Doc

Here’s the complete code:

package main

import (
	"context"
	"log"
	"time"

	"example_shop/kitex_gen/example/shop/item"
	"example_shop/kitex_gen/example/shop/item/itemservice"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/kitex/client"
	"github.com/cloudwego/kitex/client/callopt"
)

var (
	cli itemservice.Client
)

func main() {
	c, err := itemservice.NewClient("example.shop.item", client.WithHostPorts("0.0.0.0:8888"))
	if err != nil {
		log.Fatal(err)
	}
	cli = c

	hz := server.New(server.WithHostPorts("localhost:8889"))

	hz.GET("/api/item", Handler)

	if err := hz.Run(); err != nil {
		log.Fatal(err)
	}
}

func Handler(ctx context.Context, c *app.RequestContext) {
	req := item.NewGetItemReq()
	req.Id = 1024
	resp, err := cli.GetItem(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
	if err != nil {
		log.Fatal(err)
	}

	c.String(200, resp.String())
}

Next, open a new terminal, and execute the command go run . to start the API service, which listens on port 8889. You can make a request to localhost:8889/api/item to invoke the GetItem interface provided by the item service and get the response.

Testing the interface

Open a browser and visit localhost:8889/api/item. If you see the following message, it means the request was successful:

GetItemResp({Item:Item({Id:1024 Title:Kitex Description:Kitex is an excellent framework! Stock:0}) BaseResp:BaseResp({Code: Msg:})})

Running the Stock Service

In the above example, we have already completed an RPC call. However, in more common scenarios, a single RPC call is not sufficient to meet business requirements. Therefore, we will add a stock service to simulate a more typical scenario.

The code for the stock service has already been generated, and we only need to add the business logic. Similar to the item service, the business code is located in rpc/stock/handler.go. Let’s add the following logic:

package main

import (
    "context"

    stock "example_shop/kitex_gen/example/shop/stock"
)

// StockServiceImpl implements the last service interface defined in the IDL.
type StockServiceImpl struct{}

// GetItemStock implements the StockServiceImpl interface.
func (s *StockServiceImpl) GetItemStock(ctx context.Context, req *stock.GetItemStockReq) (resp *stock.GetItemStockResp, err error) {
    resp = stock.NewGetItemStockResp()
    resp.Stock = req.GetItemId()
    return
}

Since the item service and API service are already using ports 8888 and 8889, respectively, we need to modify the listening port for the stock service in the main.go file:

package main

import (
    "log"
    "net"

    stock "example_shop/kitex_gen/example/shop/stock/stockservice"

    "github.com/cloudwego/kitex/server"
)

func main() {
    addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8890")
    svr := stock.NewServer(new(StockServiceImpl), server.WithServiceAddr(addr))

    err := svr.Run()

    if err != nil {
       log.Println(err.Error())
    }
}

You can find more parameter explanations in the Option documentation.

To run the stock service, open a new terminal and execute go run .. If you see the following output, it means the service is running successfully:

2024/01/21 00:09:47.076192 server.go:83: [Info] KITEX: server listen at addr=127.0.0.1:8890

Supplementing the Item Service

Now that we have successfully run the stock service, let’s supplement the item service to make a call to the stock service. Similar to the API service, all we need to do is create a client and make the call with the constructed parameters. To achieve client reuse, we supplement the field stockservice. Client in itemserviceimpl. In the rpc/item/handler.go file, let’s add the following methods:

package main

import (
    "context"
    "log"

    item "example_shop/kitex_gen/example/shop/item"
  	"example_shop/kitex_gen/example/shop/stock"
    "example_shop/kitex_gen/example/shop/stock/stockservice"

    "github.com/cloudwego/kitex/client"
)

type ItemServiceImpl struct{
  	stockCli stockservice.Client
}

func NewStockClient(addr string) (stockservice.Client, error) {
    return stockservice.NewClient("example.shop.stock", client.WithHostPorts(addr))
}

func (s *ItemServiceImpl) GetItem(ctx context.Context, req *item.GetItemReq) (resp *item.GetItemResp, err error) {
    resp = item.NewGetItemResp()
    resp.Item = item.NewItem()
    resp.Item.Id = req.GetId()
    resp.Item.Title = "Kitex"
    resp.Item.Description = "Kitex is an excellent framework!"

    stockReq := stock.NewGetItemStockReq()
    stockReq.ItemId = req.GetId()
    stockResp, err := s.stockCli.GetItemStock(context.Background(), stockReq)
    if err != nil {
        log.Println(err)
        stockResp.Stock = 0
    }
    resp.Item.Stock = stockResp.GetStock()
    return
}

We need to initialize the client for the stock service before we can use it. We will perform the initialization in rpc/item/main.go.

package main

import (
    "log"

    item "example_shop/kitex_gen/example/shop/item/itemservice"
)

func main() {
    itemServiceImpl := new(ItemServiceImpl)
    stockCli, err := NewStockClient("0.0.0.0:8890")
    if err != nil {
       log.Fatal(err)
    }
    itemServiceImpl.stockCli = stockCli

    svr := item.NewServer(itemServiceImpl)

    err = svr.Run()

    if err != nil {
       log.Println(err.Error())
    }
}

Since the stock service is running on port 8890, we specify that port when creating the client.

With this, the item service code is complete. Recompile and launch the item service as mentioned earlier. If you see the following output, it means the service is running successfully:

2024/01/21 00:18:29.522546 server.go:83: [Info] KITEX: server listen at addr=[::]:8888

Testing the API

Open your browser and visit localhost:8889/api/item. If you see the following information, it means the request was successful:

GetItemResp({Item:Item({Id:1024 Title:Kitex Description:Kitex is an excellent framework! Stock:1024}) BaseResp:BaseResp({Code: Msg:})})

You can see that Stock: 1024, indicating that our item service successfully requested the stock service and received a response from the API service.

Accessing the Service Registry

To simulate a real environment, we will now integrate our service with a registry. In this example, we have chosen etcd as the registry. You can refer to the installation and usage of etcd at etcd.io or the following docker compose file. Assuming you have already installed and started the etcd service instance.

version: "3"

services:
  etcd:
    image: bitnami/etcd:3.5
    container_name: etcd
    ports:
      - 2379:2379
      - 2380:2380
    volumes:
      - ./etcd/data:/bitnami/etcd-data
    environment:
      - TZ=Asia/Shanghai
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379

Kitex, as a microservice framework, also provides service governance capabilities. In the context of service registration and discovery, Kitex has adapted to etcd. Please refer to the etcd registry usage documentation. Additionally, Kitex provides support for other common registrys, as documented in the Service Discovery section.

First, we need to fetch the dependencies. Run the following command in the project’s root directory:

go get github.com/kitex-contrib/registry-etcd

Service Register

In Kitex, we have abstracted the interface for the service registry:

type Registry interface {
    Register(info *Info) error
    Deregister(info *Info) error
}

This interface includes two methods: Register for registering a service and Deregister for unregistering a service. Both methods require the service information as input. Any implementation of this interface can be used as a service registry. You can also customize your own service registry, as described in the Service Registration Extension documentation.

The process of using the registry in Kitex is relatively simple and can be divided into two steps:

  1. Create a Registry.
  2. Specify the Registry and service basic information as options when creating the service instance.

When using etcd, you can use the following functions to create a Registry:

func NewEtcdRegistry(endpoints []string, opts ...Option) (registry.Registry, error)

func NewEtcdRegistryWithAuth(endpoints []string, username, password string) (registry.Registry, error)

func NewEtcdRegistryWithRetry(endpoints []string, retryConfig *retry.Config, opts ...Option) (registry.Registry, error)

To specify the Registry and service basic information, use server.WithRegistry and server.WithServerBasicInfo as option parameters when creating the service instance.

In this example, we will register the stock Service and item Service to make them available externally.

Register Stock Service

Add the following logic to rpc/stock/main.go:

package main

import (
    "log"
    "net"

    stock "example_shop/kitex_gen/example/shop/stock/stockservice"

    "github.com/cloudwego/kitex/pkg/rpcinfo"
    "github.com/cloudwego/kitex/server"
    etcd "github.com/kitex-contrib/registry-etcd"
)

func main() {
    // Replace with the actual etcd service address, in this example it is 127.0.0.1:2379
    r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
    if err != nil {
        log.Fatal(err)
    }

    addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8890")
    svr := stock.NewServer(new(StockServiceImpl),
        server.WithServiceAddr(addr),
        // Specify the Registry and service basic information
        server.WithRegistry(r),
        server.WithServerBasicInfo(
            &rpcinfo.EndpointBasicInfo{
                ServiceName: "example.shop.stock",
            },
        ),
    )

    err = svr.Run()

    if err != nil {
        log.Println(err.Error())
    }
}

Register Item Service

Add the following logic to rpc/item/main.go:

package main

import (
    "log"

    item "example_shop/kitex_gen/example/shop/item/itemservice"

    "github.com/cloudwego/kitex/pkg/rpcinfo"
    "github.com/cloudwego/kitex/server"
    etcd "github.com/kitex-contrib/registry-etcd"
)

func main() {
    // Replace with the actual etcd service address, in this example it is 127.0.0.1:2379
    r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
    if err != nil {
       log.Fatal(err)
    }

    itemServiceImpl := new(ItemServiceImpl)
    stockCli, err := NewStockClient("0.0.0.0:8890")
    if err != nil {
        log.Fatal(err)
    }
    itemServiceImpl.stockCli = stockCli

    svr := item.NewServer(itemServiceImpl,
        // Specify the Registry and service basic information
       server.WithRegistry(r),
       server.WithServerBasicInfo(
          &rpcinfo.EndpointBasicInfo{
             ServiceName: "example.shop.item",
          }),
    )

    err = svr.Run()

    if err != nil {
       log.Println(err.Error())
    }
}

Testing

After completing the code, start both services separately. The terminals for both services will display similar output:

2024/02/04 18:25:22.958727 etcd_registry.go:274: [Info] start keepalive lease 694d8d736e961295 for etcd registry

To confirm if the services are registered successfully, use etcdctl and execute the command etcdctl get --prefix "kitex". It should output:

kitex/registry-etcd/example.shop.item/192.168.196.240:8888
{"network":"tcp","address":"192.168.196.240:8888","weight":10,"tags":null}
kitex/registry-etcd/example.shop.stock/127.0.0.1:8890
{"network":"tcp","address":"127.0.0.1:8890","weight":10,"tags":null}

If both outputs are correct, it means that our services have been successfully registered.

Service Discovery

In Kitex, the following interface is abstracted for service discovery:

type Resolver interface {
    Target(ctx context.Context, target rpcinfo.EndpointInfo) string
    Resolve(ctx context.Context, key string) (Result, error)
    Diff(key string, prev, next Result) (Change, bool)
    Name() string
}

Any implementation of this interface can be used as a service discovery center. Therefore, you can also customize your own service discovery center. For more details, please refer to the Service Discovery Extension documentation.

The process of using a discovery center in Kitex is relatively simple and can be divided into two steps:

  1. Create a Resolver.
  2. Specify the Resolver as an option parameter when creating a service client.

When using etcd, you can use the following functions to create a Resolver:

func NewEtcdResolver(endpoints []string, opts ...Option) (discovery.Resolver, error)

func NewEtcdResolverWithAuth(endpoints []string, username, password string) (discovery.Resolver, error)

Specify the Resolver using client.WithResolver() as the option parameter when creating the client.

In this example, the item service and the API service need to call other services, so we will use a service discovery center for these two services.

Item Service Integration

In the previous code, we placed the logic for the item service to call the stock service in rpc/item/handler.go. Therefore, we will add the logic in the NewStockClient function in this file:

func NewStockClient() (stockservice.Client, error) {
    // Please provide the actual address of the etcd service when using. In this example, it is 127.0.0.1:2379.
    r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
    if err != nil {
        log.Fatal(err)
    }
    return stockservice.NewClient("example.shop.stock", client.WithResolver(r)) // Specify the Resolver
}

API Service Integration

The API service has only one file, so we can directly add the relevant logic in the main function in api/main.go:

func main() {
  	// Please provide the actual address of the etcd service when using. In this example, it is 127.0.0.1:2379.
	resolver, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
	if err != nil {
		log.Fatal(err)
	}

	c, err := itemservice.NewClient("example.shop.item", client.WithResolver(resolver)) // Specify the Resolver
	if err != nil {
		log.Fatal(err)
	}
 	cli = c

  	hz := server.New(server.WithHostPorts("localhost:8889"))

	hz.GET("/api/item", Handler)

	if err := hz.Run(); err != nil {
		log.Fatal(err)
	}
}

Testing

After adding the code for the item service, you need to restart it, and then start the API service. Open your browser and access localhost:8889/api/item. If you see the following message, it means the request was successful:

GetItemResp({Item:Item({Id:1024 Title:Kitex Description:Kitex is an excellent framework! Stock:1024}) BaseResp:BaseResp({Code: Msg:})})

Summary

In this section, we used Kitex to develop both the RPC server and client, and achieved RPC invocation. The development process can be summarized as follows:

  • On the server side, we wrote the IDL and generated the code using Kitex. After filling in the business logic, the server can be run.
  • On the client side, we used the same IDL as the server, generated the code using Kitex, created a client instance, and made the RPC call by constructing the request parameters.

In this example, we only demonstrated the basic usage of Kitex. Kitex provides various microservice governance features. You can find more information in the Guide or explore the example code to unlock more advanced usage.