Методология Twelve-Factor App (Приложение двенадцати факторов), которую создал сооснователь платформы Heroku Адам Уиггинс, направлена на разработку SaaS-приложений. Документ, описывающий эту методологию, включает в себя множество идей, применение которых сделало использование и, я уверен, создание платформы Heroku, увлекательным и благодарным трудом.

Часто бывает так, что старые добрые времена разработки и развёртывания приложений превозносят как дни, когда всё было лучше, чем теперь. Сегодняшнее воплощение этого явления представляют воспоминания о Heroku десятилетней давности. Не поймите меня неправильно — Heroku тех времён была замечательной платформой, которая открыла всему миру новый путь к разработке веб-приложений и к их развёртыванию в распределённых системах. Похоже, что команда Heroku многое сделала правильно. Ведь не случайно же сейчас, более чем через 10 лет, мы пытаемся добиться той же простоты разработки программ, которая была характерна для Heroku.

Как, со времени представления методологии Twelve-Factor App в 2011 году, эволюционировали принципы, положенные в её основу? Чему они могут нас научить сегодня? Как эти принципы изменили положение дел в недавнем прошлом?

I. Кодовая база

Одна кодовая база, отслеживаемая в системе контроля версий, — множество развёртываний
Одна кодовая база, отслеживаемая в системе контроля версий, — множество развёртываний

Когда формировалась идея об одной кодовой базе и множестве развёртываний, платформы наподобие GitHub только начинали развиваться. Тогда не принято было считать, что версионирование кода — это нечто, само собой разумеющееся. Может, звучит это и жёстко, но так оно и было. Помню, когда я только начинал программировать, мне приходилось разбираться с проблемами, связанными с Subversion.

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

Если есть несколько кодовых баз, то это не приложение — это распределённая система. Каждый компонент в распределённой системе является приложением и каждый компонент может индивидуально соответствовать двенадцати факторам.

Особенно — в мире, ориентированном на микросервисы. Дополнительные затраты сил и времени, необходимые на поддержание нескольких репозиториев, могут оказаться довольно значительными.

II. Зависимости

Все зависимости должны быть объявлены, приложение не полагается на неявное существование системных инструментов или библиотек
Все зависимости должны быть объявлены, приложение не полагается на неявное существование системных инструментов или библиотек

Мне сложно оценить количество читателей этой статьи, которым приходилось самостоятельно готовить компьютеры к работе во времена, когда ещё не было программ наподобие Ansible и Puppet. Но и когда такие программы появились, особенности взаимодействия операционных систем и различных версий библиотек были настоящим кошмаром.

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

В современных приложениях применяются различные файлы-манифесты, что позволяет воссоздавать в разных системах рабочие среды приложений в точности в том виде, в котором они описаны разработчиками. Более того, инструменты для сборки приложений, вроде Bazel, пошли ещё дальше. Они включают в себя множество мощных средств, позволяющих инженерам, ответственным за сборку проектов, создавать воспроизводимые релизы. Не стоит и говорить о том, что сегодня мы можем отправлять в продакшн простые образы, в которые упаковано только то, что нужно для развёртывания приложения (подробнее об этом — ниже).

III. Конфигурация

Конфигурацию приложения, которая меняется между развёртываниями, нужно хранить в среде выполнения программы
Конфигурацию приложения, которая меняется между развёртываниями, нужно хранить в среде выполнения программы

Этот фактор остался неизменным, но, рассматривая отделение конфигурации от кода приложения, стоит обратить внимание на несколько моментов. Так, инструменты для автоматизации разработки достаточно настойчиво предлагают пользователям свои подходы к хранению конфигурации. Поэтому, при выборе конкретного подхода, пользователи этих инструментов всё сильнее полагаются на них. Среди таких инструментов можно отметить нечто вроде Vault, или средств для обнаружения сервисов, которые управляют конфигурациями и хранят их в системах, учитывающих роль среды выполнения программ в работе с конфигурациями.

IV. Сторонние службы

Все сторонние службы считаются подключаемыми ресурсами, их подключение и отключение осуществляется средой выполнения программы
Все сторонние службы считаются подключаемыми ресурсами, их подключение и отключение осуществляется средой выполнения программы

