It seems like your approach to object-oriented programming may need some clarity. If I understand correctly, you are attempting to choose a class implementation to instantiate based on the program's environment.
Instead of using inheritance, I suggest favoring composition. Take a look at the code snippet below:
import { makeSendMailUseCase } from "./make-email-service";
async function main() {
const sendMail = await makeSendMailUseCase();
const result = await sendMail.execute("<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7b161e3b1314080f55181416">[email protected]</a>", "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="03776c436b6c70772d606c6e">[email protected]</a>", "testing my email", "Hello ");
if (result) console.log(' email was sent ')
else throw new Error('failed sending mail')
}
main().catch(console.log.bind(console))
In this code snippet, the application aims to send an email by instantiating a SendMailUseCase
class to manage this task. To send the email, it requires an external service. This service is injected into the use case, making use of the factory pattern to determine which service will be used for consumption. As a result, the main program remains unaware of the specific service being utilized.
import { createTestAccount } from "nodemailer";
import { SendEmailUseCase } from "./sendmail-usecase";
import { ProductionEmailService } from "./real-email-service";
import { FakeEmailService } from "./fake-email-service";
import { IEmailService } from "./email-service";
export async function makeSendMailUseCase() {
const isProduction = process.env.NODE_ENV === 'production';
let emailService: IEmailService;
if (isProduction) {
const testAccount = await createTestAccount();
emailService = new ProductionEmailService(testAccount)
} else {
emailService = new FakeEmailService();
}
return new SendEmailUseCase(emailService);
}
The makeSendMailUseCase
function serves as a factory method to provide a new instance of the SendMailUseCase, selecting the appropriate implementation of the IEmailService
based on the current environment.
import { EmailFormat, IEmailService } from "./email-service";
export class SendEmailUseCase {
constructor(private emailProvider: IEmailService) { }
execute(from: string, to: string, subject: string, content: string): Promise<boolean> {
const params = {
from, to, subject, content, format: EmailFormat.Text
}
return this.emailProvider.sendEmail(params);
}
}
The SendEmailUseCase
delegates the email-sending task to the concrete implementation provided in its constructor.
export enum EmailFormat {
Text,
HTML
}
export type EmailOptions = {
from: string;
to: string;
subject: string;
content: string;
format: EmailFormat
}
export interface IEmailService {
sendEmail(options: EmailOptions): Promise<boolean>;
}
Next up is the implementation of the FakeEmailService
:
import { EmailOptions, IEmailService } from "./email-service";
export class FakeEmailService implements IEmailService {
async sendEmail(options: EmailOptions): Promise<boolean> {
console.log(options)
return Promise.resolve(true);
}
}
This implementation meets the contract requirements by simply fulfilling them without any real action.
import { TestAccount, Transporter, createTransport } from "nodemailer";
import { EmailFormat, EmailOptions, IEmailService } from "./email-service";
export class ProductionEmailService implements IEmailService {
private readonly transporter: Transporter
constructor(account: TestAccount) {
this.transporter = createTransport({
host: account.smtp.host,
port: account.smtp.port,
secure: account.smtp.secure,
auth: {
user: account.user, // generated ethereal user
pass: account.pass, // generated ethereal password
},
});
}
async sendEmail(options: EmailOptions): Promise<boolean> {
const info = await this.transporter.sendMail({
from: options.from,
to: options.to,
subject: options.subject,
text: options.format === EmailFormat.Text ? options.content : "",
html: options.format === EmailFormat.HTML ? options.content : "",
});
return info;
}
}
The production email service relies on the nodemailer
library to handle email delivery.
That wraps it up!