В разработке ПО выбор правильных паттернов архитектуры может сильно повлиять на производительность и масштабируемость. Эксперт Solvery расскажет о топовых практиках и их применении.
074 открытий410 показов
Когда речь идет о проектировании системы, важно понимать, что выбор архитектурного паттерна зависит от множества факторов. Универсального решения вы не найдете, но есть проверенные подходы, которые помогают сделать выбор в пользу эффективных инструментов. При этом важно учитывать:
- каких вы придерживаетесь трейдофов;
- какие требования у бизнеса, команды;
- какое техническое окружение;
- насколько система должна быть масштабируемой и отказоустойчивой;
- какая модель данных наиболее эффективная.
Вместе с Игорем Панасюком, разработчиком в финтехе Яндекса и ментором Solvery, разбираемся, какие есть антипаттерны в разработке архитектуры, а какие подходы действительно эффективны и для чего нужны.
Игорь Панасюк
разработчик в финтехе Яндекса, ментор Solvery
Для начала: антипаттерны и частые ошибки
Антипаттерны могут привести к сложному, плохо поддерживаемому или неэффективному коду. Ниже — несколько примеров.
Distributed Monolith
На мой взгляд, частая проблема в настоящее время — это Distributed Monolith, то есть микросервисы, которые имеют очень сильную связность — в таком случае лучше сделать обычный монолит.Игорь Панасюкразработчик в финтехе Яндекса, ментор Solvery
В результате:
- При деплое одного сервиса нужно обновлять все остальные.
- Сервисы жёстко связаны через API.
- Не получается горизонтально масштабировать.
Например, у нас есть три микросервиса: заказы, оплата и пользователи. При этом сервис «Заказы» напрямую вызывает остальные сервисы, и наоборот. Так, если в одном сервисе поменяется API, остальные перестанут работать.
# Orders напрямую вызывает Payment def create_order(order): process_payment(order.payment_info)
Чтобы исправить, можно использовать, например, асинхронную коммуникацию через Kafka.
Микролиты
Архитектура по идее микросервисная, но каждый микросервис настолько сложный, что превращается в «маленький монолит». Да, у сервиса есть собственная база данных, но код все равно сложно поддерживать и масштабировать.
Например, есть сервис «Каталог товаров». По идее, он должен быть простым, но в него добавили еще и данные о всех заказах, пользователях и платежах. Как результат — десятки зависимостей и сложная бизнес-логика.
class CatalogService: def get_products(): # Запрос к своей базе данных def get_user_orders(user_id): # Запрос уже к другой таблице — архитектура разрастается
Чтобы не было такой путаницы, упрощайте сервисы и следуйте принципу: «один сервис — одна ответственность».
Многослойность
Архитектура перегружена кучей слоев, например, API → сервис → репозиторий → DAO → DTO → и так далее. Причем каждый слой выполняет какую-то минимальную работу и банально вызывает следующий.
class PaymentDAO: def get_payment(id): return DB.query(f"SELECT * FROM payments WHERE id={id}") class PaymentRepository: def get_paymentt(id): return ProductDAO.get_payment(id) class PaymentService: def get_payment(id): return ProductRepository.get_payment(id)
Каждый слой просто прокидывает вызовы и практически не несет ценности. Здесь важно помнить: если все, что делает слой, — это делегирует вызовы, он вам не нужен.
А теперь рассказываем, какие паттерны при разработке архитектуры будут эффективными.
Паттерны для предоставления данных и управления
Наиболее популярные паттерны для предоставления данных — MVC, MVVM и MVP. Рассказываем, чем они отличаются.
MVC (Model-View-Controller)
Этот паттерн делит приложение на три компонента: Model — хранит бизнес-логику, View — отображает UI, и Controller — обрабатывает ввод пользователя, следит за Model и View. Работает это так:
Пример — пользователь хочет авторизоваться на сайте:
- Юзер вводит логин и пароль окно входа — через View.
- View отправляет данные в Controller.
- Controller передает логин и пароль в Model, которая проверяет их в базе данных.
- Model возвращает результат (успешная или неуспешная аутентификация).
- Controller обновляет View — он выводит либо сообщение, что все успешно, либо ошибку, например, «Неверный логин или пароль».
MVVM (Model-View-ViewModel)
В этом паттерне тоже три компонента, но немного другие: Model — тоже отвечает за логику, View — UI, который поддерживает привязку данных (data binding), ViewModel — преобразует данные Model для View, содержит команды для обработки действий. Вот схема:
Пример — пользователь хочет добавить новую задачу в список.
- Пользователь вводит новую задачу в поле ввода через View.
- View автоматически передает данные в ViewModel через двустороннюю привязку.
- ViewModel обновляет Model, например, заносит новую задачу в базу данных.
- Model говорит ViewModel, что данные изменились.
- ViewModel обновляет View, и на экране появляется новая задача, но без явного вызова апдейта интерфейса.
MVP (Model-View-Presenter)
В этом паттерне лежат: Model, View — здесь это пассивный UI, Presenter — посредник между View и Model, плюс в нем есть логика отображения. Работает это вот так:
Пример — приложение, где есть чат.
- Пользователь набирает сообщение и нажимает «Отправить» или просто кнопку через View.
- View передает этот текст в Presenter, и он вызывает Model, который отправляет сообщение на сервер.
- Когда сообщение отправлено, сервер возвращает подтверждение Model, а он говорит об этом Presenter.
- Presenter обновляет View, добавляя это сообщение в интерфейс.
- Когда приходит новое сообщение от друга/коллеги, Model получает его из сети и передает Presenter.
- Presenter обновляет View и отображает новое сообщение в чате.
Мы используем MVC при разработке бэкенда, MVVM — при кодинге на Angular и Vue, а MVP — для разработки UI на Android. Игорь Панасюкразработчик в физтехе Яндекса, ментор Solvery
А для управления данными можно использовать:
- Repository — абстрагирует доступ к данным и минимизирует зависимость от конкретной базы данных.
Важно разделять Repository на несколько интерфейсов (interface segregation). Для транзакций в распределенных системах можно использовать Saga или outbox + event bus.Игорь Панасюкразработчик в физтехе Яндекса, ментор Solvery
- Unit of Work — контролирует транзакции на уровне логики.
- Active Record — связывает объекты с таблицами баз данных и просто упрощает работу.
Паттерны распределительных систем
Микросервисы, Event-driven архитектура и CQRS помогают создавать масштабируемые и отказоустойчивые приложения. Рассмотрим их подробнее.
Микросервисы
В микросервисах приложение разбивается на небольшие, независимые сервисы, каждый из которых отвечает за конкретную бизнес-логику. Сервисы взаимодействуют друг с другом через API (обычно это HTTP или gRPC).
Микросервисы позволяют развертывать и масштабировать каждый сервис по отдельности. Плюс они могут разрабатываться на разных технологиях, например, языках и базах данных.
Пример
В приложении онлайн-магазина могут быть такие сервисы — User/Client Service, Product Order Service и Payment Service. Очевидно, что такой паттерн подойдет только большим приложениям, поэтому использовать микросервисы ради того, чтобы использовать — бессмысленно. В таком случае лучше остановиться на монолите.
На мой взгляд, самая частая проблема — согласованность данных. В монолите гораздо проще её обеспечить средствами единого адресного пространства. Если мы говорим про подходы к декомпозиции, обычно используют декомпозицию по данным или доменной логике.Игорь Панасюкразработчик в физтехе Яндекса, ментор Solvery
Чтобы микросервисы работали четко, нужно грамотно организовать мониторинг, логирование и управление конфигами. Плюс нужен Kubernetes и API-шлюзы.
Event-driven архитектура
Это один из главных трендов последних лет. Подробнее о этой архитектуре можно почитать в нашей статье.
Event-driven архитектура — это подход, при котором компоненты системы взаимодействуют через события. Сами события генерируются при изменении состояния системы и обрабатываются нужными компонентами.
Другими словами, все работает ассинхронно, то есть компоненты не блокируют друг друга, ожидая ответа — они буквально не знают друг о друге, а только реагируют на события. Плюс можно легко масштабировать и добавлять новые обработчики событий.
Пример
Тот же самый интернет-магазин:
- Пользователь создает заказ (создается событие).
- Сервис уведомлений отправляет, например, пуш или письмо юзеру.
- Сервис склада бронирует товары.
- Сервис оплаты проводит транзакцию.
CQRS (Command Query Responsibility Segregation)
CQRS — это паттерн, который разделяет логику команд и запросов. Для чтения (Query) и записи (Command) используются разные модели данных — так можно оптимизировать каждую операцию.
Архитектура здесь будет непростая, поэтому использовать CQRS нужно в приложениях, где будет высокая нагрузка на чтение.
Пример
Это может быть большой блог или медиа. Работать будет так:
- Через Command будут добавляться новые статьи или обновляться предыдущие.
- Через Query будет, например, происходить поиск по ключевым словам или собираться список статей по тегам.
Эти паттерны часто используются вместе. Например, в микросервисной архитектуре можно применять Event-driven подход, чтобы взаимодействовать между сервсами, а CQRS — для оптимизации операций чтения и записи.
Паттерны для масштабируемости
Если у вас падает один компонент, он не должен тянуть за собой всю систему и влиять на пользователей. Для этого есть паттерны, которые повышают надежность приложения.
Circuit Breaker
Этот паттерн работает как электрический предохранитель: если в одном из сервисов вылезла ошибка, например, он медленно отвечает, Circuit Breaker разрывает соединение и предотвращает перегрузку всей системы и каскадные сбои. Так, система не продолжает отправлять неудачные запросы, а переключается в режим ожидания, и затем периодически проверяет, работает ли сервис. Подходит для распределенных систем, где один упавший сервис может повлиять на всю цепочку вызовов.
Например, вы делаете покупку на маркетплейсе, и у в нем упал платежный сервис. В таком случае Circuit Breaker временно блокирует запросы и говорит пользователю, что нужно попробовать оплатить позже.
Load Balancer
Этот паттерн помогает равномерно распределять нагрузку и логический роутинг, предотвращая перегрузку отдельных узлов. Он следит за доступностью сервисов и отправляет запросы только к работающим компонентам. Особенно подходит облачным средах, где количество активных серверов может динамически изменяться.
Например, в стриминговом сервисе Load Balancer распределяет пользователей между разными серверами, чтобы не было перегрузки и они могли спокойно смотреть контент без задержек.
Retry (exponential backoff with jitter)
Паттерн нужен для повторения идемпотентного действия, которое завершилось безуспешно. Так, некоторые ошибки бывают кратковременными, и вместо немедленной блокировки запроса, Retry позволяет повторить запрос, но с увеличенным интервалом между попытками (экспоненциальная задержка). А добавление случайного разброса времени (jitter) помогает избежать ситуации, когда все запросы повторяются одновременно, вызывая новую перегрузку.
Например, вы пытаетесь купить билеты в театр на большую премьеру, но вместе с вами на сайте сидят еще 10 000 человек. Сервер временно перегружен, но система не сразу выдаст ошибку, а будет пытаться найти вам хотя бы какое-то место (где-то на последнем ряду балкона).
Паттерны безопасности
- OAuth 2.0 — стандартное решение. Он позволяет пользователям предоставлять доступ к своим данным сторонним сервисам без передачи пароля. Например, зарегистрироваться на сайте через Google, Яндекс или VK.
- TLS (Transport Layer Security), криптографический протокол, шифрует данные при передаче.
- AES один из самых надежных алгоритмов симметричного шифрования. Его часто используют в базах данных или кеше.
- Token-based Security — безопасная аутентификация с JWT. В основе лежит цифровая подпись (обычно HMAC или RSA) — она гарантирует подлинность данных.
- В финтехе используют HSM для генерации и защиты криптографических ключей на аппаратном уровне.
Выбор паттерна — компромисс между гибкостью, производительностью и надежностью. Главное — учитывать требования бизнеса и технические ограничения. Плюс сейчас практически везде есть искусственный интеллект и облачные технологии, которые позволяют автоматизировать инфраструктуру, например, с помощью Terraform — о них тоже не нужно забывать.
И поскольку даже опытные разработчики сталкиваются с дилеммами при выборе подходящей модели, на Solvery вы можете разобрать ваш проект и получить обратную связь от архитекторов и разработчиков уровня Яндекса, Авито и Т-Банка.