2

I'm trying to avoid using the DTO antipattern when different EndPoint are called, where each returns a distinct representation of the same entity. I'd like to take advantage of the serialization that Jackson performs when I return the entity in the Rest EndPoint. This means that serialization is only done once and not twice as it would be with a DTO (entity to DTO and DTO to Json):

EndPoints example:

@GetMapping("/events")
public ResponseEntity<List<Event>> getAllEvents(){
    try {
        List<Event> events = (List<Event>) eventsRepository.findAll();
        return new ResponseEntity<List<Event>>(
                events, HttpStatus.OK);
    }catch(IllegalArgumentException e) {
        return new ResponseEntity<List<Event>>(HttpStatus.BAD_REQUEST);
    }
}

@GetMapping("/events/{code}")
public ResponseEntity<Event> retrieveEvent(@PathVariable String code){
    Optional<Event> event = eventsRepository.findByCode(code);
    return event.isPresent() ? 
            new ResponseEntity<Event>(event.get(), HttpStatus.OK) :
            new ResponseEntity<Event>(HttpStatus.BAD_REQUEST);
}

Serializer (class that extends of StdSerializer):

@Override
public void serialize(Event value, JsonGenerator gen, 
        SerializerProvider provider) throws IOException {

    if(firstRepresentation) {
        //First Representation
        gen.writeStartObject();
        gen.writeNumberField("id", value.getId());
        gen.writeObjectField("creation", value.getCreation());

        gen.writeObjectFieldStart("event_tracks");
        for (EventTrack eventTrack : value.getEventsTracks()) {

            gen.writeNumberField("id", eventTrack.getId());
            gen.writeObjectField("startTime", eventTrack.getStartTime());
            gen.writeObjectField("endTime", eventTrack.getEndTime());
            gen.writeNumberField("priority", eventTrack.getPriority());

            gen.writeObjectFieldStart("user");
            gen.writeNumberField("id", eventTrack.getUser().getId());
            gen.writeEndObject();

            gen.writeObjectFieldStart("state");
            gen.writeNumberField("id", eventTrack.getState().getId());
            gen.writeStringField("name", eventTrack.getState().getName());
            gen.writeEndObject();

        }

        gen.writeEndObject();
        gen.writeEndObject();
    }else if(secondRepresentation) {
       //Second Representation
    }
}

Entity:

@JsonSerialize(using = EventSerializer.class)
@RequiredArgsConstructor
@Getter
@Setter
public class Event implements Comparable<Event>{

    private Long id;

    @JsonIgnore
    private String code;

    private Timestamp creation;

    @NonNull
    private String description;

    @JsonUnwrapped
    @NonNull
    private EventSource eventSource;

    @NonNull
    private String title;

    @NonNull
    private Category category;

    @NonNull
    @JsonProperty("event_tracks")
    private List<EventTrack> eventsTracks;

    @JsonProperty("protocol_tracks")
    private List<ProtocolTrack> protocolTracks;

    public void addEventTrack(@NonNull EventTrack eventTracks) {
        eventsTracks.add(eventTracks);
    }

    @JsonIgnore
    public EventTrack getLastEventTrack() {
        return eventsTracks.get(eventsTracks.size() - 1);
    }

    @JsonIgnore
    public int getLastPriority() {
        return getLastEventTrack().getPriority();
    }

    public void generateUUIDCode() {
        this.code = UUID.randomUUID().toString();
    }

    @Override
    public int compareTo(Event o) {
        return this.getLastPriority() - o.getLastPriority();
    }
}

So, so far I have been able to serialize a representation type with a class that extend of StdDeserializer, but this doesn't give me the flexibility to extend the representations of the same entity attributes in multiple ways. Although I've tried it with Json annotations, but I realize that the more representations the entity class has, it can get very complex, something that it should be simple. Maybe some idea how I could do it.

Thank you.

ByteBat
  • 337
  • 4
  • 14
  • 1
    Why do you think that DTOs are an antipattern? You can directly create DTOs from your query. That's a real good pattern. – Simon Martinelli Apr 21 '20 at 14:06
  • And the translation of Entities to DTO except if they are running on different machines is not `serialization`.. it is just `conversion`.. on the same jvm still java object to another java object. – CodeScale Apr 21 '20 at 14:12
  • @SimonMartinell I think about it because I repeat information to such a degree that I can repeat the entity class almost completely. Why not work with the base class and transform it with the responsible class for serializing it directly, Json Serializaer? – ByteBat Apr 21 '20 at 14:17
  • Don't think only about translate entities to DTOS. Prefer load good dto directly from database... This will avoid waste of resources because here you always load the full entity then use a serializer to filter some fields... it is waste.. – CodeScale Apr 21 '20 at 14:38
  • @CodeScale It's a very good point and I like it. Although with the base entity the necessary information is also brought or is requested, and this satisfies the abstraction design principle. So I was looking for a Jackson class that was single responsible for transforming the Json corresponding representation. For now, I will follow your advice. – ByteBat Apr 21 '20 at 14:56
  • **Possible solution:** I want to propose the following solution in order to continue complying with the abstraction design principle. Adding a metadata property that indicates the context to serialize, likewise can be used to deserialize, it's populated in the EndPoint controller. This allows that when Spring uses Jackson the StdSerializer class can know how to serialize the corresponding entity to what is needed. This would also comply with the extensibility principle when a representation of a entity field has complex post-processing. – ByteBat Apr 21 '20 at 15:32

1 Answers1

3

If you want to define multiple representations of the same bean you could use Jackson JsonView.

With json views you can set different strategies to define which property will be serialized in the response and so use different views by endpoint.

Documentation here : https://www.baeldung.com/jackson-json-view-annotation

Just don't forget that you doing REST here....avoid expose too many representations of the same resource

CodeScale
  • 3,046
  • 1
  • 12
  • 20
  • I had seen that annotation, but it also doesn't allow for the flexibility and extensibility that I would need. Imagine that you have 10 or more different representations, the JsonView becomes very complex to handle. So my question is for the purpose of whether you can avoid work with DTO using the StdSerializer class to make multiple representations. – ByteBat Apr 21 '20 at 14:31
  • not with `Serializer`. Only with views because you can link views to specific endpoint – CodeScale Apr 21 '20 at 14:39
  • Question: instead of using jackson serializer... why not a custom bean that will act as a filter by set ignored fields to null. Then add this on your entity `JsonInclude(Include.NON_NULL)`. so then you can call this custom filter by endpoint... – CodeScale Apr 21 '20 at 15:58
  • I prefer personally using `JsonView` because you will reinvent the wheel here... but it is a choice. – CodeScale Apr 21 '20 at 16:04
  • Well I think about the following, I have a base entity class which is what allows me to manage the CRUD with the database. By abstraction I want that class to be the encapsulation of the information. What I don't want is to put too many annotations or complex annotation hierarchies. Nor would I want there to be a Wrapper or DTO that duplicates info or increases one more layer of transfromation. Ideally, in my opinion, it should have my base class and there should be a component that handles the representations as needed by those who call the API. – ByteBat Apr 21 '20 at 16:09
  • yes, in my opinion JsonView is a good option when you have few representations of the same entity, but imagine that you have too many representations (extensibility) it becomes complex to handle it. – ByteBat Apr 21 '20 at 16:11
  • 1
    don't forget that you doing REST here....avoid too many representations of the same resource – CodeScale Apr 21 '20 at 16:16
  • thinking, you are right, the API should have a consistent and general representation, others who call it should deal with how they represent the information obtained. Thank you. – ByteBat Apr 21 '20 at 16:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/212179/discussion-between-bitebat-and-codescale). – ByteBat Apr 21 '20 at 16:25