“Ué? Por que a senha está aparecendo?”
Logo no início da minha vida na empresa, eu estava desenvolvendo de forma frenética, indo e voltando entre frontend e backend. Um dia, eu estava integrando na tela do frontend a ‘API de listagem de membros’ criada por um colega que entrou comigo.
Abri a aba Network do Chrome DevTools (F12) para conferir se os dados estavam chegando direito e, no instante em que vi a resposta, duvidei dos meus próprios olhos.
[
{
"id": 1,
"username": "tester",
"password": "$2a$10$D...", // Senha criptografada exposta
"ssn": "900101-1...", // CPF exposto
"createdAt": "2024-01-01"
}
]
Assustado, perguntei para ele: “Ei, por que na resposta dessa API dá para ver a senha e até o número de identificação?”
Ele respondeu numa boa: “Ah, aquilo? De qualquer forma, a tela só mostra o nome, né? Fiquei com preguiça, então devolvi direto o resultado de userRepository.findAll(). Tem algum problema?”
Me deu um arrepio na espinha. Ele só estava buscando praticidade, mas, na prática, tinha plantado a semente de um incidente grave de segurança.
“Só porque não aparece na tela não quer dizer que está seguro!”
Qualquer pessoa pode abrir a aba de rede do navegador. E se um atacante malicioso chamasse essa API?
Enviar uma entidade do jeito que está para fora era praticamente como transformar o quarto principal da nossa casa numa parede inteira de vidro.

DTO: o ‘papel de presente’ dos dados
Uma ‘Entity’ é uma espécie de ‘matéria-prima’ manipulada apenas dentro da fábrica. Nela ficam grudadas coisas que jamais deveriam ser mostradas ao cliente, como segredos (senhas) ou informações de controle interno (data de criação, data de alteração, flag de exclusão).
Já um ‘DTO(Data Transfer Object)’ é o ‘produto final’, devidamente embalado para ser entregue ao cliente.
Retornar uma entidade diretamente é como servir ao cliente, em vez de um bife preparado, um pedaço de carne crua e ensanguentada jogada no prato. A gente precisa passar obrigatoriamente por uma etapa de ‘mapping’.
[Code Verification] O pântano do loop infinito (referência circular)
Mais do que problemas de segurança, o que enlouquece desenvolvedores é o problema da ‘referência circular’. Você lembra do mapeamento bidirecional do JPA que vimos da outra vez, User <-> Team?
O que acontece se você devolver a entidade User diretamente depois de convertê-la para JSON?
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne // Um usuario pertence a um time
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // Um time tem varios usuarios
private List<User> users = new ArrayList<>();
}
Se você devolver User nesse estado, o serializador JSON (Jackson) funciona mais ou menos assim.
Resultado: o servidor cai heroicamente com um StackOverflowError. As relações entre entidades ficam perseguindo umas às outras sem parar, então, se você expõe tudo isso sem cortar a corrente, cai direto num loop infinito. Esse é o motivo decisivo para usar um DTO e levar apenas os campos necessários, como teamName.

A condição de uma boa API: ser RESTful
Se os dados já foram bem embalados com DTOs, agora falta definir o ‘endereço’ certo para onde esse pacote vai. Isso é exatamente o que significa design de REST API.
Na faculdade, eu dava nome para URLs do jeito que queria.
Colocar verbos como join ou update dentro da URL não é uma boa ideia. Em um design RESTful, o ‘recurso’ (substantivo) fica na URL e a ‘ação’ (verbo) fica a cargo do método HTTP.
Quando você estrutura assim, basta olhar para a URL para entender: “Ah, isso lida com recursos de usuário.” Além disso, a comunicação com desenvolvedores frontend fica muito mais fácil. Esse é o acordo comum compartilhado por developers no mundo todo.
Conselho prático: o nome já faz metade do trabalho (estratégia de naming de DTO)
Quando abro o código do projeto atual da empresa, às vezes já solto um suspiro. UserDto, MemberVo, InfoParam, ResultData… quem veio antes deu nome para tudo do jeito que quis, então você não consegue saber se o objeto serve para request ou response antes de abrir o código.
Um DTO é um ‘recipiente’ para dados. Se o uso desse recipiente não está escrito no nome, surge a confusão clássica de colocar arroz na tigela de sopa e água na tigela de arroz. A regra de naming que mais recomendo na prática é a seguinte.
public class UserDto {
// Requisicao de cadastro
public static class SignUpRequest {
private String email;
private String password;
}
// Resposta com info do usuario
public static class Response {
private String name;
private String email;
}
}
Encerrando a série 2: agora a casa está pronta
Com isso, a série [Monolith Builder] chega ao fim. Lançamos a base com Spring Boot (DI), separamos frontend e backend (CORS), aprendemos a lidar com banco de dados com JPA e também a nos comunicar por meio de DTOs e REST APIs.
Agora conquistamos a capacidade de construir sozinhos um bom serviço web. Mas a jornada de um developer não termina aqui. De que adianta se o serviço que eu criei só funciona no meu computador, no localhost? Para mostrá-lo a usuários reais, ele precisa ir para um servidor.
Só que esse servidor não é Windows, é Linux: uma tela preta e nada mais. Sem nem mesmo um mouse, como instalar programas e executá-los ali?
Na próxima série, [Works on My Machine], vamos entrar no mundo de Linux e Docker para resolver o problema eterno dos developers: “Na minha máquina funciona, mas no servidor não.”