Just dton't

That is a typo in the title, but I tried to make it cute and clever. I am not so sure it works. What I want to state though is you do not need DTO's in almost all cases. I did however find a use case for it, but I am not sure I would call it a DTO.

Guilty

I am guilty of this for sure. Just look at this post. It has DTOs, a DAO even and some Entities. Just recently I was rewriting/refactoring my personal project, that is just a website with apps for me and my family. As I was looking at it, I found it so needlessly complicated. Data was duplicated everywhere.

Also looking at this post, not to criticise it, you can always see the main pattern coming forth when you see you do not need it. That pattern is the following:

public class CandidateModelMapper {

    public static CandidateDTO mapToCandidateDto(Candidate candidate){
        return new CandidateDTO(
                candidate.getCandidateId(),
                candidate.getFirstName(),
                candidate.getLastName(),
                candidate.getAge()
               
        );
    }

    public static Candidate mapToCandidate(CandidateDTO canidateDto){
        return new Candidate(
                canidateDto.getCandidateId(),
                canidateDto.getFirstName(),
                canidateDto.getLastName(),
                canidateDto.getAge();
        );
    }
}

At first sight it is even difficult to spot the differences between them. Well the class name and the methods accessible is the only difference, not the properties.

That last bit is what is key. The reason a DTO got invented was because you never knew where your code would go in a remote system structure. So you just sent it the data only, not the connection to the database and so forth.

However all these systems, including the one I was making for myself, operate in the same space all the time. None of my code will ever leave my own application. So yes in theory when I return a model to serialize to the frontend as a response in that controller I could do database changes, but why would I?

Also why would anyone in the team do that? If it did happen on accident by a junior, it would surely be caught by a simple code review.

Also because the services all look like this now:

@Override
    public Mono<CandidateDTO> getCandidate(String candidateId) {
        Mono<Candidate> candidateMono = candidateRepository.findById(candidateId);
        return candidateMono.map((candidate -> CandidateModelMapper.mapToCandidateDto(candidate)));
    }

Whereas they might as well be just this:

@Override
    public Mono<Candidate> getCandidate(String candidateId) {
        return candidateRepository.findById(candidateId);
    }

Done. It will get serialized just all the same in the controller method. If you want to omit something, like a password field for example, then you just use a special circumstance serializer. Maybe add a computed field, or do some other stuff just add it to the serialization step. You don't have to create a DTO to hold that information to ship around one method call deep.

Copying

In any case you are just making the code worse, because you have to copy and serialize internally continuously between classes, that are for all intents and purposes the same. When I was refactoring my own code, I found out that instead of converting things, or mapping things I could just pass along the Model itself. It made the code very light, and then when I also switched database frameworks I could even make the service methods static as the Model itself handled the calls to the database.

Are services necessary?

The services are a good thing though, for several reasons. One is it is nice to know that there is one place where the connection to the database is made and should also be closed at the end of that scope. Otherwise you just call the database whenever you need a model instance and that can be in several places in the codebase even maybe several times in one codepath.

Other thing is if you ever need something added, like pagination for example, you only need to add it in one place and then maybe change some places where you invoke that method. So a centralized places is nice.

Loosely coupled

I used the wrong reasoning when stating you have to do this because you want to decouple everything from your frameworks and so on. So you have a DAO layer because you need to abstract away how it gets the data from a database or store somewhere. I think I can ask a room full of developers the question did you ever switch databases on an existing project that was already running in production, then raise your hand?

I think I might get no hands 99.99% of the time. I only once seen it on a project I just arrived at. I did not ever switch out for another ORM framework though.

Most of the time when you want to do something like that, or you are forced to do it, you make a v2 of something and then deploy that. You take the opportunity to rewrite things.

Actual use case

I did see an actual use case in a project that was a simple storage system built in Golang. It had one DTO, which I am not sure we should call that anymore. It was a message struct. Just to pass a message to the frontend. It was not stored in a database anywhere, it was only sent to the frontend in responses.

That is about the one actual use case I can think of, or you might want to ship two properties somewhere or a combined object made up of a number of other objects or parts of them.

Conclusion

At the end of the day, just ship models everywhere. Especially if you are in your own codebase only. Make it easier on yourself not harder.