jianyi-gronk commented on code in PR #394: URL: https://github.com/apache/dubbo-js/pull/394#discussion_r1605793782
########## docs/guide/dubboForNode/GettingStarted.md: ########## @@ -1 +1,196 @@ -# GettingStarted +# Getting started + +Dubbo-Node is a library for serving Dubbo, gRPC, and gRPC-Web compatible HTTP APIs using Node.js. It brings the Dubbo Protocol to Node with full TypeScript compatibility and support for all four types of remote procedure calls: unary and the three variations of streaming. Review Comment: Dubbo-Node -> Dubbo-js ########## docs/guide/dubboForNode/GettingStarted.md: ########## @@ -1 +1,196 @@ -# GettingStarted +# Getting started + +Dubbo-Node is a library for serving Dubbo, gRPC, and gRPC-Web compatible HTTP APIs using Node.js. It brings the Dubbo Protocol to Node with full TypeScript compatibility and support for all four types of remote procedure calls: unary and the three variations of streaming. + +This ten-minute walkthrough helps you create a small Dubbo service in Node.js. It demonstrates what you'll be writing by hand, what Connect generates for you, and how to call your new API. + + +# Prerequisites +We'll set up a project from scratch and then augment it to serve a new endpoint. + +- You'll need [Node.js](https://nodejs.org/en/download) installed - we recommend the most recent long-term support version (LTS). +- We'll use the package manager `npm`, but we are also compatible with `yarn` and `pnpm`. +- We'll also use [cURL](https://curl.se/). It's available from Homebrew and most Linux package managers. + + +# Project setup + +Let's initialize a project with TypeScript, and install some code generation tools: + +```shell +mkdir dubbo-example +cd dubbo-example +npm init -y +npm install typescript tsx +npx tsc --init +npm install @bufbuild/protoc-gen-es @bufbuild/protobuf @apachedubbo/protoc-gen-apache-dubbo-es @apachedubbo/dubbo +``` + +# Define a service +First, we need to add a Protobuf file that includes our service definition. For this tutorial, we are going to construct a unary endpoint for a service that is a stripped-down implementation of [ELIZA](https://en.wikipedia.org/wiki/ELIZA), the famous natural language processing program. + +```shell +mkdir -p proto && touch proto/example.proto +``` + +Open up the above file and add the following service definition: + +```Protobuf +syntax = "proto3"; + +package apache.dubbo.demo.example.v1; + +message SayRequest { + string sentence = 1; +} + +message SayResponse { + string sentence = 1; +} + +service ExampleService { + rpc Say(SayRequest) returns (SayResponse) {} +} +``` + + +# Generate code + +Create the gen directory as the target directory for generating file placement: +```Shell +mkdir -p gen +``` +Run the following command to generate a code file in the gen directory: + +```Shell +PATH=$PATH:$(pwd)/node_modules/.bin \ + protoc -I proto \ + --es_out gen \ + --es_opt target=ts \ + --apache-dubbo-es_out gen \ + --apache-dubbo-es_opt target=ts \ + example.proto +``` + +After running the command, the following generated files should be visible in the target directory: + +```Plain Text +├── gen +│ ├── example_dubbo.ts +│ └── example_pb.ts +├── proto +│ └── example.proto +``` + +Next, we are going to use these files to implement our service. + + +# Implement the service + +We defined the `ElizaService` - now it's time to implement it, and register it with the `DubboRouter`. First, let's create a file where we can put the implementation: + +Create a new file `dubbo.ts` with the following contents: + +```typescript +import { DubboRouter } from "@apachedubbo/dubbo"; Review Comment: import { DubboRouter } from "@apachedubbo/dubbo"; -> import type { DubboRouter } from "@apachedubbo/dubbo"; ########## docs/guide/dubboForNode/ImplementingServices.md: ########## @@ -1 +1,234 @@ # ImplementingServices + +Dubbo handles HTTP routes and most plumbing for you, but implementing the actual business logic is still up to you. + +You always register your implementation on the DubboRouter. We recommend to create a file dubbo.ts with a registration function in your project: + + +```ts +import { DubboRouter } from "@apachedubbo/dubbo"; Review Comment: import { DubboRouter } from "@apachedubbo/dubbo"; -> import type { DubboRouter } from "@apachedubbo/dubbo"; ########## docs/guide/dubboForNode/ImplementingServices.md: ########## @@ -1 +1,234 @@ # ImplementingServices + +Dubbo handles HTTP routes and most plumbing for you, but implementing the actual business logic is still up to you. + +You always register your implementation on the DubboRouter. We recommend to create a file dubbo.ts with a registration function in your project: + + +```ts +import { DubboRouter } from "@apachedubbo/dubbo"; + +export default (router: DubboRouter) => {} +``` + + +# Register a service + +Let's say you have defined a simple service in Protobuf: + +``` +message SayRequest { + string sentence = 1; +} +message SayResponse { + string sentence = 1; +} +service ElizaService { + rpc Say(SayRequest) returns (SayResponse) {} +} +``` + +To register this service, call `router.service()`: + +```ts +import { DubboRouter, HandlerContext } from "@apachedubbo/dubbo"; +import { ElizaService } from "./gen/eliza_dubbo"; +import { SayRequest, SayResponse } from "./gen/eliza_pb"; + +export default (router: DubboRouter) => + router.service(ElizaService, { + async say(req: SayRequest, context: HandlerContext) { + return new SayResponse({ + sentence: `You said ${req.sentence}`, + }); + } + }); +``` + +Your method `say()` receives the request message and a context object, and returns a response message. It is a plain function! + + +# Plain functions + +Your function can return a response message, or a promise for a response message, or just an initializer for a response message: + +```ts +function say(req: SayRequest) { + return new SayResponse({ sentence: `You said ${req.sentence}` }); +} +``` + +```ts +async function say(req: SayRequest) { + return { sentence: `You said ${req.sentence}` }; +} +``` + +```ts +const say = (req: SayRequest) => ({ sentence: `You said ${req.sentence}` }); +``` + +You can register any of these functions for the ElizaService. + + +# Context + +The context argument gives you access to headers and service metadata: + +```ts +import { HandlerContext } from "@apachedubbo/dubbo"; +import { SayRequest } from "./gen/eliza_pb"; + +function say(req: SayRequest, context: HandlerContext) { + ctx.service.typeName; // the protobuf type name "ElizaService" + ctx.method.name; // the protobuf rpc name "Say" + context.requestHeader.get("Foo"); + context.responseHeader.set("Foo", "Bar"); + return new SayResponse({ sentence: `You said ${req.sentence}` }); +} +``` + +It can also be used to access arbitrary values that are passed from either server plugins or interceptors. Please refer to the docs on [interceptors](Interceptors.md) for learn more. + + +# Errors + +Instead of returning a response, your method can also raise an error: + +```ts +import { Code, DubboError } from "@apachedubbo/dubbo"; + +function say() { + throw new DubboError("I have no words anymore.", Code.ResourceExhausted); +} +``` + +`Code` is one of Connects [error codes](). Besides the code and a message, errors can also contain metadata (a Headers object) and error details. + + +# Error details + +Error details are a powerful feature. Any protobuf message can be transmitted as an error detail. Let's use `google.rpc.LocalizedMessage` to localize our error message: + +```shell +buf generate buf.build/googleapis/googleapis +``` + +```ts +import { Code, DubboError } from "@apachedubbo/dubbo"; +import { ElizaService } from "./gen/eliza_dubbo"; +import { LocalizedMessage } from "./gen/google/rpc/error_details_pb"; + +function say() { + const details = [ + new LocalizedMessage({ + locale: "fr-CH", + message: "Je n'ai plus de mots.", + }), + new LocalizedMessage({ + locale: "ja-JP", + message: "もう言葉がありません。", + }), + ]; + const metadata = new Headers({ + "words-left": "none" + }); + throw new DubboError( + "I have no words anymore.", + Code.ResourceExhausted, + metadata, + details + ); +} +``` + + +# Streaming + +Before showing the various handlers for streaming endpoints, we'd like to reference the [Streaming]() page from Dubbo-Go as a caveat. Because while Dubbo for Node.js does support all three variations of streaming endpoints, there are tradeoffs that should be considered before diving in. + +Streaming can be a very powerful approach to APIs in the right circumstances, but it also requires great care. Remember, with great power comes great responsibility. + +In **client streaming**, the client sends multiple messages. Once the server receives all the messages, it responds with a single message. In Protobuf schemas, client streaming methods look like this: + +``` +service ElizaService { + rpc Vent(stream VentRequest) returns (VentResponse) {} +} +``` + +In TypeScript, client streaming methods receive an asynchronous iterable of request messages (you can iterate over them with a for [await...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loop): + +```ts +async function vent(reqs: AsyncIterable<VentRequest>): Promise<VentResponse> {} +``` + +In **server streaming**, the client sends a single message, and the server responds with multiple messages. In Protobuf schemas, server streaming methods look like this: + +``` +service ElizaService { + rpc Introduce(IntroduceRequest) returns (stream IntroduceResponse) {} +} +``` + +In TypeScript, server streaming methods receive a request message, and return an asynchronous iterable of response messages, typically with a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*). + +```ts +async function *introduce(req: IntroduceRequest) { + yield { sentence: `Hi ${req.name}, I'm eliza` }; + yield { sentence: `How are you feeling today?` }; +} +``` + +In **bidirectional streaming** (often called bidi), the client and server may both send multiple messages. Often, the exchange is structured like a conversation: the client sends a message, the server responds, the client sends another message, and so on. Keep in mind that this always requires end-to-end HTTP/2 support (regardless of RPC protocol)! + + +# Helper Types + +Service implementations are type-safe. The `service()` method of the `DubboRouter` accepts a `ServiceImpl<T>`, where `T` is a service type. A `ServiceImpl` has a method for each RPC, typed as `MethodImp<M>`, where `M` is a method info object. + +You can use these types to compose your service without registering it right away: + +```ts +import type { MethodImpl, ServiceImpl } from "@apachedubbo/dubbo"; + +export const say: MethodImpl<typeof ElizaService.methods.say> = ... + +export const eliza: ServiceImpl<typeof ElizaService> = { + // ... +}; + +export class Eliza implements ServiceImpl<typeof ElizaService> { + async say(req: SayRequest) { + return { + sentence: `You said ${req.sentence}`, + }; + } +} +``` + +Registering the examples above: + +```ts +import { DubboRouter } from "@apachedubbo/dubbo"; Review Comment: import { DubboRouter } from "@apachedubbo/dubbo"; -> import type { DubboRouter } from "@apachedubbo/dubbo"; -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: notifications-unsubscr...@dubbo.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@dubbo.apache.org For additional commands, e-mail: notifications-h...@dubbo.apache.org