Agregados lista de correo

Punto a) Posibilidades de moderación

Además de tener como opciones de envío:
a. Abierto
b. Sólo los miembros pueden enviar un mensaje

Se quiere agregar estas posibilidades:
c. Los mensajes de no-miembros son moderados.
d. Los miembros nuevos son moderados. Se considera que un usuario es nuevo cuando tiene menos de un mes de suscripto a la lista. Ejemplo: Laura Iturbe se suscribe a la lista el 01/05/2011, hasta el 01/06/2011 será un usuario nuevo y el 02/06 ya no.
e. Moderación de usuarios específicos: se cargará una lista con los usuarios a moderar.

Al recibir un mensaje de una dirección moderada el sistema manda el mail primero a los moderadores, con un encabezado especial. El mensaje queda en espera hasta que un moderador aprueba y entonces este mensaje se envía. Si en 5 días nadie aprueba el mensaje, éste debe ser eliminado.

Pensarlo
1. como opciones excluyentes (a, b, c, d ó e exclusivamente)
2. como opciones combinadas (podríamos tener alguna de las opciones o varias superpuestas)

Caso de uso que afecta

  • Enviar un correo

Código

El método original es

>>ListaCorreo
public void enviarCorreo(Mensaje mensaje) {
    tipoEnvioMails.validarEnvio(this, mensaje);
    try {
        NotificadorPorMail.getInstance().sendMail(MAILER_DAEMON, this.getMailsUsuariosActivos(), mensaje.getTitulo(), mensaje.getTexto());
    } catch (SystemException e) {
        // No hacemos nada según pide el negocio
    }
}

Primero vamos a pensar en que las opciones de moderación o envío son excluyentes.

Introducimos la moderación y nos hacemos algunas preguntas:

  • ¿tengo que mandar mail? Sí, podría distinguir si el mail es de moderación o un mail común a la lista. Pero en definitiva se parecen mucho entre sí y me lleva a tener código duplicado. Si en cambio nos concentramos en cambiar solamente el encabezado (ponerle un prefijo "MODERATE") como la lista de destinatarios (que en lugar de los usuarios de la lista sean los moderadores), tenemos entonces lo que queremos tener: polimorfismo y no un if que pregunte si el tipo de envío es de los que moderan o no (porque ahí la responsabilidad queda en la lista de correo, lo que queremos hacer es delegar esa pregunta)
    • corolario: hay strategies que moderan y strategies que no moderan (¿qué hacen? definen si se puede enviar el mail a la lista, a quiénes mandar el mail y el encabezado del mail)
  • si un mensaje se modera, entonces tenemos que crear otro caso de uso para aprobar o rechazar un envío moderado, y otro para borrar los mensajes que tienen más de 5 días. Eso por el momento lo dejamos aparte.
  • para poder saber qué mensajes pendientes hay, necesitamos guardar los mensajes moderados. ¿Quién conoce a esos mensajes? Posiblemente la Lista de Correo (igual que los usuarios pendientes).

Repasemos qué pasa cuando envío un mail que debe ser moderado:

  1. El usuario pedrito envía un mail a la lista
  2. La lista delega en el strategy, que se da cuenta que debe moderar el mensaje
  3. Tenemos que agregar una copia del mensaje a moderar y meterlo en la colección de mensajes pendientes de la lista de correo
  4. La lista le pide al strategy que defina los destinatarios y el encabezado
  5. La lista manda el mail a los moderadores
  6. En otro momento, un moderador busca los mensajes pendientes de la lista
  7. El moderador lo aprueba, en ese caso se invoca al caso de uso enviar correo. Nuestra tarea es evitar que se vuelva a moderar, así que debería haber otro método que no haga la validación, sino que simplemente envíe el mensaje a la lista pero que además elimine el mensaje pendiente de la lista.

