2024/06/25 15:20:23

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

Паттерны в программировании и проектировании архитектуры программного обеспечения – это устоявшиеся шаблоны, лучшие проверенные практики, применяемые специалистами для решения типовых задач: например, для ускорения процесса разработки и тестирования, повышения качества программного кода и т. д. Одновременно с этим распространено и понятие антипаттернов – таких же устоявшихся наборов действий, которые, наоборот, приводят к негативным последствиям. Подробнее об их влиянии на разработку высоконагруженных приложений рассказывает Степан Бочаров, ведущий руководитель группы компании IT_ONE.

Содержание

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

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

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

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

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

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

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

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

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

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

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

CQRS (Command Query Responsibility Segregation)

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

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

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

Плюсы:

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

Минусы:

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

Подходы к управлению процессами

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

При оркестрации у нас появляется отдельный сервис, который для выполнения бизнес-процесса поэтапно взаимодействует с разными подсистема для достижения результата. Таким образом, мы имеем централизованное место управляющее процессом. Это является как плюсом, так и минусом. Плюс в том, что мы можем в одном месте увидеть, на каком этапе находится наш процесс. Минус в том, что это связующее звено и при выходе из строя этого сервиса, процесс остановится.

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

Принцип единственной ответственности

В заключении хотелось бы отметить этот принцип. Не уверен, что к нему подходит слово шаблон. Этот принцип нам широко известен из базовых принципов разработки SOLID, но также его можно распространить и на проектирование распределенных систем. Следует избегать повторения бизнес-логики в разных подсистемах, вне зависимости как это делается. Через библиотеку или просто копированием. В любом случае при необходимости изменить бизнес-логику мы будем вынуждены это делать в нескольких местах, что приведет к накладным расходам как в реализации, так и тестировании. Если в нескольких местах мы видим схожую бизнес-логику, то ее следует унифицировать и вынести в отдельный сервис. При этом вызов из других сервисов будет проходить с параметризацией.