I am currently exploring Nestjs and experimenting with implementing a clean-architecture structure. I would appreciate validation of my approach as I am unsure if it is the most effective way to do so. Please note that the example provided is somewhat pseudo-code, and many types are left out or generalized as they are not the main focus of this discussion.
Starting from my domain logic, one way to implement it could be in a class like the following:
@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)
}
}
Accessing the profileRepository
, however, goes against the principles of clean architecture. To address this, an interface is created for it:
interface IProfilesRepository {
getOne (profileId: string): object
updateOne (profileId: string, profile: object): bool
}
The next step is to inject this dependency into the constructor of the ProfileDomainEntity
and ensure it follows the expected interface:
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)
}
}
Subsequently, a simple in-memory implementation is created to test the code:
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)
}
}
Now, everything needs to be wired together using a module:
@Module({
providers: [
ProfileDomainEntity,
ProfilesRepository
]
})
export class ProfilesModule {}
An issue arises where ProfileRepository
implements IProfilesRepository
but there is no direct match between them. This leads to Nest not being able to resolve the dependency properly.
The workaround found is to use a custom provider to manually set the token:
@Module({
providers: [
ProfileDomainEntity,
{
provide: 'IProfilesRepository',
useClass: ProfilesRepository
}
]
})
export class ProfilesModule {}
This requires modifying the ProfileDomainEntity
by specifying the token to use with @Inject
:
export class ProfileDomainEntity {
constructor(
@Inject('IProfilesRepository') private readonly profilesRepository: IProfilesRepository
){}
}
Is this a reasonable way to handle dependencies, or am I missing the mark entirely? Are there better solutions available? Being relatively new to NestJs, clean architecture/DDD, and Typescript, I may have misunderstood some concepts here.
Thank you