lunes, 14 de febrero de 2022

3 patrones de diseño tipo wrapper ( proxy, adapter y decorator)

version en video:


https://youtu.be/bswx9qvuCdg

 

Estaba realizando una transformacion que necesitaba una cache que otra persona del equipo estaba haciendo(en paralelo), entonces pensé en un proxy que se encargara de cargar ese cache y retornare la información que necesitaba(desde un cache quemado en memoria) mientras otra persona hacia el desarrollo.

luego cuando se realizo la integración real la función del proxy se convirtió en transformar el string json que devolvía la cache a una clase que yo pudiera usar con sus valores. 

en la revisión del código me dijeron eso no es un cache, eso es un adapter. y tenían toda la razón aunque ambos son wrappers tienen sus diferencias, de hay nació este articulo.

Proxy

un proxy se usa para encapsular otro servicio o método(usualmente un servicio externo al no tenemos acceso directo) el proxy se encarga entonces de agregar algún comportamiento al método del otro servicio antes o después de llamarlo.con la salveda de que para que sea proxy debe tener la misma interfaz que el servicio origina ( osease que los métodos reciban y devuelvan el mismo tipo mediante una interfaz) por esto el error de arriba (el cache devolvía un string(cadena de texto) pero luego el proxy malo retornaba una clase). algunos ejemplo de proxy pueden ser.

  • virtual proxy: se encarga de cargar otro servicio solo cuando se vaya a usar ese recurso, para hacer un lazy load(carga perezosa) por que el otro servicio es pesado de subir y puede que no siempre sea necesario.
  • proxy de segurida: se usa para restringir acceso a cierto recurso a algún usuario.
  • proxy de loggeo: para loggear o imprimir en pantalla informacion que recibe o responde un servicio.

Adapter.

Es otro tipo de wrapper que se usa cuando no queremos la interfaz original del servicio base, en la historia anterior ese servicio no era un proxy sino un adapter pues estaba encargado de transformar un json como string del cache original en un clase kotlin. (este tipo de transformacion por ejemplo si el servicio original retorna un xml pero nosotros queremos integrarnos con json se les llama adapter)

imagen de: https://zh.wikivoyage.org/wiki/File:FTT102017.jpgFile:FTT102017.jpg - 来自维基导游的旅行指南

Decorator

este es el mas complejo de los 3, un decorador es como un proxy dentro de otro, dentro de otro ......

image de: https://www.publicdomainpictures.net/es/view-image.php?image=19582&picture=munecas-rusas

Muñecas rusas Stock de Foto gratis - Public Domain Pictures

 

la funcion del decorator es posibilitar encadenar comportamiento(en un orden personalizable) conservando la misma interfaz (decorar) y es frecuentemente usado en aplicaciones tipo stream.

en mi caso no entendía como funcionaba el decorator hasta que hice un ejemplo que se puede encontrar en https://github.com/chalimbu/decorator-java/tree/feature/solucionEjercisio1 aqui hice un decorador bobo para entender el concepto una clase que imprime en pantalla y unos decoradores para agregarle un mensaje(la fecha, una tag de error o info) al mensaje original por medio de decoradores que se construye en el Main.java. recomendado ver el ejemplo en el siguiente orden.

  •  https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/Logger.java
  • https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/decorators/SystemLogger.java
  • https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/decorators/SystemLoggerDecorator.java -> el decorador que sirve para encapsular los otros 3 siguientes.
  • https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/decorators/SystemLoggerDateDecorator.java
  • https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/decorators/SystemLoggerErrorDecorator.java
  • https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/decorators/SystemLoggerInfoDecorator.java
  • https://github.com/chalimbu/decorator-java/blob/feature/solucionEjercisio1/src/com/decorator/Main.java -> usando las clases definidas antes.

 estos 3 patrones tienen sus diferencias y su similitud que todos se encargan de servir de envoltura para otro servicio.

fuente:

la información viene de https://refactoring.guru/design-patterns/decorator donde se explica muy bien.

martes, 4 de enero de 2022

Notacion big O

version en video:


 

La notación de big o es una forma de medir y comparar el tiempo de ejecución de algoritmos.
Esta notación se puede usar para comparar dos implementaciones de algoritmos y posiblemente escoger la implementación más rápida.
hay que ser cuidadoso con esto en proyectos más halla de ejercisios por que para mi sigue siendo más prioritario hacer un código más mantenible y fácil de entender que un código que sea más rápido o teóricamente mas rapido(en mi caso por ejemplo me gusta mucho la programación funcional y la inmutabilidad para entender el código como bloques funcionales que reciben una entrada y retornan la misma salida para misma entrada, otro tema).
con esta aclaración igual creo que en el libro cracking the coding interview de gayle dan un ejemplo analogía muy bueno para entender el impacto de big o. Imaginemos que tenemos que transferir un archivo a un amigo y tenemos dos opciones.
transferir el archivo por internet(mandándolo por ejemplo por un correo electrónico).
llevar el archivo físico en un avión(por ejemplo en un disco duro)

en el primer caso transfiriendo el archivo por internet, entre mas grande el archivo más tiempo nos va tomar transferirlo por tanto tenemos una relación lineal.
tiempo transferencia = tiempo para transferir cantida * tamaño archivo + tiempo inicial, esto en notación de big O lo podríamos representar como O(n) entre mas grande el archivo mas tiempo nos 

tomaría y esto aumenta linealmente.
para el caso 2 llevar el archivo en un avión sin importar que tan grande es el archivo(podrían ser terabytes) el avión va tardar lo mismo (o una constante) en big O representamos esto como O(1)




este ejemplo creo que es muy bueno para entender la relevancia de la notación big O que nos muestra las diferencias de ejecución para cantidades enormes de datos, por ejemplo en general podemos decir que O(1) es mejor que O(n) pero si queremos transferir un archivo pequeño seguramente no lo vayamos hacer por un avion.

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