Нотифікації і лог активності — це дві різні речі, хоча їх часто плутають. Нотифікація — це повідомлення комусь (“редактору”, “автору”, “адміну”, “клієнту”) про подію. Лог активності — це аудиторський слід: факт, що подія відбулася, хто її зробив, що саме змінилося, і коли. Нотифікації можуть не дійти (email вимкнули, телеграм упав) — але лог активності має залишитися завжди, бо це джерело правди для контролю і розбору інцидентів.
Почнімо з “коли писати” в activity log. Пишуть його на події, які змінюють стан системи або мають значення для відповідальності. У CMS це: створення матеріалу, зміна статусу (draft → review → published → archived), зміна ключових полів (заголовок, URL/slug, дата публікації), модерація (approve/reject з причиною), прикріплення/заміна медіа, зміна автора або розділу, видалення/відновлення, ручні правки, які впливають на показ/SEO. В облікових модулях це: posting/unposting, зміна сум, проведення платежу, рознесення, зміна контрагента, закриття періоду. Правило просте: якщо потім може виникнути питання “хто це зробив?” — це має бути в activity.
Далі — “що саме писати”. Мінімальний склад запису в activity-таблиці має бути стандартизований. Ви фіксуєте: actor (хто зробив), action (що зробив), entity (над чим), timestamp (коли), і контекст (звідки/за яких умов). На практиці це виглядає як: actor_user_id, action (наприклад, news.created, news.status_changed, media.attached, invoice.posted), entity_type (назва сутності), entity_id, created_at. Далі дуже бажані поля: ip, user_agent, request_id/correlation_id, meta (але у вас, судячи з принципів, небажано JSON — тоді робіть нормалізовані колонки або окрему таблицю деталей для “що змінилось”).
Критичний момент для навчання — відрізняти “лог події” від “лог дебагу”. Activity log — це про бізнес-події і аудит. Він не має перетворюватися на смітник типу “відкрив сторінку” або “натиснув кнопку”. Якщо ви почнете логувати дрібниці, ви втратите сигнал у шумі. Тому вчимо правило: логуються state-changing події та критичні доступи/операції, а не кожен рух.
Тепер — де це має жити в коді. Найгірший варіант — писати activity log прямо в Blade або в контролері в десяти місцях. Ви отримаєте дублювання, пропуски і різні формати записів. Правильне місце — сервісний шар, де відбувається зміна стану. Тобто: контролер прийняв запит → FormRequest провалідив → service виконав дію в транзакції → в кінці цієї ж транзакції зафіксував activity. Так ви гарантуєте, що якщо транзакція відкотилась, activity не запишеться “про те, чого не було”.
Є нюанс: інколи activity треба писати синхронно (обов’язково), а нотифікації — асинхронно (черга). Це здоровий стандарт. Лог активності — частина факту зміни, нотифікація — побічний ефект. Тому: activity записали одразу; нотифікацію dispatch у job. Якщо нотифікація впаде — у вас є факт в activity і ви можете повторити відправку.
Коли нотифікації доречні. Нотифікації потрібні тоді, коли хтось має відреагувати: “матеріал на модерації”, “матеріал відхилено”, “потрібно заповнити SEO”, “завантаження медіа впало”, “платіж прострочений”, “доступ змінено”. Але тут важливо не перетворити систему на спам. Тому вчимо: нотифікації прив’язані до ролі/відповідального і до значущих переходів стану. В ідеалі — з налаштуваннями: хто отримує, які канали (email/telegram/in-app), і можливість вимкнути неважливе.
Як робити “in-app” нотифікації правильно. Це по суті та сама таблиця повідомлень, але вона має бути легка і керована: recipient_user_id, type, title, body, url, read_at, created_at. Важливо: in-app нотифікація не замінює activity log. Вона — “донести інформацію”, а activity — “аудит”.
Тепер про correlation id (request id). Це дуже корисно, коли ви розбираєте інцидент: “після збереження зникли медіа” або “чому статус став published”. Correlation id дозволяє зв’язати: HTTP-запит → service дію → activity log → jobs, які стартували → їх логи. Мінімальна дисципліна: генерувати request id на вході (middleware) і прокидати його в логування, activity і jobs. Тоді ви зможете по одному ID підняти весь ланцюжок подій.
Окрема важлива тема — захист від дублікатів через retries. Якщо нотифікації йдуть через queue, job може повторитися. Якщо ви кожного разу вставляєте нову нотифікацію, ви заспамите користувача. Тому нотифікації мають бути або ідемпотентними (наприклад, “одна активна нотифікація цього типу на цю сутність”), або мати унікальний ключ (entity_id + type + recipient) і робити upsert. Те саме стосується “лог активності з черги”: якщо ви лог пишете асинхронно (не рекомендую для критичного аудиту), він теж може дублюватися.
Питання “що змінилося” без JSON. Якщо ви принципово не хочете JSON, правильний підхід — окрема таблиця деталей змін, де кожен рядок — це зміна одного поля: activity_id, field, old_value, new_value. Так ви зберігаєте аудит змін структуровано, можете робити пошук, і не ламаєте нормалізацію. Для навчання це корисно: людина відразу бачить, що аудит — це дані, а не “рядок тексту”.
Як навчати це поетапно. На першому етапі — робимо тільки activity log на ключові дії (create/update/status change/delete) синхронно в сервісі. На другому — додаємо in-app нотифікації на переходи станів, але відправку робимо через queue. На третьому — додаємо correlation id і правила ідемпотентності для нотифікацій. Це дає поступове ускладнення без перевантаження.
Висновок: лог активності — це аудит і “історія правди”, він має писатися синхронно в сервісному шарі разом зі зміною стану, бажано в транзакції. Нотифікації — це побічний ефект, їх краще робити асинхронно через jobs, з контролем дублікатів і настройками отримувачів. Якщо ви стандартизуєте формат activity (actor/action/entity/time + correlation id) і відокремите його від нотифікацій, система стане керованою: ви завжди знатимете “що сталося”, і зможете правильно повідомляти “кому треба знати”.