Pull to refresh

Comments 58

А почему бы вместо java не использовать какой-нибудь популярный скриптовый язык, который не требует деплоя, для разработки бизнес приложений? За счёт низкой строгости и динамической типизации дополнительный выигрыш в простоте и времени разработки, выигрыш в стоимости инженерных кадров и т.д. и т.п.?
В первую очередь, дело в том, что мы занимаемся разработкой java-приложений.
Это основная причина того, что мы решаем проблему hot deploy именно таким способом.

А сравнение Java с другими языками — это тема отдельной статьи (или холивара).
А не боитесь, что вашу компанию могут «подвинуть» конкуренты, которые смогут предложить более низкую цену и меньшие сроки разработки решений (за счёт использования узкоспециализированных инструментов, а не java для всего на свете)? Или вы монополисты на своём рынке?
А можно поинтересоваться, какие вы имеете ввиду узкоспециализированные инструменты?
Скажем так — мы сами для себя разработали такой инструмент — это наша платформа CUBA. Она дает нам реальные конкурентные преимущества. Если вам интересно — можете ознакомиться подробнее — нам будет приятно.
Я как раз знакомлюсь, вижу IDE, стандартный код, и, как следствие, автогенераторы CRUD, очень интересно. Как правило, на java и Spring разработка CRUD приложений представляет бОльшую сложность, по сравнению с узкоспециальными средами/инструментами, поэтому и спрашивал как вы с этим боретесь.
А как в CUBA дела с валидацией обстоят, есть фреймворк? Есть ли стандартные виджеты для работы со списками (фильтры, пагинаторы и т.п) и полями ввода (автокомплит, мультиселеки и т.п.)? Шаблоны представлений?
У нас довольно богатый функционал UI — есть таблицы, деревья, фильтры (задаваемые пользователем в UI и исполняемые в виде преобразованного SQL), много разных полей ввода, поиска. Использование платформы может сильно помочь в проектах с большим количеством форм ввода, таблиц, графиков. Никаких сложностей при создании CRUD в приложениях на платформе мы давно в глаза не видывали.

Наши приложения сильно отличаются от классических Java + Spring MVC, и больше тяготеют к большим десктоп-решениям.

Список основных возможностей
Библиотека визуальных компонентов
Вопрос, конечно, задавался автору, но я могу сказать, что по моему опыту, в большом приложении от динамической типизации будет больше проблем из за низкой строгости и отсутствия проверок компилятора. А с учетом современной поддержки java со стороны IDE, почти с полной уверенностью могу сказать, что и удобство будет вполне сравнимое, если не выше для больших поектов.
в проектах были сотни экранов

