По мере развития большинства корпоративных информационных систем мы можем наблюдать тенденцию к падению производительности и надёжности в течение их жизненного цикла. То, насколько эта тенденция выражена, определяется подходами к проектированию и разработке, которые применяет разработчик с первых дней жизни продукта.
При разработке ESM-платформы SimpleOne и ITSM-системы на её основе мы использовали несколько ключевых стратегий, которые помогли нам заложить надёжный фундамент развития этой продуктовой линейки: DDD, TDD, BDD, минимизация запросов к БД.
Эти стратегии помогли нам выстроить чёткую и понятную архитектуру кода, который обеспечивает работу платформы SimpleOne. Эта архитектура даёт платформе две важнейшие особенности:
- Система стабильно обеспечивает высокую производительность, не снижающуюся в ходе обновлений и доработок.
- Доработки в рамках развития продукта реализуются быстро, дёшево и не приводят к снижению стабильности работы.
Domain Driven Design (DDD, предметно-ориентированное проектирование)
Программирование, которое ориентируется на модели предметной области продукта. В эти модели входит бизнес-логика, устанавливающая связь между реальными условиями области применения продукта и кодом. В нашем случае это область корпоративных сервисных систем.
Приступая к разработке, наши программисты в первую очередь знакомятся с тем, какие процессы происходят в нашей предметной области и какие термины используют участники этих процессов. При этом крайне важно, чтобы термины совпадали с тем, как их используют владельцы продуктов (PO), менеджеры, эксперты предметной области (Subject Matter Expert, SME). Это нужно для того, чтобы все говорили об одном и том же: те, кто ставит функциональные задачи (как это должно работать на практике), и те, кто их реализует (как это будет прописано в коде).
Так выстраивается специфическая архитектура, подходящая конкретно под задачи и процессы крупных корпоративных пользователей. Используемые модели начинают соответствовать процессам, а не тому, как их удобнее хранить.
Два плюса, которые даёт такой подход:
1. Легко корректировать систему. Модель становится близка к реальным процессам. И когда часть процесса меняется, эти изменения программистам видны и понятны. Сразу понятно, в какой части кода следует внести изменение. При этом без DDD нужно было бы либо переписывать весь код, либо разрабатывать «костыли» (делать код длиннее, излишне сложным, из-за чего он становится менее производительным и более тяжёлым в сопровождении).
2. Прирост производительности. Это прямое следствие того, что изменения вносятся легче, а система ориентирована строго на бизнес-процессы.
Особенность DDD — слоистая архитектура. Это деление кода на отдельные слои, которые ответственны за конкретную область / уровень системы. Слоистая архитектура выстраивается посредством интерфейсов, что позволяет впоследствии подменять реализацию одних классов реализацией других классов, если они поддерживают тот же интерфейс.
DDD помогает в проведении тестов, так как определённые классы можно подменять классами, которые ничего на самом деле не делают, но эмулируют нужные события. Когда в тесте не требуется получать достоверные данные, а нужно лишь проверить функциональность, это чрезвычайно полезно и в разы ускоряет соответствующие проверки.
Также DDD-подход характеризуется разбиением на контексты. Это некоторая область программы, которая может быть в достаточной степени изолированной. Один контекст может взаимодействовать с другими контекстами. Это могут быть как контексты внутри нашего собственного продукта, так и обращения к каким-либо сторонним API. Идея в том, что отдельные команды разработчиков работают конкретно в своих контекстах и таким образом пишут более сфокусированный и продуктивный код.
Главное, что даёт нам DDD, — это гарантия того, что платформа SimpleOne останется на там же уровне производительности через условные десять лет, а доработка и сопровождение системы не будут требовать больше ресурсов и времени, чем сейчас. Все необходимые изменения мы сможем вносить, не создавая хаос из нагромождений кода, – всё благодаря чётко структурированной архитектуре.
Что было бы без DDD? Взгляните на рисунок ниже. Первое воплощение системы без различимой архитектуры вполне жизнеспособно, но из-за недостаточного внимания к модели предметной области последующие расширения дадутся такой системе с трудом, рано или поздно она рухнет.
Test Driven Development (TDD, разработка через тестирование)
Если кратко — это повторение коротких циклов разработки: изначально пишется тест, покрывающий желаемое изменение, затем пишется программный код, который реализует желаемое поведение системы и позволит пройти написанный тест. Затем проводится рефакторинг написанного кода с постоянной проверкой прохождения тестов.
В философии TDD-тесты являются спецификацией того, как программа должна вести себя. Если вы рассматриваете свой набор тестов как обязательную часть процесса сборки, если ваши тесты не проходят, то программа не собирается, потому что она неверна.
Эта методология позволяет добиться создания пригодного для автоматического тестирования приложения и очень хорошего покрытия кода тестами, так как ТЗ переводится на язык автоматических тестов, то есть всё, что программа должна делать, проверяется. Неожиданности после прохождения круга dev –> test –> uat –> prod сводятся к минимуму. Также TDD часто упрощает программную реализацию: исключается избыточность реализации — если компонент проходит тест, он считается готовым.
Архитектура программных продуктов, разрабатываемых таким образом, обычно лучше. В приложениях, которые пригодны для автоматического тестирования, очень хорошо распределяется ответственность между компонентами, а выполняемые сложные процедуры декомпозированы на множество простых. Стабильность работы приложения выше за счёт того, что все основные функциональные возможности программы покрыты тестами и их работоспособность постоянно проверяется.
TDD так же, как и DDD, ведёт к облегчению доработок системы: сопровождаемость проектов, где тестируется всё или практически всё, очень высока — разработчики могут не бояться вносить изменения в код; если что-то пойдёт не так, то об этом сообщат результаты автоматического тестирования.
Behavior Driven Development (BDD, проектирование, ориентированное на события)
Самый частый подход при программной разработке – отталкиваться от данных. Например, при проектировании инцидентов разработчик смотрит на их составляющие: дату, автора, свойства. От них он потихоньку выстраивает классы.
При проектировании же от событий разработчик формирует список того, что может происходить с системой. То есть продумывается, как органичнее выстроить функции добавления, удаления инцидентов, выгрузки списка инцидентов, списка последних инцидентов и т. п. Таким образом, мы получаем список действий, которые описывают всю программу в этой части. Мы знаем, что от системы будет требоваться, и исходя из этого проектируем.
Благодаря BDD архитектура системы дружит с потребностями, которые на неё возлагаются. В отличие от варианта ориентации на данные, при котором в итоге можно обнаружить, что под данные-то система подходит, но стандартные действия приходится реализовывать в пять приёмов.
Минимизация количества запросов к базам данных
В сложных системах производительность по большей части упирается в БД. Самая тяжёлая обработка происходит там. SimpleOne старается разгрузить эту составляющую следующими способами:
1. Подход SPA существенно уменьшает количество обращений к БД. Как это происходит? Например, при нажатии на какой-либо пункт меню система не просит сервер заново сформировать все данные пользователя, нужно только переформировать рабочую область в интерфейсе. При этом формировать меню заново не нужно.
2. Следующая составляющая сокращения количества запросов к БД — кеширование. Например, когда пользователь хочет получить информацию со списком колонок в таблице и известно, что с момента формирования прошлого ответа ничего не поменялось, система берёт соответствующую информацию из кеша, не нагружая базу данных.
3. Когда мы работаем с кодом, мы можем делать различную последовательность обращений к БД и различную группировку соответствующих обращений. Это важно, если у пользователя возникает потребность получить табличку с несколькими колонками, которые нужны для её отображения. Итак, каждый запрос получает список из разного количества колонок. При этом каждый раз пользователи запрашивают отличающиеся друг от друга колонки. В этом случае мы не можем использовать данные из кеша предыдущих похожих запросов. Потому что один запрос возвращал восемь колонок, а другой шесть, возможно, других. Однако даже в таком сценарии нам удалось избежать запросов к БД. Ведь если требуется не одна, а много колонок, то почему бы не получить сразу все? Так запрос делается единожды на все колонки сразу (даже если просят не все), кешируется, когда нужно получить информацию о любом количестве колонок из этого сектора, нужные данные отображаются без обращения к БД из кеша.
4. Часто один запрос требует информацию, расположенную глубже. Отдельные части таблиц, колонок и прочего могут требоваться для выдачи формирования ответов на определённые запросы. Например, есть сервис, который уточняет уровень доступа пользователя, сервис, который контролирует структуру данных и их отображение. Эту информацию тоже надо откуда-то брать. То есть мы приходим к той же проблеме обращений к БД: каждый запрос требует не только запрошенную информацию, но и специфические детали. Что приводит к тому, что одни и те же данные запрашиваются у БД по несколько раз. Чтобы решить этот парадокс, мы сформировали агрегат, в котором уже сохранена подобная информация от сервисов, что тоже сильно сокращает количество запросов к БД.
Заключение
У нашей команды большой опыт внедрения и эксплуатации различных корпоративных систем. Мы не раз видели, как на протяжении своего жизненного цикла такие продукты обрастали дополнительными функциями, которые с большим трудом вписывались в уже существующую архитектуру и создавали в ней «костыли» разного масштаба. В итоге продукт в целом продолжал работать, но избежать появления избыточного количества обращений к БД уже не удавалось, что сказывалось на его производительности. Если бы разработчики изначально знали, что всё это понадобится, они бы сделали определённые части кода несколько иначе и число запросов к БД удалось бы сократить. При этом далеко не во всех системах используется слоистая архитектура, что делало для них рефакторинг на порядок сложнее.
Мы же, имея опыт установки и обслуживания подобных платформ, не только обозначили для себя набор функциональности, который потребуется разработать в ближайшие годы, но и заложили в архитектуру возможности, которые помогут нам продолжать развитие проекта в долгосрочной перспективе. За счёт этого разработка идёт более системно, а архитектура поддерживает высокие требования к производительности и надёжности нашей платформы и реализованной на её основе ITSM-системы.