This paper mainly focuses on the micro services in Nest documents, and selects TCP as the communication mechanism between micro services. Practice in the form of demo. Before that, a quick look at microservices.

Pain points for individual services

  • When the code is updated, the whole system involved in the test, deployment will be re-executed, the whole process is very slow.
  • When a problem is encountered, the entire server is unavailable due to the difficulty of fixing Bug location in a system repository.
  • Extending services and introducing new features can be difficult, and may involve an entire system refactoring, pulling the strings

In order to solve the business pain points of a single service, microservice architecture comes into being. Microservice can redesign giant stone applications according to the architecture of microservice. The whole project became simple and manageable.

Introduction to Microservices

Microservice architecture is an architectural concept that aims to decouple solutions by decomposing functionality into discrete services.

Microservice is not a specific technology, but a collection of architectural styles. Therefore, microservice itself is not clearly defined, but it can be known that it is an overall architecture composed of more than one independent service.

Features of microservices architecture

  • The function modules are independent, which eliminates the code interdependence between services and enhances the expansibility of applications
  • Each module can be deployed independently, which shortens the deployment time of applications and helps locate errors more quickly

Microservice design guidelines

Microservices themselves will bring many advantages compared to the traditional architecture, but at the same time will increase the additional complexity and management costs, so do not microservices for the sake of microservices, depending on the project, business scenarios, sometimes a single service can solve the problem, do not use microservices to solve. The scenarios for microservices are huge projects or megalithic applications.

Nest Microservices architecture

Service to service communication protocol

Microservices architecture is one of the features supported by Nest.js, which decouples the solution by breaking up functionality into discrete services.

Nest has several different microservices Transport layer implementations built in. The default is TCP, defined in the Transport module of the @Nestjs/MicroServices package, which are simply categorized as:

  • Direct transmission: TCP
  • Message transfer: REDIS, NATS, MQTT, RMQ, KAFKA
  • Remote process scheduling: GRPC

We need to select a communication protocol as the communication mechanism between micro-services. It is very convenient for Nest framework to switch the transmission protocol. We need to decide according to the characteristics of our own project.

Inter-service communication mode

Nest MicroService has two communication modes:

  • Request-response mode is used when messages need to be exchanged between internal services. Asynchronous response functions are also supported. The return result can be an Observable.
  • Event-based mode is the best choice when services are based on events — we just want to publish events instead of subscribes to events, so we don’t need to wait for the response function.

In order to accurately transmit data and events between microservices, a value called pattern is needed. Pattern is a common object value customized by us, or a string. Pattern is the language used for communication between microservices. It is automatically serialized and a matching service module is found through a network request.

Build a simple microservice architecture

demo1

The default communication protocol of Nest microservice is TCP. The main project and microservice are not in the same folder, they are separate and need to be started separately. The architecture diagram at this time is as follows:

Micro service

steps

  1. Create a microservice and install the built-in microservice module
nest new ms-math
yarn add @nestjs/microservices
Copy the code
  1. Modify the main.ts file in the MS-Math microservice
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')); Here the code is reporting an error app.listen(); } bootstrap();Copy the code
  1. Example Modify the app.service.ts file in mS-Math microservice
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }

  calculateWordCount(str: string) {
    const words = str.trim().split(/\s+/);
    return words.reduce((a, c) => ((a[c] = (a[c] || 0) + 1), a), {});
  }
}
Copy the code
  1. Modify the app.controller.ts file in mS-Math microservice

In the controller, @GET or @POST is no longer used to expose the interface, but @Messagepattern is used to set the pattern for identification between micro-services.

import { Controller } from '@nestjs/common'; import { AppService } from './app.service'; import { MessagePattern } from '@nestjs/microservices'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @MessagePattern('math:wordcount') wordCount(text: string): { [key: string]: number } { return this.appService.calculateWordCount(text); }}Copy the code

At this point the microservice is created, let’s start it, yarn start:dev

The main project

steps

  1. Create the main project to install microservice dependencies
nest new ms-app
yarn add @nestjs/microservices
Copy the code
  1. Register micro service client in app.module

Register a client for data transfer to the microservice, registering the mathService using the register() method provided by ClientsModule