Моя фантазия затрудняется придумать проект с сотней экранов (
Возьмем простой пример.

Один из наших проектов — система, управляющая бизнесом коллекторов в Великобритании. В ней 434 экрана (специально посмотрел).

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

Например, есть экраны просмотра и редактирования сущностей. У нас 150 сущностей и для каждой нужно 2 экрана — экран списка и экран редактирования. Если у нас 100+ сущностей — экранов будет 200+. На каждом экране могут быть доступны действия связанные только с конкретной сущностью, разные специальные поля или колонки.

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

Так что у нас именно 400+ экранов, потому что система довольно большая и содержит массу функциональности.
Я думаю, что многие корпоративные приложения имеют и гораздо больше экранов. Это, если так можно выразиться, у них в крови. Очень много пользовательской функциональности.
Это скорее следствие отсутствия стандартизированного CRUD интерфейса, который подразумевает 4-5 стандартных представлений («экранов») для любого объёма функциональности любой системы.
Взгляните пожалуйста на такой экран.

Как вы думаете — возможно ли его реализовать с вашим подходом?

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

Про доработку под пользователей это точно, а еще бывает, что «в старой системе было не так, мы привыкли, сделайте как было».
Да, заменять старые системы наверное тяжелее, чем писать с нуля. Мы не раз с таким сталкивались.

Самое же противное — параллельная работа старой и новой системы.
Да, страшновато. Но очевидно же, что выводятся стандартные представления, а интерфейсных шаблонов (комбинирующих представления на одном экране) действительно много, но конкретных представлений информации 4-5.
Но в секции импорта же могут быть не все зависимсоти, нет? Например если статическая констата используется с полным путём к ней, то в секции импорта её не будет.
Вы совершенно правы. Full qualified imports действительно не поддерживаются. В последней версии своего класслоадера мы решаем эту проблему.
Т.е. для деплоя на продакшн тоже используется такой подход хот-деплоя?
В какой то степени да. Часть системы может быть реализована так, что задеплоить ее можно исходными кодами.
В основном конечно мы стараемся на продакшен деплоить jar-ки и только в особых случаях подкладывать исходники, чтобы поменять поведение системы.
Отличная статья, интересно было почитать!
Для разработки я использовал DCEVM (вот статья habrahabr.ru/post/236075/ ) Для прода не подойдёт, т.к. там JVM подменяется, но для локального использования самое то — бесплатен и хорошо работает.
Про jRebel согласен — стоит очень дорого. Я даже не представлю такого use-case для себя, чтобы он окупился.
P.S. Спасибо за выкладывание classloader'а на GitHub!
Спасибо за ссылку, почитаю про DCEVM, раньше про такое не слышал.
JRebel не стоит больших денег. У JRebel есть вполне определённый ROI и его легко посчитать. Затраты отбиваются за пару недель. Иногда медленнее, иногда быстрее.
А в остальном, да — JRebel для продакшена использовать не надо. Более того, это запрещается лицензией.
В первую очередь, спасибо за комментарий (а то я уже думал, что вы не заглянете совсем).

Я предлагаю взглянуть на этот вопрос с нашей точки зрения. В нашей компании 100+ человек. Для простоты давайте представим, что нас ровно 100.

JRebel по последним данным на сайте стоит $365 в год. Таким образом затраты на компанию в год составят $36500. Это довольно большие деньги. Согласитесь, что если бы у вас была возможность их как то сэкономить — вы бы это сделали. Мы для себя создали такую возможность.

Я при этом не утверждаю, что наше решение лучше, чем JRebel. Просто конкретно для нас оно подходит лучше.
Несомненно абсолютные цифры на бумаге выглядят устрашающе. Но ведь ROI от этого хуже не становить, а при таких покупках обычно можно срезать довольно большие суммы при помощи скидок. Если все купленные лицензии будут использованы, и все, кому это в команде надо, получат от этого пользу, то цифры на бумаге — это курам на смех :)

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

P.S. если уж ждали комментария, так стучались бы в личку :)
Ну для меня математика проста — я пользуюсь IDEA, она покрывает процентов 70 моих потребностей на работе — тут и Java, Groovy, Python, Gradle, возможность подключаться к СУБД, редактирование xml, xslt. И при всём этом она обошлась мне в этом году долларов за 60 (случайно при продлении лицензии попал на скидку в 40%). В прошлом году она для меня стоила $200. И тут JRebel, который (конкретно в моём случае) может только чуть чуть ускорить деплой изменений при разработке. Это, в процентном соотношении, не более 2% всей моей работы. И при этом есть бесплатные альтернативы. Да, не такие мощные, как jRebel, но часть его функций могут покрыть. И при этом стоит он $365, а это в 6 раз дороже моего основного инструмента для работы. В общем, для меня нет ни малейшего повода его покупать.
P.S. Спасибо за коммент.
Вполне разумное сравнение. Ценообразование — это штука тонкая. И уверяю вас, цена на JRebel не взята «с потолка» :) Вот вы пробовали реально посчитать — откуда цифра 2%?

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

Поэтому кажется, что тесты для разработки UI — это overkill. Для всего остального — да, тесты сильно ускоряют разработку.
Видимо, важна методика. Многие верят что используя Selenium, или тот же Selenide можно легко писать UI тесты. Но я соглашусь — это трудозатраты.
А кстати, хороший вопрос! Что вы понимаете под тестированием пользовательского интерфейса? Полную имитацию нажатия на кнопочки, галочки, выбор из списка и т.п.? Или проверку логики работы UI? Я в работе использовал и использую два подхода для тестирования UI — и полная имитация пользователя (например, с помощью того же пресловутого Selenium'а) и тестирование логики UI (MVP в этом оооочень сильно помогает). Так вот, тестирование только логики, на мой взгляд вполне оправдано — при это покрывается очень большая часть функционала. А нетестируемая часть пишется насколько простой, чтобы в ней можно было совершать минимум ошибок. Я тут даже статью написал по этому поводу habrahabr.ru/post/246285/
P.S. Вообще это мысли не мои — в офф. доках GWT пишут про MVP и Мартин Фаулер это говорил.
Мы тестируем пользовательский интерфейс платформы при помощи Selenium, но это действительно требует времени. Дополнительно мы тестируем код компонентов и код UI слоя при помощи тестов с Mock-объектами, когда большая часть инфраструктуры мокается. Но всё же это далеко от хорошего покрытия тестами UI.
У нас всё усугубляется тем, что прикладной UI код исполняется на стороне сервера, но код компонентов исполняется и на стороне браузера. Из-за такого размазывания приходится больше полагаться на Selenium тесты, если тестируем компоненты и сложный UI.
MVP к сожалению нам не подошло, у нас больше компонентный подход к UI.
а какие UI фреймворки кроме Vaadin вы ещё смотрели?
Этот выбор был сделан нами очень давно (более 5 лет назад) и с тех пор мы смотрели несколько веб фреймворков — ZK, Smart GWT, и даже JS фреймворки. Но ни разу мы не находили необходимой нам функциональности и гибкости. Наработок по Vaadin у нас очень много и в перспективе мы не планируем от него отказываться.
Возможно, в данном случае проще было бы воспользоваться OSGI, где приложение изначально разрабатывается как набор модулей, которые можно «перезаливать» независимо друг от друга.
Я согласен с вами, это один из вариантов и я его в статье не осветил.

Однако, не думаю, что он проще.

Во-первых, чтобы использовать OSGI, нужно менять архитектуру приложения. Наш подход этого не требует.
Во-вторых, по опыту разработки плагинов для JIRA (там используется OSGI), могу сказать, что с OSGI тоже все не просто. Вы не можете в плагинах использовать версии библиотек, отличные от версий, используемых в ядре (по крайней мере в JIRA это так). Во-вторых, плагины изолированы друг от друга, а нам бы хотелось, чтобы части системы могли кооперировать напрямую.
В-третьих, загрузка плагина ведет к полной перезагрузке всех классов из bundle, и это медленнее, чем скомпилировать 1 класс, который поменялся.
В-четвертых, сама по себе проблема разделения на плагины наших систем довольно сложна. Надо определиться по какому признаку объединять классы (ведь на каждый экран или бин не будешь создавать плагин). Учитывая то, что Spring-бины используют друг друга, задача становится довольно сложной.

Поэтому я считаю, что хотя OSGI и подходит для hot deploy, этот подход внес бы неоправданную сложность в наши системы.

Конечно же у OSGI есть и преимущества перед нашим подходом. Например, в плагин можно добавить библиотеки, которых нет на сервере, в ядре. Я думаю, что это полезно в таких системах как JIRA (продукт + куча сторонних плагинов), но плохо подходит для наших систем (корпоративные системы и продукты вроде Taxi).
Идея отличная. Вопрос — а почему компиляцию java исходников переложили на сторону сервера? Почему бы не подсовывать только измененные class файлы?
В статье я указал этот способ, как один из вариантов, сейчас попробую развернуть свою мысль.

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

Я думаю, что деплой классов подходит для разработки (в grails например так и делают), но плохо подходит для деплоя на продакшен сервера, а нам хотелось бы и ускорения разработки, и возможности менять поведение системы на продакшене.
Спасибо. Второй пункт похоже самый существенный.

А как вы организовываете работу, чтобы не терять изменения которые кто-то внес на ходу?
Обычно мы такие изменения вносим в SVN сразу, тестируем и загружаем на сервер.

Есть еще один момент. У нас IT-отдел заказчика тоже может загружать исходники на сервер (в первую очередь новые отчеты), и нам пришлось в итоге им также дать доступ в SVN, чтобы их изменения не терялись.
А если для второго пункта использовать serialVersionUID? Это, конечно, определенные требования к написанию кода, но можно автоматизировать как проверки, так и собственно добавление этого поля.
В принципе, можно. Но сравнивать plain-code гораздо приятнее — можно видеть именно то, что менялось (буквально diff)
Вобщем-то получился хороший костыль. Нивкоем случае не имею в виду что-то плохое. К сожалению, такую задачу в Java можно решить только костылями. И без специальной подгонки архитектуры всего приложения это довольно трудно сделать вообще.

Из описанных плюсов, возникло сомнение: «нет ни перезагрузки, ни периода недоступности приложения».

После обновления, для того, чтобы стал работать новый код, и без потерь для пользователя, нужно чтобы состояние сессии и приложения вообще, а также внутреннее состояние фреймворка, были восстановлены. Поэтому вы и перегружаете спринговый контекст. Это занимает некоторое время в любом случае. Или же имеется в виду, что имеющийся пользователь закончит свою работу на старой версии кода, а все новые пользователи обслуживаются уже новой версией?
Из описанных плюсов, возникло сомнение: «нет ни перезагрузки, ни периода недоступности приложения».
Поэтому вы и перегружаете спринговый контекст.

Позвольте не согласиться. Во-первых, spring-контекст не обязательно перезагружать полностью. Я об этом упомянул в статье. Существует возможность перезагрузить определенный бин и тех кто от него зависит (это гораздо быстрее). Во-вторых, если вы разрабатываете UI на платформе CUBA, вы вообще не перезагружаете никакие контексты, просто все вновь открывающиеся экраны будут получать перезагруженный класс, и соответственно — иметь новое поведение.

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

Что тогда со внутренним состоянием, сессией, итп? Это как то предусматривается платформой. Или всё таки имеет место т.е. вымещающая замена, где старый пользователь ещё работает со старой версией функциональности, а новый — уже с новой?
Попробую объяснить.

1) Экраны пользовательского интерфейса. Каждый экран перед открытием получает класс из класслоадера, таким образом замена логики (и верстки) экрана происходит незаметно для пользователя — в следующий раз когда будет открыт экран, пользователь получит новую версию.При этом пользовательская сессия не затрагивается, и повторный логин не требуется.
2) Stateless бины. В большинстве случаев заменяются прозрачно для пользователя и без рестарта контекста, без сброса сессии, и т.д.
3) Stateful бины. В нашей платформе они организованы так, чтобы можно было загрузить состояние по команде через jmx. То есть, даже если возникает необходимость замены такого бина, мы подкладываем исходник на сервер, вызываем компиляцию, заменяем бин в контексте и затем руками даем команду на загрузку состояния.
Только хотел попробовать сделать, а тут такая хрень:
В общем случае необходимо строить архитектуру приложения таким образом, чтобы она позволяла менять реализацию на лету, например не использовать конструкторы, а получать классы по имени и создавать объекты с помощью рефлексии

