Инъекция зависимостей Nestjs и DDD / Чистая архитектура - PullRequest
0 голосов
/ 24 октября 2018

Я экспериментирую с Nestjs, пытаясь реализовать структуру с чистой архитектурой, и я хотел бы проверить свое решение, потому что я не уверен, что понимаю лучший способ сделать это.Обратите внимание, что пример - почти псевдокод, и многие типы отсутствуют или являются общими, потому что они не являются предметом обсуждения.

Начиная с логики моего домена, я мог бы захотеть реализовать ее вКласс, подобный следующему:

@Injectable()
export class ProfileDomainEntity {
  async addAge(profileId: string, age: number): Promise<void> {
    const profile = await this.profilesRepository.getOne(profileId)
    profile.age = age
    await this.profilesRepository.updateOne(profileId, profile)
  }
}

Здесь мне нужно получить доступ к profileRepository, но, следуя принципам чистой архитектуры, я не хочу сейчас беспокоиться о реализации, поэтому янапишите для него интерфейс:

interface IProfilesRepository {
  getOne (profileId: string): object
  updateOne (profileId: string, profile: object): bool
}

Затем я внедряю зависимость в конструктор ProfileDomainEntity и проверяю, что она будет соответствовать ожидаемому интерфейсу:

export class ProfileDomainEntity {
  constructor(
    private readonly profilesRepository: IProfilesRepository
  ){}

  async addAge(profileId: string, age: number): Promise<void> {
    const profile = await this.profilesRepository.getOne(profileId)
    profile.age = age

    await this.profilesRepository.updateOne(profileId, profile)
  }
}

И затем я создаюпростая реализация в памяти, позволяющая мне запустить код:

class ProfilesRepository implements IProfileRepository {
  private profiles = {}

  getOne(profileId: string) {
    return Promise.resolve(this.profiles[profileId])
  }

  updateOne(profileId: string, profile: object) {
    this.profiles[profileId] = profile
    return Promise.resolve(true)
  }
}

Теперь пришло время соединить все вместе с помощью модуля:

@Module({
  providers: [
    ProfileDomainEntity,
    ProfilesRepository
  ]
})
export class ProfilesModule {}

Проблема здесь в том, что, очевидно, ProfileRepository реализует IProfilesRepository, но это не IProfilesRepository, и поэтому, насколько я понимаю, токен отличается, и Nest не может разрешить зависимость.

Единственное решениеИон, который я нашел для этого, заключается в том, чтобы пользовательский провайдер вручную установил токен:

@Module({
  providers: [
    ProfileDomainEntity,
    {
      provide: 'IProfilesRepository',
      useClass: ProfilesRepository
    }
  ]
})
export class ProfilesModule {}

и изменил ProfileDomainEntity, указав токен для использования с @Inject:

export class ProfileDomainEntity {
  constructor(
    @Inject('IProfilesRepository') private readonly profilesRepository: IProfilesRepository
  ){}
}

Это разумный подход для решения всех моих зависимостей или я совершенно не в курсе?Есть ли лучшее решение?Я новичок во всех этих вещах (NestJ, чистая архитектура / DDD и Typescript), поэтому я могу быть совершенно не прав.

Спасибо

Ответы [ 2 ]

0 голосов
/ 13 февраля 2019

Вы действительно можете использовать интерфейсы, ну абстрактные классы.Одной из возможностей машинописи является вывод интерфейса из классов (которые хранятся в мире JS), поэтому что-то вроде этого будет работать

IFoo.ts

export abstract class IFoo {
    public abstract bar: string;
}

Foo.ts

export class Foo 
    extends IFoo
    implement IFoo
{
    public bar: string
    constructor(init: Partial<IFoo>) {
        Object.assign(this, init);
    }
}
const appServiceProvider = {
  provide: IFoo,
  useClass: Foo,
};

0 голосов
/ 29 октября 2018

Невозможно разрешить зависимость с помощью интерфейса в NestJS из-за языковых ограничений / возможностей (см. Структурная или номинальная типизация) .

И,если вы используете интерфейс для определения (типа) зависимости, то вы должны использовать строковые токены.Но вы также можете использовать сам класс или его имя в качестве строкового литерала, поэтому вам не нужно упоминать его во время внедрения, скажем, в конструктор зависимого.

Пример:

// *** app.module.ts ***
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppServiceMock } from './app.service.mock';

process.env.NODE_ENV = 'test'; // or 'development'

const appServiceProvider = {
  provide: AppService, // or string token 'AppService'
  useClass: process.env.NODE_ENV === 'test' ? AppServiceMock : AppService,
};

@Module({
  imports: [],
  controllers: [AppController],
  providers: [appServiceProvider],
})
export class AppModule {}

// *** app.controller.ts ***
import { Get, Controller } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  root(): string {
    return this.appService.root();
  }
}

Вы также можете использовать абстрактный класс вместо интерфейса или дать интерфейсу и классу реализации аналогичное имя (и использовать псевдонимы на месте).

Да, по сравнению с C # / Java это может выглядеть какгрязный хак.Просто помните, что интерфейсы предназначены только для разработки.В моем примере AppServiceMock и AppService даже не наследуют ни от интерфейса, ни от абстрактного / базового класса (в реальном мире, конечно, они должны), и все будет работать, пока они реализуют метод root(): string.

Цитата из документации NestJS по этой теме :

УВЕДОМЛЕНИЕ

Вместо пользовательского токена мы использовали класс ConfigService, и поэтому мыпереопределил реализацию по умолчанию.

...