import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; @module ({imports: [ClientsModule. Register ([{name: 'MATH_SERVICE', transport: Transport.TCP }, ]), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}Copy the code
  1. After the module is successfully registered, it is referenced by dependency injection in app.controller
constructor(@Inject('MATH_SERVICE') private client: ClientProxy) {}
Copy the code

The specific code is as follows:

import { Controller, Get, Inject, Post, Body } from '@nestjs/common'; import { AppService } from './app.service'; import { ClientProxy } from '@nestjs/microservices'; import { Observable } from 'rxjs'; @Controller() export class AppController { constructor( private readonly appService: AppService, @Inject('MATH_SERVICE') private client: ClientProxy ) { } @Post('/math/wordcount') wordCount( @Body() { text }: { text: string }, ): Observable<{ [key: string]: number }> { this.client.emit('math:wordcount_log', text) return this.client.send('math:wordcount', text); }}Copy the code

Here, the ClientProxy send method is used to communicate with another microservice.

ClientProxy objects have two core methods
  • Send () is a message sending method in request-response mode. This method invokes the microservice and returns the response body of an Observable, so it is easy to subscribe to the data returned by the microservice. Note that the corresponding message body is sent only after you subscribe to the object.

  • Emit (), event-based message sending method that will be sent immediately regardless of whether you subscribe to data or not

  1. At this point, the main project is completed and the mS-APP main project is started

test

Curl to test:

CD ms-app, run the following command

curl --location --request POST 'http://localhost:3000/math/wordcount' \
--header 'Content-Type: application/json' \
--data-raw '{
    "text": "a b c c"
}'
Copy the code

Output:

Event-based transport

Ms – app in the app. The controller

Math :wordcount_log = 'math:wordcount_log' and add this to the original '/math/wordcount' routing method: this.client.emit(' Math :wordcount_log', text)Copy the code

In MS-Math, register the corresponding subscriber in app.controller

@eventpattern ('math:wordcount_log') wordCountLog(text: string): void {console.log(text, 'event-based transfer '); }Copy the code

Run the curl command to view the following information on the terminal of the MS-math service

Use Redis as the message broker

What is a message broker

Message Broker is an intermediate program module used to exchange messages in computer networks. It is the building block of message-oriented middleware and therefore does not include responsibility for remote process scheduling (RPC).

Message broker is also an architectural pattern for message validation, transformation, and routing. Tune application communication to minimize mutual awareness (dependency) and effectively decouple. For example, a message broker can manage a workload queue or message queue for multiple receivers, providing reliable storage, guaranteed message distribution, and transaction management

Why redis

  • Redis itself is lightweight and efficient enough to be popular with high usage
  • I have a certain understanding of Redis, so I can get used to it faster

Changes to the service architecture

Here’s a picture, very clear:

The demo implementation

  1. createdocker-compose.ymlFor managing the Redis service
Version: '3.7' services: redis: image: redis:latest container_name: service-redis command: redis-server --requirepass rootroot ports: - "16377:6379" volumes: - ./data:/dataCopy the code

Docker-compose up -d docker ps

  1. To install Redis in MS-app and MS-math, rely on YARN Add Redis

  2. In the bootstrap function in MS-Math, replace Transport with redis and attach the service address

// before
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
    },
  );

// after
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.REDIS,
      options: {
        url: "redis://:rootroot@localhost:16377",
      }
    },
  );
Copy the code

This is where the MS-Math transformation is complete

  1. Then open mS-app and make the corresponding substitution where the client is registered
// before
ClientsModule.register([
      { name: 'MATH_SERVICE', transport: Transport.TCP },
    ]),

// after
ClientsModule.register([
      {
        name: 'MATH_SERVICE',
        transport: Transport.REDIS,
        options: {
          url: 'redis://:rootroot@localhost:16377',
        },
      },
    ]),
  ],
Copy the code
  1. Finally, test with curl

demo2

The main demo project and micro service are managed in a unified manner in a folder. Start only the outermost YARN start:dev, not the main project and microservice separately.

  1. Generates an outer file project that will be used as the main project
nest new nest-app -p yarn
Copy the code

The directory structure is as follows:

. ├ ─ ─ the README. Md ├ ─ ─ nest - cli. Json ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ app. The controller. The spec. The ts │ ├ ─ ─ app. The controller. The ts │ ├ ─ ─ App. The module. Ts │ ├ ─ ─ app. Service. The ts │ └ ─ ─ main. Ts ├ ─ ─ the test │ ├ ─ ─ app. E2e - spec. Ts │ └ ─ ─ jest - e2e. Json ├ ─ ─ Tsconfig. Build. Json ├ ─ ─ tsconfig. JsonCopy the code
  1. Regenerate into another project as a microservice project:
