Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring circular dependencies in Typescript

I have an API which denormalizes data causing circular dependency. Is there a way to refactor the following using abstract classes, interfaces, composition or other techniques where I wouldn't need to create N partial classes for each entity to avoid a circular dependency in my Angular application's type definitions?

model.ts

export abstract class Model {
  // ... Model related data members and functions
}

person.ts

import { Model } from './model';
import { Site } from './site';

export class Person extends Model {

  // ... Data

  site: Site; // Partially or Fully saturated site entity
}

site.ts

import { Model } from './model';
import { Person } from './person';

export class Site extends Model {

  // ... Data

  people: Person[]; // array of partially saturated people entities, site is left undefined
}

I would like to maintain a single definition for each model, instead of redefining Site, Person, etc each time there is a dependency like this.

like image 531
JME Avatar asked Jul 25 '19 15:07

JME


2 Answers

This problem is caused by a bad class model design and it does not depend on the language. I hope this helps :)

The circular dependency problems generally include the chicken & egg problem, because when you want to instantiate an object you do not know which you should instantiate first.

 Solution 1

This problem can be easily solved by only referencing objects by their identities, and instead of having a direct dependency on a big object as a part of constructor. For example, modifying Person class:

import { Model } from './model';

export class Person extends Model {

  // ... Data

  siteId: number;
}

 Solution 2

If you need the full object inside Person, I think a good approach would be to change the inheritance tree. I understand that your data model is about a website, its users and its owner. Well, lets break this down: make Person an abstract class and extend it with User and Owner classes.

person.ts

import { Model } from './model';

export abstract class Person extends Model {

  // ... Data
  
  // Does not include the Site attribute!
}

user.ts

Users will be the "readers" of the Site, but don't need a reference to it.

import { Person } from './person';

export class User extends Person {

  // ... Data

}

owner.ts

Owner will be the creator of the Site. This is the class that has the reference to the Site.

import { Person } from './person';

export class Owner extends Person {

  // ... Data

  site: Site;

}

site.ts

Finally, the Site keeps track of its users.

import { Model } from './model';
import { User } from './user';

export class Site extends Model {

  // ... Data

  users: User[];
}

I think this related article could be of help for understanding circular dependencies: How to fix nasty circular dependency issues once and for all in JavaScript & TypeScript

like image 165
adrisons Avatar answered Oct 13 '22 07:10

adrisons


Why don't use internal module pattern which export all the class and used internally by models to load dependencies.

Example Application: https://codesandbox.io/s/circular-deps-fix-using-internal-module-pattern-mtfro

internal.ts

export * from './model'
export * from './person'
export * from './site'

model.ts

export abstract class Model {
  // ... Model related data members and functions
}

person.ts

import { Model, Site  } from './internal';

export class Person extends Model {

  // ... Data

  site: Site; // Partially or Fully saturated site entity
}

site.ts

import { Model, Person } from './internal';

export class Site extends Model {

  // ... Data

  people: Person[]; // array of partially saturated people entities, site is left undefined
}

the above rules only apply to our local dependencies. External module imports are left as is. They are not involved in our circular dependency problems after all.

like image 33
Kathak Dabhi Avatar answered Oct 13 '22 08:10

Kathak Dabhi