使用TypeScript和InversifyJS在Node.js中实现SOLID和分层架构

哈Ha!我提请您注意Remo H. Jansen撰写的使用TypeScript和InversifyJS在Node.js中实现SOLID和洋葱架构的文章的翻译。


在本文中,我们将介绍称为洋葱的体系结构。分层架构-一种遵循SOLID原理的构建应用程序架构的方法它是在DDD和一些函数式编程原理的影响下创建的,并且还积极地应用了依赖注入的原理。


背景


本节描述了解分层体系结构所需的一些软件开发方法和原理。


责任分工原则


责任是指软件功能的各个方面。例如,“业务逻辑”和使用它的接口是不同的职责。


职责分离允许隔离实现每个职责的代码,例如,更改接口不需要更改业务逻辑代码,依此类推。


SOLID原则


SOLID是以下五个原则的首字母缩写:
图片


唯一责任原则


一堂课只有一个责任。(译者注:我认为更准确的措词是:“一个班级必须有一个并且只有一个改变的理由”)

破坏应用程序的最有效方法是创建一个神圣的类。


神圣的阶级是一个知道并且做得太多的阶级。这种方法是反模式的一个很好的例子。

. , , , . , , .


TypeScript . email, .


class Person {
    public name : string;
    public surname : string;
    public email : string;
    constructor(name : string, surname : string, email : string){
        this.surname = surname;
        this.name = name;
        if(this.validateEmail(email)) {
          this.email = email;
        }
        else {
            throw new Error("Invalid email!");
        }
    }
    validateEmail(email : string) {
        var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
        return re.test(email);
    }
    greet() {
        alert("Hi!");
    }
}

email Email:


class Email {
    public email : string;
    constructor(email : string){
        if(this.validateEmail(email)) {
          this.email = email;
        }
        else {
            throw new Error("Invalid email!");
        }        
    }
    validateEmail(email : string) {
        var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
        return re.test(email);
    }
}

class Person {
    public name : string;
    public surname : string;
    public email : Email;
    constructor(name : string, surname : string, email : Email){
        this.email = email;
        this.name = name;
        this.surname = surname;
    }
    greet() {
        alert("Hi!");
    }
}

, , , /.


/


.

, /:


class Rectangle {
    public width: number;
    public height: number;
}

class Circle {
    public radius: number;
}

function getArea(shapes: (Rectangle|Circle)[]) {
    return shapes.reduce(
        (previous, current) => {
            if (current instanceof Rectangle) {
                return current.width * current.height;
            } else if (current instanceof Circle) {
                return current.radius * current.radius * Math.PI;
            } else {
                throw new Error("Unknown shape!")
            }
        },
        0
    );
}

( ). , . , ( ), , getArea, , .


, , :


interface Shape {
    area(): number;
}

class Rectangle implements Shape {

    public width: number;
    public height: number;

    public area() {
        return this.width * this.height;
    }
}

class Circle implements Shape {

    public radius: number;

    public area() {
        return this.radius * this.radius * Math.PI;
    }
}

function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

( ) ( ).



.

, , . :


function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

Shape , . , getArea , Shape . , TypeScript, (, Shape area, . , , .



, , , .

.
, : Rectangle Circle. , , . Shape:


interface Shape {
    area(): number;
    serialize(): string;
}

class Rectangle implements Shape {

    public width: number;
    public height: number;

    public area() {
        return this.width * this.height;
    }

    public serialize() {
        return JSON.stringify(this);
    }
}

class Circle implements  Shape {

    public radius: number;

    public area() {
        return this.radius * this.radius * Math.PI;
    }

    public serialize() {
        return JSON.stringify(this);
    }

}

, :


function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

, , , :


// ...
return rectangle.serialize();

, serialize Shape . -, — . .


, - , , , :


interface RectangleInterface {
    width: number;
    height: number;
}

interface CircleInterface {
    radius: number;
}

interface Shape {
    area(): number;
}

interface Serializable {
    serialize(): string;
}

, .


class Rectangle implements RectangleInterface, Shape {

    public width: number;
    public height: number;

    public area() {
        return this.width * this.height;
    }
}

class Circle implements CircleInterface, Shape {

    public radius: number;

    public area() {
        return this.radius * this.radius * Math.PI;
    }
}

function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

, .


class RectangleDTO implements RectangleInterface, Serializable {
    public width: number;
    public height: number;

    public serialize() {
        return JSON.stringify(this);
    }
}

class CircleDTO implements CircleInterface, Serializable {
    public radius: number;

    public serialize() {
        return JSON.stringify(this);
    }
}

, ( ) ( , ).


, RectangleDTO Rectangle (DRY). , . , , . , .


DRY, DRY SOLID. , DRY , SOLID " " .



. - .

. , .


, SOLID D. , , SOLID. , SOLID . , , , :


  • , , .
  • ( ).
  • , , ( /).

SOLID . , , . , JavaScript ES5 ES6, SOLID . , TypeScript .


-- MVC


: , .


图片



. , . , Product , SQL Server.


, . , , , . , .



. . , , , .



, . MVC, ; . , , .


MVC ( , ) . , . . — . - — . , . , , -.


MVC , , . , , -. MVC — .


-


MVC , - . , . -, . - , . , , -.


- . -, , - . - . - :


  • -.
  • -.
  • , .

. , . , , . .


图片


. , , , , .


-. , , (Data Mapper) .


. , catalog , . , , SQL- Sharepoint (CAML). .



( ):



— . , , , , , .


DDD, "". :



, , - . . ( ) - . , , ( SQL) ( HTTP).


, , , . -. (, ) (, ) , , - .


: (), (). , — HTTP , , . AircraftController AircraftRepository:


import { inject } from "inversify";
import { response, controller, httpGet } from "inversify-express-utils";
import * as express from "express";
import { AircraftRepository } from "@domain/interfaces";
import { Aircraft } from "@domain/entities/aircraft";
import { TYPE } from "@domain/types";

@controller("/api/v1/aircraft")
export class AircraftController {

    @inject(TYPE.AircraftRepository) private readonly _aircraftRepository: AircraftRepository;

    @httpGet("/")
    public async get(@response() res: express.Response) {
        try {
            return await this._aircraftRepository.readAll();
        } catch (e) {
            res.status(500).send({ error: "Internal server error" });
        }

    }

    // ...

}

AircraftController HTTP AircraftRepository. AircraftRepository HTTP. :


图片


. "comp" (composition) , AircraftRepository AircraftController. "ref" (reference) , AircraftController Aircraft.


AircraftRepository , , AircraftController AircraftRepository, :


图片


, () ( ). , , .


图片


AircraftRepository . , , - . "" InversifyJS. InversifyJS , @inject, . , :


@inject(TYPE.AircraftRepository) private readonly _aircraftRepository: AircraftRepository;

, InversifyJS :


container.bind<AircraftRepository>(TYPE.AircraftRepository).to(AircraftRepositoryImpl);

AircratRepository Repository<T>, .


import { Aircraft } from "@domain/entities/aircraft";

export interface Repository<T> {
    readAll(): Promise<T[]>;
    readOneById(id: string): Promise<T>;
    // ...
}

export interface AircraftRepository extends Repository<Aircraft> {
    // Add custom methods here ...
}

:


图片


Repository<T> AircraftRepository:


  • Repository<T> Gene- ricRepositoryImpl<D, E>
  • AircraftRepository AircraftRepositoryImpl.

Repository<T>


import { injectable, unmanaged } from "inversify";
import { Repository } from "@domain/interfaces";
import { EntityDataMapper } from "@dal/interfaces";
import { Repository as TypeOrmRepository } from "typeorm";

@injectable()
export class GenericRepositoryImpl<TDomainEntity, TDalEntity> implements Repository<TDomainEntity> {

    private readonly _repository: TypeOrmRepository<TDalEntity>;
    private readonly _dataMapper: EntityDataMapper<TDomainEntity, TDalEntity>;

    public constructor(
        @unmanaged() repository: TypeOrmRepository<TDalEntity>,
        @unmanaged() dataMapper: EntityDataMapper<TDomainEntity, TDalEntity>
    ) {
        this._repository = repository;
        this._dataMapper = dataMapper;
    }

    public async readAll() {
        const entities = await this._repository.readAll();
        return entities.map((e) => this._dataMapper.toDomain(e));
    }

    public async readOneById(id: string) {
        const entity = await this._repository.readOne({ id });
        return this._dataMapper.toDomain(entity);
    }

    // ...

}

, EntityDataMapper TypeOrmRepository . .


, EntityDataMapper:


export interface EntityDataMapper<Domain, Entity> {

    toDomain(entity: Entity): Domain;
    toDalEntity(domain: Domain): Entity;
}

EntityDataMapper:


import { toDateOrNull, toLocalDateOrNull } from "@lib/universal/utils/date_utils";
import { Aircraft } from "@domain/entities/aircraft";
import { AircraftEntity } from "@dal/entities/aircraft";
import { EntityDataMapper } from "@dal/interfaces";

export class AircraftDataMapper implements EntityDataMapper<Aircraft, AircraftEntity> {

    public toDomain(entity: AircraftEntity): Aircraft {
        // ...
    }

    public toDalEntity(mortgage: Aircraft): AircraftEntity {
        // ...
    }
}

EntityDataMapper , TypeOrmRepository . :


图片


, , AircraftRepository:


import { inject, injectable } from "inversify";
import { Repository as TypeOrmRepository } from "typeorm";
import { AircraftRepository } from "@domain/interfaces";
import { Aircraft } from "@domain/entities/aircraft";
import { GenericRepositoryImpl } from "@dal/generic_repository";
import { AircraftEntity } from "@dal/entities/aircraft";
import { AircraftDataMapper } from "@dal/data_mappers/aircraft";
import { TYPE } from "@dal/types";

@injectable()
export class AircraftRepositoryImpl
    extends GenericRepositoryImpl<Aircraft, AircraftEntity>
    implements AircraftRepository {

    public constructor(
        @inject(TYPE.TypeOrmRepositoryOfAircraftEntity) repository: TypeOrmRepository<AircraftEntity>
    ) {
        super(repository, new AircraftDataMapper())
    }

    // Add custom methods here ...

}

, , :


图片


: (, ) (, ).


, — — .


图片


在过去的十年中,这种架构方法已经在大型企业项目中为我工作。另外,我最终将一个巨大的分层整体划分为遵循相同体系结构的微服务。我想说的是,在基于分层体系结构的微服务中,我们有一个“灯泡袋”。


希望您喜欢这篇文章!请在评论中分享您的想法,或直接与作者@RemoHJansen分享


对于那些读到最后的人来说,额外的好处是带有示例代码示例存储库


All Articles