Sesión 5 — La interfaz en Vaadin¶
Rama: sesion-5 (= main)
Lo que vas a lograr: una interfaz web para crear pagos y verlos en una tabla, escrita en Kotlin con Vaadin, sin una línea de HTML ni JavaScript. Y, lo más importante para esta charla, vas a ver que la UI es otro adaptador de entrada: llama al mismo caso de uso que el REST, sin tocar el dominio.
Qué es Vaadin
Vaadin Flow es un framework para construir interfaces web en el servidor, con Kotlin o Java. Vos escribís componentes (botones, campos, tablas) como objetos, y Vaadin se encarga del HTML, el CSS y la comunicación con el navegador. No escribís JavaScript. Para un equipo de backend, es la forma más directa de ponerle cara a un servicio.
Parte 1 — Agregar Vaadin al proyecto (10 min)¶
En payments/build.gradle.kts, agregá el plugin y la dependencia de Vaadin:
plugins {
// ...los que ya tenías...
id("com.vaadin") version "25.1.5"
}
dependencies {
// ...las que ya tenías...
implementation("com.vaadin:vaadin-spring-boot-starter")
developmentOnly("com.vaadin:vaadin-dev")
}
dependencyManagement {
imports {
mavenBom("com.vaadin:vaadin-bom:25.1.5")
}
}
Compatibilidad de versiones
Vaadin 25 requiere Java 21 y va con Spring Boot 4. Si usás otra versión de Boot, revisá en vaadin.com la versión de Vaadin compatible antes de fijarla.
Qué hace vaadin-dev
developmentOnly("com.vaadin:vaadin-dev") agrega las herramientas de desarrollo de Vaadin (recarga en caliente, el menú de debug en el navegador). Solo se incluye en desarrollo, no en el build de producción.
Recargá Gradle en IntelliJ. La primera vez Vaadin descarga su frontend (tarda un poco).
Parte 2 — La vista de pagos (20 min)¶
La UI es un adaptador de entrada, así que vive en infrastructure/adapter/in/ui. Inyecta el mismo caso de uso que usa el controlador REST.
PaymentView.kt:
package com.baqjug.payments.infrastructure.adapter.`in`.ui
import com.baqjug.payments.application.port.`in`.ProcessPaymentUseCase
import com.baqjug.payments.domain.model.Money
import com.baqjug.payments.domain.model.Payment
import com.vaadin.flow.component.button.Button
import com.vaadin.flow.component.grid.Grid
import com.vaadin.flow.component.notification.Notification
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.component.textfield.BigDecimalField
import com.vaadin.flow.router.Route
@Route("")
class PaymentView(
private val useCase: ProcessPaymentUseCase
) : VerticalLayout() {
private val amountField = BigDecimalField("Monto a cobrar")
private val grid = Grid(Payment::class.java)
init {
val payButton = Button("Cobrar") {
val payment = useCase.process(Payment(amount = Money(amountField.value)))
Notification.show("Pago ${payment.status}")
refresh()
}
add(amountField, payButton, grid)
configureGrid()
refresh()
}
private fun configureGrid() {
grid.setColumns()
grid.addColumn { it.id.value }.setHeader("ID")
grid.addColumn { it.amount.value }.setHeader("Monto")
grid.addColumn { it.status.name }.setHeader("Estado")
}
private fun refresh() {
grid.setItems(useCase.findAll())
}
}
Kotlin y Vaadin al paso
@Route("")registra esta vista en la raíz (/). Vaadin la convierte en una página web.- La clase extiende
VerticalLayout: un contenedor que apila componentes de arriba a abajo. BigDecimalField,Button,Grid,Notificationson componentes de Vaadin. Los creás como objetos; Vaadin los pinta en el navegador.Button("Cobrar") { ... }usa una lambda de Kotlin como el manejador del clic. Limpio y directo.grid.addColumn { it.amount.value }define una columna a partir de cadaPayment. Otra vezit, el parámetro implícito."Pago ${payment.status}"es un string template: mete el valor dentro del texto sin concatenar.
Parte 3 — Probar y la idea clave (10 min)¶
Levantá provider-sim y payments, y abrí http://localhost:8080 en el navegador. Escribí un monto, hacé clic en Cobrar, y mirá cómo aparece el pago en la tabla con su estado.
La idea que cierra la charla
Mirá lo que hicimos: agregamos una interfaz web completa y el dominio no cambió, el servicio no cambió, los puertos no cambiaron. La vista de Vaadin es un adaptador de entrada más, igual que el controlador REST. Los dos llaman al mismo ProcessPaymentUseCase.
Entrada intercambiable, salida intercambiable, negocio intacto en el centro. Eso es la arquitectura hexagonal funcionando de punta a punta.
Cierre¶
git add .
git commit -m "sesion-5: interfaz en Vaadin como adaptador de entrada"
git branch sesion-5
git branch -f main sesion-5
Listo. Construiste un servicio de pagos hexagonal en Kotlin, cambiaste de proveedor sin tocar el negocio, y le pusiste una UI en Vaadin sin ensuciar el dominio. Mirá las Referencias para seguir.