Creando tu primer endpoint
Finalmente, manos a la obra. Vamos a crear un endpoint simple que devuelve un usuario: cuando visitemos http://localhost:8080/users, deberíamos obtener algo así:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz"
}
]
Paso 0: Inicializar git
git init
git add .
git commit -m "Initial commit"
Paso 1: Agregar un formatter (Spotless)
-
Agregá el plugin en la sección de plugins (generalmente al principio de
build.gradle
):build.gradleid 'com.diffplug.spotless' version '6.25.0'
-
Agregá la configuración de Spotless (al final de
build.gradle
). Lo siguiente es mi preferencia personal:build.gradlespotless {
java {
target 'src/*/java/**/*.java'
googleJavaFormat()
removeUnusedImports()
cleanthat()
formatAnnotations()
}
groovyGradle {
target '*.gradle'
greclipse()
}
}Para más info sobre configuraciones, chequeá el repositorio de Spotless en GitHub.
-
Auto-formateá en cada build, agregando una nueva tarea:
build.gradletasks.named("build") {
dependsOn 'spotlessApply'
dependsOn 'spotlessGroovyGradleApply'
} -
Ahora que todo está configurado, ejecutá la tarea Build.
-
Guardá los cambios hechos hasta ahora:
git add .
git commit -m "spotless"
Paso 2: Crear el modelo user
Un modelo es un plano para tus datos — define la estructura de la información que maneja tu aplicación.

Otras carpetas se omiten por simplicidad.
Creá User.java
.
package dev.pollito.users_manager.domain.model;
import static lombok.AccessLevel.PRIVATE;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Builder
@Data
@FieldDefaults(level = PRIVATE)
public class User {
Long id;
String name;
String username;
String email;
}
Vamos a usar Lombok para evitar código boilerplate. Lombok genera automáticamente código Java repetitivo al momento de compilar.
- Si tu IDE no tiene el plugin de Lombok instalado, vas a ver errores de compilación. Chequeá Optimizando IntelliJ IDEA con plugins (para Java) para ver cómo agregarlo.
Paso 3: Crear el primary port y su implementación
UserService
es el Primary Port, definiendo las operaciones de usuario.UserServiceImpl
es la implementación, conteniendo la lógica del dominio.

Otras carpetas se omiten por simplicidad.
Creá UserService.java
.
package dev.pollito.users_manager.domain.port.in;
import dev.pollito.users_manager.domain.model.User;
import java.util.List;
public interface UserService {
List<User> findAll();
}
Creá UserServiceImpl.java
.
package dev.pollito.users_manager.domain.service;
import dev.pollito.users_manager.domain.model.User;
import dev.pollito.users_manager.domain.port.in.UserService;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private static final User USER_1 =
User.builder()
.id(1L)
.name("Leanne Graham")
.username("Bret")
.email("Sincere@april.biz")
.build();
@Override
public List<User> findAll() {
return List.of(USER_1);
}
}
- Por el momento, vamos a devolver un usuario hardcodeado.
@Service
le dice a Spring acá tenés una implementación deUserService
.@Override
indica que el métodopublic List<User> findAll()
cumple el contrato de la interfaz.
Paso 4: Crear el primary adapter
- El controlador actúa como un adaptador primario, convirtiendo peticiones HTTP a llamadas al servicio del dominio.
- El modelo de dominio no se expone directamente a los clientes; en su lugar, se usan DTOs.

Otras carpetas se omiten por simplicidad.
Creá UserResponseDTO.java
. Contiene los datos a devolver como respuesta.
package dev.pollito.users_manager.adapter.in.rest.dto;
import static lombok.AccessLevel.PRIVATE;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@FieldDefaults(level = PRIVATE)
public class UserResponseDTO {
Long id;
String name;
String username;
String email;
}
Creá UserMapper.java
. Convierte entre modelos de dominio y DTOs.
package dev.pollito.users_manager.adapter.in.rest.dto;
import static java.util.Objects.isNull;
import dev.pollito.users_manager.domain.model.User;
import org.springframework.stereotype.Component;
@Component
public class UserMapper {
public UserResponseDTO map(User user) {
if (isNull(user)) {
return null;
}
return UserResponseDTO.builder()
.id(user.getId())
.name(user.getName())
.username(user.getUsername())
.email(user.getEmail())
.build();
}
}
Creá UserController.java
. Actúa como un adaptador primario que convierte peticiones HTTP a llamadas al servicio.
package dev.pollito.users_manager.adapter.in.rest;
import dev.pollito.users_manager.adapter.in.rest.dto.UserResponseDTO;
import dev.pollito.users_manager.adapter.in.rest.mapper.UserMapper;
import dev.pollito.users_manager.domain.port.in.UserService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
@GetMapping
public ResponseEntity<List<UserResponseDTO>> findAll() {
return ResponseEntity.ok(userService.findAll().stream().map(userMapper::map).toList());
}
}
- El controlador solo depende de la interfaz del servicio (puerto), no de su implementación.
- La lógica de mapeo se extrae a una clase mapper separada.
- Los objetos de dominio no se exponen a los clientes de la API.
Ejecutá la aplicación
Hacé clic derecho en la clase principal → Run. Luego andá a http://localhost:8080/users.
- El cliente invoca al controlador: Un cliente externo hace una petición GET al endpoint
/users
, que es manejado por elUserController
. - Controlador invoca al servicio: El
UserController
llama al métodofindAll()
de la instancia inyectada deUserService
(que está implementada porUserServiceImpl
). - Servicio retorna al controlador: El
UserServiceImpl
obtiene o genera la lista de objetos de dominioUser
y la devuelve alUserController
. - Nota sobre controlador: El
UserController
procesa la lista de objetosUser
que recibió. - Respuesta enviada al cliente: Después de mapear, el
UserController
envuelve la lista en unResponseEntity.ok()
y devuelve la respuesta HTTP final (con un estado 200 OK) al cliente.

¡Felicidades! Tu app Spring Boot está lista, funcionando y exponiendo un endpoint. Commiteá el progreso hasta ahora.
git add .
git commit -m "/users devuelve usuario hardcodeado"