代码之家  ›  专栏  ›  技术社区  ›  Ayoub k

我应该使用模型类还是负载类来序列化json响应

  •  3
  • Ayoub k  · 技术社区  · 6 年前

    我使用springboot和mysql来创建restfulapi。下面是一个如何返回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> {
    }
    

    我还有一个有效载荷类 代表电影而不是 电影 例如,如果我需要额外的字段或者需要返回特定的字段。

    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个列表(演员、导演和编剧)

    绘制一个 电影回应 我在用 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;
        }
    }
    

    电影服务 我在控制器中调用的服务:

    @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序列化的有效负载类,这样可以吗?或者有更好的方法。
    另外,如果我的代码有任何问题,请随时发表评论。

    5 回复  |  直到 6 年前
        1
  •  2
  •   so-random-dude    6 年前

    不久前我就遇到了这个难题,这是我思考的过程。我这里有 https://stackoverflow.com/questions/44572188/microservices-restful-api-dtos-or-not

    仅仅公开域对象的优点

    1. 你写的代码越少,产生的bug就越少。

      • 尽管在我们的代码库中有大量的(可论证的)测试用例,但我还是遇到了一些bug,这是由于从域到DTO或viceversa的字段复制丢失/错误造成的。
      • 如果我必须添加一个新属性,我当然不必添加Domain、DTO、Mapper和testcases。不要告诉我,这可以实现使用反射beanCopy utils像推土机或mapStruct,它破坏了整个目的。
      • 我知道龙姆伯克、格洛夫、科特林,但这只会让我省去一个令人头痛的问题。
      • 我知道这属于“过早的性能优化是万恶之源”的范畴。但这仍然会节省一些CPU周期,因为不必为每个请求再创建(以及稍后的垃圾收集)一个对象(至少)

    欺骗

      • 这是一个值得关注的问题。如果我使用JPA或MyBatis作为我的持久化框架,域对象可能有这些注释,那么也会有Jackson注释。如果您使用的是springboot,那么您可以通过使用诸如 mybatis.configuration.map-underscore-to-camel-case: true , spring.jackson.property-naming-strategy: SNAKE_CASE

    短篇小说

    免责声明

        2
  •  1
  •   Gaurav Srivastav    6 年前

    我们应该把每一层都分开。与您的案例一样,您已经定义了实体和响应类。这是分离事物的正确方法,我们永远不应该发送实体中的响应。即使是要求的东西,我们也应该有一节课。

    如果我们发送的是实体而不是响应dto,问题是什么。

    在将请求转换为域、实体转换为域等方面存在一些开销,但保持更有序是可以的。ModelMapper是翻译的最佳选择。

    尝试使用构造注入而不是setter来实现任务依赖。

        3
  •  1
  •   TheSprinter    6 年前

    通常建议将DTO和实体分开。 实体 应该与 数据库/模板 DTO公司 实体 DTO公司 一样。

    这里是实体 Movie DTO是 MovieResponse

    使用现有类 电影回应
    从不使用 电影 请求和响应类。 还有班级 MovieServiceImpl 应包含用于转换的业务逻辑 DTO公司 推土机

    分离原因:

    • 如果两个实体具有双向映射(例如一对多/多对多关系),则 如果对象具有嵌套数据,则无法创建JSON对象,这将在序列化时引发错误
    • 如果数据库或实体中有任何更改,则这将 不影响 JSON响应(大多数情况下)。
        4
  •  1
  •   ACV    6 年前

    如果以后决定更改数据层呢?你需要重写你所有的客户端吗?

    另一方面,还有映射的问题。为此,可以使用性能损失较小的库。

        5
  •  1
  •   S.K.    6 年前

    对于像您这样的简单应用程序,dto往往类似于实体类。但是,对于某些复杂的应用程序,可以扩展dto来组合来自不同实体的数据,以避免对服务器的多个请求,从而节省宝贵的资源和请求响应时间。

    我建议不要在这样一个简单的情况下复制代码,并使用模型类来响应api。使用单独的响应类作为dto并不能解决任何问题,只会使代码的维护变得困难。

        6
  •  0
  •   bluelurker    5 年前

    虽然大多数人都回答了使用DTO对象的利弊,但我想给你2美分。在我的例子中,DTO是必要的,因为并不是数据库中持久存在的所有字段都是从用户捕获的。有几个字段是根据用户输入(其他字段的输入)计算的,不向用户公开。此外,它还可以减小有效负载的大小,从而在这种情况下获得更好的性能。