Изначально этот фактор был создан в расчёте на компоненты отдельной системы. Он, в основном, был ориентирован на базы данных и нечто подобное. Даже хотя его авторы не упоминают о какой-либо разнице между локальными и сторонними системами, смысл этого фактора получил дальнейшее развитие при разработке систем, которыми, как платформами, могут пользоваться другие программисты. При их разработке используется подход API-first, когда API этих систем является наиболее важной их частью. Данный подход оказался настолько успешным, что многие команды разработчиков начали внедрять эту стратегию для создания чётких контрактов между командами, ответственными за работу над разными блоками систем. Это, в итоге, приводит к тому, что системы, по мере их роста, не теряют гибкости и эффективности.

V. Сборка, релиз, выполнение

Конвейер разработки программного обеспечения должен чётко разделяться на стадии сборки, релиза и выполнения
Конвейер разработки программного обеспечения должен чётко разделяться на стадии сборки, релиза и выполнения

Применение принципов этого фактора — вполне обычное дело для любого сервиса, разрабатываемого в наши дни. Мне лишь хотелось бы обратить ваше внимание на одно явление, с которым я постоянно сталкиваюсь. Речь идёт о нарушении принципов этого фактора, когда шаги сборки и релиза проекта не разделяются. А это разделение очень важно для организации адекватных процессов разработки с возможностью возврата систем в предыдущие состояния. По мере того, как новые порции кода объединяются с основной кодовой базой и тестируются, каждый сеанс сборки проекта приводит к появлению образа (или некоего двоичного представления проекта). Подобные образы нужно сохранять с помощью некоего механизма, делая это так, чтобы их, при необходимости, можно было бы загрузить в более позднее время при работе с релизами и развёртываниями проекта. Подобное разделение позволяет организовать более простой цикл разработки, меньше подверженный ошибкам.

Это может и не потребоваться в небольших проектах или на ранних стадиях разработки проектов. В подобных условиях такие изменения сильно связаны с разработкой приложения и его инфраструктуры. GitOps — это сфера, где многие из этих концепций получили дальнейшее развитие.

VI. Процессы

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

Принципы этого фактора остаются актуальными и в мире REST API. В случае с веб-сервисами это означает, что данные предметной области приложения никогда не следует хранить в памяти между запросами. Вместо этого все взаимодействия в рамках приложения должны либо производиться без хранения состояния, либо с хранением данных в сторонних службах.

Управление состоянием приложения и его хранение входят в состав проблем масштабирования, с которыми сталкиваются все системы. Это очень важно для горизонтального масштабирования в модели SaaS. Это, кроме того, хорошо сочетается с концепцией ограниченного контекста. Благодаря такому подходу к работе с данными обеспечивается то, что каждая система отвечает за работу с собственным слоем хранения информации. Если же другой системе нужен доступ к этим данным, она должна делать это посредством хорошо документированного API.

VII. Привязка портов

Самодостаточные сервисы должны предлагать свои услуги другим сервисам посредством заданных портов
Самодостаточные сервисы должны предлагать свои услуги другим сервисам посредством заданных портов

Некоторое время принципы, положенные в основу этого фактора, были стандартной практикой. В наше время в этой сфере ничего серьёзно не изменилось. Более того, многие стандарты контейнеризации приложений подталкивают разработчиков к следованию этим принципам. То же самое можно сказать и о прокси-серверах, и о балансировщиках нагрузки.

Самое главное тут то, что у каждого приложения должны иметься сведения относительно привязки заданных портов.

Обычно некий порт ориентирован на работу с информацией, возможность работы с которой ожидается от этого порта. Это возможно лишь при наличии сетевой привязки контейнеров к хостам. Концепция привязки портов, которая заключается в использовании единообразных номеров портов, может рассматриваться как наилучший способ вывода процесса в сеть. Например — порт 80 — это стандартный порт для веб-серверов, работающих по протоколу HTTP. Аналогично — 22 — это стандартный SSH-порт.

VIII. Параллелизм

Параллельная работа программ обеспечивается путём масштабирования отдельных процессов
Параллельная работа программ обеспечивается путём масштабирования отдельных процессов

Фактор параллелизма заключается в том, что приложение должно налаживать работу каждого процесса в соответствии с его предназначением. Инженеры могут добиться выполнения принципов этого фактора путём распределения подобных процессов по различным группам. Этот фактор включает в себя различные аспекты горизонтального масштабирования проектов и достаточно современные идеи из этой сферы — наподобие шаблона «функция как услуга». Например, принципы этого фактора могут пригодиться, когда известно, что в определённых ситуациях множество сравнительно слабых машин справятся с задачей лучше, чем несколько более мощных компьютеров.