всё желание сразу пропало.

Давайте разберемся.

1) Если вы используете Spring (или другой IOC-контейнер) — специальных действий не требуется. Нужно только научить контейнер пользоваться класслоадером, также как мы научили Spring.
2) Если вы так или иначе загружаете классы самостоятельно (как мы делаем с контроллерами экранов) — специальных действий не требуется. Нужно просто использовать компилирующий класслоадер.
3) Если вы пользуетесь фабрикой для создания объектов — специальных действий не требуется. Просто научите фабрику пользоваться компилирующим класслоадером.

И так далее.

Общий случай — это когда мы заранее про приложение ничего не знаем. Поэтому и посыл такой. В каждом отдельном случае скорее всего можно реализовать все проще и быстрее.

1. Не использую
2. Классы грузятся автоматически. Я просто пишу код с их использованием.
3. Не использую.

печалька. Вообще возможно обойти те ограничения, которые вы описали?
В теории можно. Есть например свойство -Djava.system.class.loader, которое устанавливает системный класслоадер для всей JVM.

Я так делать пока не пробовал. Возможно это сработает, возможно нет.
> У кого-то может возникнуть вопрос — как мы определяем, от чего зависит класс? Ответ простой — мы разбираем секцию импортов.

А если в коде используется полное имя класса? Видимо придется анализировать весь исходник или байт-код… Интересно, что проще?
К сожалению разобрать байт код нельзя, потому что, чтобы его получить, нужно скомпилировать код. А зависимости мы ищем как раз для того, чтобы этот самый код скомпилировать.
Ах да, ступил.
Поэтому у тех hot-reload либ что я видел, невозможно изменить, скажем иерархию класса.
В импортах есть еще сложности с wildcard, но впрочем, имея список доступных подлежащих компиляции классов это небольшая проблема.
И все-таки как быть с квалифицированными именами…
Наверное реально еще разобрать файл лексически и выделить квалифицированные имена классов, которые не фигурируют в секции импорта. Да, прямым путем (sun tools) этого скорее всего не сделать из-за проблемы курицы и яйца. Но если игнорировать классы из дефолтного пакета, мне кажется это реально сделать даже некоторой эвристикой.
Как вы думаете?
Не проблема распарсить код, но это влияет на время компиляции. Особенно если классов много.
Все зависит от задачи. Нам, как оказалось, оно не сильно нужно)
Sign up to leave a comment.