miércoles, 24 de noviembre de 2021

¿Como podemos aprender?

 version en video:


https://youtu.be/kESKSuopWXo

 

creo que nosotros como desarrolladores debemos enfocarnos mucho en aprender nuevas cosas, el mundo de la tecnología cambia constantemente aunque los principios sigan siendo los mismos, por lo que nos toca optimizar el aprendizaje más que las funcionalidades a nivel individual(esto es una idea que saque del canal https://www.youtube.com/c/ContinuousDelivery ) tiene sentido entonces que entedamos un poco qué métodos podemos usar para aprender mejor.

lo primero es reconocer cuales son los pasos mediante los que aprendemos algo nuevo


  1. codificación(encoding): aquí transformamos los estímulos visuales y las entradas sensoriales que recibimos en un modelo mental.
  2. consolidación(consolidation): aquí vinculamos lo que aprendemos a otra ideas o conocimientos previos que teníamos.
  3. Recuperación(retrieval): en este paso repasamos la información aprendida a intervalos cada vez más amplios para poderla recordar y usar en el futuro.

(pasos del libro Make it stick the science of successful learning)


la parte de codificación para mi pasa naturalmente cuando leemos o estudiamos algo, y lo vamos practicando siguiendo el tutorial, tomando notas resumen de lo estudiado(ojala a mano por que se aprende mejor con estas), explicárselo a otra persona, o sin ver intentado recordar qué fue lo que acabamos de leer.

luego para el paso de consolidación deberíamos pensar en cómo se relaciona lo que aprendimos con otras cosas que sabemos, en cómo se parece a otro lenguaje, en que se diferencia a otro patrón de diseño, esto es un hijo de cual concepto padre o base etc. estos vínculos nos van a ayudar a que podamos usar con mayor facilidad el conocimiento adquirido porque estamos creando más caminos para llegar a él. y puede hacerse reflexionando durante el día sobre lo aprendido como lo dicen en make it stick the science of successful learning, haciendo un diagrama donde conectamos las ideas visualmente, o usando una herramienta para tomar notas vinculadas como joplin o obsidian(últimamente estoy evaluando usar la ultima con zettelkasten).

finalmente la recuperación  creo que a todos nos ha pasado que hemos estudiado un montón de cosas durante la vida pero seguramente lo hemos olvidado, y es olvidar es normal y hace parte del aprendizaje como lo dice en make it stick the science of successful learning, pero hay cosas que quisiéramos recordar y recordar es fundamental para poder usar el conocimiento que adquirimos si no podemos recordar algo no vamos a poder usarlo.para recordar lo recomendado es realizar pruebas espaciadas de lo que queremos recordar para esto lo que más me gusta a mi es usar anki para hacer flashcards.

lo que hacemos en anki es crear cartas donde por un lado tenemos una pregunta y por el otro lado tenemos la respuesta, el software se va encargar de mostrarnos las tarjetas en el momento correcto para que no olvidemos lo estudiado, nosotros revisaremos a diario indicando si pudimos recordar la parte de atrás sin mirar, si siempre podemos recordar una carta esta se ira no la preguntara cada vez menos a menudo, es un gran resumen a groso modo pero recomiendo que si vas a usar anki busque más información al respecto por que siempre tiene su ciencia: algunas recomendaciones serían hacer cartas con solo 1 concepto por carta, responder antes de ver la respuesta, poner imagenes  en la carta si tiene sentido y limitar el número de nuevas cartas por dia.

este tema del meta learning/aprender a aprender es muy muy amplio intente resumir un poquito al respecto hace ratico lei el libro learning how to learn (del que hay un curso en coursera en español gratis) que me pareció muy bueno y ahora ando leyendo (sin terminar) el libro make it stick the science of successful learning tambien me ha parecido muy bueno.



lunes, 18 de octubre de 2021

Spring boot, cache con ttl diferente y reactor

 La idea es mostrar como tener un cache en memoria con caffeine(una forma de hacer cache en spring de manera muy optima) funcional en spring boot, usando reactor(akka flux/mono) y que podamos definir diferentes ttl(time to live, o tiempo de vida es decir cuando dura el cache hasta que deje de ser valido y se borre) para diferentes caches en una aplicacion.

El cache es para mi una estrategia de optimizacion que es conveniente usar en varios casos por ejemplo:

  • si tenemos una respuesta de un consumo de una api que es valida por 10 horas, pero la usamos cada hora podriamos hacer un cache que viva 9 horas pues asi no ahorramos la espera en cada uno de los consumos pues guardamos los resultados en una capa de memoria intermedia
  • si vemos que nuestra aplicacion va consumir de manera intensiva otra aplicacion y no queremos que la otra api tenga que procesar esos niveles de carga(haciendole un dos no intencionado).
  • si estamos preocupados por el tiempo que va tomar un proceso en nuestra aplicacion y es aceptable tener información no actualizada para disminuir el tiempo que toma hacer una operación.

el primer llamado a un servicio puede lucir asi

 

pero luego de que el servicio esta cacheado nos ahorramos el llamado al servicio usando la información del llamado anterior.

en una aplicacion podemos tener diferentes servicios con diferentes necesidades y por tanto también podemos necesitar que unos caches vivan mas o menos que otros. por ejemplo si tenemos un cache para una apikey que nos sirve por 5 minutos y otro servicio que lo cacheamos por que lo llamamos muy seguido y lo cacheamos 5 segundos. por eso en algunos casos hace sentido que tengamos diferentes tiempo de vida.


bueno para hacer la demo lo primero que vamos a hacer es ir a https://start.spring.io/ y vamos a crear un proyecto con gradle, kotlin, spring boot 2.5.5 group com.cacheejemplo, nombre cacheejemplo, packaging jar, y java 17.

agregamos la dependencia de spring reactive web nos queda algo asi.


los descargamos, descomprimimos y abrimos en mi caso con intellij ide.

para usar caffeine un gestor de cache muy bueno vamos a agregarlo en el archivo build.gradle.kts en dependencies.

implementation("com.github.ben-manes.caffeine:caffeine")
implementation( "org.springframework:spring-context-support")

 Colocamos la anotacion @EnableCaching en el archivo  CacheejemploApplication (para que spring nos ponga a funcionar el cache), ademas de la anotacion @ConfigurationPropertiesScan para que podamos definir nuestra configuracion para cada uno de los caches nos queda asi.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
import org.springframework.cache.annotation.EnableCaching

@SpringBootApplication
@EnableCaching
@ConfigurationPropertiesScan
class CacheejemploApplication

fun main(args: Array<String>) {
runApplication<CacheejemploApplication>(*args)
}

 luego vamos a crear un archivo llamado CacheStockApi.kt donde vamos a definir nuestros cache consumiendo estoy usando una api que encontre en el internet  que devuelve lastUpdateAt por que me servia para validar manualmente que las respuestas se estaban cacheando y no requeria autentificacion ni nada https://walltime.info/api.html

import com.cacheejmplo.demo.CacheStockApi.CacheStockApi.min1Cache
import com.cacheejmplo.demo.CacheStockApi.CacheStockApi.min5Cache
import org.springframework.cache.annotation.CacheConfig
import org.springframework.cache.annotation.Cacheable
import org.springframework.http.MediaType
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
@CacheConfig(cacheNames=[min5Cache,min1Cache])
 
class CacheStockApi {

val webClient = WebClient.create("https://s3.amazonaws.com")
@Cacheable(min5Cache)
fun getApiInfo5Min(): Mono<String> =
getApiInfo().cache();


@Cacheable(min1Cache)
fun getApiInfo1Min(): Mono<String> =
getApiInfo().cache();


    private fun getApiInfo(): Mono<String> {
    return webClient.get().uri("/data-production-walltime-info/production/dynamic/walltime-info.json?now=1528962473468.679.0000000000873")
     .accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(String::class.java);
    }
object CacheStockApi{
const val min5Cache="5min"
const val min1Cache="1min"
}
}

 

aqui resaltar poner la anotacion @CacheConfig arriba de la clase con los nombres de los diferentes caches que vamos a usar, luego poner en cada metodo que queremos cachear poner la anotacion @Cacheable con el nombre del cache para ese metodo, y luego .cache al Mono que nos retorna la respuesta(esto es un hack para lograr que el cache nos funcione con spring https://stackoverflow.com/questions/48156424/spring-webflux-and-cacheable-proper-way-of-caching-result-of-mono-flux-type/48156827#comment122359092_48156827


lo siguiente es entonces habilitar estas configuraciones para el cache para estos vamos a crear un archivo CacheConfig.kt con el siguiente contenido.

import com.github.benmanes.caffeine.cache.Caffeine
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
import org.springframework.cache.CacheManager
import org.springframework.cache.support.SimpleCacheManager
import org.springframework.context.annotation.Bean
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors
import com.github.benmanes.caffeine.cache.Ticker
import org.springframework.cache.caffeine.CaffeineCache

@ConstructorBinding
@ConfigurationProperties(prefix = "caching")
data class CacheConfig(
val specs: Map<String, CacheSpecification>
) {
private val logger: Logger = LoggerFactory.getLogger(CacheejemploApplication::class.java)

@Bean
fun cacheManager(ticker: Ticker): CacheManager {
val manager = SimpleCacheManager()
if (specs != null) {
val caches = specs.entries.stream()
.map { (key, value): Map.Entry<String, CacheSpecification> ->
buildCache(
key,
value,
ticker
)
}.collect(Collectors.toList())
manager.setCaches(caches)
}
return manager
}

private fun buildCache(name: String, cacheSpec: CacheSpecification, ticker: Ticker): CaffeineCache {
logger.info("Cache {} specified timeout of {} min, max of {}", name, cacheSpec.timeoutSeconds?:0, cacheSpec.maxEntries)
val caffeineBuilder: Caffeine<Any, Any> = Caffeine.newBuilder()
.expireAfterWrite(cacheSpec.timeoutSeconds, TimeUnit.SECONDS).maximumSize(cacheSpec.maxEntries)
.ticker(ticker)
return CaffeineCache(name, caffeineBuilder.build())
}

@Bean
fun ticker(): Ticker {
return Ticker.systemTicker()
}
}

data class CacheSpecification(
val timeoutSeconds: Long,
val maxEntries: Long
)


con esto lo que logramos es usar caffeine como cache manager y habilitar que le pasemos configuracion al cache para que cada nombre de cache tenga su tiempo de vida en este caso en segundos.

vamos entonces a definir las configuraciones de nuestro cache en el archivo src.main.resources.application.properties

spring.main.allow-bean-definition-overriding=true
caching.specs.5min.timeoutSeconds=300
caching.specs.5min.maxEntries=200
caching.specs.1min.timeoutSeconds=60
caching.specs.1min.maxEntries=200

la linea de bean definition overriding es importante para que la aplicacion no nos falle, luego definimos por cada nombre de cache de antes cuanto tiempo dura en segundo y cuantas entradas queremos que mantenga maximas. en este caso definimos 2 caches uno de 5 minutos(60 segundos * 5=300) y otro de 1 minuto.

ya con esto listo para poder probarlo vamos a crear un archivo en src.main.kotlin.cacheejemplo.demo como controlador rest para usar nuestro cache. creamos un archivo ControladorApi.kt y pasamos el siguiente contenido.

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class ControladorApi(val cacheStockApi: CacheStockApi) {

@GetMapping("/5min")
fun min5()=
cacheStockApi.getApiInfo5Min()


@GetMapping("/1min")
fun min1()=
cacheStockApi.getApiInfo1Min()
}

ya con esto podemos correr la aplicacion, probar nuestro cache y revisar que unas respuestas se guardan por 5 minutos y otras solo 1 minuto.

localhost:8080/5min

localhost:8080/1min

quiero recordar que para que el cache funcione debe hacerse un llamado desde otro clase, cuando se llama a un método cacheado desde la misma clase spring no lo coge(no lo cachea lo llama)y es como si el metodo no fuera cacheado. por eso es mejor crear una clase que solo se encargue de cachear para que siempre la podamos llamar desde afuera(desde otra clase). tambien recordar poner todas las anotaciones relacionadas al cache EnabledCaching, CacheConfig, Cacheable y el .cache al Flux/mono para que el cache funcione.

 este mismo proceso en video https://youtu.be/GmCBTNpVDRo

referencias:

.cache() para el mono extraida de https://stackoverflow.com/questions/48156424/spring-webflux-and-cacheable-proper-way-of-caching-result-of-mono-flux-type/48156827#comment122359092_48156827 respondida por Oleh Dokuka.

y la configuracion base para el cache con multiples tiempos de vida extraida de https://stackoverflow.com/questions/49885064/is-it-possible-to-set-a-different-specification-per-cache-using-caffeine-in-spri/61096118#comment122304131_61096118 respondida por nokieng