thumnail

Hướng dẫn xây dựng gRPC server bằng typescript đơn giản

Dạo này mình làm một dự án microservices và có gặp phải các vấn đề về giao tiếp giữa các service trong khối microservices với nhau. Sau thời gian nghiên cứu và tìm hiểu thì gRPC là một giải pháp khá tốt cho vấn đề này. nên hôm nay mình sẻ chia sẻ cho các bạn cách sử dụng gRPC trong Typescript.

Hẳn chúng ta ai cũng quen làm việc với các REST API. Tuy nhiên, trong môi trường microservice, việc sử dụng REST API để giao tiếp giữa các service sẽ gây ra độ trễ đáng kể. gRPC ra đời để giải quyết vấn đề này. Trong blog này mình sẽ trình bày nội dung cơ bản liên quan đến gRPC và làm một todo list app demo để chúng ta biết cách sử dụng gRPC trong thực tế nhé.

gRPC là gì ???

gRPC là một RPC platform được phát triển bởi Google nhằm tối ưu hoá và tăng tốc việc giao tiếp giữa các service với nhau trong kiến trúc microservice.

gRPC dùng Protocal Buffer giảm kích thước request và response data, RPC để đơn giản hoá trong việc tạo ra các giao tiếp giữa các service với nhau, HTTP/2 để tăng tốc gửi/nhận HTTP request.

Có thể hiểu nôm na gRPC là tương tự như REST dùng để giao tiếp giữa các service, tuy nhiên tốc độ gRPC nhanh hơn REST rất nhiều, bù lại gRPC khó sử dụng và rườm rà hơn. Bạn chỉ nên sử dụng gRPC khi có vấn đề về độ trễ trong việc giao tiếp giữa các service trong kiến trúc microservice.

Tại sao nên sử dụng gRPC ???

Vấn đề là gì và tại sao cần nó ?

RPC có thể được xem là một giao thức request-respone thông thường tuy nhiên nó được dùng cho việc giao tiếp giữa các server với nhau (server-server) nhiều hơn là client-server. Việc này có ý nghĩa rất quan trọng vì trong các hệ thống phân tán (distributed system), application code ở nhiều server hơn là một server. Ví dụ thường thấy nhất chính là kiến trúc Microservices.

Điều này nghĩa là: một request phía client có thể sẽ phải cần nhiều service chạy trên các server này để tổng hợp thông tin rồi mới response cho client. Sự liên lạc giữa các server lúc này sẽ là vấn đề mà trước đó tất cả service chạy trên 1 server thì khoẻ re, vì local call nên chẳng ngại gì cả. Chính xác là khi đó, khi một server muốn “nói chuyện” với server khác sẽ cần phải encode data (JSON, XML), phía nhận cũng phải làm công việc ngược lại là decode data mới hiểu thằng kia nói gì với mình rôi lại phải encode lại tiếp. Việc này tiêu tốn khá nhiều tài nguyên xử lý (CPU) mà lẽ ra chỉ cần làm ở bước đầu và cuối (đầu nhận và trả về cuối cùng).

Tối ưu cho việc “giao tiếp” giữa các server là lý do gRPC ra đời.

Để giải bài toán trên, gRPC đã sử dụng binary để truyền đi thay vì phải encode chúng thành các ngôn ngữ trung gian JSON/XML. Việc này rõ ràng đã làm tăng tốc giao tiếp các servers lên rất nhiều, giảm overhead cho CPUs. Google cũng “tiện tay” làm luôn cả protobuf (protocol buffers), đây là ngôn ngữ mà gRPC dùng như một default serialization format. Implement phần này thật sự phải là tay to lắm nên Google xử dụng protobuf như một script trung gian để generate phần hard core cho các dev ở các ngôn ngữ phổ biến như: C++, C#, Go, Java, Pyhon.

