哈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> {
}
:

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())
}
}
, , :

: (, ) (, ).
, — — .

在过去的十年中,这种架构方法已经在大型企业项目中为我工作。另外,我最终将一个巨大的分层整体划分为遵循相同体系结构的微服务。我想说的是,在基于分层体系结构的微服务中,我们有一个“灯泡袋”。
希望您喜欢这篇文章!请在评论中分享您的想法,或直接与作者@RemoHJansen分享。
对于那些读到最后的人来说,额外的好处是带有示例代码示例的存储库。