Ещё один важный урок, который можно извлечь из принципов этого фактора, заключается в том, что приложению стоит организовывать параллельную обработку данных вокруг своих рабочих нагрузок. А приложение с конкурирующими рабочими нагрузками нужно разбивать на отдельные части, которые масштабируются независимо от других.

IX. Утилизируемость

Быстрый запуск и корректное завершение работы приложения способствуют увеличению его надёжности и устойчивости к сбоям
Быстрый запуск и корректное завершение работы приложения способствуют увеличению его надёжности и устойчивости к сбоям

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

Мне доводилось наблюдать и недостаточное внимание разработчиков к процессам завершения работы приложений. Корректное завершение работы приложения — задача достаточно сложная, её нелегко решить правильно. Этой задаче сопутствуют и давние рассуждения о том, имеет ли какое-то значение правильное завершение работы программы, если после этого она, всё равно, работать не будет. Ну, если работу программы не завершить корректно в распределённом окружении, то это может привести к «эффекту домино» в системах, которые полагаются на эту программу. Кроме того, если не обращать внимания на корректное завершение работы программ, это может привести к ухудшению надёжности системы и, в итоге, плохо сказаться на пользователях этой системы.

X. Паритет разработки/работы приложения

Все окружения приложения должны быть максимально похожими друг на друга
Все окружения приложения должны быть максимально похожими друг на друга

На мой взгляд, принципы этого фактора нарушаются всё сильнее и сильнее по мере того, как мы переносим наши инфраструктуры в приватные облака, привязанные к поставщику услуг. Конечно, системы вроде Docker и Kubernetes позволяют, с практической точки зрения, выполнять код в эквивалентных окружениях, как в процессе разработки, так и в продакшне. Но, если перейти к существу вопроса, то вряд ли можно говорить об их «эквивалентности». Любой, кому приходилось отлаживать ошибку, воспроизводимую в продакшн-окружении, может развлечь слушателей страшными историями на эту тему. К сожалению, у операторов приватных облачных служб почти нет стимулов для раскрытия деталей своих систем. И, на самом деле, то, что эти системы предлагаются в виде сервисов, является одним из основных их привлекательных качеств.

Более того, попытка уместить все эти системы на компьютере, доступном для использования всеми разработчиками, само по себе — сизифов труд.

XI. Журналирование

Приложение должно создавать записи журналов в виде потоков событий, передавая задачу их агрегации среде выполнения приложений
Приложение должно создавать записи журналов в виде потоков событий, передавая задачу их агрегации среде выполнения приложений

Этот фактор превратился в отдельную сферу деятельности программистов, хотя то, как его описал Адам Уиггинс, создаёт такое впечатление, будто журналирование было не его проблемой, а проблемой кого-то другого. Задача журналирования, в основном, изменилась, превратившись из чего-то такого, что делается с выходными данными приложений (журналами), в нечто такое, о чём приложение должно позаботиться само.

По мере того, как приложения эволюционируют, им нужно следить за журналами, за различными метриками, за диагностическими сообщениями. Благодаря этому достигается возможность понимания того, что происходит в системах, и поддержания работоспособности этих систем по мере их роста. В результате формулировку этого фактора, скорее всего, стоит привести в соответствие с современными условиями: приложение должно быть таким, чтобы за ним можно было бы наблюдать, не модифицируя при этом само это приложение.

XII. Задачи администрирования

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

Над всем, что меняет состояние приложения, в данном случае — над средствами администрирования, нужно работать, используя те же инструменты, что и для работы над самим этим приложением. Средства администрирования так же важны, как и само приложение, их нужно тестировать и подвергать код-ревью. Такой подход к подобным средствам сложно обеспечить в «боевых» условиях работы над проектами, они легко выходят из-под контроля. Уверен, это может являться причиной очень большого количества сбоев и неприятных «приветов» из прошлого.

Достаточно зрелые языки программирования, в экосистемах которых существуют хорошие фреймворки, дают разработчикам простые механизмы для создания и тестирования средств администрирования приложений. Но если вы пользуетесь языком, для которого нет популярных и надёжных механизмов такого рода, вы должны создать их самостоятельно. Сюда входят описания процессов миграции кода приложения, управление зависимостями и даже «одноразовые» задачи администрирования для очистки неких данных и для управления чем-либо.

О, а приходите к нам работать? ???? ????

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде.

Комментарии (0)