I am trying to provide a different service based on a value from my ConfigService
.
The problem I am running into is that the mongoose model that gets injected does not return any values when executing query methods such as findOne()
(result is null
) or countDocuments()
(result is 0
).
My service classes are defined as follows:
export class BaseService {
constructor(@InjectModel('Cat') public readonly catModel: Model<Cat>) {}
createService(option: string) {
if (option === 'OTHER') {
return new OtherService(this.catModel);
} else if (option === 'ANOTHER') {
return new AnotherService(this.catModel);
} else {
return new BaseService(this.catModel);
}
}
async findOne(id: string): Promise<Cat> {
return await this.catModel.findOne({_id: id});
}
async count(): Promise<number> {
return await this.catModel.countDocuments();
}
testClass() {
console.log('BASE SERVICE CLASS USED');
}
}
@Injectable()
export class OtherService extends BaseService {
constructor(@InjectModel('Cat') public readonly catModel: Model<Cat>) {
super(catModel);
}
testClass() {
console.log('OTHER SERVICE CLASS USED');
}
}
@Injectable()
export class AnotherService extends BaseService {
constructor(@InjectModel('Cat') public readonly catModel: Model<Cat>) {
super(catModel);
}
testClass() {
console.log('ANOTHER SERVICE CLASS USED');
}
}
This allows me to get the correct service from my provider (testClass()
prints the expected string). My provider looks like this:
export const catProviders = [
{
provide: 'CatModelToken',
useFactory: (connection: Connection) => connection.model('CAT', CatSchema),
inject: ['DbConnectionToken'],
},
{
provide: 'BaseService',
useFactory: (ConfigService: ConfigService, connection: Connection) => {
const options = ConfigService.get('SERVICE_TYPE');
let model = connection.model('CAT', CatSchema);
return new BaseService(model).createService(options);
},
inject: [ConfigService, 'CatModelToken', 'DbConnectionToken'],
}
];
So my question is in two parts:
BaseService
instance for the sole
purpose of calling createService()
?I also cannot use the useClass
example from the documentation, since I need to be able to inject the ConfigService
.
Dependency injection is an inversion of control (IoC) technique wherein you delegate instantiation of dependencies to the IoC container (in our case, the NestJS runtime system), instead of doing it in your own code imperatively.
NestJS Modules are Singletons by default. Their Providers are also singletons when they are provided from within the same module.
With Nest. js' injector system, you can manage your objects without thinking about the instantiation of them, because that is already managed by the injector, which is there to resolve the dependencies of every dependent object.
Service. In enterprise applications, we follow the SOLID principle, where S stands for Single Responsibility . The controllers are responsible for accepting HTTP requests from the client and providing a response. For providing the response, you may need to connect to some external source for data.
You can solve this by using a factory approach, try this:
Interface to determine the "shape" of your services:
export interface IDatabaseService {
findOne(id: string): Promise<Cat>;
count(): Promise<number>;
testClass(): void;
}
The BaseService must implement that interface:
export class BaseService implements IDatabaseService {
constructor(@InjectModel('Cat') public readonly catModel: Model<Cat>) {}
async findOne(id: string): Promise<Cat> {
return await this.catModel.findOne({_id: id});
}
async count(): Promise<number> {
return await this.catModel.countDocuments();
}
testClass() {
console.log('BASE SERVICE CLASS USED');
}
}
The dynamic services are not injected so they do not use the @Injectable()
decorator:
export class OtherService extends BaseService {
constructor(@InjectModel('Cat') public readonly catModel: Model<Cat>) {
super(catModel);
}
testClass() {
console.log('OTHER SERVICE CLASS USED');
}
}
export class AnotherService extends BaseService {
constructor(@InjectModel('Cat') public readonly catModel: Model<Cat>) {
super(catModel);
}
testClass() {
console.log('ANOTHER SERVICE CLASS USED');
}
}
The factory class is the thing that gets injected:
@Injectable()
export class DatabaseServiceFactory {
constructor(@InjectModel('Cat') private readonly catModel: Model<Cat>) {}
createService(name: string) : IDatabaseService {
switch(name) {
case 'other': return new OtherService(this.catModel);
case 'another': return new AnotherService(this.catModel);
default: throw new Error(`No service has been implemented for the name "${name}"`);
}
}
}
export const catProviders = [
{
provide: 'CatModelToken',
useFactory: (connection: Connection) => connection.model('CAT', CatSchema),
inject: ['DbConnectionToken'],
},
{
provide: 'BaseService',
useFactory: (ConfigService: ConfigService, connection: Connection, dbFactory: DatabaseServiceFactory) => {
const options = ConfigService.get('SERVICE_TYPE');
let model = connection.model('CAT', CatSchema);
//return new BaseService(model).createService(options);
return dbFactory.createService(options);
},
inject: [
ConfigService,
'CatModelToken',
'DbConnectionToken',
DatabaseServiceFactory
],
}
];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With