Введение

Привет, Хабр! В данной статье хотелось бы поделиться мнением о плюсах и минусах использования фреймворка Vaadin версии 14.

До знакомства с Vaadin фронт приходилось делать с помощью различных JS фреймворков или библиотек, в связки с html и css. Поэтому особо никогда не задумывался о том, что можно разрабатывать фронт на Java в то время, когда космическое пространство бороздят React, Angular, Vue и им подобные. Но мое мнение поменялось с приходом в новую команду где уже использовался во всю Vaadin и с которым я познакомился впервые.  

О Vaadin

Для тех кто не знает, Vaadin — это фреймворк который позволяет разрабатывать веб на чистой Java, который включает в себя большую библиотеку компонентов пользовательского интерфейса. Данный фреймворк располагает на сервере всю логику пользовательского интерфейса и его состояние, а в браузере Vaadin сам реализует отображении пользовательского интерфейса и AJAX-взаимодействие между браузером и сервером. Таким образом браузер будет отображать лишь то, что скажет сервер, а все события, инициируемые пользователем, будут отправляться и обрабатываться на сервере. Разработка под вебом, становится похожа на разработку настольного Java-приложения с доступом к данным и сервисам на сервере

Плюсы

1. Реализован на популярном языке программирования Java

  • на данном языке можно реализовывать задачи различной степени сложности;

  • для Java-разработчика при разработке фронта нет необходимости переходить на другой язык, он остается в своей естественной среде обитания;

  • можно создавать общие библиотеки, которые могут использоваться как для бэка, так и для фронта.

2. Нет необходимости в изучение CSS, HTML, JavaScript

Для того чтобы реализовать фронт нет необходимости знать css, html, JavasSript и тонну различных фреймворков и библиотек, все что нужно знать это с помощью каких инструментов можно создать тот или иной компонент. К примеру, придумаем задачу, где нам необходимо 2 кнопки которые находились по горизонтали и при нажатии на которые фронт обращался к серверу, на сервере происходило вычисление и обратно отдавал фронту, а фронт выводил ответ в отдельное поле вывода:

Если мы делали это без Vaadin, то нам необходимо было написать стиль на css, разметку на html и на javascript реализовать ajax запросы. Достаточно много надо реализовывать для двух кнопок и поля вывода результата.

Теперь сделаем это все на Vaadin:

//Создаем блок в котором располагаются элементы по вертикали
VerticalLayout vrLayout = new VerticalLayout();
//input в котором будет выводиться результат
TextField input = new TextField("Результат");
//первая кнопка
Button button1 = new Button("Кнопка 1");
//вешаем событие на нажатие кнопку 1
button1.addClickListener(e -> input.setValue("результат с сервера " + calculate(1)));
//вторая кнопка
Button button2 = new Button("Кнопка 2");
//вешаем событие на нажатие кнопку 1
button2.addClickListener(e -> input.setValue("результат с сервера " + +calculate(2)));
//теперь добавляем все компоненты в блок, при это кнопки добавляем в блок, где элементы по вертикали расплогаются
vrLayout.add(input, new HorizontalLayout(button1, button2));

Из примера мы видим Vaadin облегчает задачу разработчику, теперь ему не нужно писать стили, разметку или реализовывать взаимодействие между сервером и клиентом, все это делает за него движок. А все что требуется от разработчика это указать нужные компоненты и их логику, а движок уже их сам обработает и выведет в браузер клиента.

3. Использование JavaScript, CSS, HTML

Да, хоть я уже и сказал, что не нужно знать JavaScript, css и html, но все же можно их использовать. Vaadin, к счастью, не запрещает нам этого делать. Если не нравится то, как выглядит или работает тот или иной компонент из коробки, у нас есть возможность его настроить. Например, есть кнопка и у нее цвет букв из коробки синий, а нужно чтобы был красный. Ничего сложного, мы можем использовать для этого старый добрый css. Подключаем файл, в котором будет указан стиль кнопки. Делаем это с помощью аннотации @CssImport(value = "./css/style.css").

4. Нет необходимости в дополнительном Frontend-разработчики

