paint-brush
Cómo introducir una nueva API rápidamente usando Spring Boot y Gradlepor@johnjvester
355 lecturas
355 lecturas

Cómo introducir una nueva API rápidamente usando Spring Boot y Gradle

por John Vester14m2025/03/24
Read on Terminal Reader

Demasiado Largo; Para Leer

El tiempo de comercialización puede determinar el éxito o el fracaso de cualquier idea o solución. Descubra lo rápido que se puede crear una API RESTful aprovechando ChatGPT, Spring Boot, Gradle y Heroku.
featured image - Cómo introducir una nueva API rápidamente usando Spring Boot y Gradle
John Vester HackerNoon profile picture
0-item
1-item
2-item


Durante los últimos cinco años, he tenido la cita “todo comienza con una idea” en la pared de mi oficina.


Mi esposa encontró este producto en Etsy poco después de que empezara a desarrollar una colección de API para una aplicación de fitness. Me encanta esta afirmación porque refleja la pasión que me consume durante la creación de un nuevo proyecto. Este sigue siendo mi aspecto favorito de ser ingeniero, incluso después de tres décadas de carrera.


Lo que he aprendido durante este tiempo es que una idea solo importa si alguien tiene la oportunidad de experimentarla. Si una idea tarda demasiado en hacerse realidad, se pierde una oportunidad porque alguien se adelanta. Por eso las startups siempre se apresuran a lanzar sus ideas al mercado lo antes posible.


Veamos cómo podemos hacer realidad una idea… rápidamente.

Suposiciones

En este artículo, simplificaremos las cosas. Usaremos Java 17 y Spring Boot 3 para crear una API RESTful. En este ejemplo, usaremos Gradle para la automatización de la compilación.


Si bien la idea de servicio que planeamos llevar al mercado normalmente utilizaría una capa de persistencia, la dejaremos de lado para este ejemplo y definiremos estáticamente nuestros datos dentro de una clase de repositorio.


No nos preocuparemos por agregar ninguna seguridad para este ejemplo, simplemente permitiremos el acceso anónimo para esta prueba de concepto.

La API de citas motivacionales

Supongamos que nuestra idea es una API de frases motivacionales. Para asegurarnos de que corramos lo más rápido posible, le pedí a ChatGPT que creara una especificación OpenAPI.


En cuestión de segundos, ChatGPT proporcionó la respuesta:


Aquí está la especificación OpenAPI, en YAML, que ChatGPT generó:


 openapi: 3.0.0 info: title: Motivational Quotes API description: An API that provides motivational quotes. version: 1.0.0 servers: - url: https://api.example.com description: Production server paths: /quotes: get: summary: Get all motivational quotes operationId: getAllQuotes responses: '200': description: A list of motivational quotes content: application/json: schema: type: array items: $ref: '#/components/schemas/Quote' /quotes/random: get: summary: Get a random motivational quote operationId: getRandomQuote responses: '200': description: A random motivational quote content: application/json: schema: $ref: '#/components/schemas/Quote' /quotes/{id}: get: summary: Get a motivational quote by ID operationId: getQuoteById parameters: - name: id in: path required: true schema: type: integer responses: '200': description: A motivational quote content: application/json: schema: $ref: '#/components/schemas/Quote' '404': description: Quote not found components: schemas: Quote: type: object required: - id - quote properties: id: type: integer quote: type: string


Solo tuve que hacer una actualización manual: asegurarme de que las propiedades id y quote fueran obligatorias para el esquema Quote . Y eso fue solo porque olvidé mencionar esta restricción a ChatGPT en mi mensaje original.

Con esto, estamos listos para desarrollar el nuevo servicio utilizando un enfoque API-First .

Construyendo el servicio Spring Boot usando API-First

Para este ejemplo, usaré la CLI de Spring Boot para crear un nuevo proyecto. Así es como se instala la CLI con Homebrew:


 $ brew tap spring-io/tap $ brew install spring-boot

Crear un nuevo servicio Spring Boot

Llamaremos al proyecto quotes , creándolo con el siguiente comando:


 $ spring init --dependencies=web quotes


Examinemos el contenido de la carpeta quotes :


 $ cd quotes && ls -la total 72 drwxr-xr-x@ 11 jvester 352 Mar 1 10:57 . drwxrwxrwx@ 90 jvester 2880 Mar 1 10:57 .. -rw-r--r--@ 1 jvester 54 Mar 1 10:57 .gitattributes -rw-r--r--@ 1 jvester 444 Mar 1 10:57 .gitignore -rw-r--r--@ 1 jvester 960 Mar 1 10:57 HELP.md -rw-r--r--@ 1 jvester 545 Mar 1 10:57 build.gradle drwxr-xr-x@ 3 jvester 96 Mar 1 10:57 gradle -rwxr-xr-x@ 1 jvester 8762 Mar 1 10:57 gradlew -rw-r--r--@ 1 jvester 2966 Mar 1 10:57 gradlew.bat -rw-r--r--@ 1 jvester 28 Mar 1 10:57 settings.gradle drwxr-xr-x@ 4 jvester 128 Mar 1 10:57 src