Una jerarquía posible es:

  • Lista de Correo
  • Mensaje en estado pendiente, con encabezado, usuario original y asunto (titulo), también necesitamos la fecha en que se envió, para eliminar los mensajes pendientes (esto es un caso de uso nuevo, lo anotamos aparte)
  • Tipo de envío de mails sigue siendo el strategy pero ahora se divide en:
    • TipoEnvioModerado, que podría tener métodos default getDestinatarios(), getEncabezado() y tres subclases:
      • ModerarNoMiembros
      • ModerarMiembrosNuevos (sabemos que es un mensaje que enviamos a un objeto Usuario)
      • ModerarUsuariosEspecíficos (conoce la colección de usuarios a moderar)
    • heredando de Tipo de envío de mails, hermana de TipoEnvioModerado, tenés a una superclase TipoEnvioValidado, EnvioAbierto y EnvioSoloMiembros
Punto%20a%29%20Lista%20Correo.PNG

La otra pregunta es: ¿cómo agrego el mensaje en la lista de moderados?
Una simple es agregar una línea más en ListaCorreo:

>>ListaCorreo
public void enviarCorreo(Mensaje mensaje) {
    this.tipoEnvioMails.validarEnvio(this, mensaje);
    try {
        tipoEnvioMails.guardarMensaje(this, mensaje);
        NotificadorPorMail.getInstance().sendMail(MAILER_DAEMON, this.tipoEnvioMails.getDestinatarios(this), this.tipoEnvioMails.getEncabezado(mensaje), mensaje.getTexto());
    } catch (SystemException e) {
        // No hacemos nada según pide el negocio
    }
}

En TipoEnvioValidado se pueden implementar los métodos:

public List<String> getDestinatarios(Lista lista) {
    return lista.getMailsUsuariosActivos();
}

public String getEncabezado(Mensaje mensaje) {
    return mensaje.getTitulo();
}

Hacemos el ejemplo con el strategy que modera miembros nuevos:

>>ModerarMiembrosNuevos
public void validarEnvio(ListaCorreo lista, Mensaje mensaje) {
    // Se permite enviar cualquier mensaje, a lo sumo se modera
}

public void guardarMensaje(ListaCorreo lista, Mensaje mensaje) {
    if (mensaje.getUsuario().esNuevo()) {
        lista.agregarMensajeModerado(mensaje);
    }
}

public List<String> getDestinatarios(Lista lista) {
    if (mensaje.getUsuario().esNuevo()) {
        return lista.getModeradores();
    } else {
        return lista.getMiembros();  // o mejor, delegar con super hacia la superclase
    }
}

public String getEncabezado(Mensaje mensaje) {
    String encabezado = mensaje.getTitulo();
    if (mensaje.getUsuario().esNuevo()) {
        encabezado = "MODERATE: " + encabezado; 
    } 
    return encabezado;
}

Fíjense que la pregunta (mensaje.getUsuario().esNuevo()) está muchas veces (3!!)
Lo ideal es crear un test que pruebe que funcione.
Pero también podemos hacer un Extract Method y generar un nuevo método

// pull-up ?
public void validarEnvio(ListaCorreo lista, Mensaje mensaje) {
    // Se permite enviar cualquier mensaje, a lo sumo se modera
}

// pull-up ?
public void guardarMensaje(ListaCorreo lista, Mensaje mensaje) {
    if (this.hayQueModerar(mensaje)) {
        lista.agregarMensajeModerado(mensaje);
    }
}

public boolean hayQueModerar(Mensaje mensaje) {
    return mensaje.getUsuario().esNuevo();
}

// pull-up ?
public List<String> getDestinatarios(Lista lista) {
    if (this.hayQueModerar(mensaje)) {
        return lista.getModeradores();
    } else {
        return super.getDestinatarios(lista);
    }
}

// pull-up ?
public String getEncabezado(Mensaje mensaje) {
    String encabezado = mensaje.getTitulo();
    if (this.hayQueModerar(mensaje)) {
        encabezado = "MODERATE: " + encabezado; 
    } 
    return encabezado;
}

