# 微服務
## 概述
除了傳統的(有時稱為單片)應用程序架構之外,`Nest` 還支持微服務架構風格的開發。本文檔中其他地方討論的大多數概念,如依賴項注入、裝飾器、異常過濾器、管道、保護和攔截器,都同樣適用于微服務。`Nest` 會盡可能地抽象化實現細節,以便相同的組件可以跨基于 `HTTP` 的平臺,`WebSocket` 和微服務運行。本節特別討論 `Nest` 的微服務方面。
在 `Nest` 中,微服務基本上是一個使用與 `HTTP` 不同的傳輸層的應用程序。

`Nest` 支持幾種內置的傳輸層實現,稱為傳輸器,負責在不同的微服務實例之間傳輸消息。大多數傳輸器本機都支持請求 - 響應和基于事件的消息樣式。`Nest` 在規范接口的后面抽象了每個傳輸器的實現細節,用于請求 - 響應和基于事件的消息傳遞。這樣可以輕松地從一個傳輸層切換到另一層,例如,利用特定傳輸層的特定可靠性或性能功能,而不會影響您的應用程序代碼。
### 安裝
首先,我們需要安裝所需的軟件包:
```bash
$ npm i --save @nestjs/microservices
```
### 開始
為了創建微服務,我們使用 `NestFactory` 類的 `createMicroservice()` 方法。
> main.ts
```typescript
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
},
);
app.listen(() => console.log('Microservice is listening'));
}
bootstrap();
```
> 默認情況下,微服務通過 **TCP協議** 監聽消息。
`createMicroservice ()` 方法的第二個參數是 `options` 對象。此對象可能有兩個成員:
|||
|---|---|
| `transport` | 指定傳輸器,例如`Transport.NATS` |
| `options` | 確定傳輸器行為的傳輸器特定選項對象 |
`options` 對象根據所選的傳送器而不同。`TCP` 傳輸器暴露了下面描述的幾個屬性。其他傳輸器(如Redis,MQTT等)參見相關章節。
|||
|---|---|
| `host` | 連接主機名 |
| `port` | 連接端口|
| `retryAttempts` | 連接嘗試的總數 |
| `retryDelay` | 連接重試延遲(ms) |
### 模式(patterns)
微服務通過 **模式** 識別消息。模式是一個普通值,例如對象、字符串。模式將自動序列化,并與消息的數據部分一起通過網絡發送。因此,接收器可以容易地將傳入消息與相應的處理器相關聯。
### 請求-響應
當您需要在各種外部服務之間交換消息時,請求-響應消息樣式非常有用。使用此范例,您可以確定服務確實收到了消息(不需要手動實現消息 `ACK` 協議)。然而,請求-響應范式并不總是最佳選擇。例如,使用基于日志的持久性的流傳輸器(如 `Kafka` 或 `NATS` 流)針對解決不同范圍的問題進行了優化,更符合事件消息傳遞范例(有關更多細節,請參閱下面的基于事件的消息傳遞)。
為了使服務能夠通過網絡交換數據,`Nest` 創建了兩個通道,其中一個負責傳輸數據,而另一個負責監聽傳入的響應。對于某些底層傳輸,比如 `NATS`,這種雙通道是支持開箱即用的。對于其他人,`Nest` 通過手動創建單獨的渠道進行補償。 這樣做可能會產生開銷,因此,如果您不需要請求-響應消息樣式,則應考慮使用基于事件的方法。
基本上,要創建一個消息處理程序(基于請求 - 響應范例),我們使用 `@MessagePattern()` ,需要從 `@nestjs/microservices` 包導入。
> math.controller.ts
```typescript
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
}
}
```
在上面的代碼中,`accumulate()` 處理程序正在監聽符合 `{cmd :'sum'}` 模式的消息。模式處理程序采用單個參數,即從客戶端傳遞的 `data` 。在這種情況下,數據是必須累加的數字數組。
### 異步響應
每個模式處理程序都能夠同步或異步響應。因此,支持 `async` (異步)方法。
> math.controller.ts
```typescript
@MessagePattern({ cmd: 'sum' })
async accumulate(data: number[]): Promise<number> {
return (data || []).reduce((a, b) => a + b);
}
```
此外,我們能夠返回 [Rx](https://github.com/reactivex/rxjs) `Observable`,因此這些值將被發出,直到流完成。
```typescript
@MessagePattern({ cmd: 'sum' })
accumulate(data: number[]): Observable<number> {
return from([1, 2, 3]);
}
```
以上消息處理程序將響應3次(對數組中的每個項)。
### 基于事件
雖然 `request-response` 方法是在服務之間交換消息的理想方法,但是當您的消息樣式是基于事件的時(即您只想發布事件而不等待響應時),它不太適合。它會帶來太多不必要的開銷,而這些開銷是完全無用的。例如,您希望簡單地通知另一個服務系統的這一部分發生了某種情況。這種情況就適合使用基于事件的消息風格。
為了創建事件處理程序,我們使用 `@EventPattern()`裝飾器, 需要 ` @nestjs/microservices` 包導入。
```typescript
@EventPattern('user_created')
async handleUserCreated(data: Record<string, unknown>) {
// business logic
}
```
> 你可以為單獨的事件模式注冊多個事件處理程序,所有的事件處理程序都會并行執行
該 `handleUserCreated()` 方法正在偵聽 `user_created` 事件。事件處理程序接受一個參數,`data` 從客戶端傳遞(在本例中,是一個通過網絡發送的事件有效負載)。
### 裝飾器
在更復雜的場景中,您可能希望訪問關于傳入請求的更多信息。例如,對于通配符訂閱的 `NATS`,您可能希望獲得生產者發送消息的原始主題。同樣,在 `Kafka` 中,您可能希望訪問消息頭。為了做到這一點,你可以使用內置的裝飾如下:
```typescript
@MessagePattern('time.us.*')
getDate(@Payload() data: number[], @Ctx() context: NatsContext) {
console.log(`Subject: ${context.getSubject()}`); // e.g. "time.us.east"
return new Date().toLocaleTimeString(...);
}
```
> `@Payload()`、`@Ctx()` 和 `NatsContext` 需要從 `@nestjs/microservices` 包導入。
> 你也可以為 `@Payload()` 裝飾器傳入一個屬性key值,來獲取通過此裝飾器拿到的對象的value值,例如 `@Payload('id')`
### 客戶端
為了交換消息或將事件發布到 `Nest` 微服務,我們使用 `ClientProxy` 類, 它可以通過幾種方式創建實例。此類定義了幾個方法,例如`send()`(用于請求-響應消息傳遞)和`emit()`(用于事件驅動消息傳遞),這些方法允許您與遠程微服務通信。使用下列方法之一獲取此類的實例。
首先,我們可以使用 `ClientsModule` 暴露的靜態`register()` 方法。此方法將數組作為參數,其中每個元素都具有 `name`屬性,以及一個可選的`transport`屬性(默認是`Transport.TCP`),以及特定于微服務的`options`屬性。
`name`屬性充當一個 `injection token`,可以在需要時將其用于注入 `ClientProxy` 實例。`name` 屬性的值作為注入標記,可以是任意字符串或`JavaScript`符號,[參考這里](https://docs.nestjs.com/fundamentals/custom-providers#non-class-based-provider-tokens)。
`options` 屬性是一個與我們之前在`createMicroservice()`方法中看到的相同的對象。
```typescript
@Module({
imports: [
ClientsModule.register([
{ name: 'MATH_SERVICE', transport: Transport.TCP },
]),
]
...
})
```
導入模塊之后,我們可以使用 `@Inject()` 裝飾器將`'MATH_SERVICE'`注入`ClientProxy`的一個實例。
```typescript
constructor(
@Inject('MATH_SERVICE') private client: ClientProxy,
) {}
```
> `ClientsModule`和 `ClientProxy`類需要從 `@nestjs/microservices` 包導入。
有時候,我們可能需要從另一個服務(比如 `ConfigService` )獲取微服務配置而不是硬編碼在客戶端程序中,為此,我們可以使用 `ClientProxyFactory` 類來注冊一個[自定義提供程序](https://docs.nestjs.com/fundamentals/custom-providers),這個類有一個靜態的`create()`方法,接收傳輸者選項對象,并返回一個自定義的 `ClientProxy` 實例:
```typescript
@Module({
providers: [
{
provide: 'MATH_SERVICE',
useFactory: (configService: ConfigService) => {
const mathSvcOptions = configService.getMathSvcOptions();
return ClientProxyFactory.create(mathSvcOptions);
},
inject: [ConfigService],
}
]
...
})
```
> `ClientProxyFactory` 需要從 `@nestjs/microservices` 包導入 。
另一種選擇是使用 `@client()`屬性裝飾器。
```typescript
@Client({ transport: Transport.TCP })
client: ClientProxy;
```
> `@Client()` 需要從 `@nestjs/microservices` 包導入 。
但是,使用 `@Client()` 裝飾器不是推薦的方法(難以測試,難以共享客戶端實例)。
`ClientProxy` 是惰性的。 它不會立即啟動連接。 相反,它將在第一個微服務調用之前建立,然后在每個后續調用中重用。 但是,如果您希望將應用程序引導過程延遲到建立連接為止,則可以使用 `OnApplicationBootstrap` 生命周期掛鉤內的 `ClientProxy` 對象的 `connect()` 方法手動啟動連接。
```typescript
async onApplicationBootstrap() {
await this.client.connect();
}
```
如果無法創建連接,則該 `connect()` 方法將拒絕相應的錯誤對象。
### 發送消息
該 `ClientProxy` 公開 `send()` 方法。 此方法旨在調用微服務,并返回帶有其響應的 `Observable`。 因此,我們可以輕松地訂閱發射的值。
```typescript
accumulate(): Observable<number> {
const pattern = { cmd: 'sum' };
const payload = [1, 2, 3];
return this.client.send<number>(pattern, payload);
}
```
`send()` 函數接受兩個參數,`pattern` 和 `payload`。`pattern` 具有 `@MessagePattern()` 修飾符中定義的這個模式,而 `payload` 是我們想要傳輸到另一個微服務的消息。該方法返回一個`cold Observable`對象,這意味著您必須在消息發送之前顯式地訂閱它。
### 發布事件
另一種是使用 `ClientProxy` 對象的 `emit()`方法。此方法的職責是將事件發布到消息代理。
```typescript
async publish() {
this.client.emit<number>('user_created', new UserCreatedEvent());
}
```
該 `emit()`方法有兩個參數,`pattern` 和 `payload`。`pattern` 具有 `@EventPattern()` 修飾符中定義的這個模式,而`payload` 是我們想要傳輸到另一個微服務的消息。此方法返回一個 `hot Observable`(不同于`send()`方法返回一個 `cold Observable`),這意味著無論您是否顯式訂閱該 `Observable`,代理都將立即嘗試傳遞事件。
### 作用域
對于不同編程語言背景的人來說,可能會意外地發現,在 `Nest` 中,幾乎所有內容都在傳入的請求之間共享。例如,我們有一個到數據庫的連接池,帶有全局狀態的單例服務,等等。請記住,`Node.js` 并不遵循`request-response`的多線程無狀態模型,在這種模型中,每個請求都由單獨的線程處理。因此,對于應用程序來說,使用單例實例是完全安全的。
但是,在某些情況下,當應用程序是基于生命周期的行為時,也存在邊界情況,例如 `GraphQL` 應用程序中的每個請求緩存、請求跟蹤或多租戶。在[這里](/8/fundamentals)學習如何控制范圍。
請求作用域的處理程序和提供程序可以使用 `@Inject()` 裝飾器結合`CONTEXT` (上下文)令牌注入`RequestContext`:
```typescript
import { Injectable, Scope, Inject } from '@nestjs/common';
import { CONTEXT, RequestContext } from '@nestjs/microservices';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(CONTEXT) private readonly ctx: RequestContext) {}
}
```
還提供了對 `RequestContext` 對象的訪問,該對象有兩個屬性:
```typescript
export interface RequestContext<T = any> {
pattern: string | Record<string, any>;
data: T;
}
```
`data` 屬性是消息生產者發送的消息有效負載。 `pattern` 屬性是用于標識適當的處理程序以處理傳入消息的模式。
#### 處理超時
在分布式系統中,有時微服務可能宕機或者無法訪問。要避免無限等待,可以使用超時,超時是一個和其他服務通訊的可信賴的方法。要在微服務中應用超時,你可以使用`RxJS`超時操作符。如果微服務沒有在指定時間內返回響應,會拋出異常以便正確捕獲與處理。
要處理該問題,可以使用`[rxjs](https://github.com/ReactiveX/rxjs)`包,并在管道中使用`timeout`操作符。
```TypeScript
this.client
.send<TResult, TInput>(pattern, data)
.pipe(timeout(5000))
.toPromise();
```
> `timeout`操作符從`rxjs/operators`中引入
5秒后,如果微服務沒有響應,將拋出錯誤。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?