A continuación, editamos el archivo build.gradle como se muestra a continuación para adoptar el enfoque API-First.


 plugins { id 'java' id 'org.springframework.boot' version '3.4.3' id 'io.spring.dependency-management' version '1.1.7' id 'org.openapi.generator' version '7.12.0' } openApiGenerate { generatorName = "spring" inputSpec = "$rootDir/src/main/resources/static/openapi.yaml" outputDir = "$buildDir/generated" apiPackage = "com.example.api" modelPackage = "com.example.model" configOptions = [ dateLibrary: "java8", interfaceOnly: "true", useSpringBoot3: "true", useBeanValidation: "true", skipDefaultInterface: "true" ] } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.openapitools:jackson-databind-nullable:0.2.6' implementation 'io.swagger.core.v3:swagger-annotations:2.2.20' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } sourceSets { main { java { srcDirs += "$buildDir/generated/src/main/java" } } } compileJava.dependsOn tasks.openApiGenerate tasks.named('test') { useJUnitPlatform() }


Finalmente, colocamos la especificación OpenAPI generada en la carpeta resources/static como openapi.yaml .

Generar los objetos API y Modelo

Después de abrir el proyecto en IntelliJ, ejecuté el siguiente comando para construir los stubs de API y los objetos del modelo.


 ./gradlew clean build


Ahora podemos ver los objetos api y model creados a partir de nuestra especificación OpenAPI. Aquí está el archivo QuotesAPI.java :


Añadir la lógica de negocio

Con el servicio base listo y ya adherido a nuestro contrato OpenAPI, comenzamos a agregar algo de lógica comercial al servicio.


Primero, creamos una clase QuotesRepository que devuelve los datos de nuestro servicio. Como se mencionó anteriormente, estos normalmente se almacenan en una capa de persistencia dedicada. Para este ejemplo, codificar los datos equivalentes a cinco citas funciona perfectamente y nos permite mantenernos enfocados.


 @Repository public class QuotesRepository { public static final List<Quote> QUOTES = List.of( new Quote() .id(1) .quote("The greatest glory in living lies not in never falling, but in rising every time we fall."), new Quote() .id(2) .quote("The way to get started is to quit talking and begin doing."), new Quote() .id(3) .quote("Your time is limited, so don't waste it living someone else's life."), new Quote() .id(4) .quote("If life were predictable it would cease to be life, and be without flavor."), new Quote() .id(5) .quote("If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success.") ); public List<Quote> getAllQuotes() { return QUOTES; } public Optional<Quote> getQuoteById(Integer id) { return Optional.ofNullable(QUOTES.stream().filter(quote -> quote.getId().equals(id)).findFirst().orElse(null)); } }


A continuación, creamos un QuotesService que interactuará con QuotesRepository . Este enfoque mantendrá los datos separados de la lógica de negocio.


 @RequiredArgsConstructor @Service public class QuotesService { private final QuotesRepository quotesRepository; public List<Quote> getAllQuotes() { return quotesRepository.getAllQuotes(); } public Optional<Quote> getQuoteById(Integer id) { return quotesRepository.getQuoteById(id); } public Quote getRandomQuote() { List<Quote> quotes = quotesRepository.getAllQuotes(); return quotes.get(ThreadLocalRandom.current().nextInt(quotes.size())); } }


