Module 在 Nest 的世界里是非常重要的成员,它主要是把相同性质的功能包装在一起,并依照各模组的需求来串接,而前面有提过整个 Nest App 必定有一个根模组,Nest 会从根模组架构整个应用。
「把相同性质的功能包装在一起」是什麽意思呢?
以餐厅的例子来说,我们将餐厅分成了台湾美食、日式料理与美式风味三个区块,每个区块都有他们负责的范围,不会有在台湾美食区点日式豚骨拉面的情况,因为台湾美食区只提供台湾道地的美食;换成 Nest 的角度来举例的话,我们有三个模组,分别是:TodoModule
、UserModule
与 AuthModule
,正常来说我们不会希望在 UserModule
里面设计可以拿到 Todo 资讯的功能吧?UserModule
就应该只提供与 User 最相关的资源,达到各司其职的功效。
「依照各模组的需求来串接」又是什麽意思呢?
事实上,Module 的功能 不一定 要包含 Controller,它可以只是一个很单纯的功能所包装而成的模组,比如说:MongooseModule
。以餐厅的例子来说,我们希望在台湾美食区可以使用「筷子」这个餐具,而日式料理区同样也会使用「筷子」这个餐具,然而在美式风味区就不太适合了,所以把「筷子」视为一个共享的模组,在台湾美食区与日式料理区共用。
所有的 Module 都必须使用 @Module
装饰器来定义。可以用 NestCLI 快速生成 Module:
$ nest generate module <MODULE_NAME>
注意:
<MODULE_NAME>
可以含有路径,如:features/todo
,这样就会在src
资料夹下建立该路径并含有 Module。
这边我建立了一个名为 todo
的 Module:
$ nest generate module features/todo
提醒:如果先前有按照教学建立
TodoController
的话,可以先移除,这边将会建立新的 Controller。
在 src/features
底下会看见一个名为 todo
的资料夹,里面有 todo.module.ts
:
todo.module.ts
的内容如下:
import { Module } from '@nestjs/common';
@Module({})
export class TodoModule {}
在建立完 Module 後会发现 @Module
装饰器里面只有一个空物件,这是因为 NestCLI 不确定使用者建立该模组的用途为何,所以留空给使用者自行填入。那具体有哪些参数可以使用呢?共有以下四大项目:
controllers
:将要归纳在该 Module 下的 Controller 放在这里,会在载入该 Module 时实例化它们。providers
:将会使用到的 Provider 放在这里,比如说:Service。会在载入该 Module 时实例化它们。exports
:在这个 Module 下的部分 Provider 可能会在其他 Module 中使用,此时就可以把这些 Provider 放在这里进行汇出。imports
:将其他模组的 Provider 汇入。提醒:Provider 会在後面篇章做更详细的说明。
大多数的 Module 都属於功能模组,其概念就是前面一直强调的:把相同性质的功能包装在一起。这边我们就先把 Controller 加到 Module 中,透过指令建立 Controller:
$ nest generate controller <CONTROLLER_NAME>
这边我指定的 <CONTROLLER_NAME>
为 features/todo
,会看到 TodoModule
自动汇入了该 Controller 到 controllers
里:
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
@Module({
controllers: [TodoController]
})
export class TodoModule {}
前面有提过一个含有路由功能的模组通常都有 Controller 与 Service,这边我们先透过指令产生一个 Service,後续会再针对 Service 做说明:
$ nest generate service <SERVICE_NAME>
这边我指定的 <SERVICE_NAME>
为 features/todo
,会看到 TodoModule
自动汇入了该 Service 到 providers
里:
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
@Module({
controllers: [TodoController],
providers: [TodoService]
})
export class TodoModule {}
稍微修改一下 todo.service.ts
的内容,大致上就是在 TodoService
建立一个 getTodos
方法回传 todos
的资讯:
import { Injectable } from '@nestjs/common';
@Injectable()
export class TodoService {
private todos: { id: number, title: string, description: string }[] = [
{
id: 1,
title: 'Title 1',
description: ''
}
];
getTodos(): { id: number, title: string, description: string }[] {
return this.todos;
}
}
然後再修改 todo.controller.ts
的内容,在 TodoController
的 constructor
注入 TodoService
:
import { Controller, Get } from '@nestjs/common';
import { TodoService } from './todo.service';
@Controller('todos')
export class TodoController {
constructor(
private readonly todoService: TodoService
) {}
@Get()
getAll() {
return this.todoService.getTodos();
}
}
这样就完成一个可以作动的功能模组了,那要如何使用它呢?很简单,只要在根模组汇入它就可以了,不过在产生 Module 的时候就自动汇入了,不需要手动去新增,是不是很方便呢!赶快打开浏览器查看 http://localhost:3000/todos :
在 Nest 的世界里,预设情况下 Module 都是单例的,也就是说可以在各模组间共享同一个实例。事实上,每一个 Module 都算是共享模组,只要遵照设计原则来使用,每个 Module 都具有高度的重用性,这也是前面强调的「依照各模组的需求来串接」。这里我们可以做个简单的验证,把 TodoService
从 TodoModule
做汇出:
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
@Module({
controllers: [TodoController],
providers: [TodoService],
exports: [TodoService]
})
export class TodoModule {}
接着,建立一个新的 Module 与 Controller,这里我使用的指令如下:
$ nest generate module features/copy-todo
$ nest generate controller features/copy-todo
这边调整一下 todo.service.ts
的内容,在 TodoService
新增一个 createTodo
的方法:
import { Injectable } from '@nestjs/common';
@Injectable()
export class TodoService {
private todos: { id: number, title: string, description: string }[] = [
{
id: 1,
title: 'Title 1',
description: ''
}
];
getTodos(): { id: number, title: string, description: string }[] {
return this.todos;
}
createTodo(item: { id: number, title: string, description: string }) {
this.todos.push(item);
}
}
在 CopyTodoModule
里汇入 TodoModule
:
import { Module } from '@nestjs/common';
import { TodoModule } from '../todo/todo.module';
import { CopyTodoController } from './copy-todo.controller';
@Module({
controllers: [CopyTodoController],
imports: [TodoModule]
})
export class CopyTodoModule {}
修改 copy-todo.controller.ts
的内容,在 CopyTodoController
的 constructor
注入 TodoService
,并建立一个方法来调用 createTodo
:
import { Body, Controller, Post } from '@nestjs/common';
import { TodoService } from '../todo/todo.service';
@Controller('copy-todos')
export class CopyTodoController {
constructor(
private readonly todoService: TodoService
) {}
@Post()
create(@Body() body: { id: number, title: string, description: string }) {
this.todoService.createTodo(body);
return body;
}
}
透过 Postman 来测试:
接着,我们再透过浏览器打开 http://localhost:3000/todos 来查看 Todo 是否有增加:
这里我们可以得出一个结论,像 Service 这种 Provider 会在 Module 中建立一个实例,当其他模组需要使用该实例时,就可以透过汇出的方式与其他 Module 共享。下方为简单的概念图:
当有 Module 要与多数 Module 共用时,会一直在各 Module 进行汇入的动作,这时候可以透过提升 Module 为 全域模组,让其他模组不需要汇入也能够使用,只需要在 Module 上再添加一个 @Global
的装饰器即可。以 TodoModule
为例:
import { Module, Global } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
@Global()
@Module({
controllers: [TodoController],
providers: [TodoService],
exports: [TodoService]
})
export class TodoModule {}
注意:虽然可以透过提升为全域来减少汇入的次数,但非必要情况应少用,这样才是好的设计准则。
这是一种设计技巧,Module 可以不含任何 Controller 与 Provider,只单纯把汇入的 Module 再汇出,这样的好处是可以把多个常用的 Module 集中在一起,其他 Module 要使用的话只需要汇入此 Module 就可以了。下方为范例程序码:
@Module({
imports: [
AModule,
BModule
],
exports: [
AModule,
BModule
],
})
export class CommonModule {}
Module 在 Nest 是非常重要的角色,特别是有很核心的机制与 Provider 息息相关,下一篇会介绍这个机制,这里就先懒人包一下今天的内容:
controllers
、providers
、imports
与 exports
四个参数。
<<: Day6-React Hook 篇-useReducer
>>: [Day 6] Reactive Programming - Java 9(SubmissionPublisher、Processor)
如果你是一个有野心且不安於现状的人,薪水谈判是你一定要去做的事情。 在阅读这篇文章前,你可以先思考...
前言 今天来讨论另一个容易被忽略的主题,如果要表达「有值」的情况,大家都很熟悉: const sco...
今天要认识的迭代器模式我个人觉得需要多花一点心思,才能够了解它并善用它,程序当中也算是经常使用的...
今天来补充之前没有提到的 Eloquent 关联绑定,我们在 Day7 的时候曾规划过资料库关联,D...