I have below use cases:
CreateUser Use Case: This use case will handle the creation of a user.
SendEmail Use Case: This use case will handle sending an email.
InviteUser Use Case: This use case will handle inviting a user.
RegisterUser Use Case: This use case will coordinate the registration process, utilizing the other use cases as needed.
while injecting the use cases in the registerUser use case, However, it’s breaking SRP.
what is the solution for that, shall I call those use cases in the controller?
import { CreateUserUseCase } from './CreateUserUseCase';
import { SendEmailUseCase } from './SendEmailUseCase';
import { InviteUserUseCase } from './InviteUserUseCase';
class RegisterUserUseCase {
constructor(
private createUserUseCase: CreateUserUseCase,
private sendEmailUseCase: SendEmailUseCase,
private inviteUserUseCase: InviteUserUseCase
) {}
async execute(registrationData: any) {
const user = await this.createUserUseCase.execute(registrationData.user);
if (registrationData.sendWelcomeEmail) {
await this.sendWelcomeEmail(user);
}
if (registrationData.invite) {
await this.inviteUser(user, registrationData.invite);
}
return user;
}
private async sendWelcomeEmail(user: any) {
await this.sendEmailUseCase.execute({
to: user.email,
subject: 'Welcome!',
body: 'Thank you for registering!'
});
}
private async inviteUser(user: any, inviteData: any) {
await this.inviteUserUseCase.execute({
userId: user.id,
...inviteData
});
}
}
export { RegisterUserUseCase };
A use-case aggregates a wide range of aspects, some of which you do not want to apply when running under a “parent” use-case.
For example, parent use-case should have a single output-port, however calling child use-cases generates multiple outputs. How are you going to convert those outputs into a single output? Such task cannot be assumed to be straightforward.
Also, as your system evolves you may want “SendEmail” to behave somewhat different when running under “RegisterUser” than when running by itself. So you might end up twitching “SendEmail” to serve the needs of “RegisterUser”.
Reuse is often a good principle, however in cases like yours I would go for softer reuse and have a few application services encapsulating pieces of flows. “RegisterUser” should end up running application services also running inside each of the other three use-cases.
This way, “RegisterUser” will have a single input-port and a single output-port, with well-described flow composed of self-explanatory application services.