node.js - NestJS injectează un repository TypeOrm personalizat bazat pe o interfață


În prezent lucrez prin documentația de integrare a bazei de date pentru NestJS folosind TypeOrm. În aceste documentații există exemple care arată cum să injectezi un repository de bază de date personalizat folosind app.module din NestJS. Toate aceste exemple injectează clase folosind tipul real al repository-ului personalizat.

@Injectable()
export class AuthorService {
  constructor(private authorRepository: AuthorRepository) {}
}

Acest cod este injectat prin app.modules prin furnizarea unui import astfel:

@Module({
  imports: [TypeOrmModule.forFeature([AuthorRepository])],
  controller: [AuthorController],
  providers: [AuthorService],
})
export class AuthorModule {}

Acest lucru funcționează bine dacă ești de acord să programezi împotriva unei implementări, dar eu prefer să folosesc o interfață în clasele mele. Am găsit deja soluția pentru injectarea claselor printr-o interfață cu NestJS într-o întrebare anterioară, dar când încerc să injectez astfel repository-ul meu personalizat, se pare că nu se instanțiază corect și devine nedefinit.

(node:16658) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'save' of undefined

Din această cauză, presupun că poți injecta dependențe de tip customRepositories doar prin apelul forFeature() în app.module, dar până unde știu, acest lucru nu îmi va permite să folosesc interfețe pentru injecție. Există vreo altă modalitate prin care pot injecta un repository TypeOrm personalizat fără să fiu nevoit să înlocuiesc toate interfețele mele cu implementarea repository-ului personalizat? Mulțumesc anticipat!

Editare

Iată codul meu curent, am reușit să îl fac să injecteze, dar tot mă forțează să folosesc implementarea în locul interfeței de fiecare dată când apelez constructorul. Aceasta este în principal o problemă atunci când fac teste din cauza simulării (mocking-ului).

  @CommandHandler(FooCommand)
export class FooHandler
  implements ICommandHandler<FooCommand> {

  private fooRepository: IFooRepository; // Using Interface as a private property.
  private barEventBus: IEventBus;
  
  constructor(fooRepository: FooRepository,
     barEventBus: EventBus) { // Forced to use implementation in constructor for injection.
    this.fooRepository = fooRepository;
    this.barEventBus = barEventBus;
  }
@EntityRepository(FooEntity)
export class FooRepository extends Repository<FooEntity> implements IFooRepository {

  getFoo() {
    // Do stuff
  }
}

@Module({
  imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([FooRepository]],

  // Other module setup
})
export class AppModule {}

Răspuns #1

Ar trebui să funcționeze folosind decoratorul InjectRepository unde specificați Repository-ul, dar apoi îl tastați ca interfața dvs. și când testați pur și simplu furnizați IFooRepository!

Exemplu de cod:

  constructor(@InjectRepository(FooRepository) fooRepository: IFooRepository,
 barEventBus: EventBus) { 

Răspunsul #2

Edit: Acest răspuns este prost, acel truc cu clasa abstractă folosit ca interfață nu funcționează deoarece metodele definite par să fie opționale de implementat, în ciuda faptului că sunt marcate ca abstracte.

Ei bine, într-un fel am reușit să-l fac să funcționeze. Bazându-mă pe acest răspuns https://stackoverflow.com/a/74561702/913136 am folosit o clasă abstractă ca interfață (poți de fapt să o implementezi) pentru a nu fi necesar să transmiți stringuri ca token-uri. Singurul dezavantaj este folosirea incorectă a clasei abstracte. Nu sunt încă sigur dacă îmi place.

Folosirea unei interfețe reale în același mod pare să nu fie posibilă, din păcate. Urgh.

@Module({
  imports: [
    TypeOrmModule.forRoot({
      ...dataSource.options,
      autoLoadEntities: true,
    }),
    TypeOrmModule.forFeature([Listing]),
  ],
  controllers: [ViewListingController],
  providers: [
    {
      provide: ListingRepository,
      useClass: TypeOrmListingRepository,
    },
  ],
})

interfață improvizată:

import { Listing } from "./Listing";

export abstract class ListingRepository {
  abstract findMostRecent: () => Promise<Listing[]>;
}

implementare :

import { Listing, ListingRepository } from "../../Domain";
import { Injectable } from "@nestjs/common";
import { Repository, DataSource } from "typeorm";

@Injectable()
export class TypeOrmListingRepository
  extends Repository<Listing>
  implements ListingRepository
{
  constructor(private dataSource: DataSource) {
    super(Listing, dataSource.createEntityManager());
  }

  findMostRecent() {
    return this.find({});
  }
}
import { Controller, Get } from "@nestjs/common";
import { ListingRepository } from "../Domain";

@Controller("listings")
export class ViewListingController {
  constructor(private readonly listingRepo: ListingRepository) {}

  @Get("/most-recent")
  listMostRecent() {
    return this.listingRepo.findMostRecent();
  }
}