Я занимаюсь коммерческой разработкой ПО уже 14 лет. В основном работал над корпоративными или очень похожими на них проектами и успел оценить, какие технологии и решения работают хорошо, а какие не очень или даже откровенно плохо.
В этой статье поделюсь взглядом на то, как стоит выбирать инструменты. Свою точку зрения не навязываю, но хочу привести аргументы, почему некоторые решения оказались лучше других. В ответ хотелось бы услышать ваше мнение о том, какие технологии работали или нет именно для корпоративных приложений.
В чем особенность разработки корпоративных приложений
Корпоративные приложения - по крайне мере те, о которых я буду говорить в данной статье - не слишком инновационны. На фоне прочей разработки они выделяются рядом моментов:
Более-менее известны требования, сущности, бизнес процессы и ожидаемый функционал. Функционал достаточно широк, но сходен у большинства приложений.
Имеют большой срок жизни: долго разрабатываются, а потом еще дольше используются, поскольку их тяжело заменить. Длительное время сопровождаются, дописываются и развиваются.
В общем случае количество пользователей варьируется. Но здесь речь пойдет о крупных корпоративных приложениях – с сотнями и тысячами пользователей. Десятки или сотни из них работают одновременно.
Предъявляют средние требования к железу и оптимизации кода. Приложения не дешевы и для организаций не составляет проблем приобрести достаточное количество мощных серверов (под известное количество пользователей).
Предъявляют высокие требования к надежности, целостности и защите данных, аудиту, проверке прав доступа.
Должны интегрироваться с существующими (иногда устаревшими) системами и легко расширяться.
Чтобы реализовать запланированный широкий функционал в разумное время (от полугода до двух лет), да и в целом соответствовать перечисленным требованиям, разработку ведет большая команда - десяток и более специалистов. Уровень опыта и производительность в ней могут сильно варьироваться, поэтому важно на ранних этапах выбрать правильную архитектуру. Не менее важно создавать кодовую базу с учетом лучших практик, чтобы менее опытные сотрудники могли ориентироваться на уже написанные компоненты.
Перечисленные особенности проектов определяют подход к выбору библиотек, технологий, инструментов и прочих артефактов. Их можно разделить на три категории: очень маленькие и легковесные (не важно зрелые или незрелые), большие и зрелые, а также то, что посередине - большие, но недостаточно зрелые. Для корпоративных приложений можно выбирать или легкие, или зрелые. Большие, но не зрелые выбирать рискованно. Причина проста – небольшую библиотеку проще пропатчить своими силами или даже переписать, если что-то пойдет не так. Зрелый продукт обычно имеет удовлетворительное качество и большое сообщество. Для проблем, с которыми сталкиваешься, скорее всего уже есть патч или инструкция по обходу.
А вот с большим, но незрелым (или даже недостаточно зрелым) продуктом, могут быть самые разные неожиданности. Небольшое сообщество не сможет помочь, обходных решений не будет, а самостоятельно доработать, переписать или даже заменить библиотеку будет трудозатратно или технически сложно, даже когда над проектом работает большая команда. Для инновационного продукта или стартапа это может подойти. Но для корпоративного приложения это неоправданный риск.
Кто выбирает технологии для корпоративных приложений
Я не раз видел, как принятие важных решений доверяли сотрудникам, у которых недостаточно опыта и знаний. Даже если это очень хороший программист с большим стажем, ему может не хватать практики использования широкого спектра различных технологий и библиотек, опыта принятия важных технических решений или понимания их последствий. Большой опыт у такого программиста может складываться из работы с одними и теми же технологиями, на одном проекте или в одной компании (или из всего перечисленного одновременно). Это развивает компетенции в решении проблем, а не в выборе технологии. Если специально не заниматься архитектурой, такого опыта недостаточно для определения судьбы большого и ответственного проекта.
Помочь в таком случае может только здоровый климат совместного обсуждения и принятия решений в команде, т.к. именно команде, а не лиду или архитектору лично, предстоит потом все это реализовывать.
С другой стороны, специалист может быть настолько компетентный и опытный, что руководство дает ему несколько проектов. В результате его вовлеченность в каждый из них падает, какие-то вещи идут самотеком. Сохранить качество решений в этом случае можно, только если команда достаточно квалифицирована и ответственна.
Выбор не технического руководства
Еще один важный пункт – когда вас лишают возможности выбора соответствующих проекту технологий по не техническим причинам: в компании принято использовать только определенные технологии, не важно насколько они устаревшие, или кто-то имеет хорошую комиссию с продаж дорогих продуктов.
Для бизнеса выбор существующих технологий действительно может быть более выгодным. Если это не так и вам не удается убедить руководство их поменять, остается либо уйти, либо принять сознательное решение работать как есть.
Базовые архитектурные решения
Ниже приводятся те архитектурные решения, которые при разработке корпоративных приложений для меня работали лучше.
Платформа или прототип
Я успел поработать в разных компаниях - где только мечтали о своей платформе; где мы делали приложение с расчетом, что это будет платформа, и где к моменту моего прихода платформу разрабатывали уже 7 лет. Я создавал платформу сам и работал с решениями крупного вендора, но в целом натыкался только на минусы такого подхода.
Идея вполне логичная: если часто что-то повторяется, то почему бы не сделать платформу и не настраивать ее каждый раз под задачу. Если требования хорошо совпадают с возможностями платформы и есть специалисты с опытом, то должно получиться хорошо. Однако реальность суровее.
Несмотря на схожий функционал, корпоративные приложения имеют свою специфику и требования по интеграции с существующими системами.
В любой платформе всегда чего-то не хватает. Разработка и добавление функционала - дело долгое и трудоемкое.
Возможности расширения всегда ограничены.
UI по настройке обычно используют не аналитики, а программисты, которым это не очень удобно.
После специфических расширений на проекте сложно перейти на новую версию платформы.
Не все заказчики готовы принять зависимость от производителя платформы.
Но выход есть. В противовес термину “платформа” я назвал это “прототипом”. Суть в том, что для разных проектов используется одна архитектура и набор проверенных зрелых технологий (например, на базе Spring). При таком подходе нет вендор лока. Когда постоянно используется один стек, растет компетенция команды, а наработки из одного проекта можно перенести в другой. Когда есть работающий проект, особенно с которым ты успел поковыряться, при добавлении такого же функционала в другой проект его можно использовать в качестве примера. А основу должны создавать опытные разработчики с учетом лучших практик.
Так как это не платформа, ничто не мешает какие-то вещи делать иначе, в некоторых местах использовать другие технологии.
Хороший пример этой тенденции - постепенный закат крупных серверов приложений все-в-одном и расцвет легковесных решений на базе Spring с возможностью подключения любых технологий или своих реализаций.
Монолит или микросервисы
Это очень обширная тема, которая периодически всплывает и на Хабре. Сообщество накопило большой опыт работы с микросервисной архитектурой, вопрос подробно освещается книгах, поэтому я ограничусь лишь своим опытом.
Большие монолитные приложения становятся слишком сложны для понимания, потому что в них все переплетено; они труднее поддаются масштабированию для отказоустойчивости, разные их части не масштабируются по отдельности. С одной стороны, они легче разворачиваются, потому что обычно представляют собой один артефакт. А с другой стороны этот артефакт должен быть максимально хорошо собран и протестирован. И обычно это выливается в то, что крупные монолиты не получается поставлять часто и быстро. Новый функционал до бизнеса доходит позже, а сейчас это становится критично.
Еще одна большая проблема – фиксация на технологиях и версиях, доступных на момент начала разработки, в то время как срок жизни монолита может быть очень долгий.
Но и с микросервисными приложениями на самом деле все тоже не так хорошо, как хотелось бы. Если не уделять достаточного внимания архитектуре, то оно становится сложнее в разработке и поставке, т.е. теряет все свои преимущества.
Классические микросервисы должны быть очень маленькими, так чтобы их было проще переписать, нежели дорабатывать. Каждый должен иметь свою базу данных. Уже по такому описанию становится понятно, что ничего хорошего это не принесет, по крайне мере для обычного корпоративного приложения. Основная часть функций будет реализовываться через несколько микросервисов, им постоянно нужно будет взаимодействовать между собой. Даже при всех возможностях современных инструментов это очень трудозатратно тестировать и менять.
Я работал над двумя крупными приложениями, где было более 20 микросервисов. Даже с таким относительно небольшим количеством постоянно возникала необходимость в одном микросервисе что-то предоставить, а в другом что-то потребить. Мой опыт показывает, что для корпоративных приложений гораздо удобное промежуточное решение - крупные микросервисы. По сути это монолиты, но каждый со своей специализацией. Мы получаем плюсы микросервисной архитектуры (но избегаем части минусов):
независимое развертывание;
индивидуальное масштабирование - это важно, потому что разный функционал производит различную нагрузку. Так мы можем, например добавить экземпляры сервиса отчетов,
легкое понимание роли каждого сервиса;
большую надежность всей системы. Если из-за ошибки в монолитном приложении отказывают экземпляры приложения, система перестает работать. Если же падает только одна подсистема (микросервис), например те же отчеты, то остальная часть функционала системы доступна. Поправить или откатить один микросервис будет проще и быстрее, чем всю систему;
для разных микросервисов можно выбрать разные технологии, например для AI функционала – Python, для трудоемких вычислений - Go, для бизнес логики - Groovy и т.д. Аналогично можно менять версии выбранных ранее технологий.
Event Sourcing, стандартная архитектура или что-то между (Kafka)
Обычно для корпоративных приложений используется реляционная база, в которой лежат изменяемые бизнес-сущности (одна строка - одна сущность). Такой подход можно назвать стандартным. Он продвигается Spring, который изначально и разрабатывался, чтобы упростить написание корпоративных приложений. А Spring Boot вывел это на новый уровень.
В рамках стандартной архитектуры приложение состоит из слоев: UI, контролеры, один или два слоя сервисов, объекты доступа к данным и как их частный случай - клиенты удаленных систем (а также утилиты, модель данных и dto, объекты запуска по расписанию, мапперы и т.д.). Все это прекрасно показало себя в разработке.
Однако иногда без достаточных на то оснований принимается решение использовать другие подходы, например хранение сущностей в виде событий и их изменений - Event Sourcing. Это одно из архитектурных решений, с которыми я не работал лично, но знакомился с материалом и много общался со знакомым, в команде которого решили переписать свою большую систему в соответствии с данным принципом. Они столкнулись со множеством проблем, компетенции архитектора оказались под большим вопросом.
Мое впечатление - это сложнее в разработке, отладке и сопровождении без ощутимых плюсов именно для корпоративных приложений.
Частое требование – интеграция приложений с другими системами компании. Оказалось, что легче интегрироваться не напрямую, а через Kafka и адаптеры унаследованных систем. Адаптеры - те же сервисы, которые независимо работают по расписанию, самостоятельно вызывая другие системы или принимая от них запросы. Они унифицировано предоставляют данные в Kafka и при необходимости могут масштабироваться.
Kafka можно использовать для внутреннего взаимодействия подсистем приложения, например для:
обновления индексов в системе поиска,
создания событий аудита,
отправки уведомлений,
отправки заданий на ресурсоемкие работы, которые выполняются отдельными сервисами-исполнителями.
Один из подходов - использование Kafka в качестве центральной нервной системы корпоративного приложения. Но мне еще не доводилось применять его в таком центральном месте, поэтому я не знаю, не будет ли от этого больше минусов, нежели плюсов.
Изменяемые и неизменяемые классы
Джошуа Блох в своей книге “Эффективная Java” говорит: “по возможности используйте неизменяемые классы”. Идея вроде как хороша. Если классы не меняются, то их проще разрабатывать и использовать, они лучше защищены от ошибок и более безопасны. Но на практике это работает не всегда. Причина в том, что объекты, с которыми мы работаем в корпоративных приложениях, по своей природе изменяемые. Мы сталкиваемся с трудностями, когда для их описания используем неизменяемые классы. А решать их вынуждены разными костыльными методами.
Поэтому если настоящий бизнес-объект изменяемый, то для его представления удобнее использовать изменяемые классы. А возникающие проблемы решать именно по их сути – оптимистичные и пессимистичные блокировки, доступ по ролям, история и аудит, описание переходов и т.д. Кстати, Hibernate хорошо работает именно с изменяемыми классами.
Из этой рекомендации есть одно исключение – Data Transfer Object (DTO). Сама их суть - это что-то передать куда-то (в UI, другой микросервис или систему). Тут можно использовать неизменяемые объекты.
Реактивные или обычные технологии
По разным оценкам реактивные технологии позволяют на 10-30% лучше утилизировать аппаратное обеспечение. Но я работал с микросервисным реактивным проектом. Такие технологии сложнее в разработке, отладке и сопровождении.
Корпоративные приложения большие и сложные, количество пользователей ограничено и постоянно. Нет проблем купить дополнительное железо. А подход в виде крупных микросервисов частично решает проблему эффективности использования аппаратного обеспечения за счет индивидуально масштабирования разных функций. В итоге для корпоративных приложений реактивные технологии возможно не стоят того, чтобы тратить на них ресурсы.
Опять же история от бывшего коллеги. Они решили перейти на реактивный стек. Перешли, но испытывали столько проблем, в первую очередь с транзакциями и более сложной разработкой, что вынуждены были вернуться обратно.
В общем кому-то скучно сидеть на одном месте. Но действительно ли вашему корпоративному приложению нужен реактивный стек? Как вариант его можно применять в конкретных микросервисах, например при проксировании вызовов.
Написать свое или взять open source
У меня были возможности писать что-то свое. Я сполна реализовал свой потенциал велосипедостроения и Not invented here. Это действительно интересно, помогает лучше понять работу больших библиотек, но дает очень мало ценности бизнесу. Ценность может быть только если это та самая инновационная фишка, на которой держится весь продукт. Но в случае с корпоративными приложениями это не так.
Использование открытых (и как я указывал ранее, зрелых) технологий выгодно как бизнесу, так и вам. Бизнес получает полнофункциональный и стабильный компонент с документацией и сообществом. Когда придет новый разработчик, он либо уже будет знать данную библиотеку, либо без проблем разберется. А вы сможете применять данные открытые технологии на другом проекте, на другой работе, в собственных разработках.
Как пример, во многих системах все сами пишут управление пользователями, ролями и группами. И вот на одном проекте мы использовали Keycloak (JBoss, Red Hat). Оказалось, что все невероятно удобно - Spring интеграция по стандартным протоколам, пользователи, роли, группы, кастомные атрибуты, интеграция с другими SSO системами. Да, админка там своя. Но с ней столкнутся администраторы. Пользователи же видят только окно ввода пароля, которое можно изменить через настройки. А убедить заказчика помогает счет на стоимость разработки и допиливания аналогичного функционала своими силами.
Открытые технологии под патронажем крупной фирмы - это самый лучший вариант. Крупная компания старается поддерживать качество, потому что она делает это для себя. Технология не является ее критичным продуктом для продажи. Ее выкладывают, чтобы сообщество в обмен на функционал тоже помогало править ошибки и улучшать качество.
Выбор технологий
Maven или Gradle
В начале карьеры я работал с Ant - системой сборки, в которой можно было указать, какие задачи нужно выполнить, перед тем как перейти к данной.
Появление Maven было своего рода откровением. Это была не просто система сборки. Maven описывал структуру проекта и его папок, весь жизненный цикл, а также предоставлял репозиторий библиотек. Вместо того, чтобы вручную качать нужные библиотеки, достаточно было указать, что и какой версии взять. Maven загружал не только указанную зависимость, но и все, что необходимо для ее работы. Также он мог запускать плагины в виде обычных зависимостей и встраивать их в жизненный цикл проекта.
Maven стал стандартом де-факто и используется практически в каждом Java проекте. И это очень удобно - всегда понимаешь, из каких частей он состоит и как собирается.
В моем понимании Gradle - это шаг не вперед, а назад, ко временам Ant (несмотря на то, что Ant декларативный, а Gradle скрипт - это по сути код). Проблема в том, что сборку в Gradle уже нельзя так просто понять, как проект Maven.
Gradle появился, потому что Maven ограничивает программистов. Он использует похожую структуру папок, те же механизмы зависимостей и многие идеи, добавляя возможность писать полноценный код. Но в течение 14 лет работы мне всегда было достаточно Maven на проектах любого уровня сложности. Один раз очень давно пришлось писать плагин, но больше для удобства. Везде, где я встречал Gradle, он появился не потому, что без него было нельзя, а потому что кому-то этого просто захотелось.
Многие в плюс Gradle ставят более короткие конструкции. Сам по себе этот момент неплох. Но как часто по сравнению с основным кодом вам надо что-то делать в скриптах сборки? И как часто вы это делаете руками, а не копипастом, а среда разработки не предлагает автоподстановку?
Проблема Gradle в том, что это не декларативные скрипты на специальном языке, а настоящий код, который через Gradle API имеет доступ ко всей структуре проекта и элементам сборки. А как известно, если есть возможность делать все, то когда-нибудь кто-то непременно сделает что-то неочевидное. Куски кода с непонятной магией будут что-то подкручивать в проекте и сборке. Формальные правила и ревью не помогут этого избежать, потому что кто-то скажет, что по-другому нельзя.
Но можно же использовать Maven. Если вам нужно будет что-то кастомное, вы всегда сможете написать плагин, который будет красиво подключен и сконфигурирован через явные параметры, скрывая за своим фасадом сложную магию и не забивая ей основные скрипты.
Несмотря на свою многословность, Maven проще в изучении, понимании, использовании и сопровождений. Для корпоративных приложений, которые долго разрабатываются и еще дольше поддерживаются большой командой с разным уровнем знаний, это важнее, нежели возможность Gradle сделать что угодно (что вообще и не нужно).
Java или что-то другое
Для корпоративных приложений на Java стеке, как правило, выбор стоит между Java, Kotlin и Scala.
Когда-то давно я слышал такую версию, что крупным компаниям стоит делать проекты на новых технологиях, потому что так они привлекают наиболее талантливых разработчиков, которые пробуют что-то новое - не застревают на месте, а учатся и развиваются. Но я вижу тут противоречие. Тот факт, что разработчик любит изучать новые технологии, никак не доказывает то, что он готов добивать задачи и проект до конца на уже выбранных технологиях, и что через полгода-год он не уйдет в другую фирму, где будет что-то посвежее. Новые технологии - это всегда риск, а для корпоративных приложений он просто не нужен.
Java достаточно долго не развивалась. Было понятно желание использовать более удобные конструкции, особенно у тех, кто знаком с другими языками. Но период стагнации в прошлом. В Java появилось много интересных вещей. Такие библиотеки как Lombok, MapStruct, Spring AOP сделали разработку корпоративных приложений удобнее. Поэтому другие JVM языки просто утратили преимущество в том, что они более современные. А теперь представьте, какое количество компаний поддерживают Java, каковы масштабы Java сообщества, сколько на рынке именно Java разработчиков.
Kotlin разрабатывали 5 лет и он вышел через год после Java 8. Определенные преимущества у него есть. Но для корпоративных приложений они не играют большой роли, а некоторые даже запутывают, несмотря на одну из декларируемых целей языка - быть проще в чтении и написании.
Я уже какое-то время разрабатываю на Kotlin, но не вижу никаких критических преимуществ для корпоративных приложений. Зато вижу недостатки:
хуже работает в IDEA, несмотря на то, что создатель у них один,
как язык, Kotlin менее понятный и иногда даже более многословный, что идет вразрез с целями его создания,
меньше разработчиков, меньше сообщество,
меньше крупных поддерживающих фирм,
встречались проблемы с производительностью,
навязанная null безопасность, которая больше мешает, чем помогает при наличии того же Optional,
и т.д.
Для фирмы-разработчика IDEA заняться своим языком было однозначно верным выбором, особенно когда Java не развивалась. Возможно, Kotlin - это хороший вариант для Android. Но для современных корпоративных приложений никакой необходимости в использовании Kotlin нет.
Один из плюсов Kotlin - это возможность создавать DSL. Поэтому в микросервисном приложении, где для каждого сервиса можно выбирать свой стек технологий, ничто не мешает реализовать кусочек бизнес-логики с DSL на базе Kotlin.
Spring (также здесь Mapstruct, Liquibase)
Spring (и Spring Boot) - очень функциональный, зрелый и действительно удобный фреймворк, который реально ускоряет разработку корпоративных приложений и делает лучше их архитектуру. Вместе со Spring удобно использовать другие библиотеки - Liquibase, Lombok, MapStruct и т.д.
Поэтому я ужасаюсь, когда для сложного и ответственного корпоративного проекта кто-то пытается протолкнуть что-то вместо Spring, часто даже не имея опыта работы ни со Spring, ни с предполагаемой заменой. Особенно это неприятно, если это что-то еще сырое, разрабатывается в сотни раз меньшим сообществом и имеет неизвестное количество багов. Это большие риски для бизнеса.
Исключением могут быть только небольшие микросервисы в составе приложения, которые можно написать на новых фреймворках, например специально разработанных для быстрого запуска в облаке (Quarkus). Но использовать это как основу всего корпоративного приложения очень рискованно.
Hibernate или что-то другое
Тут приведу один пример. Уже лет 10 практически везде для корпоративных приложений используется Hibernate. Но в одной фирме для большого крупного корпоративного приложения человек, принимающий решения, выбирает MyBatis. Я прошу его привести хоть какие-то аргументы в пользу этого выбора. На что он говорит нечто о том, что Hibernate неудобен и “нет времени спорить”.
А когда на это есть время?
Ошибки в требованиях и архитектуре - самые дорогие. С MyBatis мы вынуждены были строгать кучу CRUD SQL запросов по 20 полей, хотя Hibernate сделал бы это сам.
Hibernate и Spring Data сделают за вас огромную кучу рутинной работы и практически никак не будут мешать, если вы захотите напрямую поработать с базой. Вот пара примеров:
При использовании нативных запросов в Spring Data репозиториях Spring сам помещает это в проекции (доступ через методы интерфейса, которые вы сами напишите).
Spring Data позволяет писать запросы через названия методов, т.е. пишете название метода определенным образом, указываете параметры и все.
Огромное количество методов доступно при наследовании от обобщенных репозиториев. Репозитории также просто открыть на rest доступ, защитив ролями.
Конечно, у Hibernate и Spring Data есть свои проблемы, как фундаментальные, так и просто баги каких-то комбинаций возможностей. Но для корпоративных приложений, где много сущностей и еще больше полей, все эти инструменты просто незаменимы.
Angular, React, Vue, .. ? Bootstrap, Clarity UI
Хотя последние годы я концентрируюсь в основном на архитектуре и серверной части, всегда был full stack разработчиком и успел поэкспериментировать с библиотеками для фронта. Начинал еще с prototype и jquery. Немного поработал с Vaadin (писал админку). Много работал с ExtJs, Dojo. Написал по одному небольшому приложению на Angular, React и Vue.
Когда-то давно ExtJs был идеальным для корпоративных приложений – удобное и быстрое создание форм и табличек с всевозможным функционалом, панелями инструментов, деревьями и т.д. Но потом они начали играться с лицензированием (вместо 1к$ минимальная стоимость стала 5к$), убрали полезные пользовательские комментарии в документации, стали переходить на менее приятный новый UI и движок… В целом у меня создалось ощущение их морального устаревания и я перестал следить за ними.
Поначалу Angular я не оценил. Порог вхождения даже для меня, Java программиста, был очень высок. Многие вещи - тот же роутинг, но не только он - даже в старших версиях делаются каким-то костыльным образом. В общем было ощущение неудобства из-за многословности и перегруженности.
Vue показался невероятно приятным в работе. С ним также легко, как когда-то давно было с jquery. React оказался где-то посередине.
Я бы так и недолюбливал Angular, но в одной фирме он использовался почти на всех проектах, с которыми я сталкивался. Это помогло мне провести параллель - я и сам, как серверный разработчик, предпочитаю экосистему Java, несмотря на ее перегруженность, и мне удобно на ней разрабатывать. С точки зрения корпоративных приложений Angular имеет одно неоспоримое преимущество - задает строгую архитектуру и единообразие, чего нет в React и Vue. Это особенно важно для команд с разным уровнем и опытом и на длительных проектах, над которыми работает большое количество людей.
По поводу UI опыт показал, что Bootstrap легок в использовании и кастомизации под дизайн заказчика. При этом результирующие приложения выглядят совершенно отлично от оригинального Bootstrap. Как вариант можно выбрать открытую UI-библиотеку с широким набором компонентов, руководствами по правильному дизайну, корпоративным видом и поддержкой крупной фирмы (например, Clarity UI).
SQL vs NoSQL (также Elasticsearch)
На фоне реляционных БД, NoSQL имеют ряд преимуществ. В частности, они более отказоустойчивы и имеют широкие возможности масштабирования. Но в корпоративных приложениях, где достаточно кластера и бэкапов, это просто не востребовано. Зато есть целый список аргументов против их использования:
Сущности корпоративных систем часто сильно связаны друг с другом, при этом в запросах различных данных учитываются эти связи. Это хорошо реализовано в реляционных базах данных, но гораздо хуже в NoSQL, где упор делается на одну сущность.
Для корпоративных систем гораздо важнее ACID характеристики, нежели возможность работать в рамках CAP.
Целостность данных должна быть для любой завершенной транзакции, а не когда-то в произвольный момент в будущем. Хотя такие инструменты пытаются добавлять в NoSQL базы, например MongoDB.
Когда-то давно я читал много сравнений баз и даже хотел использовать MongoDB для системы электронного документооборота. Я думал, что это позволит сэкономить трудозатраты, сделать системы электронного документооборота быстрее и таким образом быть эффективнее конкурентов.
Но экономия в начале приводит к потерям впоследствии. На старте функционал действительно появлялся быстрее. Но чем дальше, тем сложнее реализовывать новые требования.
Опыт показал, что гораздо безопаснее для корпоративного приложения взять современную SQL базу данных. Особенно когда там встроены NoSQL возможности. Например в PostgreSQL можно не только положить json, в том числе в оптимизированном формате, но и делать индексы по полям json объекта, и использовать эти поля в запросах.
У Elasticsearch здесь отдельная роль. В любой корпоративной системе нужна фильтрация и сортировка данных по полям, при этом обычно всегда требуется пагинация. Проблема в том, что таблица UI может содержать данные из разных источников, а это значит, что не существует нормального способа сортировать по одному источнику данных, а фильтровать по другому. Проблема решается разнообразными костылями. Например при каких-то запросах можно догружать данные из одного источника, где-то отключать пагинацию… Elasticsearch все упрощает.
Корпоративные системы обычно не содержат большого объема данных, по крайне мере в тех разделах, где хранятся обычные сущности, добавляемые и редактируемые руками (аналитику big data мы сейчас не берем в расчет). Поэтому можно просто сложить все в Elasticsearch. И каждое изменение отправлять туда же. Какая-то задержка получения данных словарями и таблицами из Elasticsearch, безусловно, будет. Но для корпоративных приложений это обычно не критично. Плюс современные системы могут мержить данные, которые еще не попали в индекс, с результатами поиска в индексе. Решение получается универсальным для всех справочников и таблиц, соответственно удобным для добавления новых сущностей или правки существующих.
Инфраструктура
Один репозиторий или много
По классике для каждого микросервиса рекомендуют создавать свой отдельный репозиторий. Но с большим количеством репозиториев трудно работать. На одном проекте так начали, но впоследствии слили все в один репозиторий.
Другой вариант – разбивать на репозитории по большим функциональным блокам или командам. Оказалось удобным разделение на бэк, фронт, приемочные тесты и инфраструктуру. Даже если команды по бэку и фронту большие и делятся на подкоманды, обычно границы между ними не такие строгие, люди переходят. И в целом не возникает проблем, что большой функциональный блок работает только с одним репозиторием и туда идут все коммиты.
Сколько нужно тестов, приемочные тесты
По идее чем больше тестов, тем лучше. Но польза от тестов разная и они требуют очень много усилий. Не каждый работодатель/заказчик готов оплачивать эти усилия.
Приведу определение типов тестов, чтобы мы говорили на одном языке.
Юнит тесты – тестируют небольшой кусок кода, обычно метод, а в качестве зависимых объектов используются только моки и заглушки.
Интеграционные тесты – тестируют какую-то функцию. Используются как мок-сервисы, так и реальные объекты, в том числе настоящие базы, брокеры сообщений и т.д. Удобно запускать тестовый Spring контекст вместе с MVC сервером, Embedded Postgres, Test Containers и т.д. и тестировать rest запросы.
Приемочные тесты – тестируют отдельно запущенное приложение через API или UI, обычно как черный ящик, но могут что-то положить или запросить из БД.
Практика показала, что юнит тесты для корпоративных приложений самые бесполезные. По мере внесения изменений их постоянно надо править, поэтому они требуют много трудозатрат, особенно на первых этапах разработки, когда много что меняется. При этом они практически никогда не показывают реального регресса и постоянно ломаются из-за изменений в коде.
Приемочные тесты самые полезные, потому что они эмулируют пользователя. Если они находят ошибку, эту ошибку нашел бы пользователь (или тестировщик). Но они также хрупкие, потому что изменяется UI. Эти тесты достаточно медленные, даже если используются ожидания не по времени, а по условиями.
Одно из решений - разделить приемочные тесты на несколько наборов:
смоук набор, который содержит только основные функции и может быть запущен при слиянии реквеста;
быстрый набор, который запускается каждую ночь и содержит базовый функционал;
медленный набор или специфичные наборы, которые запускаются в выходные, раз в месяц или по необходимости.
Интеграционные тесты - это некоторый баланс. Они менее хрупкие, чем приемочные, и при этом гораздо лучше показывают регресс, чем юнит тесты. Поэтому я бы порекомендовал максимально сосредоточиться на интеграционных тестах, т.е. не принимать новый код, пока он не будет ими покрыт. А для приемочных тестов стоит иметь отдельного специалиста-автоматизатора, и какое покрытие он будет в состоянии обеспечить, на таком и остановиться. Это важно, потому что если специалист по тестированию уходит в отпуск, то он ничего не тестирует. А приемочные тесты можно запускать и ночью, и в выходные, и когда автоматизатор в отпуске. Даже если какие-то тесты будут падать по причине изменений, другие все равно будут показывать статус проекта.
CI, CD, Docker, Kubernetes, Nexus, мониторинг
Думаю все понимают, что сборка и развертывание должны выполняться автоматически. И чтобы разгрузить персонал (и нанимать его меньше), и чтобы уменьшить количество ошибок при ручном выполнении операции. Это не сложно реализовать для корпоративных приложений. Но есть нюанс – скрипты сборки, развертывания и инфраструктуры должны существовать в виде текстовых файлов и храниться в системе контроля версий, чтобы всегда была возможность выяснить кто, что и когда менял.
Часто на практике на CI/CD сервер все ходят под админом, могут что-то сломать. Выяснить, что это сломано, можно через большой промежуток времени. А хранение в системе контроля версий дает возможность все удобно откатить, скопировать и т.д. Тот, кто уже поработал с этим на уровне текстовых файлов и Infrastructure-as-Code, просто не захочет возвращаться к UI.
Части системы должны быть упакованы в артефакты, корректно индивидуально версионированы и храниться в репозитории артефактов, например Nexus. Это позволяет быстро разворачивать любые сборки системы через указание версий подсистем/модулей, откатываться на предыдущие версии и т.д.
Как правило, корпоративные приложения запускаются в нескольких экземплярах на разных физических машинах с целью повысить надежность. Упростить ручное управление ресурсами и мониторинг состояния помогает дополнительный слой абстракции в виде Kubernetes.
Имеет смысл добавлять метрики и собирать их в одном месте даже для тестовых развертываний, чтобы как можно раньше увидеть проблемы, если они существуют. Тут себя хорошо показали Grafana и ELK
Организационные решения
Почти все сейчас работают по какой-то уменьшенной версии Scrum. Я прочитал и всем рекомендую книгу Essential Scrum: A Practical Guide to the Most Popular Agile Process. Очень хорошее описание Scrum.
Считаю Scrum удобным, если не зарываться в формализм. Например, можно оценивать задачи в условных часах, а не story point. Так мозгу гораздо удобнее и ничего не мешает подстроечный динамический коэффициент работы команды применять не к story point, а к условным часам.
Ретро можно проводить реже, чем раз в итерацию, если там нечего говорить. Planning poker можно начинать в уме, представив трудоемкость задачи, потом попросить всех высказать свою оценку, затем согласовать и т.д. Техдолги нужно не просто фиксировать, а исправлять. Правильные решения позволят делать это проще.
О Kanban я имею лишь общее представление. Но мне понравилось, как один из руководителей Яндекс говорил о том, что когда у них все спокойно, работа идет по Scrum, а когда начинает гореть, команда переходит на Kanban.
Другая реальность
Для тех у кого возник вопрос, а почему бы это все не написать на HTML, CSS, JavaScript и PHP. Или на NodeJs. Или на фреймворке N…
Я тоже задавал себе этот вопрос. Раньше.
Почему бы СЭД не написать на серверном JavaScript? Все разрабатывалось бы гораздо быстрее и проще. К чему эти чудовищно перегруженные Java, Spring, Hibernate и даже SQL, когда JSON объект можно напрямую положить в базу целиком? Я даже начинал писать СЭД на JavaScipt и действительно реализовывал функционал с большей скоростью.
Ответ на этот вопрос - конечно же можно. Но проблема в том, что корпоративные приложения вроде и не сказать, что особо сложные, но очень объемные. Много сущностей, бизнес процессов, таблиц, форм. Все это очень сильно друг на друге завязано. И чтобы даже один человек мог все это держать под контролем, нужны границы. Я уже не говорю о ситуации, когда 10 и более человек работают над такими объемными системами годами.
Все эти технологии не только создают границы, благодаря чему разработку системы можно держать под контролем, но и предоставляют общий функционал. Те же SQL БД абстрагируют работу с файловой системой и позволяют получать определенные данные, необходимые корпоративному приложению, которые напрямую было бы очень тяжело получить. Технологии позволяют выстраивать крепкий каркас у системы. Она становится менее гибкой, но более устойчивой к деградации, когда десятки людей годами вносят доработки.
Согласитесь, на BASIC писать гораздо проще (особенно на старых версиях с GOTO). Ведь оформлять классы, методы, параметры вместо глобальных переменных - это долго. Но никто не пишет на нем большие системы. На нем можно быстро написать что-то очень маленькое - решение квадратного уравнения или какой то скрипт для Word. Но системы на BASIC, которые надо постоянно читать и что-то добавлять, просто никто не сможет понимать. Поэтому для какой-то небольшой доски объявлений, конечно подойдет NodeJs или фреймворк N, а вот для большого корпоративного приложения - далеко не факт.
Заключение
Все зависит от вашего проекта. Для корпоративных приложений следующий моменты оказались удобными и работающими:
Написание не платформы или обобщенного приложения, а конкретной реализации для данных требований, но с учетом современных достижений и лучших практик
Крупные микросервисы с общей базой данных показали себя хорошо
Обычная Spring архитектура гораздо понятнее и удобнее в работе
Для интеграции систем и подсистем удобно использовать брокер сообщений, например Kafka
Использовать везде неизменяемые классы неудобно
Реактивные технологии трудоемки в работе и должны использоваться только там, где нужно выжать из железа все и характер решаемых задач это позволит, что не совсем актуально для корпоративных приложений
Написание своих компонентов - часто слишком трудоемкий и тупиковый путь
Нужно выбирать зрелые и проверенные технологии и библиотеки
Отдавать предпочтение более распространенным и простым в понимании и использовании технологиям (даже в некоторый ущерб удобству, что может быть субъективно) – Maven, Java, и т.д.
Использовать де-факто технологии для корпоративных приложений – Spring и Hibernate
Angular по совокупным показателям оказался удобнее для крупных корпоративных приложений, нежели другие фреймворки
Не стоит отказываться от SQL базы для корпоративного приложения, а обобщенные табличные данные и словари лучше складывать в Elasticsearch
Удобно небольшое количество репозиториев по крупным функциональным блокам – бэк, фронт, тесты, возможно инфраструктура
Много тестов, особенно полезных – интеграционных и приемочных - улучшают качество и уменьшают нагрузку на QA команду, особенно в области регресса
Сборка и развертывание должны быть описаны в текстовых файлах, храниться в системе контроля версий и легко запускаться при обновлении веток или по нажатию кнопки
Части системы должны быть запакованы в артефакты (jar, docker) и корректно версионированы
Должен быть настроен мониторинг
Выбор правильных решений делает более удобным работу с проектом, а также позволяет проводить рефакторинг, что необходимо для предотвращения усталости и хрупкости системы
Главное то, что решения должны быть практичными. Чересчур сильное стремление к идеалу приводит к худшим результатам, чем не очень красивое, но более практичное решение (я к сожалению это встречал много раз). Классические великолепные антипримеры https://habr.com/ru/post/153225/ и https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition. При принятии решений стоит вспоминать про них.
Как-то просматривая ответы на вопрос, какой git flow выбрать, я встретил ответ – выбирайте то, что именно вам удобно и именно для вас будет работать. Это применимо и к выбору технологий для корпоративных приложений. Выше я поделился тем, что было удобно и хорошо работало (и тем, что было неудобно и работало не так хорошо) в командах, где я работал. Буду рад услышать в комментариях, что удобно и работает у вас :)
Автор статьи: Анатолий Раковский, редактор Екатерина Дерик
P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на наши страницы в VK, FB, Instagram или Telegram-канал, чтобы узнавать обо всех наших публикациях и других новостях компании Maxilect.
Комментарии (4)
XaBoK
02.02.2022 14:53+1Хороший лонгрид. Ваши выводы согласуются с моим опытом. Хоть мой стек с десяток лет в .нет, но обоснования для того или иного выбора технологий и архитектуры всё те же. Идеально сочетать разные варианты в одном решении. Очевидный пример - реляционая база данных для операций, а история/архтив в nosql.
LaoTsing
02.02.2022 22:11Более 5 лет пишу на Kotlin в Enterprise в реактивном стеке Webflux: ERP, CRM, СЭД - псевдофункциональный язык в 100 крат читабельнее Java, да и код в купе с реактивным стеком в 5 раз меньше становится. При этом, все сущности в БД хранятся только как NoSQL в JSON-формате со всеми внешними связями между собой без проблем. В Hibernate невозможно отключить или очистить кэш 1-го уровня со странным поведением - это реальная угроза для серьёзного Enterprise. Keycloak имеет ограничение при работе с моделью SaaS, когда производительность обрушивается до нуля - проблема выявлена в 2016 году и не решена до сих пор. Тесты позволяют выявить не более 30% от всех ошибок и используются для фиксации реализованного функционала.
Написание своих компонентов - часто слишком трудоемкий и тупиковый путь
абсолютно неверно! в каждом проекте всегда придумываю и внедряю новые технологии, что позволяет уменьшить сроки сдачи проекта в несколько раз, но причина такого подхода в другом - зрелые и проверенные годами технологии, а также именитые от Oracle при высоких нагрузках и инфраструктурных стесс-тестах показывают слишком много проблем. И только такой подход позволяет повысить производительность, стабильность, безопасность, обойти ограничения, нивелировать неуправляемость и неоднозначность поведения ширпотребных технологий - и уже как бонус получить ускорение реализации проекта. Описание технологий в статье слишком поверхностная.
djamali
03.02.2022 09:06Да java , круто конечно, но на laravel либо symfony можно написать неплохое И(-э, ы)нтерпрайз приложение.
z0ic
Подходит любая технология с приставкой Enterprise. Некоторые корпорации даже VisualBasic не брезгуют (я имею ввиду Dassault Systemes и её флагманский продукт Catia). Я удивляюсь, как ещё не выпустили Node.js Enterprise Edition сжирающий 500GB ОЗУ и ежемесячно требующий вагон денег для обслуживания. Как мы знаем, любой hello world можно превратить в Enterprise с помощью большого количества костылей https://github.com/Hello-World-EE/Java-Hello-World-Enterprise-Edition.