Русский
Русский
English
Статистика
Реклама

Перевод Фреймворк Quarkus как в нем реализуется чистая архитектура

Привет, Хабр!

Продолжая исследование новых фреймворков Java и учитывая ваш интерес к книге о Spring Boot, мы присматриваемся к новому фреймворку Quarkus для Java. Подробное описание его вы найдете здесь, а мы сегодня предлагаем почитать перевод простой статьи, демонстрирующей, как удобно при помощи Quarkus придерживаться чистой архитектуры.


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

В качестве отправной точки я взял простой проект Maven, в котором 5 стандартных модулей для создания приложения CRUD REST в соответствии с принципами чистой архитектуры:

  • domain: объекты предметной области и интерфейсы шлюза для этих объектов
  • app-api: интерфейсы приложения, соответствующие практическим кейсам
  • app-impl: реализация этих кейсов средствами предметной области. Зависит от app-api и domain.
  • infra-persistence: реализует шлюзы, обеспечивающие взаимодействие предметной области с API базы данных. Зависит от domain.
  • infra-web: Открывает рассматриваемые кейсы для взаимодействия с внешним миром при помощи REST. Зависит от app-api.


Кроме того, мы создадим модуль main-partition, который послужит нам развертываемым артефактом приложения.

Планируя работать с Quarkus, первым делом нужно добавить спецификацию BOM к файлу POM вашего проекта. Эта BOM станет управлять всеми версиями зависимостей, которыми вы будете пользоваться. Также вам понадобится сконфигурировать стандартные плагины для проектов maven в вашем инструменте управления плагинами, как, например, плагин surefire. По мере работы с Quarkus, вы здесь же сконфигурируете и одноименный плагин. Последнее, но немаловажное: здесь понадобится сконфигурировать плагин для работы с каждым из модулей (в <build><plugins>...</plugins></build>), а именно, плагин Jandex. Поскольку Quarkus использует CDI, плагин Jandex добавляет файл индекса в каждый модуль; файл содержит записи обо всех аннотациях, используемых в данном модуле и ссылки с указанием на то, где используется какая аннотация. В результате обращение с CDI значительно упрощается, впоследствии приходится выполнять значительно меньше работы.

Теперь, когда базовая структура готова, можно приступать к созданию полноценного приложения. Чтобы это сделать, необходимо убедиться, что main-partition создает исполняемое приложение Quarkus. Данный механизм проиллюстрирован в любом примере для быстрого старта, предоставляемом в Quarkus.

Сначала конфигурируем сборку для использования плагина Quarkus:

<build>  <plugins>    <plugin>      <groupId>io.quarkus</groupId>      <artifactId>quarkus-maven-plugin</artifactId>      <executions>        <execution>          <goals>            <goal>build</goal>          </goals>        </execution>      </executions>    </plugin>  </plugins></build>


Далее давайте добавим зависимости в каждый из модулей приложения, где они будут вместе с зависимостями quarkus-resteasy и quarkus-jdbc-mysql. В последней зависимости можно заменить базу данных на ту, что вам больше нравится (учитывая, что впоследствии мы собираемся пойти по нативному пути разработки, и поэтому не можем использовать встраиваемую базу данных, например, H2).

В качестве варианта, можно добавить профиль, позволяющий позже собрать нативное приложение. Для этого вам в самом деле потребуется дополнительный стенд для разработки (GraalVM, native-image и XCode, если вы используете OSX).

<profiles>  <profile>    <id>native</id>    <activation>      <property>        <name>native</name>      </property>    </activation>    <properties>      <quarkus.package.type>native</quarkus.package.type>    </properties>  </profile></profiles>


Теперь, если вы запустите mvn package quarkus:dev из корня проекта, у вас будет действующее приложение Quarkus! Смотреть пока особенно не на что, поскольку у нас пока нет ни контроллеров, ни контента.

Добавляем REST-контроллер

В данном упражнении пойдем с периферии к сути. Для начала создадим REST-контроллер, который будет возвращать пользовательские данные (в данном примере к ним относится всего лишь имя).

Чтобы использовать JAX-RS API, необходимо добавить зависимость к infra-web POM:

<dependency>  <groupId>io.quarkus</groupId>  <artifactId>quarkus-resteasy-jackson</artifactId></dependency>


Простейший код контроллера выглядит так:

@Path("/customer")@Produces(MediaType.APPLICATION_JSON)public class CustomerResource {    @GET    public List<JsonCustomer> list() {        return getCustomers.getCustomer().stream()                .map(response -> new JsonCustomer(response.getName()))                .collect(Collectors.toList());    }    public static class JsonCustomer {        private String name;        public JsonCustomer(String name) {            this.name = name;        }        public String getName() {            return name;        }    }


Если мы сейчас запустим приложение, то сможем вызвать localhost:8080/customer и увидим Joe в формате JSON.

Добавляем конкретный кейс

Далее добавим кейс и реализацию для данного практического случая. В app-api определим следующий кейс:

public interface GetCustomers {    List<Response> getCustomers();    class Response {        private String name;        public Response(String name) {            this.name = name;        }        public String getName() {            return name;        }    }}


В app-impl создадим простейшую реализацию данного интерфейса.

@UseCasepublic class GetCustomersImpl implements GetCustomers {    private CustomerGateway customerGateway;    public GetCustomersImpl(CustomerGateway customerGateway) {        this.customerGateway = customerGateway;    }    @Override    public List<Response> getCustomers() {        return Arrays.asList(new Response("Jim"));    }}


Чтобы CDI мог увидеть компонент GetCustomersImpl, вам понадобится специальная аннотация UseCase в том виде, как она определена ниже. Также можно использовать стандартный ApplicationScoped и аннотацию Transactional, но, создавая собственную аннотацию, вы получаете возможность более логично группировать код и откреплять код вашей реализации от таких фреймворков как CDI.

@ApplicationScoped@Transactional@Stereotype@Retention(RetentionPolicy.RUNTIME)public @interface UseCase {}


Для пользования аннотациями CDI необходимо добавить следующие зависимости в POM-файл app-impl в дополнение к зависимостям app-api и domain.

<dependency>  <groupId>jakarta.enterprise</groupId>  <artifactId>jakarta.enterprise.cdi-api</artifactId></dependency><dependency>  <groupId>jakarta.transaction</groupId>  <artifactId>jakarta.transaction-api</artifactId></dependency>


Далее нам потребуется изменить контроллер REST, чтобы использовать его в кейсах app-api.
...private GetCustomers getCustomers;public CustomerResource(GetCustomers getCustomers) {    this.getCustomers = getCustomers;}@GETpublic List<JsonCustomer> list() {    return getCustomers.getCustomer().stream()            .map(response -> new JsonCustomer(response.getName()))            .collect(Collectors.toList());}...


Если теперь вы запустите приложение и вызовете localhost:8080/customer, то увидите Jim в формате JSON.

Определение и реализация предметной области

Далее займемся предметной областью (domain). Здесь сущность domain довольно проста, она состоит из Customer и интерфейса шлюза, через который мы будем получать потребителей.

public class Customer {private String name;public Customer(String name) {this.name = name;}public String getName() {return name;}}public interface CustomerGateway {List<Customer> getAllCustomers();}


Также нам понадобится предоставить реализацию шлюза прежде, чем мы сможем приступить к его использованию. Такой интерфейс мы предоставляем в infra-persistence.

Для данной реализации мы воспользуемся поддержкой JPA, имеющейся в Quarkus, а также применим фреймворк Panache, который немного упростит нам жизнь. В дополнение к domain нам придется добавить к infra-persistence следующую зависимость:

<dependency>  <groupId>io.quarkus</groupId>  <artifactId>quarkus-hibernate-orm-panache</artifactId></dependency>


Сначала определяем сущность JPA, соответствующую потребителю.

@Entitypublic class CustomerJpa {@Id@GeneratedValueprivate Long id;private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}


Работая с Panache, можно выбрать один из двух вариантов: либо ваши сущности будут наследовать PanacheEntity, либо вы воспользуетесь паттерном репозиторий/DAO. Я не являюсь фанатом паттерна ActiveRecord, поэтому сам останавливаюсь на репозитории, но с чем будете работать вы решать вам.

@ApplicationScopedpublic class CustomerRepository implements PanacheRepository<CustomerJpa> {}


Теперь, когда у нас есть наша сущность JPA и репозиторий, можно реализовать шлюз Customer.

@ApplicationScopedpublic class CustomerGatewayImpl implements CustomerGateway {private CustomerRepository customerRepository;@Injectpublic CustomerGatewayImpl(CustomerRepository customerRepository) {this.customerRepository = customerRepository;}@Overridepublic List<Customer> getAllCustomers() {return customerRepository.findAll().stream().map(c -> new Customer(c.getName())).collect(Collectors.toList());}}


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

...private CustomerGateway customerGateway;@Injectpublic GetCustomersImpl(CustomerGateway customerGateway) {    this.customerGateway = customerGateway;}@Overridepublic List<Response> getCustomer() {    return customerGateway.getAllCustomers().stream()            .map(customer -> new GetCustomers.Response(customer.getName()))            .collect(Collectors.toList());}...


Пока мы не можем запустить наше приложение, поскольку приложение Quarkus еще требуется сконфигурировать с необходимыми параметрами персистентности. В src/main/resources/application.properties в модуле main-partition добавим следующие параметры.

quarkus.datasource.url=jdbc:mysql://localhost/testquarkus.datasource.driver=com.mysql.cj.jdbc.Driverquarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQL8Dialectquarkus.datasource.username=rootquarkus.datasource.password=rootquarkus.datasource.max-size=8quarkus.datasource.min-size=2quarkus.hibernate-orm.database.generation=drop-and-createquarkus.hibernate-orm.sql-load-script=import.sql


Чтобы просмотреть исходные данные, также добавим файл import.sql в тот же каталог, из которого добавляются данные.

insert into CustomerJpa(id, name) values(1, 'Joe');insert into CustomerJpa(id, name) values(2, 'Jim');


Если теперь вы запустите приложение и вызовете localhost:8080/customer, то увидите Joe и Jim в формате JSON. Итак, у нас получилось полноценное приложение, от REST до базы данных.

Нативный вариант

Если вы желаете собрать нативное приложение, то делать это необходимо при помощи команды mvn package -Pnative. На это может потребоваться около пары минут, в зависимости от того, каков ваш стенд для разработки. Quarkus довольно быстр при запуске и без поддержки нативного режима, стартует за 2-3 секунды, но, когда он скомпилирован в нативный исполняемый файл при помощи GraalVM, соответствующее время сокращается менее чем до 100 миллисекунд. Для приложения на Java это просто молниеносная скорость.

Тестирование

Протестировать приложение Quarkus можно при помощи соответствующего тестового фреймворка Quarkus. Если снабдить тест аннотацией @QuarkusTest, то JUnit сначала запустит контекст Quarkus, а затем выполнит тест. Тест целого приложения в main-partition будет выглядеть примерно так:

@QuarkusTestpublic class CustomerResourceTest {@Testpublic void testList() {given().when().get("/customer").then().statusCode(200).body("$.size()", is(2),"name", containsInAnyOrder("Joe", "Jim"));}}


Заключение

Во многих отношениях Quarkus лютый конкурент Spring Boot. На мой взгляд, некоторые вещи в Quarkus решены даже лучше. Даже притом, что в app-impl есть зависимость фреймворка, это всего лишь зависимость для аннотаций (в случае со Spring, когда мы добавляем spring-context, чтобы получить @Component, мы тем самым добавляем множество зависимостей ядра Spring). Если вам такое не нравится, вы также можете добавить файл Java в главный раздел, использующий аннотацию @Produces из CDI и создающий там компонент; в таком случае вам не понадобится никаких дополнительных зависимостей в app-impl. Но по какой-то причине зависимость jakarta.enterprise.cdi-api мне хочется видеть там меньше, чем зависимость spring-context.

Quarkus быстр, реально быстр. С приложениями такого типа он быстрее Spring Boot. Поскольку, согласно Чистой Архитектуре, большинство (если не все) зависимостей фреймворка должны находиться на внешней стороне приложения, выбор между Quarkus и Spring Boot становится очевиден. В данном отношении достоинство Quarkus заключается в том, что он сразу создавался с учетом поддержки GraalVM, и поэтому ценой минимальных усилий позволяет превратить приложение в нативное. Spring Boot пока отстает от Quarkus в этом отношении, но не сомневаюсь, что скоро наверстает.

Правда, эксперименты с Quarkus также помогли мне осознать, какие многочисленные несчастья ожидают тех, кто попытается применить Quarkus с классическими серверами приложений Jakarta EE. Хотя, пока при помощи Quarkus можно сделать не так много, его генератор кода поддерживает разнообразные технологии, которые пока не так просто использовать в контексте Jakarta EE с традиционным сервером приложений. Quarkus охватывает все основы, которые понадобятся людям, знакомым с Jakarta EE, а разработка на нем гораздо более гладкая. Будет интересно посмотреть, как экосистема Java переварит такую конкуренцию.

Весь код к данному проекту выложен на Github.
Источник: habr.com
К списку статей
Опубликовано: 30.07.2020 10:07:11
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании издательский дом «питер»

Java

Программирование

Профессиональная литература

Quarkus

Spring boot

Чистая архитектура

Книги

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru