Привет, Хабр!
Dropwizard — это комплексный фреймворк, созданный с целью упростить разработку RESTful веб‑сервисов, объединяя в себе множество проверенных временем библиотек и инструментов. В его основе лежат компоненты:
-
Jetty — легковесный HTTP сервер и контейнер сервлетов, обеспечивающий высокую производительность и надежность при обработке запросов.
-
Jersey — фреймворк для создания RESTful веб‑сервисов, который предоставляет удобный и мощный API для разработки REST API.
-
Jackson — библиотека для сериализации и десериализации объектов Java в JSON и обратно, широко известная своей скоростью и гибкостью.
-
Metrics — библиотека для сбора и анализа метрик производительности, позволяющая отслеживать состояние приложения в режиме реального времени.
Кроме того, Dropwizard включает в себя поддержку конфигурации, валидации, логгирования, мониторинга и тестирования.
Установим
Начнем с самого начала. Прежде всего, потребуется JDK, желательно версии 11 или выше. Далее потребуется выбрать между двумя инструментами сборки: Maven и Gradle. Оба инструмента поддерживаются Dropwizard, и выбор зависит скорее от предпочтений.
Для тех, кто предпочитает Maven, настройка достаточно проста. Создаем новый Maven‑проект, добавив в pom.xml необходимые зависимости. Пример базовой конфигурации:
<dependency> <groupId>io.dropwizard</groupId> <artifactId>dropwizard-core</artifactId> <version>2.1.0</version> </dependency>
После этого выполняем команду mvn clean install.
Если выбор пал Gradle, то процесс начинается с создания файла build.gradle с включением необходимых зависимостей:
plugins { id 'java' } repositories { mavenCentral() } dependencies { implementation 'io.dropwizard:dropwizard-core:2.1.0' }
После этого выполняемкоманду ./gradlew build, чтобы собрать проект и загрузить все необходимые библиотеки.
Приступим к созданию самого проекта. Основной класс приложения должен наследовать io.dropwizard.Application и переопределять метод run. Вот минимальный пример:
public class HelloWorldApplication extends Application<HelloWorldConfiguration> { public static void main(String[] args) throws Exception { new HelloWorldApplication().run(args); } @Override public void run(HelloWorldConfiguration configuration, Environment environment) { final HelloWorldResource resource = new HelloWorldResource( configuration.getTemplate(), configuration.getDefaultName() ); environment.jersey().register(resource); } }
Обычно, проект разделен на несколько основных частей:
-
src/main/java — содержит исходный код.
-
src/main/resources — конфигурационные файлы, включая
config.yml. -
target — директория, куда помещаются скомпилированные артефакты.
Конфигурационный файл config.yml является основным элементом в настройке приложения. Пример минимального конфигурационного файла:
server: applicationConnectors: - type: http port: 8080 adminConnectors: - type: http port: 8081 logging: level: INFO loggers: com.example.helloworld: DEBUG
Здесь можно настраивать серверные соединения, уровень логирования и другие параметры без необходимости изменять код.
Создаем базовый микросервис
Первое, что нужно сделать, — это создать REST API. Dropwizard делает этот процесс понятным благодаря интеграции с Jersey. Начнем с определения ресурса, который будет отвечать за определенный URL‑эндпоинт:
import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello-world") @Produces(MediaType.APPLICATION_JSON) public class HelloWorldResource { @GET public String sayHello() { return "{\"message\":\"Hello, World!\"}"; } }
Определяем класс HelloWorldResource, который будет обрабатывать GET‑запросы по пути /hello-world и возвращать простой JSON‑ответ. Аннотации @Path и @GET определяют маршрут и HTTP‑метод соответственно.
Чтобы этот ресурс начал работать, его нужно зарегистрировать в приложении:
import io.dropwizard.Application; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; public class HelloWorldApplication extends Application<HelloWorldConfiguration> { public static void main(String[] args) throws Exception { new HelloWorldApplication().run(args); } @Override public void run(HelloWorldConfiguration configuration, Environment environment) { final HelloWorldResource resource = new HelloWorldResource(); environment.jersey().register(resource); } }
Класс является точкой входа в приложение. Регистрируем ресурс в методе run, чтобы Dropwizard знал, как обрабатывать соответствующие HTTP‑запросы.
Теперь перейдем к другой части — реализации CRUD операций. Представим, что нужно поработать с сущностью Person, которая имеет ID, имя и возраст:
import com.fasterxml.jackson.annotation.JsonProperty; public class Person { private long id; private String name; private int age; public Person() {} public Person(long id, String name, int age) { this.id = id; this.name = name; this.age = age; } @JsonProperty public long getId() { return id; } @JsonProperty public String getName() { return name; } @JsonProperty public int getAge() { return age; } }
Теперь определим ресурс, который будет управлять операциями для Person:
import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.*; @Path("/people") @Produces(MediaType.APPLICATION_JSON) public class PersonResource { private final Map<Long, Person> personDB = new HashMap<>(); @POST public Response createPerson(Person person) { personDB.put(person.getId(), person); return Response.status(Response.Status.CREATED).entity(person).build(); } @GET @Path("/{id}") public Person getPerson(@PathParam("id") long id) { Person person = personDB.get(id); if (person == null) { throw new WebApplicationException("Person not found", Response.Status.NOT_FOUND); } return person; } @PUT @Path("/{id}") public Person updatePerson(@PathParam("id") long id, Person person) { if (!personDB.containsKey(id)) { throw new WebApplicationException("Person not found", Response.Status.NOT_FOUND); } personDB.put(id, person); return person; } @DELETE @Path("/{id}") public Response deletePerson(@PathParam("id") long id) { Person person = personDB.remove(id); if (person == null) { throw new WebApplicationException("Person not found", Response.Status.NOT_FOUND); } return Response.status(Response.Status.NO_CONTENT).build(); } }
Этот ресурс позволяет выполнять все основные CRUD операции. Здесь используется HashMap как временное хранилище данных, но в продакшене, конечно, используем базы данных.
Не забываем о том, что нужно правильно обрабатывать HTTP‑запросы и формировать ответы. В приведенном выше примере использовали объект Response для управления статусами HTTP и телом ответа. Например, при создании нового объекта Person возвращается статус 201 Created, что указывает на успешное выполнение операции.
Также мы используем аннотацию @PathParam для извлечения параметров из URL, а @JsonProperty помогает сериализовать и десериализовать объекты в JSON и обратно.
Также в визарде есть инструменты для валидации данных. Например, можно использовать аннотации @NotNull, @Size, @Min, чтобы убедиться, что данные соответствуют определенным критериям перед их обработкой.
Пример валидации:
import javax.validation.constraints.Min; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; public class Person { private long id; @NotEmpty private String name; @Min(0) private int age; // Геттеры и сеттеры }
Логгирование в Dropwizard также упрощено благодаря интеграции с SLF4J и Logback. Например, можно добавить простое логгирование:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PersonResource { private static final Logger LOGGER = LoggerFactory.getLogger(PersonResource.class); @POST public Response createPerson(Person person) { LOGGER.info("Creating person: {}", person); personDB.put(person.getId(), person); return Response.status(Response.Status.CREATED).entity(person).build(); } // Другие методы }
Для мониторинга состояния приложения Dropwizard предлагает встроенные метрики, которые можно легко добавить:
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.annotation.Timed; public class PersonResource { private final MetricRegistry metrics; public PersonResource(MetricRegistry metrics) { this.metrics = metrics; } @POST @Timed public Response createPerson(Person person) { metrics.counter("create-person-requests").inc(); personDB.put(person.getId(), person); return Response.status(Response.Status.CREATED).entity(person).build(); } }
Эта аннотация @Timed автоматически создает метрики времени выполнения метода, которые затем можно использовать для анализа производительности.
Тестирование и развертывание микросервиса
Тестирование микросервисов включает в себя несколько уровней проверки:
-
Юнит-тесты: Тестируют отдельные компоненты в изоляции. Юнит-тесты быстрые и просты в реализации.
-
Интеграционные тесты: Проверяют взаимодействие между компонентами, включая доступ к базе данных или внешним API.
-
Тесты на производительность: Определяют, насколько хорошо микросервис обрабатывает нагрузку, и выявляют узкие места в производительности.
Начнем с простого юнит-теста. В Dropwizard можно использовать JUnit и Mockito для создания юнит-тестов. Допустим, есть сервис для управления сущностями Person:
public class PersonService { private final PersonDAO personDAO; public PersonService(PersonDAO personDAO) { this.personDAO = personDAO; } public Person findPersonById(long id) { return personDAO.findById(id).orElseThrow(() -> new NotFoundException("Person not found")); } }
Юнит-тест для этого сервиса будет выглядеть так:
import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; public class PersonServiceTest { private final PersonDAO personDAO = Mockito.mock(PersonDAO.class); private final PersonService personService = new PersonService(personDAO); @Test void findPersonById_ShouldReturnPerson_WhenPersonExists() { Person person = new Person(1, "John Doe", 30); when(personDAO.findById(1L)).thenReturn(Optional.of(person)); Person foundPerson = personService.findPersonById(1L); assertEquals("John Doe", foundPerson.getName()); } @Test void findPersonById_ShouldThrowNotFoundException_WhenPersonDoesNotExist() { when(personDAO.findById(1L)).thenReturn(Optional.empty()); assertThrows(NotFoundException.class, () -> personService.findPersonById(1L)); } }
Этот тест проверяет как позитивные, так и негативные сценарии работы метода findPersonById.
Dropwizard также имеет удобные инструменты для интеграционного тестирования, включая поддержку встроенного Jetty-сервера для запуска приложения во время тестов.
Допустим, нужно протестировать REST API, который возвращает Person по ID. Можно юзать DropwizardAppRule для запуска Dropwizard-приложения в тестовом окружении:
import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.ResourceHelpers; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; public class PersonResourceTest { private static final DropwizardAppExtension<MyConfiguration> RULE = new DropwizardAppExtension<>(MyApplication.class, ResourceHelpers.resourceFilePath("config-test.yml")); @Test void getPerson_ShouldReturnPerson_WhenPersonExists() { given() .when() .get("/people/1") .then() .statusCode(200) .body("name", equalTo("John Doe")); } }
Используем DropwizardAppExtension, чтобы запустить приложение, и RestAssured для упрощения отправки HTTP-запросов и проверки ответов.
Когда тесты успешно пройдены, пора переходить к развертыванию. Один из лучших способов сделать это — использовать Docker и Kubernetes в комбинации с CI/CD пайплайном.
Создадим Dockerfile для микросервиса:
FROM openjdk:11-jre-slim COPY target/my-application.jar /app/my-application.jar WORKDIR /app CMD ["java", "-jar", "my-application.jar", "server", "config.yml"]
Соберем Docker-образ:
docker build -t my-application:latest .
Для развертывания в Kubernetes, создадим файл манифеста:
apiVersion: apps/v1 kind: Deployment metadata: name: my-application spec: replicas: 3 selector: matchLabels: app: my-application template: metadata: labels: app: my-application spec: containers: - name: my-application image: my-application:latest ports: - containerPort: 8080 envFrom: - configMapRef: name: my-application-config
Этот манифест развертывает 3 экземпляра микросервиса в Kubernetes.
Подробнее с библиотекой можно ознакомиться здесь.
В заключение напоминаем про открытый урок, на котором разберем топ ошибок при переходе с монолита на микросервисную архитектуру. В результате вебинара:
-
Вы поймете основные ошибки при переходе на микросервисы и как их избежать.
-
Узнаете о преимуществах и недостатках микросервисной архитектуры.
-
Освоите ключевые паттерны и лучшие практики работы с микросервисами.
-
Получите примеры успешных и неудачных переходов на микросервисы, которые помогут вам в вашем проекте.
Записаться на урок можно на странице курса «Microservice Architecture».
ссылка на оригинал статьи https://habr.com/ru/articles/836042/
Добавить комментарий