Composition Root 在 typescript express 中的实现

Implementation of Composition Root in typescript express

我想了解应该如何在项目中实现组合根。

据我所知,如果以错误的方式使用组合根(例如,通过在应用程序代码中的很多地方引用它),您最终会得到服务定位器。

让我向您展示一个没有组合根的项目示例。

我有以下项目结构:

server.ts:

此文件导入 API 并初始化服务器。

import express from 'express';
import API from './api'

const app = express();
const port = 3000;

app.use(express.json());

app.use(API);

// Start server
app.listen(port, () => {
    console.log('listening on port: ' + port);
});

domain.ts:

此文件包含域的核心逻辑。

export type Entity = {
    param1: string,
    param2: string,
};

export type IRepository = {
    GetMultipleEntities(filterParam: string): Entity[] | undefined
    GetEntity(filterParam: string): Entity | undefined
    CreateEntity(entity: Entity): void
    UpdateEntity(entity: Entity): void
}

application.ts:

此文件包含应用程序的用例。

import {IRepository} from './domain';

export const CheckIfEntityExists = (filterParam: string, entityRepository: IRepository): boolean => {
    let entity = entityRepository.GetEntity(filterParam);
    return typeof entity != "undefined";
};

sql-repository.ts:

此文件包含 IRepository 接口的具体实现

import {Entity, IRepository} from './domain';

export class SqlRepository implements IRepository {
    GetEntity(filterParam: string): Entity {
        //
        // some sort of logic to get entity from an sql database
        //
        return {
            param1: '',
            param2: ''
        };
    }
    GetMultipleEntities(filterParam: string): Entity[] {
        //
        // some sort of logic to get multiple entity from an sql database
        //
        return [
            {
                param1: '',
                param2: ''
            },
            {
                param1: '',
                param2: ''
            }
        ];
    }
    CreateEntity(entity: Entity): void {
        // some logic to enter new data to the sql database that represents an entity
    }
    UpdateEntity(entity: Entity): void {
        // some logic to update the entity
    }
}

api.ts:

此文件包含使用 application.ts 文件中用例的 api

import {Router} from 'express'
import {CheckIfEntityExists} from './application';
import {SqlRepository} from './sql-repository';

const router = Router();

router.get("/exists/:filterParam", async (req, res) => {
    CheckIfEntityExists(req.params.filterParam, new SqlRepository);
    res.end()
});

export default router

Ofc 这只是一个例子,但你明白项目的样子。

据您所见,在我们看到 api.ts 文件之前一切都很好。 它导入具体实现并将其注入到用例中。 如果有更多的依赖项要导入和使用怎么办,我不希望 api.ts 负责决定哪些实现去哪个地方,这不是它的责任。

但另一方面,我应该如何实现合成根呢? 我不知道我应该如何构建完整的对象图,然后将其传递给服务器对象,以便正确的实现将转到正确的对象。

提前致谢!

定义

为了给出术语 Composition Root 的一些范围和定义,这里引用了 Mark Seemann 在 two related 文章中的精彩引述:

我们应该在哪里编写对象图?

As close as possible to the application's entry point.

什么是复合根?

A Composition Root is a (preferably) unique location in an application where modules are composed together.

The Composition Root is an application infrastructure component.

A Composition Root is application-specific; it's what defines a single application. After having written nice, decoupled code throughout your code base, the Composition Root is where you finally couple everything, from data access to (user) interfaces.

影响

换句话说,您的 api.ts 可以被视为您的服务器应用程序的入口点,因此在其中编写您的对象图是完全没问题的。你也可以

  1. 选择server.ts
  2. 定义一个单独的 DI 模块,如 composition-root.ts,它完成所有的组合,并由 server.tsapi.ts 导入(甚至更有凝聚力)。

这里更重要的是,您有一个独特的位置 near/in 您项目的应用程序入口点,它负责 creating/composing 依赖项。

例子

让我们以您的具体示例为例,并假设我们想要在 api.ts 导入的 composition-root.ts 中完成所有的作曲工作。您的依赖关系图如下所示(--> 表示此处导入):

server.ts --> api.ts --> application.ts --> domain.ts 
                                        --> sql-repository.ts

composition-root.ts 之外的所有内容都与其依赖项分离。构造函数注入可以像本文示例中那样使用,也可以使用任何其他注入方法,具体取决于 language/framework/coding 样式。您的示例看起来已经很不错了,让我们为存储库添加一些 DB 抽象层并从 api.ts.

中抽象出组合

sql-repository.ts:

export class SqlRepository implements IRepository {
  constructor(private db: DB) {}
  ...
}

api.ts:

import {CheckIfEntityExists} from "./composition-root"
...

router.get("/exists/:filterParam", async (req, res) => {
    CheckIfEntityExists(req.params.filterParam);
    res.end()
});

作文-root.ts:

import {CheckIfEntityExists} from './application';
import {SqlRepository} from './sql-repository';

const db = new DB();
const sqlRepository = new SqlRepository(db);
// We do partial application here to free api.ts from 
// knowing the concrete repository.
const _CheckIfEntityExists = (filterParam: string) =>
  CheckIfEntityExists(filterParam, sqlRepository);

export { _CheckIfEntityExists as CheckIfEntityExists };

总而言之,您已经将依赖项很好地封装在一个地方composition-root.ts,而您的应用程序架构中更内层的其他代码对它们的构造一无所知。

希望对您有所帮助。