Принципы проектирования программного обеспечения
Базовые принципы разработки приложений и сервисов
Кратко пройдемся по основным принципам разработки программного обеспечения.
Каждая из тем которую рассмотрю здесь достойна отдельной заметки.
Статья является отправной точкой для изучения вещей более углубленно.
За основу взял манифест https://12factor.net/ru/. Но не пересказываем его, а пытаемся разобраться в нем более простым и доступным языком, там написано не понятно.
Код
Код приложения обязательно должен быть в системе контроля версий, дефакто стандарт - это git
.
Кодовая база - это единый репозиторий или набор репозиториев с единой точкой входа. Так же это может быть набор которые собираются в приложение.
Если у нас одна кодовая база, то развертываний (деплоев) может быть сколько угодно, например:
- Основной
- Для разработки
- Для тестирования
- Для нагрузок и т.д.
При чем все они могут разрабатываться независимо друг от друга, что дает возможность тестировать и выкладывать отдельно.
Зависимости
Программным обеспечением, нужно как то управлять. Для этого в современных языках программирования есть менеджер зависимостей.
Например, для php
это composer
, для javascript (NodeJs)
это npm
, для python
это pip
.
Мы можем установить эти зависимости для всей системы или для проекта.
Все зависимости должны быть явно описаны рядом с кодом.
Зависимости должны быть явно описаны. Для этого используются специальные файлы, например package-lock.json
или composer.lock
. В них менеджер зависимостей как раз хранит каким образом библиотеки зависят друг от друга.
Так же зависимости должны быть изолированы в разных кодовых базах. То есть в одном проекте одни зависимости в другом другие.
Системные программы такие как curl
, git
и др., нужно тоже явно ставить в систему, например добавить их в образ Docker
на этапе сборки.
Конфигурация
Конфигурация - это настройки приложения, которые должны меняться при деплое.
Что может подразумеваться под настройками:
- Подключения к базе данных.
- Настройки параметров кеширования.
- Данные для подключения к платежному шлюзу.
- Настройки почтового сервера.
- Различные логгеры и валидаторы.
- и др.
Так как у нас кодовая база одна, она должна быть устойчива к работе с разной конфигурацией, т.е с разными настройками.
Часто бывает, что у нас есть окружение разработчика development
, промежуточный этап stages
и окружение для конечного пользователя production
.
Но это разделение не всегда такое.
local
или development
- это непосредственно окружение разработчика.
testing
- различные тестовые мероприятия кода.
staging
- окружение максимально приближенное к production
, это его аналог, например здесь можно проводить нагрузочное тестирование, так как это почти прод.
production
- сервер для конечных пользователей
Критически важные данные нужно хранить в переменных окружения, а не в зашитыми в коде, так как они уже привязаны к окружению.
Но конфигурация не должна быть жестко привязана к окружению. В идеале запустить новую версию приложения в любом окружении должно быть легко.
Внешние сервисы
Ко внешним сервисам можно отнести:
- СУБД
- Брокер очередей
- Почтовый сервер
- Сервер телефонии
- api сервиса
- и др.
То есть со всеми ними идет общение по сети.
Главное, чтобы была возможность поменять базу данные без изменения кода, а просто заменив переменные окружения.
То есть в каком бы окружении приложение не разворачивали, можно с легкостью переключаться между серверами базы данных например.
В теории можно заменить версию базы данных, просто обновить ее в docker
образе.
Жизненный цикл приложения
Существуют определенные этапы работы приложения:
Порядок тут важен и его нужно строго придерживаться.
Сборка
На этапе сборки кодовая база трансформируется в исполняемый файл, docker образ или другое.
Сами разработчики решают как собрать проект.
Релиз
Теперь к исполняемому аилу или к docker образу нужно добавить конфигурацию. То есть релиз - готов к развертыванию.
В идеале у релиза должен уникальный идентификатор. То есть после его сборки внести изменения уже нельзя, в таких случаях нужно собрать новый релиз.
Выполнение
Запуск релиза с выбранной конфигурацией.
Запуск приложения может быть автоматическим, илм ручным.
Без состояния
Данные приложения должны хранится во внешнем сервисе Кеш, БД, Очередь, но не в памяти приложения, так как она не надежна.
По возможности состояния внутри приложения нужно избегать.
Лучше хранить данные в Redis
, Memcached
, а если данные меняются редко, то их можно хранить в токене JWT
.
Быстрые процессы
В идеале запуск или деплой приложения должен быть быстрым. Короткое время запуска обеспечивает большую гибкость релизов.
Например, это может помочь при переносе проекта на новую физическую машину, так как можно бесшовно деплоить.
Должно быть так подняли новый инстанс приложения, остановили старый инстанс, пользовать не должен замечать, что что-то поменялось.
Порты
Наружу должны быть видны только те порты которые нужны для конечного пользователя.
Совместимость окружений
Что мешает окружениям быть совместимыми:
- Разработчик работает над кодом месяцами, прежде чем код окажется на продакшене, в идеале возможность проверить код должна быть сразу же.
- Программист пишет код, а разворачивает его системный администратор. Разворачивать должен тот же человек, который писал.
- Разработчик использует
nginx
иwindows
локально, а на проде используетсяapache
иlinux
. Окружение должно быть идентичным.
Между окружениями должен быть минимальный разрыв
Всего этого легко добиться используя docker-compose
для локальной разработки и для продакшена.
Логи
Логи это поток сообщений запущенных сервисов, которые пишутся пока приложение работает.
Их не нужно нигде специально хранить. Логи должны архивироваться на сервере, для дальнейшего анализа. Можно пользоваться специальными программами для этого.
Запуск произвольного кода
Должна быть возможность запустить произвольный код с целью тестирования, администрирования. Такие инструменты должны поставляться вместе с приложением.