Объекты передачи данных, нежно известные как DTO, являются предметом многочисленных дискуссий, когда мы говорим о разработке приложений Java. DTO родились в мире Java в EJB 2 для двух целей.
Во-первых, чтобы обойти проблему EJB-сериализации; во-вторых, они неявно определяют этап сборки, на котором все данные, которые будут использоваться для представления, маршалируются перед фактическим переходом на уровень представления. Так как EJB больше не используется в больших масштабах, можно ли отказаться от DTO? Цель этой статьи — немного рассказать о полезности DTO и ответить на этот вопрос.
В конце концов, в среде нескольких новых тем (например, облачные и микросервисы) этот слой вообще имеет смысл? Когда дело доходит до хорошей архитектуры программного обеспечения, ответ практически единодушен: это зависит от того, насколько близко вы хотите, чтобы ваша сущность была связана с уровнем визуализации.
Думая о базовой архитектуре в слоях и разделяя ее на три взаимосвязанные части, мы имеем знаменитый MVC .
Стоит отметить, что эта стратегия не является исключительной для стека веб-приложений, таких как Spring MVC и JSF. Предоставляя ваши данные в спокойном приложении с JSON, Данные JSON работают как визуализация, даже если они не дружественны к типичным пользователь.
После краткого объяснения MVC мы поговорим о преимуществах и недостатках использования DTO. Думая о многоуровневых приложениях, DTO, прежде всего, имеет целью отделить модель от представления. Размышляя о проблемах DTO:
- Увеличивает сложность
- Есть возможность дублирования кода
- Добавление нового уровня влияет на уровень задержки, то есть на возможную потерю производительности.
В простых системах, которые не нуждаются в расширенной модели в качестве предпосылки, отказ от использования DTO приводит к большим преимуществам для приложения. Интересным моментом является то, что многие инфраструктуры сериализации в конечном итоге заставляют атрибуты иметь методы accessor или getter и setter, которые всегда присутствуют и являются общедоступными как обязательные, поэтому в какой-то момент это повлияет на инкапсуляцию приложения и безопасность.
Другой вариант — добавить слой DTO, который в основном гарантирует разделение вида и модели, как упоминалось ранее.
-
Это делает явным, какие поля перейдут на слой представления. Да, есть несколько аннотаций в различных структурах, которые указывают, какие поля не будут отображаться. Однако, если вы забудете записать, вы можете случайно экспортировать критическое поле, например, пароль пользователя.
-
Облегчает рисование в ориентации объекта. Одним из моментов, которые чистый код проясняет в отношении ориентации объекта, является то, что ООП скрывает данные, чтобы показать поведение, и инкапсуляция помогает в этом.
-
Облегчает обновление базы данных . Часто очень важно провести рефакторинг, перенести базу данных, чтобы это не повлияло на клиента. Такое разделение способствует оптимизации, модификации базы данных, не влияя на визуализацию.
-
Управление версиями, обратная совместимость — это важный момент, особенно если у вас есть API для публичного использования и для нескольких клиентов, поэтому можно иметь DTO для каждой версии и без проблем развивать бизнес-модель.
-
Еще одно преимущество заключается в простоте работы с расширенной моделью и в создании API, который одобрен пулями . Например, в моей модели я могу использовать API денег; тем не менее, в моем слое визуализации я экспортирую как простой объект только с денежным значением для визуализации. Это правильная старая строка в Java.
-
CQRS. Да, является ли разделение ответственности по командным запросам разделением ответственности за запись и чтение данных и как это сделать без DTO?
В общем, добавление слоя означает разделение и облегчение обслуживания за счет добавления большего количества классов и сложности, так как нам также нужно подумать об операции преобразования между этими слоями. Это, например, причина существования MVC, поэтому очень важно понимать, что все основано на воздействии и компромиссах или на том, что вредит тому или иному приложению или ситуации.
Отсутствие этих слоев очень плохо, это может привести к паттерну Highlander (может быть только один), из которого есть класс со всеми обязанностями. Таким же образом избыточные слои становятся луковым узором, где разработчик плачет, проходя через каждый слой.
Более частая критика в DTO работает над выполнением конверсии. Хорошая новость заключается в том, что существует несколько платформ преобразования, то есть нет необходимости вносить изменения вручную. В этой статье мы выберем тот, который является моделью .
Первый шаг — определить зависимости проекта, например, в Maven:
XML
1
<dependency>
2
<groupId>org.modelmapper</groupId>
3
<artifactId>modelmapper</artifactId>
4
<version>2.3.6</version>
5
</dependency>
6
Чтобы проиллюстрировать эту концепцию DTO, мы создадим приложение, использующее JAX-RS, подключенное к MongoDB, все это благодаря Jakarta EE, использующему Payara в качестве сервера. Мы управляем пользователем с помощью имени пользователя, зарплаты, дня рождения и списка языков, на которых пользователь может говорить. Поскольку мы будем работать с MongoDB в Джакарте, мы будем использовать Jakarta NoSQL.
xxxxxxxxxx
1
import jakarta.nosql.mapping.Column;
2
import jakarta.nosql.mapping.Convert;
3
import jakarta.nosql.mapping.Entity;
4
import jakarta.nosql.mapping.Id;
5
import my.company.infrastructure.MonetaryAmountAttributeConverter;
6
import javax.money.MonetaryAmount;
8
import java.time.LocalDate;
9
import java.util.Collections;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Objects;
13
15
public class User {
16
18
private String nickname;
19
21
MonetaryAmountAttributeConverter.class) (
22
private MonetaryAmount salary;
23
25
private List<String> languages;
26
28
private LocalDate birthday;
29
31
private Map<String, String> settings;
32
//only getter
34
}
35
В общем случае не имеет смысла иметь объекты, имеющие методы получения и установки для всех атрибутов; в конце концов, это было бы то же самое, что оставить атрибут public напрямую. Поскольку в центре нашей статьи не DDD или расширенные модели, мы опускаем детали этой сущности. Для нашего DTO у нас будут все поля, которые есть у сущности; однако для визуализации наш MonetaryAmount будет «String», а дата годовщины будет следовать той же строке.
xxxxxxxxxx
1
import java.util.List;
2
import java.util.Map;
3
public class UserDTO {
5
private String nickname;
7
private String salary;
9
private List<String> languages;
11
private String birthday;
13
private Map<String, String> settings;
15
//getter and setter
17
}
18
Большим преимуществом картографа является то, что нам не нужно беспокоиться об этом вручную. Единственное, на что следует обратить внимание, - это то, что конкретным типам, например, « MonetaryAmount » Money-API , необходимо будет создать преобразование, чтобы стать «String», и наоборот.
xxxxxxxxxx
1
import org.modelmapper.AbstractConverter;
2
import javax.money.MonetaryAmount;
4
public class MonetaryAmountStringConverter extends AbstractConverter<MonetaryAmount, String> {
6
8
protected String convert(MonetaryAmount source) {
9
if (source == null) {
10
return null;
11
}
12
return source.toString();
13
}
14
}
15
import org.javamoney.moneta.Money;
18
import org.modelmapper.AbstractConverter;
19
import javax.money.MonetaryAmount;
21
public class StringMonetaryAmountConverter extends AbstractConverter<String, MonetaryAmount> {
23
25
protected MonetaryAmount convert(String source) {
26
if (source == null) {
27
return null;
28
}
29
return Money.parse(source);
30
}
31
}
32
Конвертеры готовы; наш следующий шаг - создать экземпляр класса, который выполняет преобразование ModelMapper. Важным моментом использования внедрения зависимостей является то, что мы можем определить его как уровень приложения. Отныне все приложение может использовать один и тот же сопоставитель; для этого необходимо использовать аннотацию «Впрыск», как мы увидим дальше.
xxxxxxxxxx
1
import org.modelmapper.ModelMapper;
2
import javax.annotation.PostConstruct;
4
import javax.enterprise.context.ApplicationScoped;
5
import javax.enterprise.inject.Produces;
6
import java.util.function.Supplier;
7
import static org.modelmapper.config.Configuration.AccessLevel.PRIVATE;
9
11
public class MapperProducer implements Supplier<ModelMapper> {
12
private ModelMapper mapper;
14
16
public void init() {
17
this.mapper = new ModelMapper();
18
this.mapper.getConfiguration()
19
.setFieldMatchingEnabled(true)
20
.setFieldAccessLevel(PRIVATE);
21
this.mapper.addConverter(new StringMonetaryAmountConverter());
22
this.mapper.addConverter(new MonetaryAmountStringConverter());
23
this.mapper.addConverter(new StringLocalDateConverter());
24
this.mapper.addConverter(new LocalDateStringConverter());
25
this.mapper.addConverter(new UserDTOConverter());
26
}
27
30
31
public ModelMapper get() {
32
return mapper;
33
}
34
}
35
Одним из значительных преимуществ использования Jakarta NoSQL является простота интеграции базы данных. Например, в этой статье мы будем использовать концепцию репозитория, из которого мы создадим интерфейс, для которого Jakarta NoSQL позаботится об этой реализации.
xxxxxxxxxx
1
import jakarta.nosql.mapping.Repository;
2
import javax.enterprise.context.ApplicationScoped;
4
import java.util.stream.Stream;
5
7
public interface UserRepository extends Repository<User, String> {
8
Stream<User> findAll();
9
}
10
На последнем этапе мы подадим апелляцию в JAX-RS. Критическим моментом является то, что раскрытие данных будет осуществляться из DTO; то есть, благодаря DTO можно выполнять любые изменения внутри организации без ведома клиента. Как уже упоминалось, картограф был введен, и метод «map» значительно облегчает эту интеграцию между DTO и объектом без особого кода для этого.
xxxxxxxxxx
1
import javax.inject.Inject;
2
import javax.ws.rs.Consumes;
3
import javax.ws.rs.DELETE;
4
import javax.ws.rs.GET;
5
import javax.ws.rs.POST;
6
import javax.ws.rs.Path;
7
import javax.ws.rs.PathParam;
8
import javax.ws.rs.Produces;
9
import javax.ws.rs.WebApplicationException;
10
import javax.ws.rs.core.MediaType;
11
import javax.ws.rs.core.Response;
12
import java.util.List;
13
import java.util.stream.Collectors;
14
import java.util.stream.Stream;
15
"users") (
17
MediaType.APPLICATION_JSON) (
18
MediaType.APPLICATION_JSON) (
19
public class UserResource {
20
22
private UserRepository repository;
23
25
private ModelMapper mapper;
26
28
public List<UserDTO> getAll() {
29
Stream<User> users = repository.findAll();
30
return users.map(u -> mapper.map(u, UserDTO.class))
31
.collect(Collectors.toList());
32
}
33
35
public void insert(UserDTO dto) {
36
User map = mapper.map(dto, User.class);
37
repository.save(map);
38
}
40
42
"id") (
43
public void update( ("id") String id, UserDTO dto) {
44
User user = repository.findById(id).orElseThrow(() ->
45
new WebApplicationException(Response.Status.NOT_FOUND));
46
User map = mapper.map(dto, User.class);
47
user.update(map);
48
repository.save(map);
49
}
50
52
"id") (
53
public void delete( ("id") String id) {
54
repository.deleteById(id);
55
}
56
}
57
Управление базами данных, кодом и интеграциями всегда сложно, даже в облаке. Действительно, сервер все еще там, и кто-то должен наблюдать за ним, запускать установки и резервные копии и поддерживать работоспособность в целом. Приложение с двенадцатью факторами требует строгого отделения конфигурации от кода.
К счастью, Platform.sh предоставляет PaaS, который управляет службами, такими как базы данных и очереди сообщений, с поддержкой нескольких языков, включая Java. Все построено на концепции инфраструктуры как кода (IaC), управления и предоставления услуг через файлы YAML .
В предыдущих постах мы упоминали, как это делается на Platform.sh, в первую очередь с тремя файлами:
1) Определить сервисы, используемые приложениями ( services.yaml ).
x
1
mongodb
2
type mongodb3.6
3
disk1024
2) Один для определения общих маршрутов ( rout.yaml ).
YAML
xxxxxxxxxx
1
"https://{default}/":
2
type upstream
3
upstream"app:http"
4
"https://www.{default}/"
6
type redirect
7
to"https://{default}/"
8
Важно подчеркнуть, что маршруты предназначены для приложений, которыми мы хотим поделиться публично. Поэтому, если мы хотим, чтобы клиент имел доступ только к этим микросервисам, мы можем удалить его доступ к конференциям, сеансам и докладчикам из файла rout.yaml.
3) Platform.sh упрощает настройку отдельных приложений и микросервисов с помощью файла .platform.app.yaml. В отличие от отдельных приложений, каждое приложение микросервиса будет иметь свой собственный каталог в корневом каталоге проекта и собственный файл «.platform.app.yaml», связанный с этим отдельным приложением. Каждое приложение будет описывать свой язык и сервисы, к которым оно будет подключаться. Так как клиентское приложение будет координировать каждое из микросервисов нашего приложения, оно будет определять эти соединения, используя блок «Relations» своего файла «.platform.app.yaml».
xxxxxxxxxx
1
name app
2
type"java:11"
4
disk1024
5
hooks
7
build mvn clean package payara-micro bundle
8
relationships
10
mongodb'mongodb:mongodb'
11
web
13
commands
14
start
15
export MONGO_PORT='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].port"'
16
export MONGO_HOST='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].host"'
17
export MONGO_ADDRESS="${MONGO_HOST}:${MONGO_PORT}"
18
export MONGO_PASSWORD='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].password"'
19
export MONGO_USER='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].username"'
20
export MONGO_DATABASE='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].path"'
21
java -jar -Xmx1024m -Ddocument.settings.jakarta.nosql.host=$MONGO_ADDRESS \
22
-Ddocument.database=$MONGO_DATABASE -Ddocument.settings.jakarta.nosql.user=$MONGO_USER \
23
-Ddocument.settings.jakarta.nosql.password=$MONGO_PASSWORD \
24
-Ddocument.settings.mongodb.authentication.source=$MONGO_DATABASE \
25
target/microprofile-microbundle.jar --port $PORT
26
В этой презентации мы говорили об интеграции приложения с DTO, в дополнение к инструментам для прямой доставки и сопоставления вашего DTO с вашей организацией. Мы также рассмотрели преимущества и недостатки этого слоя.