Должен ли я использовать классы модели или классы полезной нагрузки для сериализации ответа json

Я использую весеннюю загрузку с mysql для создания Restful API. Вот пример того, как я возвращаю ответ json.

сначала у меня есть модель:

@Entity
public class Movie extends DateAudit {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Date releaseDate;
    private Time runtime;
    private Float rating;
    private String storyline;
    private String poster;
    private String rated;

    @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<MovieMedia> movieMedia = new ArrayList<>();

    @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<MovieReview> movieReviews = new ArrayList<>();

    @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<MovieCelebrity> movieCelebrities = new ArrayList<>();

    // Setters & Getters
}

и соответствующий репозиторий:

@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
}

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

public class MovieResponse {

    private Long id;
    private String name;
    private Date releaseDate;
    private Time runtime;
    private Float rating;
    private String storyline;
    private String poster;
    private String rated;
    private List<MovieCelebrityResponse> cast = new ArrayList<>();
    private List<MovieCelebrityResponse> writers = new ArrayList<>();
    private List<MovieCelebrityResponse> directors = new ArrayList<>();

    // Constructors, getters and setters

    public void setCelebrityRoles(List<MovieCelebrityResponse> movieCelebrities) {
        this.setCast(movieCelebrities.stream().filter(movieCelebrity -> movieCelebrity.getRole().equals(CelebrityRole.ACTOR)).collect(Collectors.toList()));
        this.setDirectors(movieCelebrities.stream().filter(movieCelebrity -> movieCelebrity.getRole().equals(CelebrityRole.DIRECTOR)).collect(Collectors.toList()));
        this.setWriters(movieCelebrities.stream().filter(movieCelebrity -> movieCelebrity.getRole().equals(CelebrityRole.WRITER)).collect(Collectors.toList()));
    }
}

Как видите, я разделил список знаменитостей фильма на 3 списка (актеры, режиссеры и сценаристы).

И чтобы сопоставить Кино с MovieResponse, я использую класс ModelMapper:

public class ModelMapper {

    public static MovieResponse mapMovieToMovieResponse(Movie movie) {

        // Create a new MovieResponse and Assign the Movie data to MovieResponse
        MovieResponse movieResponse = new MovieResponse(movie.getId(), movie.getName(), movie.getReleaseDate(),
                movie.getRuntime(),movie.getRating(), movie.getStoryline(), movie.getPoster(), movie.getRated());

        // Get MovieCelebrities for current Movie
        List<MovieCelebrityResponse> movieCelebrityResponses = movie.getMovieCelebrities().stream().map(movieCelebrity -> {

            // Get Celebrity for current MovieCelebrities
            CelebrityResponse celebrityResponse = new CelebrityResponse(movieCelebrity.getCelebrity().getId(),
                    movieCelebrity.getCelebrity().getName(), movieCelebrity.getCelebrity().getPicture(),
                    movieCelebrity.getCelebrity().getDateOfBirth(), movieCelebrity.getCelebrity().getBiography(), null);

            return new MovieCelebrityResponse(movieCelebrity.getId(), movieCelebrity.getRole(),movieCelebrity.getCharacterName(), null, celebrityResponse);
        }).collect(Collectors.toList());

        // Assign movieCelebrityResponse to movieResponse
        movieResponse.setCelebrityRoles(movieCelebrityResponses);
        return movieResponse;
    }
}

и, наконец, вот моя служба MovieService, которую я вызываю в контроллере:

@Service
public class MovieServiceImpl implements MovieService {

    private MovieRepository movieRepository;

    @Autowired
    public void setMovieRepository(MovieRepository movieRepository) {
        this.movieRepository = movieRepository;
    }

    public PagedResponse<MovieResponse> getAllMovies(Pageable pageable) {

        Page<Movie> movies = movieRepository.findAll(pageable);

        if (movies.getNumberOfElements() == 0) {
            return new PagedResponse<>(Collections.emptyList(), movies.getNumber(),
                    movies.getSize(), movies.getTotalElements(), movies.getTotalPages(), movies.isLast());
        }

        List<MovieResponse> movieResponses = movies.map(ModelMapper::mapMovieToMovieResponse).getContent();
        return new PagedResponse<>(movieResponses, movies.getNumber(),
                movies.getSize(), movies.getTotalElements(), movies.getTotalPages(), movies.isLast());
    }
}

Итак, вопрос: можно ли использовать для каждой модели, у меня есть класс полезной нагрузки для сериализации json? или там способ получше. также, ребята, это что-то не так с моим кодом, не стесняйтесь комментировать.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
1 281
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

В чем проблема, если мы отправляем объект вместо ответа dto.

  • Невозможно изменить их, потому что мы уже предоставляем его нашему клиенту.

  • Иногда мы не хотим сериализовать некоторые поля и отправлять их в качестве ответа.

Некоторые накладные расходы необходимы для перевода запроса в домен, объекта в домен и т.д. ModelMapper - лучший выбор для перевода.

Попробуйте использовать внедрение конструкции вместо установщика для зависимости мандата.

Всегда рекомендуется разделять DTO и Entity. Сущность должен взаимодействовать с БД / ORM, а DTO должен взаимодействовать с клиентским уровнем (уровень для запроса и ответа), даже если структура Сущность и DTO одинакова.

Здесь Entity - Movie и DTO - это MovieResponse

Используйте существующий класс MovieResponse для запроса и ответа. Никогда не используйте класс Movie для запроса и ответа. а класс MovieServiceImpl должен содержать бизнес-логику для преобразования Сущность в DTO, или вы можете использовать Бульдозер api для автоматического преобразования.

Причина расслоения:

  • Если вам нужно добавить / удалить новые элементы в запросе / ответе, вам не нужно менять много кода
  • если 2 объекта имеют двухстороннее сопоставление (например, отношение один-ко-многим / многие-ко-многим), то Объект JSON не может быть создан, если у объекта есть вложенные данные, это вызовет ошибку при сериализации
  • если что-то изменилось в DB или Entity, то это будет ответ не влияет JSON (большую часть времени).
  • Код будет понятным и простым в обслуживании.

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

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

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

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

Что, если позже вы решите изменить уровень данных? Придется ли вам переписывать всю клиентскую часть?

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

Ответ принят как подходящий

Не так давно у меня была эта дилемма, это был мой мыслительный процесс. Он у меня тут https://stackoverflow.com/questions/44572188/microservices-restful-api-dtos-or-not

Плюсы простого раскрытия объектов домена

  1. Чем меньше кода вы напишете, тем меньше ошибок вы создадите.

    • Несмотря на наличие обширных (спорных) тестовых примеров в нашей базе кода, я сталкивался с ошибками из-за пропущенного / неправильного копирования полей из домена в DTO или наоборот.
  2. Ремонтопригодность - меньше кода котельной плиты.

    • Если мне нужно добавить новый атрибут, мне, конечно, не нужно добавлять в Domain, DTO, Mapper и тестовые наборы. Не говорите мне, что этого можно достичь с помощью таких утилит beanCopy отражения, как dozer или mapStruct, это сводит на нет всю цель.
    • Я знаю Lombok, Groovy, Kotlin, но это избавит меня от головной боли с геттером.
  3. СУХОЙ
  4. Представление
    • Я знаю, что это относится к категории «преждевременная оптимизация производительности - корень всех зол». Но все же это сэкономит некоторые циклы ЦП, так как вам не придется создавать (а позже и сборщик мусора) еще один объект (по крайней мере) для каждого запроса.

Минусы

  1. DTO дадут вам больше гибкости в долгосрочной перспективе

    • Если бы мне только когда-нибудь понадобилась такая гибкость. По крайней мере, все, с чем я столкнулся до сих пор, - это операции CRUD через http, которыми я могу управлять, используя пару @JsonIgnores. Или, если есть одно или два поля, которые нуждаются в преобразовании, которое не может быть выполнено с помощью аннотации Джексона, как я сказал ранее, я могу написать собственную логику для обработки именно этого.
  2. Объекты домена раздуваются аннотациями.

    • Это серьезное беспокойство. Если я использую JPA или MyBatis в качестве постоянного фреймворка, объект домена может иметь эти аннотации, тогда также будут аннотации Джексона. Если вы используете загрузку Spring, вы можете обойтись без таких свойств приложения, как mybatis.configuration.map-underscore-to-camel-case: true, spring.jackson.property-naming-strategy: SNAKE_CASE.

Короткий рассказ, по крайней мере, в моем случае, минусы не перевешивают плюсы, поэтому не имело смысла повторяться, имея новый POJO в качестве DTO. Меньше кода, меньше шансов на ошибку. Итак, продолжаем выставлять объект «Домен» и не иметь отдельного объекта «представления».

Отказ от ответственности: это может или не может быть применимо в вашем случае использования. Это наблюдение соответствует моему варианту использования (в основном API CRUD с 15-ю конечными точками)

Хотя большинство людей ответили за и против использования объектов DTO, я хотел бы отдать свои 2 цента. В моем случае DTO был необходим, потому что не все поля, сохраненные в базе данных, были захвачены от пользователя. Было несколько полей, которые были вычислены на основе пользовательского ввода (других полей) и не были доступны пользователям. Кроме того, он также может уменьшить размер полезной нагрузки, что может привести к повышению производительности в таких случаях.

Я выступаю за отделение объекта «Полезная нагрузка» или «Данные» от объекта «Модель» или «Отображение». Практически всегда. Это просто упрощает управление.

Вот пример: Допустим, вам нужно найти API, который предоставляет данные о выставленных на продажу кошках. Затем вы анализируете данные в объект модели кошки и заполняете список кошек, который затем отображается пользователю. Прохладный.

Но теперь вы хотите интегрировать другой API и вытаскивать кошек из двух баз данных. Но вы столкнетесь с проблемой. Один API возвращает furColor для цвета, а новый возвращает catColor для цвета.

Если вы использовали тот же объект для отображения информации, у вас есть несколько вариантов:

  • Добавьте к объекту модели как furColor, так и catColor, сделайте их необязательными и выполните какое-то вычисленное свойство, чтобы проверить, какое из них установлено, и использовать его для отображения цвета.
    • На самом деле, это редко вариант, потому что ответы обычно будут намного больше отличаться, чем просто одно значение, подобное этому, поэтому вам, вероятно, в любом случае понадобится совершенно новый синтаксический анализатор.
  • Добавьте новый объект данных, а затем также новый адаптер, а затем необходимо выполнить какую-то проверку, чтобы узнать, какой адаптер использовать, когда
  • Что-то еще, с чем все еще не приятно работать

Однако, если вы создаете объект данных, который улавливает ответ, а затем экранный объект, который имеет только информацию, необходимую для заполнения списка, это становится действительно простым:

  • У вас есть объект данных, который захватывает ответ от первого API.
  • Теперь создайте объект данных, который захватывает ответ от второго API.
  • Теперь все, что вам нужно, это какой-то простой картограф для отображения ответа на Display Object.
  • Теперь оба будут преобразованы в обычный простой экранный объект, и один и тот же адаптер можно использовать для отображения новых кошек без дополнительной работы.

Это также сделает хранение данных локально более чистым.

Другие вопросы по теме