Refatoração e Implementação da Geração de Conteúdo de Email

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

  1. 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.
  2. 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"));
    }

Tags: java HTML Templates refactoring Email Generation JUnit

Publicado em 7-9 23:09