Prowid Photos растёт: вместо двух разработчиков уже шесть, а в планах тридцать. На созвоне кто-то говорит: «Давайте перейдём на микросервисы — так делают Netflix и Amazon». Звучит убедительно, но честный ответ — «зависит». В этом уроке разберём три способа собрать приложение — монолит, модульный монолит и микросервисы, — что даёт и чего стоит каждый, как мигрировать без остановки и что выбрать для Prowid Photos.

Три способа собрать одно приложение: монолит, модульный монолит и микросервисы
Три способа собрать одно приложение: монолит, модульный монолит и микросервисы

Монолит: всё приложение целиком

Монолит — это одно приложение, которое разворачивают целиком: один процесс, один репозиторий, обычно одна база данных. Сейчас API Prowid Photos именно такой: одно Express-приложение с разделами users, photos, feed, follows и notifications.

Для старта это отличный выбор:

  • Просто запускать и разворачивать: одна команда, один Docker-образ.
  • Вызов — это вызов функции: занимает наносекунды (лестница задержек из первого урока) и не может «потеряться в сети».
  • Одна база — одна транзакция: загрузка фото и увеличение счётчика либо произойдут вместе, либо не произойдут вовсе.
  • Просто отлаживать и тестировать: один стек вызовов, один лог, дёшево.
js
// the monolith: both tables live in one database, so one transaction is enough
const client = await pool.connect();
try {
  await client.query('BEGIN');
  await client.query(
    'INSERT INTO photos (id, user_id, url) VALUES ($1, $2, $3)',
    [photoId, userId, url],
  );
  await client.query(
    'UPDATE users SET photos_count = photos_count + 1 WHERE id = $1',
    [userId],
  );
  await client.query('COMMIT'); // both changes happen, or neither
} catch (err) {
  await client.query('ROLLBACK');
  throw err;
} finally {
  client.release();
}

Проблемы приходят с ростом. Код превращается в «большой ком грязи» (big ball of mud): всё импортирует всё, и правка уведомлений ломает ленту. Любое мелкое изменение требует развернуть и перетестировать всё приложение. Масштабировать можно только целиком, даже если нагружена одна часть. Команды наступают друг другу на ноги в одном репозитории.

Модульный монолит: стены внутри дома

Представьте один дом. В монолите — одна огромная комната, где все живут вместе. В модульном монолите те же стены, но внутри есть комнаты с дверями. Приложение остаётся одним процессом и одним деплоем, зато код разделён на модули по областям бизнеса с жёсткими правилами:

  • у модуля есть публичное API — один файл index.ts, который экспортирует несколько функций и событий; всё остальное внутри скрыто;
  • другие модули импортируют только публичное API, а не файлы внутри;
  • у модуля свои таблицы (схема), и чужие таблицы он не читает через JOIN;
  • модули общаются вызовами функций или событиями внутри процесса.
text
src/modules/
  photos/
    index.ts              <- the public API: the ONLY file others import
    photos.service.ts
    photos.repository.ts
  users/
  feed/
  notifications/