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
UserServicees el Primary Port, definiendo las operaciones de usuario.UserServiceImples 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.
 @Servicele dice a Spring acá tenés una implementación deUserService.@Overrideindica 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 
UserControllerllama al métodofindAll()de la instancia inyectada deUserService(que está implementada porUserServiceImpl). - Servicio retorna al controlador: El 
UserServiceImplobtiene o genera la lista de objetos de dominioUsery la devuelve alUserController. - Nota sobre controlador: El 
UserControllerprocesa la lista de objetosUserque recibió. - Respuesta enviada al cliente: Después de mapear, el 
UserControllerenvuelve 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"