Al subir (pull-up) los métodos getEncabezado y getDestinatarios a TipoEnvioModerado ganamos mucho: no repetimos código. Lo mismo con el método validar que por default no va a hacer nada y con el guardarMensaje, que termina haciendo las veces de template method. Para eso defino un método public abstract boolean hayQueModerar(Mensaje mensaje) que se redefine en cada subclase de los tipos de envío moderado.

Nuevos casos de uso a generar

  • Actualizar mensajes pendientes (sólo para moderadores, permite aprobar o rechazar)
  • Eliminar mensajes pendientes (se corre en forma automática, no lo inicia nadie)

¿Y si los tengo que combinar?

Mmm… no puedo hacer que una lista tenga una colección de strategies porque si tuviera 3 tipos de envío cada mensaje aparecería tres veces en la lista (porque cada strategy manda un mail).
Entonces vamos a dejar para otro momento esta idea.

b) Mejorar el manejo de envíos fallidos:

a. Tomar todos los mensajes fallidos y reintentarlo media hora más tarde.
b. Si falla una vez más inhabilitar la dirección de mail y no volver a intentarlo.
c. Las direcciones se pueden rehabilitar manualmente o en cualquier momento que se reciba cualquier tipo de mensaje desde esa dirección de mail.

Retomemos cómo era originalmente el método original:

>>ListaCorreo
public void enviarCorreo(Mensaje mensaje) {
    tipoEnvioMails.validarEnvio(this, mensaje);
    try {
        NotificadorPorMail.getInstance().sendMail(MAILER_DAEMON, lista.getMailsUsuariosActivos(), mensaje.getTitulo(), mensaje.getTexto());
    } catch (SystemException e) {
        // No hacemos nada según pide el negocio
    }
}

Claro, "no hacemos nada según pide el negocio" ahora cambia, la resolución no es difícil pero está bueno para ver cómo hay que agregar comportamiento en el catch. Tenemos que definir qué hacemos con el mensaje ahora que falló:
  • tenemos que tratar de mandarlo media hora después. Tip: no puedo mandar a esperar 30 minutos en el catch, el hecho de reintentar implica generar un caso de uso nuevo "Reintentar mensajes/Reenviar mensajes fallidos", que se dispara automáticamente con un cron / servicio
  • el mensaje fallido puede ir a la lista de mensajes que conoce la Lista de Correo, junto con los pendientes. O puede ir en una colección aparte. Esa decisión no es importante en este momento. Pero sí tenemos que registrar el estado en fallido.
  • Por otra parte, al segundo intento fallido hay que inhabilitar la dirección de mail. Eso impacta en el envío de mails:
    • si el emisor de un mensaje está inhabilitado, hay que habilitarlo y
    • filtrar de la lista de usuarios a enviar los que no tengan el mail inhabilitado (dentro del método getMailsUsuariosActivos)

Modificamos el método:

>>ListaCorreo
public void enviarCorreo(Mensaje mensaje) {
    Usuario emisor = mensaje.getUsuario();
    if (emisor.estaInhabilitado()) {
        emisor.habilitar();
    }
    tipoEnvioMails.validarEnvio(this, mensaje);
    try {
        NotificadorPorMail.getInstance().sendMail(MAILER_DAEMON, this.getMailsUsuariosActivos(), mensaje.getTitulo(), mensaje.getTexto());
    } catch (SystemException e) {
        this.agregarMensajeFallido(mensaje);
    }
}

Se podría delegar la pregunta y la habilitación del emisor directamente al usuario, de esta manera:
>>ListaCorreo
public void enviarCorreo(Mensaje mensaje) {
    mensaje.getUsuario().envioMensaje();
    tipoEnvioMails.validarEnvio(this, mensaje);
    try {
        NotificadorPorMail.getInstance().sendMail(MAILER_DAEMON, this.getMailsUsuariosActivos(), mensaje.getTitulo(), mensaje.getTexto());
    } catch (SystemException e) {
        this.agregarMensajeFallido(mensaje);
    }
}

public void envioMensaje() {
    if (this.estaInhabilitado()) {
        this.habilitar();
    }
}

Nuevos casos de uso a generar

  • Reintentar mensajes fallidos
  • Rehabilitar usuario
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License