Este tutorial consiste en migrar la aplicación que hicimos en el curso de MongoDB, Cryptongo, a Spring con Kotlin. Para los que no saben en qué consiste Cryptongo, es un API REST que almacena los datos de CoinMarketCap en una base de datos MongoDB. Esto quiere decir que la aplicación tiene dos partes, la primera es el agente que va al api de CoinMarketCap para almacenar la información en nuestra base de datos, la segunda parte es el api rest que te permite consultar la información que hay en la base de datos de MongoDB.
Creando El Proyecto Base
El primer paso es entrar a https://start.spring.io y generar un nuevo proyecto, usando Kotlin, la versión 2.0 de Spring Boot y escogiendo el soporte para web y mongodb. Esto les generará un zip con una aplicación que pueden importar en el IDE. Para los que no conocen de Spring les recomiendo ver el curso profesional de Java. En donde se explican las bases de este gran framework que ha evolucionado mucho con los años tomando ideas de frameworks como Rails y Django y volviéndose un framework que toma en cuenta la convención sobre la configuración. Van a ver lo fácil que es crear el proyecto y ponerlo a andar.
Creación del Agente
El primer paso es crear un DTO que represente la información retornada por https://api.coinmarketcap.com/v1/ticker/. A esta la clase la llamaremos CoinMarketCapTicker. De esta clase se puede notar que se necesitó la creación de un constructor vacío, esto es para que Jackson, la librería que hace el paseo de JSON pueda crear una instancia de CoinMarketCap.
data class CoinMarketCapTicker(
varid : String = "",
varname: String = "",
varsymbol: String = "",
varrank : String = "",
varprice_usd: String = "",
varprice_btc: String = "",
varavailable_supply: String? = "",
vartotal_supply: String? = "",
varmax_supply: String? = "",
varlast_updated: String? = "") {
constructor() : this(id = "")
}
Ahora el siguiente paso es crear la clase encargada de ir al api y obtener la lista de tickers. de esta clase es importante resaltar el uso de un companion object que no es más que un equivalente a una atributo estático y final en Java y el RestTemplate que es una cliente http que provee Spring.
import org.springframework.stereotype.Component
import org.springframework.web.client.RestTemplate
@Component
classCoinMarketCapClient() {
val restTemplate: RestTemplate = RestTemplate()
companionobject {
val API_URI : String = "https://api.coinmarketcap.com/v1/ticker/"
}
fungetCryptoCurrenciesFromApi(): Array<CoinMarketCapTicker> {val result = restTemplate.getForObject(API_URI,
Array<CoinMarketCapTicker>::class.java)return result
}
}
Ya con la clase que nos permite obtener los datos de CoinMarketCap, necesitamos una manera de almacenar estos datos en nuestra base de datos MongoDB. Lo primero es crear una clase que representa el documento de Mongo, estas clases vienen con la anotación @Document y @Id que representa el campo _id que vimos en el curso de Mongo.
@Document
data class Ticker(
@Id
varid: String? = null,
val coinMarketCapId : String,
val name: String,
val rank: Int,
val lastUpdated: Int,
varhash : Int? = null
)
El siguiente paso es crear nuestro repositorio usado SpringData, lo que nos permiten los repositorios de spring data es tener un CRUD sobre nuestro modelo de datos sin tener que preocuparnos por la base de datos que tenemos por debajo. Este repositorio es igual de válido para una base de datos MySQL como para MongoDB, si quisieramos cambiar de base de datos, solo tendríamos que modificar la clase Ticker.
Los 3 métodos que están dentro del respotorio los usaremos más adelante pero vale la pena notar que no estamos escribiendo la implemetación de estos métodos, con solo nombrarlos correactamente Spring genera la implementación de acuerdo a la base de datos que usemos. A Esto se le conoce como Query Methods, para más información sobre ellos pueden ver la documentación oficial.
import com.davidfcalle.cryptongo.model.Ticker
import org.springframework.data.repository.PagingAndSortingRepository
import java.util.*
interface TickerRepository : PagingAndSortingRepository<Ticker, String> {
funfindOneByHash(hash: Int?): Optional<Ticker>funfindTop20ByOrderByRank(): List<Ticker>fundeleteByName(name: String)
}
Ahora podemos obtener los datos de CoinMarketCap y Guardar Datos en nuetra base de datos Mogno, pero hace falta la conexión entre ambas partes, para eso crearemos una clase llamada AgentService, que cada 50 segundos vaya al api de CoinMarketCap y Guarde los Datos que hayan cambiado. Esto quiere decir que esta clase hace 3 cosas. Primero va por los datos de CoinMarketCap, Segundo convierte esos datos a nuestro documento, en este caso nuestra clase Ticker y 3 Guarda los datos que no estén repetidos. Todo esto aprovechando las capacidades de programción funcional que nos ofrece Kotlin.
@Service
classAgentService(
val client : CoinMarketCapClient,
val mapper: TickerMapper,
val repository: TickerRepository) {
@Scheduled(fixedDelay = 50000)
funsaveApiData() = repository.saveAll(
client.getCryptoCurrenciesFromApi()
.map { mapper.mapTickerToModel(it) }
.filter { !repository.findOneByHash(it.hash).isPresent() })
}
Hace falta la clase que hace la conversión entre los dos objetos (CoinMarketCapTicker y Ticker).
@Component
classTickerMapper() {
funmapTickerToModel(ticker: CoinMarketCapTicker): Ticker {val ticker = Ticker(
coinMarketCapId = ticker.id,
name = ticker.name,
rank = Integer.valueOf(ticker.rank),
lastUpdated = Integer.valueOf(ticker.last_updated)
)
ticker.hash = ticker.hashCode()
return ticker
}
}
Con esto ya tenemos el agente terminado, solo hace falta crear nuestro API, pero gracias a Spring Data este trabajo ya está muy adelantado y solo es necesario declarar los endpoints en el controller y llamar a un endpoint de nuestro repositorio. De esto es importante resaltar el método getTickers que recibe un Pageable por parámetro, esta es otra clase utiliitaria de Spring que nos ayuda a crear automáticamente paginación a nuestro api. Dado que nuestro repositorio soporta Pageable, no es necesaria ninguna implementaciónde nuestro lado.
Este controlador tiene 3 métodos; obtener todos los tickers, el top 20 y eliminar un ticker basado en el nombre. Aprovechamos las capacidades de Kotlin de hacer funciones en una sola linea para simplificar la implementación.
@RestController
class TickerController (
val tickerRepository: TickerRepository){
@GetMapping("/tickers")
fun getTickers(page: Pageable) = tickerRepository.findAll(page)
@GetMapping("/top-20")
fun getTop20(): List = tickerRepository.findTop20ByOrderByRank()
@DeleteMapping("/tickers/{name}")
fun deleteTicker(@PathVariable(required = true) name: String) = tickerRepository.deleteByName(name)
}
Para finalizar, es necesario agregar la anotación @EnableScheduling a la clase Main que permite a Spring tener la capacidad de agregar tareas programadas (el agente quese ejecuta cada 50s es una tarea programada)
@SpringBootApplication@EnableScheduling
class Cryptongo
Con esto terminamos la implementaciónde Cryptongo, ¿Realmente fácil no lo creen? Dejaré para otro post hacer pruebas unitarias y de integraciónde este proyecto. Hazme saber si te quedan dudas y lo que piensas de Spring + Kotlin.