Шаблон объекта передачи данных в Java - реализация и сопоставление

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

Вступление

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

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

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

Объект передачи данных

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

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

Мартин Фаулер описывает объект передачи данных в своей знаменитой книге « Шаблоны архитектуры корпоративных приложений» . Основная идея DTO - уменьшить количество дорогостоящих удаленных вызовов.

Мартин Фаулер также определяет объект ассемблера , используемый для преобразования данных между DTO и любыми объектами сущностей. В настоящее время для этой цели мы используем картографы.

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

Мотивация

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

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

Мы не хотим отправлять в систему безопасности другую конфиденциальную информацию, например личную информацию. Он избыточен и подвергает атакам канал связи между системами. Мы предоставим только то, что необходимо, а объем данных будет определен в DTO.

В приложениях Java - мы используем классы сущностей для представления таблиц в реляционной базе данных. Без DTO нам пришлось бы предоставлять все сущности удаленному интерфейсу. Это вызывает сильную связь между API и моделью сохранения.

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

Реализация объекта передачи данных

Давайте сделаем приложение, которое позаботится об отслеживании местоположения ваших друзей. Мы создадим приложение Spring Boot, которое предоставляет REST API. Используя его, мы сможем получать местоположения пользователей из базы данных H2.

Если вы хотите прочитать об интеграции базы данных H2 с Spring Boot , мы вам поможем!

Настройка Spring Boot

Самый простой способ начать с пустого приложения Spring Boot - использовать Spring Initializr :

инициализация весеннейзагрузки{.ezlazyload}

В качестве альтернативы вы также можете использовать Spring Boot CLI для начальной загрузки приложения:

 $ spring init --dependencies=h2 data-transfer-object-demo 

Если у вас уже есть приложение Maven / Spring, добавьте зависимость в свой файл pom.xml

 <dependency> 
 <groupId>com.h2database</groupId> 
 <artifactId>h2</artifactId> 
 <version>${version}</version> 
 </dependency> 

Или, если вы используете Gradle:

 compile group: 'com.h2database', name: 'h2', version: '${version}' 

Демо-приложение

Начнем с модели User

 @Entity 
 public class User { 
 @Id 
 @GeneratedValue(strategy = GenerationType.IDENTITY) 
 private Long id; 
 private String username; 
 private String firstName; 
 private String lastName; 
 private String password; 
 private String email; 
 
 @ManyToOne(fetch = FetchType.EAGER, optional = false) 
 @JoinColumn(name = "location_id") 
 private Location location; 
 
 // Getters and Setters 
 } 

Она содержит некоторую элементарную информацию , как username , firstName , email и т.д. Он также имеет отношение многие-к-одному с Location объекта:

 @Entity 
 public class Location { 
 @Id 
 @GeneratedValue(strategy = GenerationType.IDENTITY) 
 private Long id; 
 private double lat; 
 private double lng; 
 private String place; 
 private String description; 
 
 // Getters and Setters 
 } 

Для основных операций CRUD мы будем полагаться на надежный CrudRepository предоставляемый Spring Boot:

 @Repository 
 public interface UserRepository extends CrudRepository<User, Long>{} 

 @Repository 
 public interface LocationRepository extends CrudRepository<Location, Long> {} 

Если вы не знаете, как это работает, мы рекомендуем прочитать наше Руководство по Spring Data JPA . Короче говоря, они предоставят нам базовую функциональность CRUD для наших моделей.

На этом этапе мы хотели бы создать контроллер, который обрабатывает GET- запрос и возвращает список местоположений пользователя. Однако, если мы извлекаем User и Location из нашей базы данных и просто распечатываем необходимую информацию - другая информация, такая как пароль, также будет содержаться в этом объекте. Мы не будем его печатать, но он будет.

Давайте создадим объект передачи данных для передачи только необходимой информации. И пока мы это делаем, давайте объединим информацию о User и Location , чтобы данные передавались вместе:

 public class UserLocationDTO { 
 private Long userId; 
 private String username; 
 private double lat; 
 private double lng; 
 private String place; 
 
 // Getters and Setters 
 } 