Так как вся разработка ведется на Java, то и отпадает необходимость во Frontend-разработчике. И здесь кому-то может показаться, что в таком случае один Java-разработчик может заменить сразу двух разработчиков, но к сожалению, или к счастью это не так. В данном случае это означает, что команде, состоящей из Java-разработчиков не нужны скилы Frontend-разработчика и она может обойтись своими силами.

5. Богатый функционал Vaadin

За время работы с Vaadin у меня не было проблем с недостатком каких-либо компонентов. Если их нет в стандартном пакете Vaadin, то можно поискать это в сообществе или сделать самому.

К примеру, была такая задача где необходимо было вывести иерархию каталогов и хранимых в ней документов. И для такой задачи уже был готовый компонент TreeGrid который позволяет выводить данные в виде иерархии, а для хранения структура данных использовался класс TreeData все это позволило без каких-либо проблем выдавать структур каталогов:

Либо была задача, сделать выпадающий список при нажатии на input и при выборе значений из списка добавлять его обратно в input, а также удалять его оттуда. Такого инструмента не было в стандартном пакете Vaadin на момент разработки, но такой компонент я нашел в сообществе, назывался данный компонент MultiselectComboBox:

Но MultiselectComboBox уже появился уже в стандартном пакете Vaadin, поэтому можно сказать, что фреймворк не стоит на месте, а постоянно развивается.

6. Дизайн интерфейса из коробки

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

7. Безопасность

Вся логика приложения находится на стороне сервера и там же выполняется, поэтому она не доступна со стороны пользовательского интерфейса. К примеру, у нас есть такой код:

boolean flagEnabled = true;
Button button1 = new Button("Кнопка 1");
button1.addClickListener(e->listner1());
button1.setEnabled(flagEnabled);
Button button2 = new Button("Кнопка 2");;
button2.addClickListener(e->listner2());
button2.setEnabled(!flagEnabled);

У нас есть две кнопки которые в зависимости от флага будет заблокирована либо кнопка 1, либо кнопка 2 (в нашем примере будет заблокирована кнопка 2). И даже если на стороне клиента в браузере мы разблокируем отключенную кнопку, это не даст никакого эффекта, т.к. на сервере она выключена, она не прослушивается. Поэтому можно быть уверенным, что со стороны пользовательского интерфейса нет возможности повлиять на выполнение логики реализуемую на стороне сервера.

Минусы

1. Подходит только для Java разработчиков

Данный инструмент подойдет команде, которая знает Java, если же она его не знает, то им будет очень трудно на нем работать. Другими словами, если вы отличный фронтенд разработчик, знаете Javasript и кучу его фреймворков, то к сожалению, это не сильно поможет вам при работе с Vaadin. А если даже и попытаетесь разрабатывать исключительно на Javasript, то пользы от этого фреймворка будет минимален, а возможно даже и навредит разрабатываемому проекту.

Поэтому здесь только 2 решения данной проблемы либо изучить Java, либо использовать другой инструмент.

2. Stateful состояние

Одна из проблем которая может появиться у разработчика при использования данного фреймворка это stateful состояние. Это значит, что сессии, элементы пользовательского интерфейса и их данные хранятся на сервере, а это требует, немало ресурсов, для их хранения, что может приводить к ошибкам OutOfMemoryError.

Для избежание этой ошибки, можно сделать следующее:

  1. надо безусловно определиться какое количество пользователей будет пользоваться вашей системой и из этих требований нужно и подбирать характеристики сервера;

  2. удалять сильные ссылки, т.е. вручную обнулять наши ссылки на объекты, чтобы сборщик мусора мог их удалить, например

    Dialog dialog = new Dialog();
    dialog.open();//открываем диалоговое окно
    … 
    dialog.close(); //закрываем диалоговое окно
    после закрытия сделайте
    dialog = null;

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

  3. и конечно можно перераспределять нагрузку между серверами.

3. Обрыв сессии

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

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

С 23 версии данная проблема была решена.

4. Тенденция к трудночитаемому коду

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

Необходимо определиться с архитектурой, установить правила организации кода и не забывать про паттерны проектирования и правила SOLID. К примеру, можно воспользоваться тем же паттерном проектирования MVP:

  1. Model-слой будет содержать, логику приложения, работу с базой и т.д.;

  2. View-слой будет содержать только компоненты, лейату, стили и т.д. без какой либо логики;

  3. Presenter-слой будет содержать логику интерфейса, слой который будет соединять нашу модель и представления;

Наш проект является многомодульным и всю логику приложения, работу с базой и т.д. выделен в отдельный модуль, который реализован на Spring Boot и является у нас бэковой частью. Фронт вынесен также в отдельный модуль, который реализован на Vaadin, таким образом на фронте у нас только интерфейс и его логика, а общение между фронтом и бэком реализовано с помощью REST-сервисов. Так как у нас фронт и бэк реализован на Java, то общий код, например, представление данных, вынесен также в отдельный модуль.

Описанный подход не является истиной в первой инстанции и не говорит, что необходимо всем так делать, каждая команда может придумать свои методы и подходы. Но что главное, каждой команде необходимо строго определить правила ведения кода своего проекта.

5. Тяжело найти Vaadin разработчиков

Я практически не встречал разработчиков (даже на Java) которые хотя бы слышали о Vaadin. В основном где-то кто-то как-то слышал о GWT (Google Web Toolkit), но не о Vaadin. Поэтому здесь встает проблема найти готового Vaadin разработчика. И если команда переходит на Vaadin, то им необходимо быть готовым к тому, что разработчика на Vaadin они вряд ли найдут и им придется его взращивать самостоятельно.

Опять же если есть опыт разработки пользовательского интерфейса на Java, то освоить данный фреймворк не предоставит большого труда. В моем случае я не имел данного опыт, но с освоением фреймворка не имел трудностей.

Небольшой пример в стиле «Hello world»

Здесь я приведу пример небольшого веб-приложения, написанного на Vaadin 14 (но данный код можно запустить и на Vaadin 24).

Само приложение будет состоять из главного меню и двух страниц. Первая страница будет выводить слово «Привет» и имя, введенное в поле ввода, а вторая страница выводит список городов. Но как говорится лучше один раз увидеть, чем сто раз услышать.

На скриншоте внизу представлены основные классы, которые реализуют пользовательский интерфейс и содержат логику компонентов.

Основной класс нашего приложения является MainLayout, который наследуется от AppLayout реализующий типовой макет веб приложения. В данном классе мы описываем только шапку, в которой будет название нашего проекта и меню. Шапку добавляем через метод addToNavbar, а сами страницы будут описаны в отдельных макетах.

Для того чтобы изменить стандартный стиль компонента, необходимо либо вызвать у компонента метод getStyle() и через него изменять стиль (как приведено ниже в примере), либо создать стилевой файл и подключить его через аннотацию @CssImport.

public class MainLayout extends AppLayout {
    public MainLayout() {
        addToNavbar(createHeaderContent());
    }

    private Component createHeaderContent() {
        Header header = new Header();

        header.add(createHeaderName(),
                createMenuLayout());
        return header;
    }

    private Div createHeaderName() {
        Div layout = new Div();
        layout.getStyle().set("margin-left", "17px");

        H1 appName = new H1("Тестовое приложение");
        appName.getStyle().set("font-size", "17px")
                .set("margin-bottom", "10px")
                .set("margin-top", "10px");
        layout.add(appName);

        return layout;
    }

    private MenuLayout createMenuLayout() {
        return new MenuLayout(new MenuItemInfo("Привет мир", VaadinIcon.WORKPLACE.create(), HelloWorldView.class),
                new MenuItemInfo("О программе", VaadinIcon.GLOBE.create(), AboutView.class));
    }


}

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

public class MenuLayout extends Nav {
    public MenuLayout(MenuItemInfo ... menuItemInfos) {
        getStyle().set("padding-bottom", "7px")
                .set("padding-top", "1px")
                .set("padding-left", "16px")
                .set("overflow", "auto")
                .set("display", "flex");

        UnorderedList list = new UnorderedList();
        list.getStyle().set("list-style-type", "none")
                .set("padding", "0px")
                .set("margin", "0px")
                .set("display", "flex")
                .set("gap", "6px");
        add(list);

        for (MenuItemInfo menuItem : menuItemInfos)
            list.add(menuItem);
    }
}

Сами пункты меню описываются в классе MenuItemInfo, где ссылки на страницы реализуют с помощью класса RouterLink.

public class MenuItemInfo extends ListItem {

    public MenuItemInfo(String menuTitle, Component icon, Class<? extends Component> view) {

        RouterLink link = new RouterLink();
        link.setRoute(view);

        Span text = new Span(menuTitle);

        if (icon != null)
            link.add(icon);

        link.add(text);
        add(link);
    }

}

Теперь перейдем к нашим страницам и первой будет HelloView.

Для наименования вкладки в браузере используется аннотация @PageTitle. Для наименования страницы в пути URL используется аннотация @Route, в которой мы дополнительно прописываем наш родительский макет MainLayout. Также с помощью аннотации @RouteAlias мы можем задать дополнительный псевдоним страницы, благодаря этому мы будем выводить ее по умолчанию.

Класс HelloView наследуется от HorizontalLayout который является одним из двух основных макетов Vaadin. В нем элементы располагаются по горизонтали, а для добавления компонентов на наш макет используется метод add. В примере используются TextField и Button, который реализуют в html такие элементы как input и button. Для того чтобы реализовать обработку нажатия кнопки sayHello, необходимо добавить прослушивателя, это делается через метод addClickListener.

В addClickListener, через Notification, выводится сообщение пользователю «Привет» и имя, введенное в поле «Ваше имя».

@PageTitle("Привет мир")
@Route(value = "hello", layout = MainLayout.class)
@RouteAlias(value = "", layout = MainLayout.class)
public class HelloView extends HorizontalLayout {

    public HelloView() {
        TextField name = new TextField("Ваше имя");
        Button sayHello = new Button("Сказать Привет");

        sayHello.addClickListener(e -> {
            Notification.show("Привет " + name.getValue(), 5000, Notification.Position.MIDDLE);
        });
        sayHello.addClickShortcut(Key.ENTER);

        setMargin(true);
        setVerticalComponentAlignment(Alignment.END, name, sayHello);

        add(name, sayHello);
    }

}

Теперь перейдем к странице CityView, которая наследуется от VerticalLayout, позволяющая расположить элементы по вертикали.

@PageTitle("Города")
@Route(value = "about", layout = MainLayout.class)
public class CityView extends VerticalLayout {

    public CityView() {

        add(new CityGrid(new ListDataProvider<>(Arrays.asList(
                new City("Москва", "Москва", "Центральный\t"),
                new City("Санкт-Петербург", "Санкт-Петербург", "Северо-Западный"),
                new City("Казань", "Татарстан", "Приволжский"),
                new City("Челябинск", "Челябинская область", "Уральский")
                ))));

        setWidthFull();
    }

}

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

public class CityGrid extends Grid<City> {
    public CityGrid(ListDataProvider<City> dataProvider) {
        setDataProvider(dataProvider);

        addColumn(City::getName).setHeader("Город");
        addColumn(City::getRegion).setHeader("Регион");
        addColumn(City::getDistrict).setHeader("Федеральный округ");
    }
}

Ну и сама модель которая хранит в себе те самые города

@Data
@AllArgsConstructor
public class City {
    String name;
    String region;
    String district;
}

Итог

Vaadin по моему небольшому опыту работы с ним, является отличным инструментом для разработки фронта. Данный фреймворк позволяет разрабатывать фронт без классической связки JS + сервер, разработчик может целиком и полностью сосредоточиться на разработке серверной части и ему не придется переключаться на другой язык или фреймворк, чтобы разрабатывать фронт. Фреймворк делает множество вещей за нас, от рендеринга пользовательского интерфейса, до обмена информации между клиентом и сервером. Зная только Java можно реализовать веб-приложение, без глубоких знаний в js и css. Да безусловно можно сказать, что для того чтобы разрабатывать на Vaadin надо знать Java, а это язык не простой, но опять же и JavaScript не простой, а без знаний тех же фреймворков React, Vue и им подобных, вряд ли можно создать что-то качественное, а они в свою очередь также не тривиальны в использование. Vaadin это тот инструмент который позволяет команде разработчиков убить двух зайцев одновременно.

Комментарии (10)