cd nest-app
nest g app nest-service
Copy the code

The directory structure is now updated

Nest-cli. json file description

At this time, the main nest-app and micro-service nest-Service are stored in the same repository, called Monorepo. Notice that there is a nest-cli.josn file in the root directory, which can configure the parameters of Monorepo. Docs.nestjs.com/cli/monorep…

{ "collection": "@nestjs/schematics", "sourceRoot": "apps/nest-app/src", "monorepo": true, "root": "Apps /nest-app", // specify which project is the main project "compilerOptions": {"webpack": true, "tsConfigPath": "Apps /nest-app/tsconfig.app.json" // Specify your own tsconfig.json file path for each project}, "projects": {"nest-app": {"type": "application", "root": "apps/nest-app", "entryFile": "main", "sourceRoot": "apps/nest-app/src", "compilerOptions": { "tsConfigPath": "apps/nest-app/tsconfig.app.json" } }, "nest-service": { "type": "application", "root": "apps/nest-service", "entryFile": "main", "sourceRoot": "apps/nest-service/src", "compilerOptions": { "tsConfigPath": "apps/nest-service/tsconfig.app.json" } } } }Copy the code

Micro service

Adapt the Nest-Service project to provide the ability for microservices to be invoked.

steps

  1. Add microservice dependenciesyarn add @nestjs/microservices
  2. To create a microservice, modify the nest-service/main.ts file

CreateMicroservice is used to create a microservice instance. It takes two parameters, the first as you would normally create a Nest app, and the second to control the specific properties of the microservice to be created, such as port, address, and so on.

import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { NestServiceModule } from './nest-service.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    NestServiceModule,
    {
      transport: Transport.TCP,
      options: {
        port: 4000,
      },
    },
  );

  await app.listen();
}
bootstrap();
Copy the code
  1. Adding a message handler

Nest – service/SRC/nest – service. Controller. Ts

import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { NestServiceService } from './nest-service.service'; @Controller() export class AppController { constructor(private readonly nestServiceService: NestServiceService) {} @MessagePattern({ cmd: 'getHello' }) getHello(name: string): string { return this.nestServiceService.getHello(name); }}Copy the code

MessagePattern defines a message handler that listens for and processes the getHello message from the caller, relying on the service/ nest-service/ SRC /nest-service.service.ts

  1. Modify/nest – service/SRC/nest – service. Service. Ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class NestServiceService {
  getHello(name: string): string {
    return `Hello ${name}!`;
  }
}
Copy the code
  1. Modifying a Startup Command

Start a single project

  • nest start nest-app
  • nest start nest-service

During development, services provided by microservices need to be invoked from the main project, and both projects can be concurrently started by concurrently.

yarn add -D concurrently
Copy the code

Modify script in package.json:

"start:dev": "concurrently --kill-others "nest start nest-app --watch" "nest start nest-service --watch"",
Copy the code

Finally CD Nest-app is executed

yarn start:dev
Copy the code

The main project

The microservice caller, to invoke the microservice, first initializes a client object. Nest supports multiple types of microservices, so it provides ClientProxy object as a unified client. After initialization, users do not need to care about the differences of different types of microservices. The proxy object provides a unified call interface externally.

steps

  1. Dependency injection

/nest-app/apps/nest-app/src/app.module.ts

import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'NEST_SERVICE',
        transport: Transport.TCP,
        options: {
          port: 4000,
        },
      },
    ]),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
Copy the code

Use @inject (‘NEST_SERVICE’)

  1. App. The controller changes

/nest-app/apps/nest-app/src/app.controller.ts

import { Controller, Get, Inject, Query } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; @Controller() export class AppController { constructor(@Inject('NEST_SERVICE') private readonly client: ClientProxy) {} @Get('hello') getHello(@Query() query: any): Promise<string> { return this.client.send<string>({ cmd: 'getHello' }, query.name).toPromise(); }}Copy the code

test

Demo1 code repository address: github.com/xiaoqiao112…

Demo2 code repository address: github.com/xiaoqiao112…

Reference website:

Juejin. Cn/post / 684490… Juejin. Cn/post / 705881… Wayou. Making. IO / 2020/07/17 /…