Asynchroni maily v nette
2015-06-24 00:00:00 +0000Na projektu tescorecepty.cz neustále řeším všelijakou mailovou komunikaci. Ať to jsou potvrzení registrace, newslettery, zapomenutá hesla nebo notikace administrátorům.
Původně se maily posílaly okamžitě v modelu nebo presenteru. Což sebou samozřejmě neslo řadu problému. Namátkou zdržování requestu v případě kdy se posílalo více mailů najednou. Selhání celé stránky při neošetřené chybě v mailu a podobně.
Proto jsem začal hledat jiné řešení.
#Fronta nás zachrání
Hlavní část řešení přinesl článek Filipa Procházky o RabbitMQ a jeho rozšíření do Nette Kdyby/RabbitMq.
Dobře mám nástroj který zajistí posílání mailů na pozadí.
Jak ale elegantně dostat do RabbitMQ consumeru samotný mail.
Napsal jsem několik variant, několikrát se spálil a nakonec jsem skončil u velmi jednoduchého řešení.
Jeden producer a consumer pro všechny maily. Do produceru předávám serializovaný objekt s jednoduchým interfacem.
interface IMail {
  function sendMail(IMailer $mailer);
}Consumer je pak opět velmi jednoduchý.
class EmailConsumer extends Object
{
    /** @var IMailer */
    protected $mailer;
    /** @var Logger */
    protected $logger;
    function __construct(IMailer $mailer, Logger $logger)
    {
        $this->mailer = $mailer;
        $this->logger = $logger;
    }
    public function send(AMQPMessage $message)
    {
        $task = unserialize($message->body);
        if ($task instanceof IMail) {
            $task->sendMail($this->mailer);
        } else {
            $this->logger
            ->addError('Invalid email task ' . Debugger::dump($task, true));
        }
    }
}Pro jednotlivé maily pak vytvořím třídy které implementují IMail.
V jejich implementaci se pak nemusím moc omezovat.
V budoucnosti možná budu potřebovat předávat závislosti do deserializovaných objektů.
Druhým parametrem Imail::sendMail pak může být Nette container. Třeba mě napadne lepší řešení. Zatím jsem spokojený s tímto.