簡體   English   中英

Nestjs 依賴注入和 DDD / 清潔架構

[英]Nestjs Dependency Injection and DDD / Clean Architecture

我正在通過嘗試實現一個干凈的架構結構來試驗 Nestjs,我想驗證我的解決方案,因為我不確定我是否理解最好的方法。 請注意,該示例幾乎是偽代碼,並且缺少或通用了很多類型,因為它們不是討論的重點。

從我的域邏輯開始,我可能想在 class 中實現它,如下所示:

@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 ,但是遵循clean architecture的原則,我不想被剛才的實現所困擾,所以我為它寫了一個接口:

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)
  }
}

然后我在 memory 中創建一個簡單的實現,讓我運行代碼:

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

並通過指定要與@Inject一起使用的令牌來修改ProfileDomainEntity

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

這是用於處理我所有依賴關系的合理方法,還是我完全偏離了軌道? 有沒有更好的解決辦法? 我對所有這些東西(NestJs、干凈的架構/DDD 和 Typescript 以及)都是新手,所以我在這里可能完全錯了。

謝謝

由於語言限制/特性(參見結構與名義輸入) ,因此無法通過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相比,這可能看起來像一個骯臟的黑客。 請記住,接口只是設計時間。 在我的例子中, AppServiceMockAppService甚至不是繼承自接口或抽象/基類(在現實世界中,它們當然應該是這樣),只要它們實現方法root(): string ,一切都會起作用。

來自NestJS文檔的關於這個主題的引用:

注意

我們使用ConfigService類而不是自定義標記,因此我們覆蓋了默認實現。

你確實可以使用接口,很好的抽象類。 一個打字稿功能是推斷類的接口(保存在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,
};

我使用了一種不同的方法來幫助防止跨多個模塊的命名沖突。

我使用字符串標記和自定義裝飾器來隱藏實現細節:

// injectors.ts
export const InjectProfilesRepository = Inject('PROFILES/PROFILE_REPOSITORY');

// profiles.module.ts
@Module({
  providers: [
    ProfileDomainEntity,
    {
      provide: 'PROFILES/PROFILE_REPOSITORY',
      useClass: ProfilesRepository
    }
  ]
})
export class ProfilesModule {}

// profile-domain.entity.ts
export class ProfileDomainEntity {
  constructor(
    @InjectProfilesRepository private readonly profilesRepository: IProfilesRepository
  ){}
}

它更冗長,但可以從具有相同名稱的不同模塊安全地導入多個服務。

將符號或字符串與具有相同名稱的界面一起導出

export interface IService {
  get(): Promise<string>  
}

export const IService = Symbol("IService");

現在你基本上可以使用IService作為接口和依賴項令牌

import { IService } from '../interfaces/service';

@Injectable()
export class ServiceImplementation implements IService { // Used as an interface
  get(): Promise<string> {
    return Promise.resolve(`Hello World`);
  }
}
import { IService } from './interfaces/service';
import { ServiceImplementation} from './impl/service';
...

@Module({
  imports: [],
  controllers: [AppController],
  providers: [{
    provide: IService, // Used as a symbol
    useClass: ServiceImplementation
  }],
})
export class AppModule {}
import { IService } from '../interfaces/service';

@Controller()
export class AppController {
  // Used both as interface and symbol
  constructor(@Inject(IService) private readonly service: IService) {}

  @Get()
  index(): Promise<string> {
    return this.service.get(); // returns Hello World
  }
}

作為旁注:

如果您遵循 DDD/Clean 架構,則不應從域實體訪問存儲庫。

用例 class 或域服務將使用存儲庫獲取域實體,然后您對其進行操作,當您完成相同的用例/域服務時,將存儲域實體。

領域實體位於架構圖的中心,它不應該依賴於任何其他東西。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM