Cenário
Em vários projetos recentes, a funcionalidade de envio de email foi desenvolvida de forma independente, sem uma abordagem unificada. Por exemplo, uma classe EmailSendService continha múltiplos campos de String, como content1 a content7, todos com conteúdo HTML. A geração do email era feita por concatenação direta de strings:
context1 + userName + content2 + inviteCode + content3 + money + content4 + year + content5 + month + content6 + day + content7
O código resultava em um arquivo com menos de 200 linhas, mas o HTML ocupava mais de 100 delas, tornando-o difícil de manter e modificar.
Problemas e Melhorias
- O código HTML estava misturado com o código Java, dividido em partes, o que dificultava alterações no conteúdo e estilo do email.
- Qualquer mudança no template de email exigia reescrever o código-fonte, o que era impraticável em projetos reais.
Refatoração
Objetivos
- Armazenar templates HTML em arquivos de recursos externos, referenciando-os por caminhos no código Java.
- Suportar substituição de parâmeetros nos templates, com parâmetros nomeados.
Implementação
Após a refatoração, foram criados os seguintes componentes:
Classe de Template de Texto
Esta classe suporta substituição de parâmetros em lote, usando o formato ${nomeParametro}.
public class TemplateProcessor {
private String content;
public TemplateProcessor() {
setContent("");
}
public TemplateProcessor(String content) {
setContent(content);
}
public void setContent(String content) {
this.content = content;
}
public String processTemplate(LinkedHashMap<String, String> parameters) {
String[] keys = new String[parameters.size()];
String[] values = new String[parameters.size()];
int index = 0;
for (Map.Entry<String, String> entry : parameters.entrySet()) {
keys[index] = "${" + entry.getKey() + "}";
values[index] = entry.getValue();
index++;
}
return StringUtils.replaceEachRepeatedly(content, keys, values);
}
}
Utiliza-se a classe StringUtils do pacote org.apache.commons.lang para realizar as substituições de forma eficiente.
Utilitário para Carregar Recursos
Fornece métodos para ler arquivos de recursos a partir de caminhos relativos, retornando o conteúdo como texto.
public class ResourceLoader {
public static String loadResource(String path) {
return loadResource(path, "UTF-8");
}
public static String loadResource(String path, String encoding) {
ClassPathResource resource = new ClassPathResource(path);
StringWriter writer = new StringWriter();
try {
IOUtils.copy(resource.getInputStream(), writer, encoding);
} catch (IOException e) {
throw new RuntimeException("Erro ao carregar recurso", e);
}
return writer.toString();
}
}
Construtor de Conteúdo de Email
public class EmailContentBuilder {
private TemplateProcessor processor;
public EmailContentBuilder(String resourcePath) {
processor = new TemplateProcessor(ResourceLoader.loadResource(resourcePath));
}
public String buildContent(LinkedHashMap<String, String> data) {
return processor.processTemplate(data);
}
}
Refatorando a Classe Superior
No EmailSendService, todas as strings HTML foram removidas e substituídas por chamadas ao construtor. Os templates foram extraídos para arquivos HTML em um diretório de recursos, como templates/emailBoasVindas.html.
public String generateEmailContent(String userName, String inviteCode, String amount, int year, int month, int day) {
LinkedHashMap<String, String> data = new LinkedHashMap<>();
data.put("nomeUsuario", userName);
data.put("valor", amount);
data.put("ano", String.valueOf(year));
data.put("mes", String.valueOf(month));
data.put("dia", String.valueOf(day));
return emailContentBuilder.buildContent(data);
}
Testes Unitários
Testes para TemplateProcessor
public class TemplateProcessorTests {
@Test
public void testProcessTemplateSemParametros() {
TemplateProcessor processor = new TemplateProcessor("Olá Mundo");
assertEquals("Olá Mundo", processor.processTemplate(new LinkedHashMap<String, String>()));
}
@Test
public void testProcessTemplateComUmParametro() {
TemplateProcessor processor = new TemplateProcessor("Olá ${nome}");
LinkedHashMap<String, String> params = new LinkedHashMap<>();
params.put("nome", "João");
assertEquals("Olá João", processor.processTemplate(params));
}
@Test
public void testProcessTemplateComMultiplosParametros() {
TemplateProcessor processor = new TemplateProcessor("=== ${saudacao} ${nome} ===");
LinkedHashMap<String, String> params = new LinkedHashMap<>();
params.put("saudacao", "Bem-vindo");
params.put("nome", "Maria");
assertEquals("=== Bem-vindo Maria ===", processor.processTemplate(params));
}
}
Testes para o Método de Geração de Conteúdo
@Test
public void testGenerateEmailContent() {
String emailContent = emailSendService.generateEmailContent("Maria", "123.45", 2023, 10, 15);
assertTrue(emailContent.contains("Caro Maria"));
assertTrue(emailContent.contains("recarregou 123.45 unidades."));
assertTrue(emailContent.contains("15 de outubro de 2023"));
}