Should i use model classes or payload classes to serialize a json response

Ayoub k :

I'm using spring boot with mysql to create a Restful API. Here's an exemple of how i return a json response.

first i have a model:

@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
}

and correspond repository:

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

Also i have a payload class MovieResponse which represent a movie instead of Movie model, and that's for example if i need extra fields or i need to return specific fields.

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()));
    }
}

As you can see i divide the movieCelebrities list into 3 lists(cast, directos and writers)

And to map a Movie to MovieResponse I'm using ModelMapper class:

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;
    }
}

and finally here's my MovieService service which i call in the controller:

@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());
    }
}

So the question here: is it fine to use for each model i have a payload class for the json serialize ? or it there a better way.
also guys id it's there anything wrong about my code feel free to comment.

so-random-dude :

I had this dilemma not so long back, this was my thought process. I have it here https://stackoverflow.com/questions/44572188/microservices-restful-api-dtos-or-not

The Pros of Just exposing Domain Objects

  1. The less code you write, the less bugs you produce.

    • despite of having extensive (arguable) test cases in our code base, I have came across bugs due to missed/wrong copying of fields from domain to DTO or viceversa.
  2. Maintainability - Less boiler plate code.

    • If I have to add a new attribute, I don't have to add in Domain, DTO, Mapper and the testcases, of course. Don't tell me that this can be achieved using a reflection beanCopy utils like dozer or mapStruct, it defeats the whole purpose.
    • Lombok, Groovy, Kotlin I know, but it will save me only getter setter headache.
  3. DRY
  4. Performance
    • I know this falls under the category of "premature performance optimization is the root of all evil". But still this will save some CPU cycles for not having to create (and later garbage collect) one more Object (at the very least) per request

Cons

  1. DTOs will give you more flexibility in the long run

    • If only I ever need that flexibility. At least, whatever I came across so far are CRUD operations over http which I can manage using couple of @JsonIgnores. Or if there is one or two fields that needs a transformation which cannot be done using Jackson Annotation, As I said earlier, I can write custom logic to handle just that.
  2. Domain Objects getting bloated with Annotations.

    • This is a valid concern. If I use JPA or MyBatis as my persistent framework, domain object might have those annotations, then there will be Jackson annotations too. If you are using Spring boot you can get away by using application-wide properties like mybatis.configuration.map-underscore-to-camel-case: true , spring.jackson.property-naming-strategy: SNAKE_CASE

Short story, at least in my case, cons didn't outweigh the pros, so it did not make any sense to repeat myself by having a new POJO as DTO. Less code, less chances of bugs. So, went ahead with exposing the Domain object and not having a separate "view" object.

Disclaimer: This may or may not be applicable in your use case. This observation is per my usecase (basically a CRUD api having 15ish endpoints)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=88473&siteId=1