Теперь этот объект содержит всю информацию, которую мы хотим показать конечному пользователю. Теперь нам понадобится способ сопоставить User и Location в один объект UserLocationDTO Обычно это делается с помощью инструментов сопоставления, таких как MapStruct или ModelMapper , которые мы рассмотрим в последних разделах.

А пока выполним преобразование вручную. Поскольку нам понадобится служба, которая вызывает наш UserRepository , мы также сопоставляем результаты и возвращаем DTO:

 @Service 
 public class MapService { 
 
 @Autowired 
 private UserRepository userRepository; 
 
 public List<UserLocationDTO> getAllUsersLocation() { 
 return ((List<User>) userRepository 
 .findAll()) 
 .stream() 
 .map(this::convertToUserLocationDTO) 
                .collect(Collectors.toList()); 
 } 
 
 private UserLocationDTO convertToUserLocationDTO(User user) { 
 UserLocationDTO userLocationDTO = new UserLocationDTO(); 
 userLocationDTO.setUserId(user.getId()); 
 userLocationDTO.setUsername(user.getUsername()); 
 Location location = user.getLocation(); 
 userLocationDTO.setLat(location.getLat()); 
 userLocationDTO.setLng(location.getLng()); 
 userLocationDTO.setPlace(location.getPlace()); 
 return userLocationDTO; 
 } 

Получив список User , мы напрямую конвертируем их вместе с их информацией о Location в объекты UserLocationDTO При вызове этой службы мы получим этот список DTO.

Наконец, давайте /map чтобы кто-нибудь мог получить местоположение пользователей:

 @RestController 
 public class MapController { 
 
 @Autowired 
 private MapService mapService; 
 
 @GetMapping("/map") 
 @ResponseBody 
 public List<UserLocationDTO> getAllUsersLocation() { 
 List <UserLocationDTO> usersLocation = mapService.getAllUsersLocation(); 
 return usersLocation; 
 } 
 } 

Эта конечная точка просто возвращает @ResponseBody . Он может быть вызван пользователем или другой службой, которая анализирует результаты.

Давайте загрузим нашу базу данных фиктивной информацией для целей тестирования:

 insert into location(id, lat, lng, place, description) values (1, 49.8, 24.03 ,'Lviv', 'Lviv is one of the largest and the most beautiful cities of Ukraine.'); 
 insert into user(id, username, first_name, last_name, password, location_id) values (1, 'Romeo', 'Romeo', 'Montagues' ,'gjt6lf2nt5os', 1); 
 insert into user(id, username, first_name, last_name, password, location_id) values (2, 'Juliet', 'Juliet', 'Capulets' ,'s894mjg03hd0', 1); 

Теперь, чтобы протестировать нашу конечную точку, мы будем использовать такой инструмент, как Postman, для достижения наших конечных точек:

почтальон результаты конечной точки Spring bootrest{.ezlazyload}

Большой! Список наших пользователей возвращается только с необходимой информацией, как переданной, так и отображаемой.

Мы написали в нашей MapService который объединяет и преобразует данные, однако этот процесс можно легко автоматизировать.

Отображение с помощью ModelMapper

ModelMapper - отличная библиотека сопоставления, которая позволяет нам сопоставлять модели и DTO. Это упрощает отображение объектов, автоматически определяя, как одна объектная модель отображается на другую.

Чтобы добавить его в проект Maven, мы бы добавили зависимость:

 <dependency> 
 <groupId>org.modelmapper</groupId> 
 <artifactId>modelmapper</artifactId> 
 <version>${version}</version> 
 </dependency> 

Или, если вы используете Gradle:

 compile group: 'org.modelmapper', name: 'modelmapper', version: '${version}' 

Давайте обновим наш предыдущий пример библиотекой ModelMapper:

 @Service 
 public class MapService { 
 
 @Autowired 
 private UserRepository userRepository; 
 
 @Autowired 
 private ModelMapper modelMapper; 
 
 public List<UserLocationDTO> getAllUsersLocation() { 
 return ((List<User>) userRepository 
 .findAll()) 
 .stream() 
 .map(this::convertToUserLocationDTO) 
 .collect(Collectors.toList()); 
 } 
 
 private UserLocationDTO convertToUserLocationDTO(User user) { 
 modelMapper.getConfiguration() 
 .setMatchingStrategy(MatchingStrategies.LOOSE); 
 UserLocationDTO userLocationDTO = modelMapper 
 .map(user, UserLocationDTO.class); 
 return userLocationDTO; 
 } 
 } 

Теперь вместо всего процесса назначения, который нам приходилось делать раньше, мы просто map() user в UserLocationDTO . Этот метод сглаживает свойства User в UserLocationDTO при этом будут присутствовать как информация о пользователе, так и его местоположение.

Примечание. При работе с объектами как с свойствами, например, с нашим Location является свойством User , стандартный сопоставитель библиотеки может не соответствовать всем свойствам. Мы установили стратегию сопоставления на LOOSE чтобы библиотеке было проще находить и сопоставлять свойства.

Картографирование с помощью MapStruct

MapStruct - это генератор кода на основе Java с открытым исходным кодом, который создает код для реализации сопоставления.

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

Если вы используете Maven, установите MapStruct, добавив зависимость:

 <dependencies> 
 <dependency> 
 <groupId>org.mapstruct</groupId> 
 <artifactId>mapstruct</artifactId> 
 <version>${org.mapstruct.version}</version> 
 </dependency> 
 </dependencies> 

Эта зависимость импортирует основные аннотации MapStruct. Поскольку MapStruct работает во время компиляции и прикреплен к сборщикам, таким как Maven и Gradle, нам также придется добавить плагин в <build> :

 <build> 
 <plugins> 
 <plugin> 
 <groupId>org.apache.maven.plugins</groupId> 
 <artifactId>maven-compiler-plugin</artifactId> 
 <version>3.5.1</version> 
 <configuration> 
 <source>1.8</source> 
 <target>1.8</target> 
 <annotationProcessorPaths> 
 <path> 
 <groupId>org.mapstruct</groupId> 
 <artifactId>mapstruct-processor</artifactId> 
 <version>${org.mapstruct.version}</version> 
 </path> 
 </annotationProcessorPaths> 
 </configuration> 
 </plugin> 
 </plugins> 
 </build> 

Если вы используете Gradle , установить MapStruct так же просто:

 plugins { 
 id 'net.ltgt.apt' version '0.20' 
 } 
 
 // Depending on your IDE 
 apply plugin: 'net.ltgt.apt-idea' 
 apply plugin: 'net.ltgt.apt-eclipse' 
 
 dependencies { 
 compile "org.mapstruct:mapstruct:${mapstructVersion}" 
 annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" 
 } 

У нас User и Location , поэтому давайте создадим для них маппер:

 @Mapper 
 public interface UserLocationMapper { 
 UserLocationMapper INSTANCE = Mappers.getMapper(UserLocationMapper.class); 
 
 @Mapping(source = "user.id", target = "userId") 
 UserLocationDTO toDto(User user, Location location); 
 } 

Когда вы создаете проект, MapStruct подберет этот @Mapper и сгенерирует UserLocationMapperImpl с полнофункциональной реализацией.

MapStruct имеет широкий спектр функций и расширенный набор функций. Если вам интересно узнать об этом больше, мы настоятельно рекомендуем прочитать наше подробное руководство по MapStruct на Java .

Заключение

В этой статье мы рассмотрели шаблон проектирования объекта передачи данных с его плюсами и минусами. Этот шаблон действительно предназначен только для удаленных вызовов, потому что преобразование из и в DTO может быть дорогостоящим.

Кроме того, мы создали демонстрационное приложение Spring Boot и исследовали два популярных средства сопоставления, которые можно использовать для упрощения процесса сопоставления между моделями и DTO.

Вы можете найти весь код проекта на GitHub .

comments powered by Disqus