Saltar a contenido

Sesión 1 — El proyecto y el dominio

Rama: sesion-1 Lo que vas a lograr: el proyecto payments generado con el asistente de Spring, abierto en IntelliJ, con los paquetes hexagonales y el dominio modelado en Kotlin puro. Y de paso, entender el Kotlin que usamos.


Parte 1 — Generar el proyecto con Spring Initializr (15 min)

Spring Initializr es un asistente web que arma el esqueleto del proyecto. Entrá a https://start.spring.io y llená:

Campo Valor
Project Gradle - Kotlin
Language Kotlin
Spring Boot 4.0.6 (o la 4.x estable más cercana)
Group com.baqjug
Artifact payments
Package name com.baqjug.payments
Packaging Jar
Java 21

En Dependencies, agregá:

Dependencia Para qué
Spring Web La API REST (adaptador de entrada)
Spring Data JPA Guardar pagos (adaptador de salida)
PostgreSQL Driver El driver de la base
Validation Validar datos de entrada
Docker Compose Support Que Spring levante la base con Docker al arrancar
OpenFeign Llamar a la pasarela de pago (sesión 3)

Hacé clic en GENERATE, y descomprimí dentro de tu repo:

unzip ~/Downloads/payments.zip

Abrí payments en IntelliJ (File → Open). La primera vez baja el wrapper de Gradle y las dependencias. Esperá a que termine.


Parte 2 — Crear la estructura de paquetes (10 min)

En hexagonal se arranca por el centro: el dominio existe primero, antes que el controlador y la base. Creá esta estructura dentro de com.baqjug.payments (clic derecho → New → Package):

com.baqjug.payments
├── domain
│   └── model            ← Payment, Money, PaymentId, PaymentStatus
├── application
│   ├── port
│   │   ├── in           ← lo que ofrecemos (casos de uso)
│   │   └── out          ← lo que necesitamos (pasarela, repositorio)
│   └── service          ← implementa los casos de uso
└── infrastructure
    └── adapter
        ├── in
        │   └── web      ← REST y, más adelante, Vaadin
        └── out
            ├── persistence   ← JPA
            └── gateway       ← Feign a la pasarela

El paquete in

in es palabra reservada en Kotlin. Como nombre de paquete funciona, pero el import va entre backticks: port.`in`. Si te molesta, podés usar incoming/outgoing.


Parte 3 — El dominio (15 min)

Negocio puro, sin una sola anotación de framework. Vamos clase por clase, explicando el Kotlin.

domain/model/PaymentId.kt:

package com.baqjug.payments.domain.model

import java.util.UUID

@JvmInline
value class PaymentId(val value: String = UUID.randomUUID().toString())

Kotlin al paso: value class

Un value class (con @JvmInline) envuelve un solo valor sin costo en tiempo de ejecución: en el bytecode es casi un String, pero en tu código es un tipo distinto. Sirve para no confundir un PaymentId con cualquier otro String. El = UUID.randomUUID().toString() es un valor por defecto: si no pasás un id, se genera uno.

domain/model/Money.kt:

package com.baqjug.payments.domain.model

import java.math.BigDecimal

data class Money(
    val value: BigDecimal,
    val currency: String = "COP"
) {
    init {
        require(value > BigDecimal.ZERO) { "El monto debe ser mayor a cero" }
    }
}

Kotlin al paso: data class, val y require

Una data class te da equals, hashCode, toString y copy gratis: ideal para objetos de valor. val es inmutable (como final en Java); var sería mutable. El bloque init corre al construir el objeto, y require(...) lanza una excepción si la condición no se cumple: así la regla "el monto es positivo" vive en el dominio, no en un validador externo.

domain/model/PaymentStatus.kt:

package com.baqjug.payments.domain.model

enum class PaymentStatus { PENDING, APPROVED, REJECTED }

domain/model/Payment.kt:

package com.baqjug.payments.domain.model

data class Payment(
    val id: PaymentId = PaymentId(),
    val amount: Money,
    var status: PaymentStatus = PaymentStatus.PENDING
)

Kotlin al paso: var con intención

Casi todo es val, pero status es var a propósito: el pago nace PENDING y cambia a APPROVED o REJECTED. Es la única cosa que muta en el ciclo de vida del pago.

Lo importante: mirá lo que NO hay

Ni @Entity, ni @Component, ni un import de Spring o de JPA. Es Kotlin puro. Las reglas viven en el dominio. Esto se lee como negocio.


Cierre de la sesión

./gradlew compileKotlin
git add .
git commit -m "sesion-1: proyecto payments y dominio puro"
git branch sesion-1

En la Sesión 2 definimos los puertos, el servicio y un test que corre sin Spring.