Правила хорошего кода: Для чего нужны паттерны в разработке ПО

Правила хорошего кода: Для чего нужны паттерны в разработке ПО
25.06.2024
#новости Правила хорошего кода: Для чего нужны паттерны в разработке ПО
Правила хорошего кода: Для чего нужны паттерны в разработке ПО

Чистота и порядок: зачем разработчику нужны паттерны


Если описывать высоконагруженные приложения в двух словах, – это сложные многофункциональные системы, состоящие из множества сервисов, каждый из которых обрабатывает поступающие к нему запросы. Понятно, что объем этих запросов за единицу времени (метрика RPS – requests per second, количество запросов в секунду) имеет верхнюю границу: она определяется производительностью аппаратного обеспечения – дисковой системы, на которой расположен сервис. Поэтому масштабирование – способность увеличивать производительность под нагрузкой до заданных пределов – является важнейшим свойством высоконагруженных приложений. При их разработке необходимо с самого начала закладывать возможность работы системы одновременно в нескольких экземплярах с применением параллельных вычислений.


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


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


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


Подходы к взаимодействию сервисов с БД


Доменный сервис связан с CRUD операциями над бизнес-сущностью в БД. Здесь можно отметить шаблон работы с БД. При работе с БД можно выделить два метода взаимодействие с БД:

  • общая БД на несколько сервисов
  • отдельная БД под отдельный доменный сервис.


Каждый из типов имеет свои преимущества и недостатки. В своей практике я чаще всего предпочитаю использовать отдельную БД под сервис. Тут мы получаем несколько преимуществ.


  • Логической инкапсуляции данных за конкретной бизнес-сущностью. Здесь мы понимаем, что таблицы в БД напрямую никто кроме нашего сервиса не использует и можем без дополнительных согласований менять структуру БД, а может даже и тип СУБД для повышения производительности подсистемы. При этом для внешних систем, использующих наш сервис, ничего не меняется. Они продолжают работать с неизменным API.

  • Независимая конфигурация аппаратного обеспечения. Такой подход позволяет гибко работать под конкретные требования по нагрузке. Например, пользователь в интернет- магазине чаще выполняет поиск по каталогу, чем создает заказ. Имея общую БД, мы вынуждены выделять более производительное аппаратное обеспечение, ориентируясь на самый высоконагруженный сервис. При подходе – отдельная БД под сервис – мы можем поступить более гибко. Т.к. за сервисом «каталог товаров» и «заказы» у нас разные БД, то БД высоконагруженного сервиса «каталог товаров» мы можем вынести на отдельную машину с 
  • высокопроизводительными SSD дисками, а БД сервиса «заказы» – оставить на машине с HDD.

  • Но обычно за преимущества приходится чем-то платить, и в данном случае мы лишаемся возможности выполнять комплексные кроссдоменные (бизнес) запросы. Как решать такого рода проблемы, мы увидим при рассмотрении следующего шаблона.


CQRS (Command Query Responsibility Segregation)


Рассмотреть применение этого шаблона можно на примере BFF (backend for frontend) сервисов. В моей практике такие сервисы сочетают две цели: предоставление данных для frontend в удобном виде и разграничение по ролям. Представим, что у нас есть CRM система для менеджеров и в ней есть внутренний календарь. Как бизнес-требование стоит задача рядовым менеджерам давать видеть только свои встречи, а начальникам подразделений – как свои, так и встречи всех сотрудников подразделения. Здесь мы видим, что для работы данного BFF нам нужны данные из трех подсистем: подсистема управления встречами, подсистема управления ролями пользователей и подсистема организационно штатной структуры. Конечно, можно выполнить запрос данных, вызывая каждую из них по отдельности. Но даже в таком не сложном примере вызовов будет несколько, и на каждый из них будет потрачено время, что в сумме может дать неприемлемую задержку. Гораздо удобнее было бы иметь все эти данные в одной БД. И тут мы можем применить наш CQRS шаблон.


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


Подводя итог можно выделить плюсы и минусы такого шаблона.


Плюсы:

  • увеличивается скорость обработки пользовательских запросов, т.к. мы имеем все необходимые данные в своей БД;
  • изолируем нагрузку. Т.к. при таком подходе не вызываем другие подсистемы, то изменения по нагрузке и оптимизации будут касаться только данного сервиса, а не распространяться на связанные подсистемы.

Минусы:

  • дублирование данных. При больших объемах оно может становиться значимым;
  • eventualy consistence. Т.к изменения распространяются асинхронно через очередь, то во время выполнения запроса мы можем получить не совсем актуальные данные. Как пример, можем не увидеть только что созданную встречу в календаре

Подробнее по ссылке.