Главная Веб-разработка Паттерны проектирования архитектуры ПО – Tproger

Паттерны проектирования архитектуры ПО – Tproger

от admin

В разработке ПО выбор правильных паттернов архитектуры может сильно повлиять на производительность и масштабируемость. Эксперт Solvery расскажет о топовых практиках и их применении.

074 открытий410 показов

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

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

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

Паттерны проектирования архитектуры ПО - Tproger

Игорь Панасюк

разработчик в финтехе Яндекса, ментор 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. Работает это так:

Паттерны проектирования архитектуры ПО - Tproger

Пример — пользователь хочет авторизоваться на сайте:

  1. Юзер вводит логин и пароль окно входа — через View.
  2. View отправляет данные в Controller.
  3. Controller передает логин и пароль в Model, которая проверяет их в базе данных.
  4. Model возвращает результат (успешная или неуспешная аутентификация).
  5. Controller обновляет View — он выводит либо сообщение, что все успешно, либо ошибку, например, «Неверный логин или пароль».

MVVM (Model-View-ViewModel)

В этом паттерне тоже три компонента, но немного другие: Model — тоже отвечает за логику, View — UI, который поддерживает привязку данных (data binding), ViewModel — преобразует данные Model для View, содержит команды для обработки действий. Вот схема:

Паттерны проектирования архитектуры ПО - Tproger

Пример — пользователь хочет добавить новую задачу в список.

  1. Пользователь вводит новую задачу в поле ввода через View.
  2. View автоматически передает данные в ViewModel через двустороннюю привязку.
  3. ViewModel обновляет Model, например, заносит новую задачу в базу данных.
  4. Model говорит ViewModel, что данные изменились.
  5. ViewModel обновляет View, и на экране появляется новая задача, но без явного вызова апдейта интерфейса.

MVP (Model-View-Presenter)

В этом паттерне лежат: Model, View — здесь это пассивный UI, Presenter — посредник между View и Model, плюс в нем есть логика отображения. Работает это вот так:

Паттерны проектирования архитектуры ПО - Tproger

Пример — приложение, где есть чат.

  1. Пользователь набирает сообщение и нажимает «Отправить» или просто кнопку через View.
  2. View передает этот текст в Presenter, и он вызывает Model, который отправляет сообщение на сервер.
  3. Когда сообщение отправлено, сервер возвращает подтверждение Model, а он говорит об этом Presenter.
  4. Presenter обновляет View, добавляя это сообщение в интерфейс.
  5. Когда приходит новое сообщение от друга/коллеги, Model получает его из сети и передает Presenter.
  6. 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 архитектура — это подход, при котором компоненты системы взаимодействуют через события. Сами события генерируются при изменении состояния системы и обрабатываются нужными компонентами.

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

Пример

Тот же самый интернет-магазин:

  1. Пользователь создает заказ (создается событие).
  2. Сервис уведомлений отправляет, например, пуш или письмо юзеру.
  3. Сервис склада бронирует товары.
  4. Сервис оплаты проводит транзакцию.

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 вы можете разобрать ваш проект и получить обратную связь от архитекторов и разработчиков уровня Яндекса, Авито и Т-Банка.

Похожие статьи