“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?
- Vazamento de dados pessoais: o CPF e o telefone de todos os membros caem nas mãos de terceiros.
- Sequestro de contas: mesmo uma senha criptografada pode virar pista para um ataque de força bruta (Brute Force).
- Exposição da lógica interna: campos como
createdAtecreatedBypodem revelar a estrutura do banco de dados ou o padrão dos IDs de administrador.
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.
- Entity:
User(id, password, name, ssn, createdAt, createdBy…) -> uso interno da fábrica - DTO:
UserResponse(id, name) -> pronto para entrega
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.
- Abre o User. -> Opa, tem um Team aqui dentro?
- Abre o Team. -> Opa, tem Users aqui dentro?
- Abre o primeiro User da lista de Users. -> Opa, tem um Team aqui dentro?
- Abre o Team. -> Opa, tem Users aqui dentro?
- (e assim por diante, infinitamente…)
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.
- Cadastro de membro:
/joinUser - Edição de membro:
/updateUser - Exclusão de membro:
/deleteUser
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.
- POST
/users: cadastro de membro (crie) - GET
/users/1: consulta do membro 1 (traga) - PUT
/users/1: edição do membro 1 (substitua) - DELETE
/users/1: exclusão do membro 1 (apague)
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.
- Deixe o papel explícito com um sufixo (Suffix)
- Requisição (Request):
Ação+Domínio+Request- Ex.:
JoinUserRequest,UpdateUserRequest
- Ex.:
- Resposta (Response):
Domínio+Response- Ex.:
UserResponse,UserListResponse
- Ex.:
- Se você escrever só
UserDto, fica difícil saber se ele é usado no cadastro ou na consulta, e você acaba tendo que abrir o arquivo toda vez para conferir.
- Requisição (Request):
- Agrupe com classes internas (Inner Class) Se você não quer ver a quantidade de arquivos explodir, recomendo fortemente a estratégia de ‘inner class’. Você cria uma única classe chamada
UserDtoe reúne dentro dela os DTOs relacionados comostatic class. Assim, no código você passa a escreverUserDto.SignUpRequest, e fica claro a quem ele pertence: “Ah, é um DTO relacionado a usuário.”
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.”