Por último, solo necesitamos implementar la QuotesApi generada a partir de nuestro enfoque API-First:


 @Controller @RequiredArgsConstructor public class QuotesController implements QuotesApi { private final QuotesService quotesService; @Override public ResponseEntity<List<Quote>> getAllQuotes() { return new ResponseEntity<>(quotesService.getAllQuotes(), HttpStatus.OK); } @Override public ResponseEntity<Quote> getQuoteById(Integer id) { return quotesService.getQuoteById(id) .map(quote -> new ResponseEntity<>(quote, HttpStatus.OK)) .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @Override public ResponseEntity<Quote> getRandomQuote() { return new ResponseEntity<>(quotesService.getRandomQuote(), HttpStatus.OK); } }


En este punto, tenemos una API de citas motivacionales completamente funcional, completa con una pequeña colección de respuestas.

Algunos artículos finales

Spring Boot nos brinda la opción de una interfaz de usuario Swagger Docs basada en web a través de la dependencia springdoc-openapi-starter-webmvc-ui .


 dependencies { ... implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5' ... }


Si bien el marco permite a los ingenieros usar anotaciones simples para describir su API, podemos usar nuestro archivo openapi.yaml existente en la carpeta resources/static .


Podemos implementar este enfoque en el archivo application-properties.yaml , junto con algunas otras actualizaciones de configuración menores:


 server: port: ${PORT:8080} spring: application: name: quotes springdoc: swagger-ui: path: /swagger-docs url: openapi.yaml


Solo por diversión, agreguemos un archivo banner.txt para usarlo al iniciar el servicio. Lo colocamos en la carpeta resources .


 ${AnsiColor.BLUE} _ __ _ _ _ ___ | |_ ___ ___ / _` | | | |/ _ \| __/ _ \/ __| | (_| | |_| | (_) | || __/\__ \ \__, |\__,_|\___/ \__\___||___/ |_| ${AnsiColor.DEFAULT} :: Running Spring Boot ${AnsiColor.BLUE}${spring-boot.version}${AnsiColor.DEFAULT} :: Port #${AnsiColor.BLUE}${server.port}${AnsiColor.DEFAULT} ::


Ahora, cuando iniciamos el servicio localmente, podemos ver el banner:


Una vez iniciado, podemos validar que Swagger Docs esté funcionando visitando el punto final /swagger-docs .



Por último, crearemos un nuevo repositorio basado en Git para que podamos rastrear cualquier cambio futuro:


 $ git init $ git add . $ git commit -m "Initial commit for the Motivational Quotes API"


Ahora, veamos qué tan rápido podemos implementar nuestro servicio .

Usando Heroku para terminar el viaje

Hasta ahora, el objetivo principal de presentar mi nueva idea ha sido crear una especificación de OpenAPI y escribir la lógica de negocio para mi servicio. Spring Boot se encargó de todo lo demás.


Para ejecutar mi servicio, prefiero usar Heroku porque se adapta perfectamente a los servicios de Spring Boot. Puedo implementar mis servicios rápidamente sin complicarme con la infraestructura de la nube. Heroku también facilita la transferencia de valores de configuración para mis aplicaciones basadas en Java .


Para que coincida con la versión de Java que usamos, creamos un archivo system.properties en la carpeta raíz del proyecto. El archivo tiene una línea:


 java.runtime.version = 17


Luego, creo un Procfile en la misma ubicación para personalizar el comportamiento de la implementación. Este archivo también contiene una línea:


 web: java -jar build/libs/quotes-0.0.1-SNAPSHOT.jar


Es hora de implementar. Con la CLI de Heroku , puedo implementar el servicio con unos sencillos comandos. Primero, autentico la CLI y luego creo una nueva aplicación de Heroku.


 $ heroku login $ heroku create Creating app... done, vast-crag-43256 https://vast-crag-43256-bb5e35ea87de.herokuapp.com/ | https://git.heroku.com/vast-crag-43256.git


Mi instancia de aplicación Heroku se llama vast-crag-43256 (podría haber pasado un nombre específico) y el servicio se ejecutará en https://vast-crag-43256-bb5e35ea87de.herokuapp.com/.


Lo último que hay que hacer es implementar el servicio usando un comando Git para enviar el código a Heroku:


 $ git push heroku master


Una vez completado este comando, podemos validar una implementación exitosa a través del panel de Heroku:


¡Ahora estamos listos para probar nuestro nuevo servicio!

Citas motivacionales en acción

Con el servicio de citas motivacionales ejecutándose en Heroku, podemos validar que todo funcione como se espera utilizando una serie de comandos curl .


Primero, obtengamos una lista completa de las cinco citas motivacionales:


 $ curl \ --location 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes'


 [ { "id":1, "quote":"The greatest glory in living lies not in never falling, but in rising every time we fall." }, { "id":2, "quote":"The way to get started is to quit talking and begin doing." }, { "id":3, "quote":"Your time is limited, so don't waste it living someone else's life." }, { "id":4, "quote":"If life were predictable it would cease to be life, and be without flavor." }, { "id":5, "quote":"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success." } ]


Recuperemos una única cita motivacional por ID:


 $ curl \ --location 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/3'


 { "id":3, "quote":"Your time is limited, so don't waste it living someone else's life." }


Tomemos una cita motivacional al azar:


 $ curl --location \ 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/random'


 { "id":5, "quote":"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success." }


Incluso podemos navegar por Swagger Docs también.


Conclusión

El tiempo de comercialización puede determinar el éxito o el fracaso de cualquier idea. Por eso, las startups se centran en entregar sus innovaciones lo más rápido posible. Cuanto más tarden en llegar a la meta, mayor será el riesgo de que un competidor se adelante.


Mis lectores quizá recuerden mi declaración de misión personal, que creo que puede aplicarse a cualquier profesional de TI:


Dedique su tiempo a ofrecer características y funcionalidades que aumenten el valor de su propiedad intelectual. Aproveche los marcos de trabajo, productos y servicios para todo lo demás.

— J. Vester


En este artículo, vimos cómo Spring Boot gestionó todo lo necesario para implementar una API RESTful. Gracias a ChatGPT, incluso pudimos expresar con palabras lo que queríamos que fuera nuestro servicio, y creó una especificación OpenAPI en cuestión de segundos. Esto nos permitió implementar un enfoque API-First. Una vez listos, pudimos implementar nuestra idea usando Heroku con solo unos pocos comandos CLI.

Spring Boot, ChatGPT y Heroku me proporcionaron los frameworks y servicios necesarios para concentrarme plenamente en hacer realidad mi idea. Como resultado, pude cumplir con mi misión personal y, lo más importante, entregar mi idea rápidamente. Solo tenía que centrarme en la lógica de negocio que la sustentaba, ¡y así debe ser!


Si estás interesado, el código fuente de este artículo se puede encontrar en GitLab .


¡Que tengas un día genial!