Статья из серии "2х минутные заметки разработчика".
Облака повсю��у. Пожалуй так стоит начать эту заметку. Как только пользователь открывает браузер, он уже начинает использовать облачные технологии. Браузер делает запросы на свои серверы, подтягивает нужные данные, и пользователь испытывает тот же опыт что и раньше, несмотря на то, что уже 10 раз менял свое устройство, будь то компьютер, смартфон, смарт-ТВ или холодильник. Браузер распознал пользователя, и пользователь видит знакомый интерфейс.
Облака все больше проникают в повседневную жизнь пользователя. Поэтому все больше разработчиков берут облака в свой арсенал инструментов, обустраивая эту самую жизнь пользователя.
Проблема
Одной из популярных задач, которую приходится решать разработчикам, является динамическая настройка того, что должен видеть пользователь в тот или иной момент. Иногда вам нужно изменить содержимое сайта или веб-приложения без физического изменения самого кода. А чтобы это можно было сделать, код изначально должен поддерживать возможность динамической загрузки разного контента.
Самый распространенный случай, когда необходимо динамически менять контент, — это AB-тестирование. В код вводится условие, что если определенный флаг (называемый фиче-флагом) установлен в значение true, то пользователь видит один сценарий, иначе другой.
Более того, один из сценариев может быть не совсем рабочим, и тогда нужно быстро запретить его видеть, а значит, это может быть опасно, так как пользователи могут видеть нерабочий код в течение длительного времени, потому что нужно собирать проект заново, и доставлять сборку в продакшен каждый раз, когда что-то ломается.
Удаленное переключение фиче-флага решает эту проблему.
Делаем конфиг сервер
Конфиг сервер - это как раз таки тот переключатель, который позволяет обновить сценарий пользователя без ре-деплоя приложения.
Как бы выглядела наивная реализация этого паттерна?
Вероятно, вам нужно иметь базу данных для хранения динамических конфигов, поэтому вам нужен бэкенд-сервер, который поможет вам манипулировать этими конфигами + UI-сервис для удобной работы.
Помимо трех сервисов, нужно думать еще и об авторизации, так как просто так конфиги не изменить, а также об управлении нагрузкой, так как запросы к конфиг-серверу практически дублируют запросы к самому приложению.
Результат - много работы.
Более удачный способ
Давайте попробуем использовать облака из введения, чтобы найти лучшую реализацию.
В итоге имеем - БД и сервер заменены на две лямбда-функции. Писать и читать конфиг. Остальная работа ложится на плечи AWS.
UI сервис переезжает, например, на github или любую другую систему контроля версий. Более того, в этом случае мы также делегируем авторизацию и управление доступом github и снимаем с себя большой пласт работы.
Давайте подробнее рассмотрим этот подход.

Начнем с развертывания API Gateway. Вы можете использовать шаблон, который я подготовил для таких целей. Шаблон написан на serverless фреймворке и позволяет одной командой развернуть простой API Gateway + DynamoDB.
Далее нам нужно его немного изменить. Так как наш Gateway будет работать в обе стороны (на запись и чтение), нам нужно правильно настроить разрешения, чтобы конечное приложение, использующее конфиг, могло его только читать, но никак не могло перезаписывать. Для этих целей мы будем использовать два разных API-ключа. В целом рекомендуется использовать разные API ключи для каждой операции, чтобы отслеживать, кто и когда обращается к вашему сервису.
apiGateway:
apiKeys:
- ${self:custom.env.repoApiKey}
- ${self:custom.env.appApiKey}В параметрах нашего обработчика пишем, что функция чтения конфига приватная и теперь AWS при вызове этой лямбды будет ожидать заголовок x-api-key, значение для которого мы можем найти в AWS SSM, если, конечно, мы использовали приведенный выше шаблон. Шаблон содержит инструкции по созданию такого ключа API и сохранению его в SSM под именем /cfg-srv-exmpl/dev/apis/rest-api/app-api-key.
getConfig:
handler: src/handlers/get-config.handler
events:
- http:
method: get
path: config
cors: true
private: trueСама лямбда очень проста. Мы получаем id нашего конфига из параметров запроса и просто читаем конфиг с этим id из базы данных. При этом авторизация проходит в фоновом режиме. Если функция была вызвана, значит, AWS проверил заголовки запроса и все в порядке.
export const handler: APIGatewayProxyHandler = async (event) => {
const id = event.queryStringParameters.id;
const json = (await getConfigs()).find((i) => i.id == id);
return HttpResponse.success(json);
}Теперь давайте перейдем к части на запись.
Создаем репозиторий на github, где будет храниться наш конфиг.
Заходим в настройки и переходим на вкладку Webhooks.

В "Payload URL" вставляем URL из хранилища SSM под именем /cfg-srv-exmpl/dev/apis/rest-api/api-url, опять же, если вы использовали шаблон, то под этим именем там будет адрес самого API Gateway.
Не забудьте указать «application/json» для типа контента, а также секрет. Секрет — это наш второй сгенерированный ключ API.

Github будет использовать этот секрет для получения хэша sha256. Далее, как только что-то обновится в конфиг-репозитории, гитхаб сделает POST-запрос на указанный нами в Payload URL адрес, сгенерирует хеш и поместит его в заголовок X-hub-signature-256 и так как только мы знаем этот секрет, никто другой не может сделать такой же запрос.
Затем в нашей функции мы получаем этот запрос. Мы можем понять, что он был сделан github с нашим секретом (пример), мы делаем дополнительный запрос через github API, чтобы получить конфиг и записываем его в базу, поэтому после каждого изменения конфига в github, он будет синхронизирован с базой данных, и приложение будет использовать актуальную версию конфига.
Давайте еще раз пройдемся по шагам.
Разворачиваем API Gateway с двумя функциями (приватной и публичной) с подключением к БД.
Убеждаемся, что создаются два ключа API.
Используем один ключ для чтения в приватной функции
Делаем веб-хук для репозитория с конфигом, и подписываем каждый запрос вторым API ключом
При изменении конфига делается запрос к веб-хуку, функция проверяет подпись и если все ок, то конфиг обновляется в базе
Любое приложение, которое знает ключ API для чтения, может прочитать файл конфигурации и внедрить его в свой код.
Плюсы:
минимум усилий, почти все разворачивается в одну строчку
система авторизации для чтения конфига из коробки
система контроля версий для конфига из коробки
авторизованные изменения конфигурации из коробки
управление нагрузкой из коробки
Пример реализованного конфиг-сервера
Теперь мы можем, наконец, решить нашу проблему с динамическим контентом. Давайте использовать NextJs в качестве примера.
export async function getServerSideProps() {
const config = await fetch('https://api-gateway-url/dev/config?id=project', {
headers: {
'x-api-key': 'some-api-key-hash'
}
}).then(res => res.json())
return {
props: {
itemsPerPage: config.itemsPerPage
}
}
}Как только мы изменим itemsPerPage в нашей конфигурации, эти изменения будут автоматически применены к нашему приложению без необходимости повторного деплоя кода в продакшен.
Если вам понравилась данная заметка то можно также ознакомится с оригинальным блогом на github. Там я выпускаю заметки немного чаще и они также бывают не совсем техническими. Поэтому тыкайте на глаза и звезды :)

Прочие вопросы можно задать в twitter.