Одна из самых больших проблем в программировании - это дублирование кода. Фактически, большая половина того, что пишется о "правильном" программировании, описывает как бороться с дублированием, последствиями дублирования или последствиями борьбы с дублированиям. Я считаю, что в этой борьбе удалось достигнуть значительных успехов: благодаря composer и open source, многие вещи можно не выдумывать заново, а брать уже готовые реализации.

С другой стороны, когда мы говорим про архитектуры программ и приложений, то тут все по другому. Конечно, существуют паттерны программирования, которые говорят какими подходами стоит руководствоваться. Однако паттерны не описывают целиком архитектуру. Конечно в каждой задаче есть свои особенности, но обычно они не настолько существенны, чтобы заставить написать код, который нельзя было бы использовать несколько раз. Причины, почему пишется так много одинакового кода, я вижу в лени, желании побыстрее закончить работу и отсутствии в свободном доступе полных данных о существующих архитектурных решениях. Поэтому я решил описать некоторые сервисы, которые я разрабатывал. Я не могу утверждать, что архитектура, которую я описываю идеальная или хотя бы оптимальнаю Скорее я расказываю об одном из способов решения задачи.

Я опишу структуру системы подписок для посетителей сайта, которую я разработал некоторое время назад. Обычно, система подписок представляет из себя список пользователей, которые оставили свои контакты. Наша же система будет сложнее: она будет содержать несколько листов с пользователями (для простоты изложения будем считать что листа два (Обычно, переход от одного к двум - является самым сложным. Переход от двух к трем и более - уже тривиален.)). Пользователь сможет подписаться на одну, на обе или ни на одну из подписок. Кроме этого человек сможет отписываться от конкретной подписки, и потом подписываться еще раз.

Во время рассылки письма будут отправлять по списку целиком (будет возможен случай когда письма отправляются по части списка). Мы можем формировать одинаковые письма для всех пользователей или формировать письмо на основе наших данных о пользователе.

Нас будет интересовать следующая статистика: сколько писем отправлено, в какое конкретно время, сколько писем получено, сколько писем открыто, на какие ссылки в письме были нажатия, какое было поведение у пользователей, которые перешли на сайт из письма.

Мы не можем быть уверены в тех людях, которые создают письма, поэтому после создания письма попадают на одобрение старшим администраторам. И только после одобрения старшим администратором письма уходят клиентам.

Сама система развивалась итерационно, но для упрощения рассказа я буду все описывать с точки зрения финальной версии. Для честности изложения приведу этапы системы:

Этап Описание Причины этого этапа
1 Система позволяла создавать и редактировать письма. После создания письма, администраторов нажимается кнопка отправить, и письмо рассылается на всех клиентов из выбранного списка. Новое письмо можно или сделать заново, или склонировать одно из предыдущих. Это результат работы в сжатые сроки в ответ на неудобный механизм создания писем, который существовал до создания системы.
2 Добавлена очень рассылки для размазывания нагрузки. После создания письма администраторов нажимается кнопка отправить, и письмо попадает в очередь в БД. По мере очереди письма отправляются. Это ответ на большую нагрузку и подписание сервера при отправке письма.
3 Появляется механизм одобрения писем. Это ответ на ошибки в письмах, которые уходили клиентам.
4 Появляется возможность добавления любого количества полей к письму и отдельным частям Это ответ на появление новых требований к системе.
5 Появляется механизм отложенной отправки писем по времени. Это ответ на требование администраторов на то, что для разных регионов нужно отправлять письма в разное время
6 Появляется разделение прав и меняется система выбора группы подписчиков. Появляется роль "Старший администратор", который создает письмо, назначает по письму лист рассылки, по которому письмо будет рассылаться, и выбирает,кто это письмо будет проверять. Появляется роль "Обычный администратор", который может клонировать письмо с сохранением всех параметров, поменять время отправки и содержание письма. Это ответ на ошибки администраторов в выборе списка подписчиков.
7 Появляется механизм проверки ссылок писем. Это ответ на ошибки в письмах, которые уходили клиентам и упрощение работы администраторов.
8 Появляется возможность создавать персонализированные письма. Это ответ на запросы администраторов об возможностях для повышения конверсии
9 Появляется возможность создавать А\В тестирование. Это ответ на запросы администраторов о том, что проводить исследование аудитории стало более удобно.

Приложение работало в следующем окружении: приложение стоит на сервере где работают другие приложения, язык php, framework yii, база данных mysql, в качестве рассыльщика используется postfix.

Вот упрощенная схема базы данных приложения: Схема базы приложения.

Объект Subscriber - это данные о физическом человек, которые оставили нам на сайте. Он может содержать все те поля, которые требуются в соответствии с бизнес логикой.

Объект SubList - это сам лист, на который подписывается человек. Количество таких листов в системе не ограничено.

Объект Subscription - это один из самых интересных объектов в системе. С одной стороны, это просто связь между подписчиком и листом, с другой стороны именно в этом месте находится статус подписки человека. Но эта таблица связка несет в себе дополнительное поле status, которое являются одной из самых важных частей системы. Благодаря наличию статуса мы можем не удалять человека, когда он отписывается, а просто помечать его удаленным. Мы можем различать типы пользователей (например: добавлен в систему администратором, добавлен через форму на веб сайте, подтвердил свой email и другие).

Объект Logs - это таблица с логами по рассылкам. В ней содержатся все данные, о поведении каждого получателя письма.

Объект ContentBlock - это таблица с частями письма. Письмо разбивалось на части, при этом каждая часть считалось не связанной с остальными, но ее внешний вид мог меняться в зависимости от соседей. Разбиение письма на части, позволило создать шаблоны из частей для писем: когда человек создает письмо, он может "накликать" для себя макет письма, и в добавленных частях уже отредактировать отдельные значения.

Объект Queue - это очередь всех сообщений. Порядок в очереди осуществляется за счет даты начала отправки и поля приоритета. Благодаря дополнению системы приоритета добавилсь возможность пустить особенные письма в первую очередь. Можно было убрать это свойство и ограничиться только временем, но при этом система стала бы менее прозрачно себя вести.

Объект Delivery - это само письмо. В нем содержатся общие значения для всей рассылки, например, тема письма. Но в самом письме не хранятся данные о том, по каким подписчикам это письмо должно отправиться. Эти данные хранятся в таблице Queue. При этом добавлена возможность клонирования письма. При этом будут клонированы все данные из таблиц Queue и ContentBlock. Это позволяет создав один формат письма быстро подготавливать новую рассылку.

И последний объект AdditionFields - это дополнительные поля. Система может постоянно меняться и развиваться, заложить в нее изначально все варианты практически невозможно. С другой стороны частые изменения базы данных будут приводить к простоям, что недопустимо. Для решения этой задачи была создана таблица с дополнительными полями, которая цепляется к любой другой таблице и позволяет динамически расширять список полей у объекта. За счет перегруженных гетеров и сетеров, дополнительны поля объект будет считать родными.

В этой статье я описал, основные части системы: в следующей части я расскажу о том, как все работает и что можно улучшить.