jianyi-gronk commented on code in PR #395:
URL: https://github.com/apache/dubbo-js/pull/395#discussion_r1605658103
##########
docs/guide/dubboForWEB/GeneratingCode.md:
##########
@@ -1,2 +1,125 @@
# Generating code
-# todo
+
+We mentioned earlier that the ELIZA service defines a Protocol Buffer schema.
So what *is* that schema? It is really just a simple file that describes the
service, its methods, and their argument and return types:
+
+```protobuf
+syntax = "proto3";
+
+service ElizaService {
+ rpc Say(SayRequest) returns (SayResponse) {}
+}
+
+message SayRequest {
+ string sentence = 1;
+}
+
+message SayResponse {
+ string sentence = 1;
+}
+```
+
+You can see the full version including comments and some additional RPCs [on
the Buf Schema
Registry](https://buf.build/connectrpc/eliza/file/main:connectrpc/eliza/v1/eliza.proto)
(BSR). The `rpc` keyword stands for Remote Procedure Call — a method you can
invoke remotely. The schema is the contract between server and client, and it
precisely defines how data is exchanged down to the very details of
serialization.
Review Comment:
Modify link
##########
docs/guide/dubboForWEB/UsingClients.md:
##########
@@ -1,2 +1,102 @@
-# Using Clients
-# todo
+# Using clients
+
+Dubbo ships with two client shapes for TypeScript, one with classic callbacks,
one that is promise based. You create a client for a service using one of the
provided constructor functions, and you do not need to generate additional code.
+
+## Promises
+
+We have already been using the function `createPromiseClient` in the tutorial.
The function gives us a client that uses ECMAScript promise objects. In
combination with the `await` keyword, this lets you write asynchronous code in
a natural and easily readable way:
+
+```ts
+import { createPromiseClient } from "@apachedubbo/dubbo";
+import { ElizaService } from "../gen/buf/connect/demo/eliza/v1/eliza_dubbo.js";
Review Comment:
Need to modify
##########
docs/guide/dubboForWEB/Choosingaprotocol.md:
##########
@@ -1 +1,71 @@
# Choosing a protocol
+
+In addition to the Dubbo protocol, Dubbo ships with support for the gRPC-web
protocol. If your backend does not support the Dubbo protocol, you can still
use Dubbo clients to interface with it.
+
+## Connect
Review Comment:
Triple
##########
docs/guide/dubboForWEB/GeneratingCode.md:
##########
@@ -1,2 +1,125 @@
# Generating code
-# todo
+
+We mentioned earlier that the ELIZA service defines a Protocol Buffer schema.
So what *is* that schema? It is really just a simple file that describes the
service, its methods, and their argument and return types:
+
+```protobuf
+syntax = "proto3";
+
+service ElizaService {
+ rpc Say(SayRequest) returns (SayResponse) {}
+}
+
+message SayRequest {
+ string sentence = 1;
+}
+
+message SayResponse {
+ string sentence = 1;
+}
+```
+
+You can see the full version including comments and some additional RPCs [on
the Buf Schema
Registry](https://buf.build/connectrpc/eliza/file/main:connectrpc/eliza/v1/eliza.proto)
(BSR). The `rpc` keyword stands for Remote Procedure Call — a method you can
invoke remotely. The schema is the contract between server and client, and it
precisely defines how data is exchanged down to the very details of
serialization.
+
+The schema comes to life by generating code. For the server, an interface is
generated, and the developer can focus on filling the methods with business
logic. For the client, there really isn't anything to do — the developer can
just call the client methods, rely on the generated types for compile-time
type-safety and serialization, and focus on the application logic.
+
+## Generated SDKs
+
+In the tutorial, we have been using [generated
SDKs](https://buf.build/docs/bsr/generated-sdks/npm) with an `npm install`
command. When the package was requested on the BSR NPM registry, it ran the
schema through a code generator, and served the generated files as a package
with all required dependencies.
+
+If you want to use a Dubbo or gRPC service whose schema is published on the
BSR, you can simply use `npm` to install the package, and hit the service with
a Dubbo client.
+
+See our [documentation on generated
SDKs](https://buf.build/docs/bsr/generated-sdks/overview) for details.
+
+## Local generation
+
+We're going to generate our code using [Buf](https://buf.build/product/cli/),
a modern replacement for Google's protobuf compiler, and two compiler plugins:
+
+-
[@apachedubbo/protoc-gen-apache-dubbo-es](https://www.npmjs.com/package/@apachedubbo/protoc-gen-apache-dubbo-es)
— generates services from your Protocol Buffer schema
+-
[@bufbuild/protoc-gen-es](https://www.npmjs.com/package/@bufbuild/protoc-gen-es)
— generates base types, like request and response messages
+
+The code we will generate has three runtime dependencies:
+
+- [@apachedubbo/dubbo](https://www.npmjs.com/package/@apachedubbo/dubbo) —
provides clients, interceptors, errors, and other primitives for Dubbo
+-
[@apachedubbo/dubbo-web](https://www.npmjs.com/package/@apachedubbo/dubbo-web)
— provides the Dubbo and gRPC-web protocols for web browsers
+- [@bufbuild/protobuf](https://www.npmjs.com/package/@bufbuild/protobuf) —
provides serialization and more for the base types
+
+First, let's install `buf`, the plugins and runtime dependencies:
+
+```bash
+$ npm install --save-dev @bufbuild/buf @apachedubbo/protoc-gen-apache-dubbo-es
@bufbuild/protoc-gen-es
+$ npm install @apachedubbo/dubbo @apachedubbo/dubbo @bufbuild/protobuf
+```
+
+Next, tell Buf to use the two plugins with a new configuration file:
+
+```yaml
+# buf.gen.yaml defines a local generation template.
+# For details, see https://buf.build/docs/configuration/v1/buf-gen-yaml
+version: v1
+plugins:
+ # This will invoke protoc-gen-es and write output to src/gen
+ - plugin: es
+ out: src/gen
+ opt:
+ # Add more plugin options here
+ - target=ts
+ # This will invoke protoc-gen-apache-dubbo-es
+ - plugin: apache-dubbo-es
+ out: src/gen
+ opt:
+ # Add more plugin options here
+ - target=ts
+```
+
+If desired, you can also skip local plugin installation and use [remote
plugins](https://buf.build/docs/bsr/remote-plugins/overview).
+
+Finally, tell Buf to generate code for the ELIZA schema:
+
+```bash
+$ npx buf generate buf.build/apache-dubbo/eliza
+```
+
+If you prefer, you can use `protoc` instead of Buf — the plugins behave like
any other plugin.
+
+### Output
+
+Let's take a peek at what was generated. There are two new files:
+
+- `src/gen/apache-dubbo/eliza/v1/eliza_dubbo.ts`
+- `src/gen/apache-dubbo/eliza/v1/eliza_pb.ts`
+
+The first file was generated by `protoc-gen-apache-dubbo-es` and contains the
service:
+
+```ts
+import { SayRequest, SayResponse } from "./eliza_pb.js";
+import { MethodKind } from "@bufbuild/protobuf";
+
+export const ElizaService = {
+ typeName: "buf.connect.demo.eliza.v1.ElizaService",
Review Comment:
Need to modify
##########
docs/guide/dubboForWEB/UsingClients.md:
##########
@@ -1,2 +1,102 @@
-# Using Clients
-# todo
+# Using clients
+
+Dubbo ships with two client shapes for TypeScript, one with classic callbacks,
one that is promise based. You create a client for a service using one of the
provided constructor functions, and you do not need to generate additional code.
+
+## Promises
+
+We have already been using the function `createPromiseClient` in the tutorial.
The function gives us a client that uses ECMAScript promise objects. In
combination with the `await` keyword, this lets you write asynchronous code in
a natural and easily readable way:
+
+```ts
+import { createPromiseClient } from "@apachedubbo/dubbo";
+import { ElizaService } from "../gen/buf/connect/demo/eliza/v1/eliza_dubbo.js";
+
+const client = createPromiseClient(ElizaService, transport);
+
+const res = await client.say({
+ sentence: "I feel happy.",
+});
+console.log(res.sentence);
+```
+
+For server-streaming RPCs, the corresponding method on the client will return
an async iterable stream of response messages that can be used with the `for
await...of` statement:
+
+```ts
+for await (const res of client.introduce({ name: "Joseph" })) {
+ console.log(res);
+}
+```
+
+## Callbacks
+
+If you prefer a callback-based approach, the client returned by the function
`createCallbackClient` should suit you:
+
+```ts
+import { createCallbackClient } from "@apachedubbo/dubbo";
+import { ElizaService } from "../gen/buf/connect/demo/eliza/v1/eliza_dubbo.js";
Review Comment:
Need to modify
##########
docs/guide/dubboForWEB/Interceptors.md:
##########
@@ -1 +1,164 @@
# Interceptors
+
+An interceptor can add logic to clients, similar to the decorators or
middleware you may have seen in other libraries. Interceptors may mutate the
request and response, catch errors and retry/recover, emit logs, or do nearly
anything else.
+
+For a simple example, this interceptor logs all requests:
+
+```ts
+import { Interceptor } from "@apachedubbo/dubbo";
+import { createDubboTransport } from "@apachedubbo/dubbo-web"
+
+const logger: Interceptor = (next) => async (req) => {
+ console.log(`sending message to ${req.url}`);
+ return await next(req);
+};
+
+createDubboTransport({
+ baseUrl: "http://localhost:8080",
+ interceptors: [logger],
+});
+```
+You can think of interceptors like a layered onion. A request initiated by a
client goes through the outermost layer first. Each call to `next()` traverses
to the next layer. In the center, the actual HTTP request is run by the
transport. The response then comes back through all layers and is returned to
the client. In the array of interceptors passed to the transport, the
interceptor at the end of the array is applied first.
+
+To intercept responses, we simply look at the return value of `next()`:
+
+```ts
+const logger: Interceptor = (next) => async (req) => {
+ console.log(`sending message to ${req.url}`);
+ const res = await next(req);
+ if (!res.stream) {
+ console.log("message:", res.message);
+ }
+ return res;
+};
+```
+
+The `stream` property of the response tells us whether this is a streaming
response. A streaming response has not fully arrived yet when we intercept it —
we have to wrap it to see individual messages:
+
+```ts
+const logger: Interceptor = (next) => async (req) => {
+ const res = await next(req);
+ if (res.stream) {
+ // to intercept streaming response messages, we wrap
+ // the AsynchronousIterable with a generator function
+ return {
+ ...res,
+ message: logEach(res.message),
+ };
+ }
+ return res;
+};
+
+async function* logEach(stream: AsyncIterable<any>) {
+ for await (const m of stream) {
+ console.log("message received", m);
+ yield m;
+ }
+}
+```
+
+# Context values
+
+Context values are a type safe way to pass arbitary values from the call site
or from one interceptor to the next. You can use `createContextValues` function
to create a new `ContextValues`. The `contextValues` call option can be used to
provide a `ContextValues` instance for each request.
+
+`ContextValues` has methods to set, get, and delete values. The keys are
`ContextKey` objects:
+
+## Context Keys
+
+`ContextKey` is a type safe and collision free way to use context values. It
is defined using `createContextKey` function which takes a default value and
returns a `ContextKey` object. The default value is used when the context value
is not set.
+
+```ts
+import { createContextKey } from "@apachedubbo/dubbo";
+
+type User = { name: string };
+
+const kUser = createContextKey<User>(
+ { name: "Anonymous" }, // Default value
+ {
+ description: "Current user", // Description useful for debugging
+ },
+);
+
+export { kUser };
+```
+
+For values where a default doesn't make sense you can just modify the type:
+
+```ts
+import { createContextKey } from "@apachedubbo/dubbo";
+
+type User = { name: string };
+
+const kUser = createContextKey<User | undefined>(undefined, {
+ description: "Authenticated user",
+});
+
+export { kUser };
+```
+
+It is best to define context keys in a separate file and export them. This is
better for code splitting and also avoids circular imports. This also helps in
the case where the provider changes based on the environment.
+
+## Example
+
+Let's say you want to log the response body. But you don't want to do it for
every request. You only want to do it from a specific component. You can use
context values to achieve this.
+
+First create a context key:
+
+```ts
+import { createContextKey } from "@apachedubbo/dubbo";
+
+const kLogBody = createContextKey<boolean>(false, {
+ description: "Log request/response body",
+});
+
+export { kLogBody };
+```
+
+Then in your interceptor, check the context value:
+
+```ts
+import type { Interceptor } from "@apachedubbo/dubbo";
+import { kLogBody } from "./log-body-context.js";
+
+const logger: Interceptor = (next) => async (req) => {
+ console.log(`sending message to ${req.url}`);
+ const res = await next(req);
+ if (!res.stream && req.contextValues.get(kLogBody)) {
+ console.log("message:", res.message);
+ }
+ return res;
+};
+```
+
+Then in your component, set the context value:
+
+```ts
+import { kLogBody } from "./log-body-context.js";
+import { elizaClient } from "./eliza-client.js";
+
+const res = elizaClient.say({ sentence: "Hey!" }, { contextValues:
createContextValues().set(kLogBody, true) });
+```
+
+# Setting `fetch()` options
+
+Another valuable use case for interceptors is customizing the Fetch API for
individual requests by leveraging the `request.init` object.
+
+For example, by default, Dubbo sets the Fetch option
[redirect](https://developer.mozilla.org/en-US/docs/Web/API/fetch#redirect) to
`error`, which means that a network error will be returned when a request is
met with a redirect. However, if you wish to change this value to `follow` for
example, you can do so using an interceptor.
+
+```ts
+const followRedirects: Interceptor = (next) => async (request) => {
+ return await next({
+ ...request,
+ init: {
+ ...request.init,
+ // Follow all redirects
+ redirect: "follow",
+ },
+ });
+};
+
+const client = createPromiseClient(ElizaService, createConnectTransport({
Review Comment:
not createConnectTransport
##########
docs/guide/dubboForWEB/UsingClients.md:
##########
@@ -1,2 +1,102 @@
-# Using Clients
-# todo
+# Using clients
+
+Dubbo ships with two client shapes for TypeScript, one with classic callbacks,
one that is promise based. You create a client for a service using one of the
provided constructor functions, and you do not need to generate additional code.
+
+## Promises
+
+We have already been using the function `createPromiseClient` in the tutorial.
The function gives us a client that uses ECMAScript promise objects. In
combination with the `await` keyword, this lets you write asynchronous code in
a natural and easily readable way:
+
+```ts
+import { createPromiseClient } from "@apachedubbo/dubbo";
+import { ElizaService } from "../gen/buf/connect/demo/eliza/v1/eliza_dubbo.js";
+
+const client = createPromiseClient(ElizaService, transport);
+
+const res = await client.say({
+ sentence: "I feel happy.",
+});
+console.log(res.sentence);
+```
+
+For server-streaming RPCs, the corresponding method on the client will return
an async iterable stream of response messages that can be used with the `for
await...of` statement:
+
+```ts
+for await (const res of client.introduce({ name: "Joseph" })) {
+ console.log(res);
+}
+```
+
+## Callbacks
+
+If you prefer a callback-based approach, the client returned by the function
`createCallbackClient` should suit you:
+
+```ts
+import { createCallbackClient } from "@apachedubbo/dubbo";
+import { ElizaService } from "../gen/buf/connect/demo/eliza/v1/eliza_dubbo.js";
+
+const client = createCallbackClient(ElizaService, transport);
+
+client.say({ sentence: "I feel happy." }, (err, res) => {
+ if (!err) {
+ console.log(res.sentence);
+ }
+});
+```
+
+For server-streaming RPCs, the corresponding method on the client takes two
callback functions: one that is called every time a response message arrives,
and one that is called at the end of the stream.
+
+```ts
+import {DubboError} from "@apachedubbo/dubbo";
+
+client.introduce({name: "Joseph"}, (res) => {
+ console.log(res);
+}, (err?: DubboError) => {
+ if (err) {
+ console.error(err);
+ }
+});
+```
+
+The callback client is particularly useful if you want to migrate an existing
code base from gRPC-web to Connect clients.
Review Comment:
Connect -> connect
--
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: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]