#Pattern for avoiding circular dependency

36 messages · Page 1 of 1 (latest)

ivory plank
#

I have around 10 tables in my project... each table has it own module with the nest g resource ... with al endpoints. Each module is exporting the exports: [...UserProvider], that registers each entity. When I need to use an entity in a different module I imports: [UserModule], and therefore the UserProvider is available in that module for me by constructor(@Inject(USER_REPOSITORY) private readonly userRepository: typeof User,. I'm running into the issue of circular dependency when in many to many tables, and depending of the UX flow I'm allocating code in different modules and importing those repositories giving me the circular dependency error. I see here several discussions where but they are injecting into the constructor a service and not the entity.

Is there a pattern that I can use any entity on any module without running into circular dependency? I don't want to import the service that the controller is using but the actual entity.

undone hatch
#

You are on TypeOrm ? I don't use Inject(USER_REPOSITORY) but rather @InjectRepository(User), maybe an help in your service.
Otherwise in entity, in you relation use string instead of callable class name and TypeOrm as Relation type (otherwise create it)

like :

class User {
  //something like
  @OneToMany('Photo', 'users') 
  photo: Relation<Photo>

  // instead of
  @OneToMany(Photo, Photo.users)
  photo: Photo
}

That should avoid circular depedency

To gain in visibility use madge to view circular dependency (madge --circular ./dist --warning -i img.png) i run it on js file, it work better

ivory plank
#

I’m using sequelize. I ended up using the ModuleRef

ivory plank
idle flame
#

but that's not the only kind of circularity you may see in the wild, of course

ivory plank
#

I’m using enums to point to the magic strings like Repository.User

#

So instead I should use those in the entity relationships

#

From where are you importing Relation?

Also, the Relation<Photo>… Photo is the entity right?

#

Like the actual import Photo from photo.entity

undone hatch
#

it's a typeorm things
But you can do the same with custom type :

/**
 * Wrapper type used to circumvent ESM modules circular dependency issue
 * caused by reflection metadata saving the type of the property.
 */
export type WrapperType<T> = T;

And yes it's the entity.

idle flame
hybrid basin
ivory plank
#
@Injectable()
export class BusinessLocationService implements OnModuleInit {
  private adRepository: typeof Ad; // ad.entity.ts
  private runningAdRepository: typeof RunningAd; // running-ad.entity.ts
  constructor(
    @Inject(BUSINESS_LOCATION_REPOSITORY)
    private readonly businessLocationRepository: typeof BusinessLocation,
    private moduleRef: ModuleRef,
  ) {}

  onModuleInit() {
    this.adRepository = this.moduleRef.get(AD_REPOSITORY, { strict: false });
    this.runningAdRepository = this.moduleRef.get(RUNNING_AD_REPOSITORY, { strict: false });
  }
...
idle flame
#

to me, this is an anti-pattern

#

but sometimes it does makes sense

idle flame
# idle flame to me, this is an anti-pattern

because you're breaking the modularity between the nestjs modules -- the DI graph doesn't knows that BusinessLocationService depends on AD_REPOSITORY on so on
and god knows what will happen with you have the same provider registered in many modules haha

ivory plank
ivory plank
#

to implement this I would use ddd to track down everything

ivory plank
idle flame
#

that's basically a service locator (the strict false part)

#

which is an anti-pattern for DI

ivory plank
#

this pattern is straight from the NestJS officual course.

import { BUSINESS_REPOSITORY } from 'src/core/enums';
import { Business } from './entities/business.entity';

const BusinessProvider = [
  {
    provide: BUSINESS_REPOSITORY,
    useValue: Business,
  },
];

export { BusinessProvider };
ivory plank
idle flame
#

which one?

#

there's a bunch ahah

undone hatch
#

I'have made the same thing, i have a service that need to fill some cache and load dynamicly repository from moduleRref

ivory plank
#

they are asking for 5 things to happen when an endpoint gets hit.... at that point there is no more REST going on

undone hatch
#

something like

private serviceMap = new Map<string, CallableFunction>([
    ['Entity1Repository', Entity1Repository],
...
  ]);
  constructor(
    private moduleRef: ModuleRef
  ) {}

then 
 private getRepository(entityName: string){
    const repository = entityName+'Repository';
    const serviceClass = this.serviceMap.get(repository);
    return this.moduleRef.get(serviceClass,  { strict: false })
  }
#

hard to find a way to inject automatically 15 repository or something like that

idle flame
#

yeah, using moduleRef can be a solution