Thứ giúp gRPC giao tiếp binary ngon vậy chính là http/2, đây vốn là giao thức có rất nhiều cải tiến so với http/1.1. Bản thân http/2 cũng được coi như là sự thay thế cho SPDY, giao thức mà cũng chính Google phát triển, open source vào 2012 và ngừng hỗ trợ vào 2015 (http/2 có implement và thay thế rồi).

Tham khảo thêm tại đây

Xây dựng gRPC server bằng Typescript

Settup một project Typescript

1
mkdir ts-grpc

Khởi tạo một thư mục trống.

Và cấu trúc thư mục sẽ có dạng như sau.

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── dist/ # Compiled files
├── src/ # Source files
│ ├── handlers/ # gRPC service handlers
| │ └── greeter.ts # Greeter service definitions
│ ├── proto/ # Proto files
│ │ ├── greeter/ # Greeter gRPC service
│ │ │ └── greeter.proto
│ │ ├── index.ts # Registers all the proto typescript definitions
│ └── server.ts # Bootstrap server, add middleware (logs, graphql...)
├── scripts/ # Generation tools
│ └── protoc.sh # Script to generate protoc typescript definitions
└── ...

bây giờ chúng ta tiến hành cài đặt các dependencies cần thiết

1
2
3
npm init -y
npm install grpc google-protobuf dotenv
npm install typescript @types/node @types/google-protobuf @types/dotenv --save-dev

Đây là các package chúng ta cài

  • grpc để dùng gRPC với Node.js
  • google-protobuf để dùng Protocol Buffers (.proto) với javascript
  • dotenv để load các biến môi trường từ file .env
  • TypeScript và các gói mở rộng của typescript để dịch các package trên

Chúng ta sẽ khởi tạo project typescript với lệnh:

1
npx tsc --init

Sau khi chạy lệnh này xong chúng ta sẽ có file tsconfig.json như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"outDir": "./dist/",
"module": "commonjs",
"noImplicitAny": true,
"allowJs": true,
"esModuleInterop": true,
"target": "es6",
"sourceMap": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules"]
}

bây giờ mở file greeter.proto thêm code sau đây

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

package greeter;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloResponse);
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloResponse {
string message = 1;
}

Nó sẽ tạo ra một service SayHello có kiểu dữ liệu request là HelloRequest với một trường dữ liệu là name và trả về một response với kiểu dữ liệu là HelloResponse. Bạn có thể xem thêm cú pháp syntax về proto 3 tại đây https://developers.google.com/protocol-buffers/docs/proto.

Generating TypeScript definitions

Bây giờ chúng ta sẽ tạo ra các TypeScript definitions hỗ trợ cho gRPC service được khai báo trong file proto. Chúng ta sẽ tiến hành dịch file greeter.proto thành các class typescript có thể hiểu và sử dụng.

Chúng ta sẽ cài thêm các dependencies:

1
npm install grpc-tools grpc_tools_node_protoc_ts --save-dev

Here we installed few more dev dependencies

  • grpc-tools tạo javascript files cho proto files
  • grpc_tools_node_protoc_ts tạo các file module typescript tương ứng d.ts

Sau khi cài các package xong, chúng ta sẽ tiến hành viết bash script để dịch file src/proto/.proto. Mở file protoc.sh và thêm code sau.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
cd "${BASEDIR}"/../

PROTOC_GEN_TS_PATH="./node_modules/.bin/protoc-gen-ts"
GRPC_TOOLS_NODE_PROTOC_PLUGIN="./node_modules/.bin/grpc_tools_node_protoc_plugin"
GRPC_TOOLS_NODE_PROTOC="./node_modules/.bin/grpc_tools_node_protoc"

for f in ./src/proto/*; do

# skip the non proto files
if [ "$(basename "$f")" == "index.ts" ]; then
continue
fi

# loop over all the available proto files and compile them into respective dir
# JavaScript code generating
${GRPC_TOOLS_NODE_PROTOC} \
--js_out=import_style=commonjs,binary:"${f}" \
--grpc_out="${f}" \
--plugin=protoc-gen-grpc="${GRPC_TOOLS_NODE_PROTOC_PLUGIN}" \
-I "${f}" \
"${f}"/*.proto

${GRPC_TOOLS_NODE_PROTOC} \
--plugin=protoc-gen-ts="${PROTOC_GEN_TS_PATH}" \
--ts_out="${f}" \
-I "${f}" \
"${f}"/*.proto

done

Phân quyền cho file protoc.sh bằng lệnh sau.

1
sudo chmod +x ./scripts/protoc.sh

Bây giờ chạy script trên và nó sẽ tao các javascript file trong src/proto/greeter. Mỗi khi chỉnh sửa proto file bạn hãy nhớ chạy lại lệnh này để update các javascript file.

1
./scripts/protoc.sh

Sau đó mở file src/proto/index.ts và thêm code sau

1
2
3
4
import './greeter/greeter_pb';
import './greeter/greeter_grpc_pb';

export const protoIndex: any = (): void => {};

Nhớ là bạn luôn phải update file này khi tạo ra các proto file mới .

Tạo handlers

Chúng ta sẽ viết hàm xử lý cho SayHello service, hãy mở src/handlers/greeter.ts thêm code này.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import * as grpc from 'grpc';

import { HelloRequest, HelloResponse } from './proto/greeter/greeter_pb';
import {
GreeterService,
IGreeterServer,
} from './proto/greeter/greeter_grpc_pb';

class GreeterHandler implements IGreeterServer {
/**
_ Greet the user nicely
_ @param call
_ @param callback
_
*/
sayHello = (
call: grpc.ServerUnaryCall<HelloRequest>,
callback: grpc.sendUnaryData<HelloResponse>
): void => {
const reply: HelloResponse = new HelloResponse();

reply.setMessage(`Hello, ${call.request.getName()}`);

callback(null, reply);
};
}

export default {
service: GreeterService, // Service interface
handler: new GreeterHandler(), // Service interface definitions
};

chúng ta sẽ thấy method sayHello được implement SayHello rpc service. lấy dữ liệu name từ request HelloRequest và chuyễn thành một câu chào kiểu HelloResponse và trả về

Khởi tạo server

Bây giờ chúng ta sẽ khởi tạo gRPC server. Mở file src/server.ts và thêm code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import 'dotenv/config';
import * as grpc from 'grpc';

import { protoIndex } from './proto';
import greeterHandler from './handlers/greeter';

protoIndex();

const port: string | number = process.env.PORT || 50051;

type StartServerType = () => void;
export const startServer: StartServerType = (): void => {
// create a new gRPC server
const server: grpc.Server = new grpc.Server();

// register all the handler here...
server.addService(greeterHandler.service, greeterHandler.handler);

// define the host/port for server
server.bindAsync(
`0.0.0.0:${port}`,
grpc.ServerCredentials.createInsecure(),
(err: Error, port: number) => {
if (err != null) {
return console.error(err);
}
console.log(`gRPC listening on ${port}`);
}
);

// start the gRPC server
server.start();
};

startServer();

Ok, chúng ta đã tạo ra các instance của greeter service, đăng ký greeter service handler và chạy server.

Kiểm tra xem nào

Trước tiên mở file package.json thêm các lệnh sau vào phần script:

1
2
3
4
5
6
...
"scripts": {
"build": "npx tsc --skipLibCheck",
"start": "npx tsc --skipLibCheck && node ./dist/server.js"
}
...

Ok chạy thử nào!!!

1
2
npm run build
npm run start

Ok bây giờ chúng ta có một server gRPC chạy trên port 50051

Để test gRPC các bạn hãy dùng BloomRPC Đây là một GUI tương tữ như Postman nhưng dùng cho các api bằng gRPC. Để cài đặt các bạn xem hướng dẫn tại đây.

OK để test chúng ta hãy bắt đầu làm như sau, import greeter.proto file, đổi URL thành 127.0.0.1:50051 và click vào icon play để tận hưởng =]]]] chúc các bạn thành công.

Source code của bài viết này được cập nhật tại đây : https://github.com/ntpntp1997/gRPC-server-typescript