Несколько лет назад меня попросили разработать отдельное приложение. Оно имело единственное назначение и должно было использоваться независимо от других приложений, имеющихся в моей компании.
Поэтому я взял набор требований и разработал приложение, которое было именно таким. В нем все было автономно и, к сожалению, тесно связано с теми частями, которые его составляли.
Прошло около 7 месяцев разработки, прежде чем до меня дошел страшный слух. Это приложение должно было стать частью чего-то большего. Игроком в более крупной экосистеме приложений. Но это был всего лишь слух, поэтому мы продолжили работу.
Прошло 3 месяца, и это уже не было слухом. Почти через год после начала разработки мы официально решили изменить направление приложения. Проектные решения были приняты на основе предположений, которые теперь оказались совершенно неверными. Основные компоненты приложения, которые должны были стать общим достоянием «большой экосистемы», были жестко привязаны к невероятно узким сценариям использования.
Излишне говорить, что мы оказались в затруднительном положении. Наша модель данных нуждалась в изменении. Нам нужны были разные уровни изоляции для наших микросервисов. И нам нужно было ослабить многие связи между нашими сервисами.
Спустя годы мы все еще продолжаем ослаблять тесную связь. Несмотря на то, что нам было указано первоначальное направление, мы могли бы спроектировать приложение немного по-другому, чтобы избежать подобной ситуации.
Если бы мы следовали этим рекомендациям по разработке архитектуры для роста, мы могли бы свести к минимуму количество переделок и в целом получить более сильное решение.
Рост против масштабирования
В одной из предыдущих статей блога я писал о проектировании приложений для масштабирования. В этой статье я рассказал о том, как разрабатывать приложение для растущего количества пользователей в вашей системе. Со временем ваша пользовательская база будет расти, и на ваше приложение будет поступать все больше трафика. Это и есть масштаб.
С другой стороны, мы имеем рост, который представляет собой органическое увеличение размера, объема и сложности вашего приложения. По мере того как вы итерационно расширяете набор функций вашего приложения, вы помогаете ему расти. Площадь приложения становится все больше и больше, создавая, естественно, более сложное и трудное в обслуживании программное обеспечение.
Если рассматривать эти два понятия в одном спектре, то обработка большего трафика (масштаб) — это увеличение масштаба, а создание большего набора функций (рост) — это расширение масштаба.
В росте нет ничего плохого, это хорошая проблема. Но вы должны учитывать ее при разработке дизайна. Как вы будете учитывать новые основные функции? Есть ли вероятность того, что ваше приложение перерастет в нечто большее?
Включите в свой дизайн некоторые намеренные точки роста, чтобы в будущем можно было добавлять, итерировать и, возможно, удалять новые функции.
Архитектурные соображения
При проектировании приложения, рассчитанного на рост, исходите из того, что существует множество неизвестных. Будут новые функции, которые появятся в продукте, но вы не знаете, что это за функции. Владельцы продукта даже не знают, что это такое. Но вы должны учитывать их при разработке дизайна. Как это сделать?
Свободное соединение
Производственное приложение любого размера будет иметь множество движущихся частей. Ваша цель как архитектора решений — сделать так, чтобы каждая из этих движущихся частей была как можно более независимой. Это означает, что компоненты могут итерироваться и развертываться самостоятельно и не полагаться на другие микросервисы или компоненты для того, чтобы быть развернутыми или работать должным образом. Если изменяется один компонент, вам не придется изменять и «связанный» компонент.
Вообще говоря, проектирование системы таким образом означает, что ваши компоненты практически ничего не знают друг о друге, а когда им необходимо взаимодействовать, они делают это с помощью API, а не напрямую вызывают функции или загружаются из базы данных. Это известно как свободная связь.
Свободная связь открывает широкие возможности для роста приложения. Поскольку ваши компоненты и микросервисы не зависят друг от друга, вы можете добавлять и создавать новые компоненты без переписывания или замедления работы.
Когда речь идет об отображении свободной связи на архитектурной диаграмме, вы можете обозначить эти отношения пунктирной линией. Жестко связанные отношения (которых, надеюсь, у вас не так много) изображаются сплошными линиями, соединяющими различные компоненты.
Рассмотрим пример. Представьте, что у вас есть подкаст-приложение, состоящее из нескольких сервисов, перечисленных ниже.
Диаграмма служб приложения подкаста
- Служба подкастов тесно связана со службой управления документами, которая хранит аудио- и видеофайлы для подкастов.
- Служба подписчиков тесно связана со службой подкастов, где она подписывается на события и использует API для загрузки информации об эпизодах.
- Все сервисы тесно связаны с сервисом аутентификации и сервисом общей инфраструктуры, которые содержат пользовательский механизм аутентификации и такие вещи, как ключи KMS.
Управляемые событиями рабочие процессы
Событие — это действие, произошедшее в вашей системе. События не только обеспечивают свободную связь, но и позволяют подключаться к бизнес-процессам.
Например, в нашем приложении подкаста событие episode-published
публикуется, когда создается новый эпизод. Служба подписчиков прослушивает это событие и отправляет электронное письмо всем, кто подписан на подкаст.
Подписчики события неизвестны издателю. На определенное событие может быть ноль, один или много подписчиков, что делает управляемые событиями рабочие процессы идеальными для роста.
При начальной разработке можно публиковать события, представляющие важные бизнес-действия, даже если подписчиков нет. В ходе дальнейшей разработки службы могут подписаться на существующее событие и сразу же связать с ним свои собственные рабочие процессы. Такой способ известен как веб-крючок.
Webhooks позволяют легко расширять систему с течением времени. Пока сервисы могут подписываться на ваши события, вы можете бесконечно (до определенного момента) расширять свое приложение за счет новых свободно связанных функций. Рабочие процессы, управляемые событиями, даже прямо указаны AWS в качестве принципа проектирования serverless.
Используя веб-крючки и события в нашем примере с подкастом, мы фактически оставили дверь широко открытой для роста, открыв крючок для будущей разработки.
Если бы мы решили автоматически отправлять наш контент на хостинг таких сервисов, как Apple podcasts и Spotify, событие episode-published
уже существует для того, чтобы мы могли быстро и легко добавить эту функцию.
Тяжелый API
Сложное приложение будет иметь множество взаимодействий между сервисами. Ваши события часто требуют дополнительных поисков для получения полной информации о сущности, а в продвинутых рабочих процессах необходимы мультисервисные проверки. Чтобы облегчить это, сохраняя при этом свободную связь, вы обращаетесь к API.
Проектируйте свои приложения с упором на легко расширяемые и используемые API.
Создавая гибкие и согласованные API, вы позволяете будущим разработчикам получать именно ту информацию, которая им нужна. Это также создает возможности для потребителей манипулировать данными в рамках нового набора функций.
В приведенном ниже примере у нас есть ряд кросс-сервисных действий, которые происходят при публикации эпизода в нашем приложении для подкастов.
Кросс-сервисный рабочий процесс для публикации эпизода подкаста
- Служба подкастов выполняет вызов API к службе управления документами для сохранения аудиофайла.
- Служба подкаста запускает событие, на которое подписывается служба подписчика.
- Служба подписчиков делает вызов API к службе подкастов, чтобы получить полную информацию об эпизоде перед уведомлением подписчиков.
Хотя проектирование системы с использованием API не является технической архитектурой, это то, что следует учитывать архитектору решений. Удовлетворение вышеописанного рабочего процесса возможно только благодаря доступу к мощному API.
Чтобы сделать акцент на API, вы можете продвигать в своей организации разработку на основе API. Затрачивая время и энергию на разработку API на начальном этапе, вы сделаете свои приложения легко развиваемыми.
Заключение
В простом бизнес-процессе, приведенном в примере выше, есть два межсервисных вызова API и событие. По мере роста вашего приложения эта схема будет повторяться снова и снова.
Ваша задача как архитектора решений — придумать, как сделать так, чтобы компоненты были совершенно отдельными, но при этом работали вместе без проблем. На первый взгляд, это звучит как сложная и практически невыполнимая задача. Но если вы запомните эти три вещи, вы настроите себя на успех.
- Сохраняйте свободную связь между компонентами, чтобы их можно было дорабатывать и развертывать по отдельности.
- Публикуйте события основных бизнес-действий, чтобы добавить триггерные точки для будущих функций и интеграторов (даже если вы не используете их сейчас).
- Создавайте API для всего. Если есть возможность, сделайте их общедоступными и создайте их в рамках общей модели управления.
Помня об этом, вы оставляете себе возможность развивать свое приложение практически для всего. Вы не связываете себя бременем распространения изменений через лабиринт тесно связанных сервисов. Вы создаете намеренные точки расширения с событиями, к которым в любой момент можно подключиться. И вы создаете легкие для потребления точки входа в ваши сервисы через API.
При таком подходе не обязательно знать будущее. Вам могут бросать один кривой шар за другим, и ваше приложение сможет с этим справиться.
Счастливого кодинга!