说明

在本项目中,应用了CQRS架构模式,区别于普通的MVC+S(Service)模式,在这个项目中很少会使用到Service,而是通过定义一个指令,通过发布指令来完成某件事。
下面我们以创建一个查询商品列表的接口为例,带你走进如何在本框架中使用查询

创建Query

在application/query/product路径下,创建一个文件,文件命名为:interface.ts。

  1. //src\application\query\product\interface.ts
  2. //定义一个查询商品列表的Query接口,包含productName(商品名称)、page(页数)、limit(每页显示数量)等内容
  3. export interface IListProductQuery extends IQueryBase {//名称前添加I表示这是一个接口,每个Query应当继承IQueryBase。
  4. productName: string;
  5. page: number;
  6. limit: number;
  7. }

接下来实现这个接口。在application/query/product/impl路径下,创建一个文件,文件命名为:list-product.query.ts,单词用横杠隔开。query是一个标志,用小数点隔开,突出这是一个定义Query的文件。后面会出一份命名规范文档。

//src\application\query\product\impl\list-product.query.ts
import { QueryBase } from '../../../../infrastructure/core/application/query';
import { IListProductQuery } from '../interface';

export class ListProductQuery extends QueryBase implements IListProductQuery {
  productName: string;
  page: number;
  limit: number;
    //最好为每个属性定义set方法,在各自的set方法中添加业务逻辑。
  constructor(productName: string, page: number, limit: number) {
    super();
    this.setProductName(productName);
    this.setPage(page);
    this.setLimit(limit);
  }

  setProductName(productName: string): void {
    this.productName = productName;
  }

  setPage(page: number): void {
    if (page <= 0) {
      throw new Error('页数不能小于等于0');
    }
    this.page = page;
  }

  setLimit(limit: number): void {
    if (limit <= 0) {
      throw new Error('limit不能小于等于0');
    }

    if (limit > 200) {
      throw new Error('limit数值太大');
    }
    this.limit = limit;
  }
}

创建QueryExecutor

上面我们创建了一个查询指令,接下来我们来创建执行者。

//src\application\query.executor\product\interface.ts
export interface IListProductExecutor extends IQueryExecutorBase {
  dbContext: MongoDbContext;
}
//src\application\query.executor\product\impl\list-product.executor.ts
import { Inject, Provide } from '@midwayjs/decorator';
import {
  QueryBase,
  QueryExecutorBase,
} from '../../../../infrastructure/core/application/query';
import { MongoDbContext } from '../../../../infrastructure/db/mongodb/db-context';
import { SubscribeQuery } from '../../../../infrastructure/decorator/query';
import { ListProductQuery } from '../../../query/product/impl/list-product.query';
import { IListProductExecutor } from '../interface';

//订阅上面刚刚创建的Query指令
@SubscribeQuery(ListProductQuery)
@Provide() //添加到IoC容器 必填
export class ListProductExecutor
  extends QueryExecutorBase
  implements IListProductExecutor {
  @Inject('mongoDbContext') //从IoC获取实例 必填
  dbContext: MongoDbContext;
  async executeQuery<Q extends QueryBase>(query: Q): Promise<any> {
    if (query instanceof ListProductQuery) {
      this.dbContext
      const products = this.dbContext.list(
        'product', //模型名称
        {
          //筛选条件
          productName: new RegExp(query.productName),
        },
        null, //字段过滤
        {
          //执行参数
          skip: (query.page - 1) * query.limit,
          limit: query.limit,
        }
      );
      return products;
    }
    throw new Error('未识别的指令');
  }
}

在代码中,我们使用@Provide()和@SubscribeQuery对ListProductExecutor进行装饰,前者是将ListProductExecutor交给IoC容器托管,而后者则表示订阅某个指令,本框架会在项目启动的时候自动收集这些订阅,存储在查询总线中,在发送查询指令自动转发给查询指令执行者。

执行查询指令

在web/api下创建一个Controller。

//src\web\dto\product.ts
export class ListProductDTO {
  productName: string;
  page: number;
  limit: number;
}

//src\web\api\product.ts
@Provide()
@Controller('/products')
export class ProductController {
  @Inject()
  queryBus: IQueryBus;//查询总线

  @Inject()
  httpHelper: HttpHelper;//请求助手

  @Get('/')
  async listProduct(@Query(ALL) query: ListProductDTO): Promise<void> {
    //创建一个查询指令,通过查询总线发送给执行者
    const products = await this.queryBus.send(
      new ListProductQuery(query.productName, query.page, query.limit)
    );
    this.httpHelper.success(products, '查询商品成功');
  }
}