I'm facing some difficulty with designing a factory and/or strategy pattern for building out EmailTemplates. IMO this seems like the design pattern to go with, but I feel like the path I'm going down isn't very extensible and will be prone to spaghetti-like code in the future. The problem is this: an EmailService takes in a (hydrated) EmailTemplate to send an email. The EmailTemplate has a bunch of key/value pairs that are derived from different objects/data depending on the strategy (receipt, refund, void, etc).
Here is what I'm currently working with:
public class EmailTemplateFactory {
private static Map<TemplateType, BiFunction<EmailTemplate, EmailTemplateContext, EmailTemplate>> templates = new HashMap<>();
static {
templates.put(TemplateType.RECEIPT, EmailTemplateFactory::buildReceipt);
templates.put(TemplateType.REFUND, EmailTemplateFactory::buildRefund);
templates.put(TemplateType.VOID, EmailTemplateFactory::buildVoid);
}
public static EmailTemplate getTemplate(final EmailTemplateContext emailTemplateContext) {
if (emailTemplateContext == null) {
throw new IllegalArgumentException("Must provide context to build email template!");
}
return templates.get(emailTemplateContext.getTemplateType())
.apply(getBaseTemplate(TemplateFormat.HANDLEBARS, emailTemplateContext), emailTemplateContext);
}
private static EmailTemplate buildReceipt(final EmailTemplate baseTemplate, final EmailTemplateContext emailTemplateContext) {
//build the receipt template
return new EmailTemplate();
}
private static EmailTemplate buildVoid(final EmailTemplate baseTemplate, final EmailTemplateContext emailTemplateContext) {
// build the void template
return new EmailTemplate();
}
private static EmailTemplate buildRefund(final EmailTemplate baseTemplate, final EmailTemplateContext emailTemplateContext) {
// build the refund template
return new EmailTemplate();
}
private static EmailTemplate getBaseTemplate(final TemplateFormat templateFormat, final EmailTemplateContext emailTemplateContext) {
// setting up common data between templates
}
public enum TemplateType {
RECEIPT, REFUND, VOID;
}
}
I created a class called EmailTemplateContext which contains the multiple different pieces of data that these different strategies use to build out the template. However, this is what feels troublesome, because its lots of somewhat unrelated fields where only 1 or 2 (of the 5 or 6) are needed to build any given template. Is a context object being passed into the factory the right approach here?
Most of the code inside of the buildReceipt
, buildRefund
, and buildVoid
methods is simply emailTemplate.addVariable(key, value)
, and a fair amount of them are shared among strategies (why I created the getBaseTemplate
method.
Any help would be appreciated to point me in the right direction. Thanks!