lunes, 21 de febrero de 2022

Descubrir un servicio publicado con Eureka con Spring boot


Ya tenemos el servicio registrado en Eureka. Ahora necesitamos llamarlo sin tener conocimiento directo de la ubicación. De tal manera que este ultimo buscará la ubicación física mediante Eureka.

Para nuestros propósitos, vamos a ver tres bibliotecas de clientes de Spring/Netflix diferentes en las que un consumidor de servicios puede interactuar con Ribbon. Estas bibliotecas pasarán del nivel más bajo de abstracción para interactuar con Ribbon al más alto.

Las bibliotecas que exploraremos incluyen

  •  Spring Discovery 
  •  Spring Discovery  con RestTemplate habilitado 
  •  Netflix Feign

Spring Discovery Client ofrece el nivel más bajo de acceso a Ribbon y los servicios registrados en él. Usando Discovery Client, puede consultar todos los servicios registrados con el cliente y sus URL correspondientes.

Veamos un ejemplo simple del uso de DiscoveryClient para recuperar una de las direcciones URL de un servicio y luego llamaremos al servicio mediante una clase RestTemplate estándar. Para comenzar a usar DiscoveryClient, primero debe anotar la clase Application.java con la anotación @EnableDiscoveryClient:

@SpringBootApplication

@EnableDiscoveryClient

public class Application {
   public static void main(String[] args) {
     SpringApplication.run(Application.class, args);
  }
}

Veamos un ejemplo de un descubrimiento de un cliente : 

/*Packages and imports removed for conciseness*/
@Component
public class OrganizationDiscoveryClient {

  @Autowired
   private DiscoveryClient discoveryClient;

   public Organization getOrganization(String organizationId) {
     RestTemplate restTemplate = new RestTemplate();
     List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice");
     if (instances.size()==0) return null;
        String serviceUri = String.format("%s/v1/organizations/%s", instances.get(0).getUri().toString(),
            organizationId);
        ResponseEntity< Organization > restExchange = restTemplate.exchange(serviceUri, 
                                                                                                     HttpMethod.GET,
                                                                                                     null, Organization.class, organizationId);
      return restExchange.getBody();
   }
}

DiscoveryClient es la clase que usamos para interactuar con Ribbon. Para recuperar todas las instancias de los servicios de la organización registrados con Eureka, se puede usar el método getInstances(), pasando la clave del servicio que está buscando, para recuperar una lista de objetos ServiceInstance.

La clase ServiceInstance se utiliza para contener información sobre una instancia específica de un servicio, incluido su nombre de host, puerto y URI.

Tomamos la primera clase ServiceInstance de su lista para crear una URL de destino que luego se puede usar para llamar a su servicio. Una vez que tenga una URL de destino, puede usar un Spring RestTemplate estándar para llamar al servicio y recuperar datos.

En post posteriores veremos otras opciones. 

viernes, 18 de febrero de 2022

El poder de las type class y los implícitos en Scala


El poder de las type class y los implícitos radica en la capacidad del compilador para combinar definiciones implícitas al buscar instancias candidatas.

Anteriormente insinuamos que todas las instancias de type class son valores implícitos. Esto fue una simplificación. De hecho, podemos definir instancias de dos maneras:

  1. definiendo instancias concretas como valores implícitos del tipo requerido;
  2. mediante la definición de métodos implícitos para construir instancias a partir de otras instancias de clases de tipo.

¿Por qué construiríamos instancias a partir de otras instancias? Como ejemplo motivacional, considere definir un JsonWriter para Option. necesitaríamos un JsonWriter[Option[A]] para cada A que nos interesa en nuestra aplicación. Podríamos intentar resolver el problema por fuerza bruta creando una biblioteca de valores implícitos:

implicit val optionIntWriter: JsonWriter[Option[Int]] =

???

implicit val optionPersonWriter: JsonWriter[Option[Person]] =

???

// and so on...


Sin embargo, este enfoque claramente no escala. Terminamos requiriendo dos valores implícitos para cada tipo A en nuestra aplicación: uno para A y otro para Opción[A].

Afortunadamente, podemos abstraer el código para manejar la Opción[A] en un constructor común basado en la instancia de A:

• si la opción es Some(aValue), escriba aValue usando el escritor para A;

• si la opción es None, devuelve JsNull.

Aquí está el mismo código escrito como una definición implícita:

implicit def optionWriter[A] (implicit writer: JsonWriter[A]): JsonWriter[Option[A]] =

  new JsonWriter[Option[A]] {

    def write(option: Option[A]): Json =

       option match {

           case Some(aValue) => writer.write(aValue)

           case None => JsNull

      }

  }

Este método construye un JsonWriter para Option[A] basándose en un parámetro implícito para completar la funcionalidad específica de A. Cuando el compilador ve una expresión como esta:

  Json.toJson(Option("A string"))

busca un JsonWriter[Option[String]] implícito. Encuentra el método implícito para JsonWriter[Option[A]]:

  Json.toJson(Option("A string"))(optionWriter[String])

y busca recursivamente un JsonWriter[String] para usarlo como parámetro para optionWriter:

  Json.toJson(Option("A string"))(optionWriter(stringWriter))

De esta forma, la resolución implícita se convierte en una búsqueda a través del espacio de posibles combinaciones de definiciones implícitas, para encontrar una combinación que cree una instancia de clase de tipo del tipo general correcto.

Trabajar con implícitos en Scala


Trabajar con type class en Scala significa trabajar con valores implícitos y parámetros implícitos. Hay algunas reglas que necesitamos saber para hacer esto de manera efectiva.

Implícitos de empaquetado : En una peculiaridad curiosa del lenguaje, cualquier definición marcada como implícita en Scala debe colocarse dentro de un objeto o trait en lugar de en el nivel superior. Colocar instancias en un objeto complementario a la clase de tipo tiene un significado especial en Scala porque juega con algo llamado alcance implícito.

Ámbito implícito : El compilador busca instancias de clase de tipo candidato por tipo. Los lugares donde el compilador busca instancias candidatas se conocen como ámbito implícito. El alcance implícito se aplica en el sitio de la llamada; ese es el punto donde llamamos a un método con un parámetro implícito. El alcance implícito que consta aproximadamente de:

• definiciones locales o heredadas;

• definiciones importadas;

• definiciones en el objeto complementario de la clase de tipo o el tipo de parámetro (en este caso, JsonWriter o String).

Las definiciones solo se incluyen en el ámbito implícito si están etiquetadas con la palabra clave implícita. Además, si el compilador ve múltiples definiciones candidatas, falla con un error de valores implícitos ambiguos:

implicit val writer1: JsonWriter[String] = JsonWriterInstances.stringWriter

implicit val writer2: JsonWriter[String] = JsonWriterInstances.stringWriter

Json.toJson("A string")

// error: ambiguous implicit values:

//  both value writer1 in object App0 of type => repl.Session.App0. JsonWriter[String]

//  and value writer2 in object App0 of type => repl.Session.App0. JsonWriter[String]

// match expected type repl.Session.App0.JsonWriter[String]

// Json.toJson("A string")

// ^^^^^^^^^^^^^^^^^^^^^^^

Las reglas precisas de la resolución implícita son más complejas que esto, pero la complejidad es en gran medida irrelevante para el uso diario. Para nuestros propósitos, podemos empaquetar instancias de clases de tipo en aproximadamente cuatro formas:

1. colocándolos en un objeto;

2. colocándolos en un trait;

3. colocándolos en el objeto compañero de la clase de tipos;

4. colocándolos en el objeto complementario del tipo de parámetro.

Con la opción 1 traemos las instancias al alcance importándolas. Con la opción 2 los traemos al alcance con la herencia. Con las opciones 3 y 4, las instancias siempre están en un alcance implícito, independientemente de dónde intentemos usarlas.

Es convencional colocar instancias de clase de tipo en un objeto complementario (opción 3 y 4 anteriores) si solo hay una implementación sensata, o al menos una implementación ampliamente aceptada como predeterminada. Esto hace que las instancias de clase de tipo sean más fáciles de usar, ya que no se requiere importarlas para incluirlas en el ámbito implícito.

jueves, 17 de febrero de 2022

Type class en Scala parte 2


Seguimos con Type class en Scala

Un uso de clase de tipo es cualquier funcionalidad que requiere una instancia de clase de tipo para funcionar. En Scala esto significa cualquier método que acepte instancias de la clase de tipo como parámetros implícitos.

La forma más sencilla de crear una interfaz que utilice una clase de tipo es colocar métodos en un objeto único:

object Json {

  def toJson[A](value: A)(implicit w: JsonWriter[A]): Json =

    w.write(value)

}

Para usar este objeto, importamos cualquier instancia de clase de tipo que nos interese y llamamos al método relevante:

import JsonWriterInstances._
Json.toJson(Person("Dave", "dave@example.com"))
// res1: Json = JsObject(
// Map("name" -> JsString("Dave"), "email" -> JsString("dave@example.com"))
// )

El compilador detecta que hemos llamado al método toJson sin proporcionar los parámetros implícitos. Intenta arreglar esto buscando instancias de clase de tipo de los tipos relevantes e insertándolos en el sitio de la llamada:

Json.toJson(Person("Dave", "dave@example.com"))(personWriter)

Alternativamente, podemos usar métodos de extensión para extender los tipos existentes con métodos de interfaz. 

object JsonSyntax {
  implicit class JsonWriterOps[A](value: A) {
    def toJson(implicit w: JsonWriter[A]): Json =
      w.write(value)
  }
}

Usamos la sintaxis de la interfaz importándola junto con las instancias para los tipos que necesitamos:

import JsonWriterInstances._

import JsonSyntax._

Person("Dave", "dave@example.com").toJson

// res3: Json = JsObject(

// Map("name" -> JsString("Dave"), "email" -> JsString("dave@example.com"))

// )

Nuevamente, el compilador busca candidatos para los parámetros implícitos y los completa por nosotros:

Person("Dave", "dave@example.com").toJson(personWriter)

La biblioteca estándar de Scala proporciona una interfaz de clase de tipo genérico llamada implicitly. Su definición es muy sencilla:

def implicitly[A](implicit value: A): A = value

Podemos usar implicitly para invocar cualquier valor del alcance implícito. Proporcionamos el tipo que queremos e implícitamente hace el resto:

import JsonWriterInstances._

implicitly[JsonWriter[String]]
// res5: JsonWriter[String] = repl.

Podemos usar implicitly para invocar cualquier valor del alcance implícito. Proporcionamos el tipo que queremos e implícitamente hace el resto:

import JsonWriterInstances._
implicitly[JsonWriter[String]]
// res5: JsonWriter[String] = repl.

Podemos insertar una llamada a implícitamente dentro del flujo general de nuestro código para garantizar que el compilador pueda encontrar una instancia de una clase de tipo y asegurarse de que no haya errores implícitos ambiguos.

domingo, 13 de febrero de 2022

Haciendo test con Spock


Si deseas hacer tus test más legibles y acordes a la metodologia BDD, spock es para vos. 

Spock es un framework de test y especificación para aplicaciones Java y Groovy. Lo que lo hace destacar entre la multitud de framework de test es su hermoso y altamente expresivo lenguaje de especificaciones. Gracias a que corre sobre JUnit, Spock es compatible con la mayoría de los IDE, herramientas de compilación y servidores de integración continua. Spock está inspirado en JUnit, jMock, RSpec, Groovy, Scala, Vulcans y otros frameworks y lenguajes.

La idea de spock es que hay diferentes bloques donde uno puede definir el setup del test, el estimulo y la respuesta esperada : 


Bueno, nada mejor que un ejemplo para mostrar la propuesta de este framework: 

def "two plus two should equal four"() {

    given:

        int left = 2

        int right = 2

    when:

        int result = left + right

    then:

        result == 4

}

def "Should be able to remove from list"() {

    given:

        def list = [1, 2, 3, 4]

    when:

        list.remove(0)

    then:

        list == [2, 3, 4]

}


Dejo link: https://spockframework.org/

sábado, 12 de febrero de 2022

Typescript handbook


Si empezas con typescript un libro super recomendado es el handbook que se puede acceder de forma gratuita. 

En la introducción nos da una perspectiva del libro para diferentes desarrolladores:

También se puede bajar como Pdf : https://www.typescriptlang.org/assets/typescript-handbook.pdf

Dejo link: https://www.typescriptlang.org/docs/handbook/intro.html

viernes, 11 de febrero de 2022

Haciendo test de integración con Testcontainers


Si tenes que hacer un test de integración por ejemplo con una base de datos mongodb, o un ldap o no sé...

Lo mejor que podes hacer es que los test no se conecten al software externo, porque si esta caído o hay un problema de red, estos test van a fallar. Y eso esta mal, solo deben fallar si un desarrollador, cambia algo que no debía. 

Por ende, una buena idea es embeber el software externo en nuestros test. De este modo, el software externo se va a levantar con los test y deja de ser algo externo para ser una pieza de nuestros test, que podemos controlar. 

Claro lo malo es que tenemos que trabajar muchísimo para embeber software externo. Pero pero esto no es así, tenemos testconteiners. Donde podemos encontrar de todo para probar. 

Veamos un ejemplo de un test que usa un contenedor de la base de datos Redis : 

@Testcontainers
public class RedisBackedCacheIntTest {

    private RedisBackedCache underTest;

    // container {
    @Container
    public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
                                            .withExposedPorts(6379);
    // }


    @BeforeEach
    public void setUp() {
        String address = redis.getHost();
        Integer port = redis.getFirstMappedPort();

        // Now we have an address and port for Redis, no matter where it is running
        underTest = new RedisBackedCache(address, port);
    }

    @Test
    public void testSimplePutAndGet() {
        underTest.put("test", "example");

        String retrieved = underTest.get("test");
        assertEquals("example", retrieved);
    }
}

Este ejemplo es con JUnit 5 pero se puede utilizar con JUnit 4 y Spock. Y si ven la pagina van a encontrar un monton de tecnologías contenizadas para probar desde kafka, pulsar, oracle, mariadb, CosmosDB, Solr, y mucho más.

Sin más dejo link: https://www.testcontainers.org/

jueves, 10 de febrero de 2022

Type class en Scala


Type class es un patrón de programación que se origina en Haskell. Nos permiten ampliar las bibliotecas existentes con nuevas funciones, sin utilizar la herencia tradicional y sin alterar el código fuente de la biblioteca original.

Type class es una especie de interfaz que define algún tipo de comportamiento. Si un tipo es miembro de una clase de tipos, significa que ese tipo soporta e implementa el comportamiento que define la clase de tipos. La gente que viene de lenguajes orientados a objetos es propensa a confundir las clases de tipos porque piensan que son como las clases en los lenguajes orientados a objetos. Bien, pues no lo son. Una aproximación más adecuada sería pensar que son como las interfaces de Java, o los protocolos de Objective-C, pero mejor.

Veamos un ejemplo: 

ghci> :t (==)

(==) :: (Eq a) => a -> a -> Bool

Interesante. Aquí vemos algo nuevo, el símbolo =>. Cualquier cosa antes del símbolo => es una restricción de clase. Podemos leer la declaración de tipo anterior como: la función de igualdad toma dos parámetros que son del mismo tipo y devuelve un Bool. El tipo de estos dos parámetros debe ser miembro de la clase Eq (esto es la restricción de clase).

El tipo de clase Eq proporciona una interfaz para las comparaciones de igualdad. Cualquier tipo que tenga sentido comparar dos valores de ese tipo por igualdad debe ser miembro de la clase Eq. Todos los tipos estándar de Haskell excepto el tipo IO (un tipo para manejar la entrada/salida) y las funciones forman parte de la clase Eq.

Hay tres componentes importantes para implementar este patrón en Scala. Type class en Scala se implementan usando valores y parámetros implícitos y, opcionalmente, usando clases implícitas. Las construcciones del lenguaje Scala corresponden a los componentes de los type class de la siguiente manera:

  • traits: type classes;
  • implicit values: instancia del type class;
  • implicit parameters: donde se usa el type class use
  • implicit classes: es opcional, y facilita el uso de type class
Veamos estos puntos en más detalle : 

traits : Una clase de tipo es una interfaz o API que representa alguna funcionalidad que queremos implementar. En Scala, una clase de tipo está representada por un rasgo o traits con al menos un parámetro de tipo. Por ejemplo, podemos representar el comportamiento genérico de "serializar a JSON" de la siguiente manera:

// Define a very simple JSON AST
sealed trait Json
  final case class JsObject(get: Map[String, Json]) extends Json
  final case class JsString(get: String) extends Json
  final case class JsNumber(get: Double) extends Json
  final case object JsNull extends Json

// The "serialize to JSON" behaviour is encoded in this trait
trait JsonWriter[A] {
  def write(value: A): Json
}

JsonWriter es nuestra clase de tipo en este ejemplo, con Json y sus subtipos proporcionando código de soporte. Cuando lleguemos a implementar instancias de JsonWriter, el parámetro de tipo A será el tipo concreto de datos que estamos escribiendo.

Las instancias de una clase de tipo proporcionan implementaciones de la clase de tipo para tipos específicos que nos interesan, que pueden incluir tipos de la biblioteca estándar de Scala y tipos de nuestro modelo de dominio.

En Scala, definimos instancias creando implementaciones concretas de la clase de tipo y etiquetándolas con la palabra clave implícita:

final case class Person(name: String, email: String)
  object JsonWriterInstances {
    implicit val stringWriter: JsonWriter[String] =
      new JsonWriter[String] {
        def write(value: String): Json =
          JsString(value)
      }

implicit val personWriter: JsonWriter[Person] =
  new JsonWriter[Person] {
    def write(value: Person): Json =
      JsObject(Map(
        "name" -> JsString(value.name),
        "email" -> JsString(value.email)
      ))
  }

//etc 
}

Puff me quedo relargo el post, seguimos con implicit parameters y implicit classes en el próximo post. 

lunes, 7 de febrero de 2022

Registrar un servicio Spring boot en Eureka

 


En el post anterior levantamos un servidor Eureka, ahora vamos registrar un servicio. 

Lo primero que debe hacer es agregar la dependencia Spring Eureka al archivo pom.xml del servicio que queremos registrar :

<dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-eureka</artifactId>

</dependency>

El artefacto spring-cloud-starter-eureka contiene los archivos jar que Spring Cloud usará para interactuar con su servicio Eureka.

Después de configurar su archivo pom.xml, debe indicarle a Spring Boot que registre el servicio en el servidor Eureka. Este registro se realice tenemos que configurarlo con el archivo src/main/java/resources/application.yml 

spring:
  application:
    name: myService
  profiles:
    active:
       default
    cloud:
      config:
        enabled: true
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

Todo servicio registrado en Eureka tendrá dos componentes asociados: el ID de la aplicación y el ID de la instancia. El ID de la aplicación se utiliza para representar una instancia de servicio de grupo. En un microservicio basado en Spring-Boot, el ID de la aplicación siempre será el valor establecido por la propiedad spring.application.name. Para su servicio de organización, su spring.application.name se llama creativamente servicio de organización. El ID de la instancia será un número aleatorio destinado a representar una sola instancia de servicio.

La segunda parte de la configuración proporciona cómo y dónde debe registrarse el servicio con el servicio Eureka. La propiedad eureka.instance.preferIpAddress le dice a Eureka que desea registrar la dirección IP del servicio en Eureka en lugar de su nombre de host.

El atributo eureka.client.registerWithEureka es el disparador para decirle al servicio de la organización que se registre en Eureka. El eureka.client.fetchRegistry se utiliza para indicarle al cliente Spring Eureka que obtenga una copia local del registro. Establecer este atributo en true, almacenará en caché el registro localmente en lugar de llamar al servicio Eureka con cada búsqueda. Cada 30 segundos, el software del cliente volverá a ponerse en contacto con el servicio de Eureka para cualquier cambio en el registro.

El último atributo, el atributo eureka.serviceUrl.defaultZone, contiene una lista separada por comas de los servicios de Eureka que el cliente usará para resolver la ubicación del servicio. Para este ejemplo, solo tendrá un servicio Eureka.

Se puede usar la API REST de Eureka para ver el contenido del registro. para ver todos los
instancias de un servicio, presione el siguiente punto final GET:

http://<servicio de eureka>:8761/eureka/apps/<ID DE APLICACIÓN>

Por ejemplo, para ver el servicio de organización en el registro, puede llamar a 

http://localhost:8761/eureka/apps/myservice.

El formato predeterminado que devuelve el servicio Eureka es XML. 

jueves, 3 de febrero de 2022

Monix


Monix es una librería Scala/Scala.js de alto rendimiento para componer programas asincrónicos basados en eventos.


Monix, un proyecto de Typelevel, que ejemplifica la programación funcional pura, tipificada en Scala, sin comprometer el rendimiento.

  • Como características podemos nombrar : 
  • expone tipos de datos Observable, Iterant, Task y Coeval, junto con todo el soporte que necesitan
  • usa solo lo que necesita
  • diseñado para una verdadera asincronía, ejecutándose tanto en JVM como en Scala.js
  • excelente cobertura de prueba, calidad de código y documentación de API como política principal del proyecto

El proyecto comenzó como una implementación adecuada de ReactiveX, con influencias de programación funcional más fuertes y diseñado desde cero para la contrapresión y creado para interactuar limpiamente con la biblioteca estándar de Scala, compatible de forma inmediata con el protocolo Reactive Streams. Luego se expandió para incluir abstracciones para suspender los efectos secundarios y para el manejo de recursos, siendo uno de los padres e implementadores de Cats Effect.

Dejo link: https://monix.io/

martes, 1 de febrero de 2022

Service discovery usando Spring y Netflix Eureka


Ahora vamos a implementar el descubrimiento de servicios configurando un agente de descubrimiento de servicios y luego registrando dos servicios con el agente. Luego, un servicio llamará a otro servicio utilizando la información recuperada por el descubrimiento de servicios. Spring Cloud ofrece múltiples métodos para buscar información de un agente de descubrimiento de servicios. También analizaremos las fortalezas y debilidades de cada enfoque.

Una vez más, el proyecto Spring Cloud hace que este tipo de configuración sea trivial de realizar. Vamos a utilizar Spring Cloud y el motor de descubrimiento de servicios Eureka de Netflix para implementar el patrón de descubrimiento de servicios. Como balanceador de carga del lado del cliente, utilizaremos Spring Cloud y las bibliotecas Ribbon de Netflix.

Vamos a tener que seguir los siguentes pasos:

  1. A medida que los servicios se inician se registrarán en el Servicio Eureka. Este proceso de registro le indicará a Eureka la ubicación física y el número de puerto de cada instancia de servicio junto con una ID de servicio para el servicio que se está iniciando.
  2. Cuando un servicio llame a otro, utilizará Ribbon, que se comunicará con el servicio de Eureka para recuperar la información de ubicación del servicio y luego almacenarla esto en la caché localmente.
  3. Periódicamente, la biblioteca de Netflix Ribbon hará ping al servicio Eureka y actualizará su memoria caché local de ubicaciones de servicio.

Cualquier nueva instancia de servicios será visible, mientras que cualquier instancia que no esté en buen estado se eliminará de la memoria caché local.

A continuación, implementará este diseño configurando su servicio Spring Cloud Eureka. 

Para empezar vamos a agregar la dependencia a Eureka, si usamos maven tenemos que agregar : 

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka-server</artifactId>

</dependency>

Ahora tenemos que configurar nuestro server, en el archivo application.yml : 

server:
   port: 8761
 
eureka:
   client:
      registerWithEureka: false
      fetchRegistry: false
   server:
       waitTimeInMsWhenSyncEmpty:5

Las propiedades clave que se establecen son el atributo server.port que establece el puerto predeterminado utilizado para el servicio Eureka. El atributo eureka.client.registerWithEureka le dice al servicio que no se registre con un servicio Eureka cuando se inicia la aplicación Spring Boot Eureka porque este es el servicio Eureka. El atributo eureka.client.fetchRegistry se establece en falso para que cuando se inicie el servicio Eureka, no intente almacenar en caché su información de registro localmente. Cuando ejecute un cliente de Eureka, querrá cambiar este valor para los servicios de Spring Boot que se registrarán con Eureka.

Notará que el último atributo, eureka.server.waitTimeInMsWhenSyncEmpty , está comentado. Cuando esté probando su servicio localmente, debe descomentar esta línea porque Eureka no anunciará de inmediato ningún servicio que se registre con él. Esperará cinco minutos de forma predeterminada para dar a todos los servicios la oportunidad de registrarse antes de anunciarlos. Eliminar los comentarios de esta línea para las pruebas locales ayudará a acelerar la cantidad de tiempo que tardará el servicio Eureka en iniciarse y mostrar los servicios registrados en él.

El registro de servicios individuales tardará hasta 30 segundos en aparecer en el servicio Eureka porque Eureka requiere tres pings de latidos consecutivos del servicio separados por 10 segundos antes de decir que el servicio está listo para usarse. Tenga esto en cuenta cuando implemente y pruebe sus propios servicios.

El último trabajo de configuración que va a realizar para configurar su servicio Eureka es agregar una anotación a la clase de arranque de la aplicación que está utilizando para iniciar su servicio Eureka.

package com.assembly.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaServerApplication.class, args);
   }

}

Y ya esta, tenemos nuestro server Eureka andando. En post posteriores vamos a registrar nuestros servicios. 

Scala With Cats

Me tope con el libro : Scala with Cats 2 que es gratuito, por lo que les dejo una reseña y a bajarlo y estudiar: 


El objetivo principal de este libro es enseñar arquitectura y diseño de sistemas utilizando las técnicas de la programación funcional moderna. Esto significa diseñar sistemas como pequeñas unidades componibles, expresando restricciones e interacciones a través del sistema de tipos y utilizando la composición para guiar la construcción de grandes sistemas de una manera que mantenga la visión arquitectónica original.

El libro también sirve como una introducción a la biblioteca Cats. Usando abstracciones de Cats y explicando la estructura de Cats para que pueda usarla. Las ideas generales no son específicas de Cats, pero Cats proporciona una excelente implementación que es beneficiosa para aprender.

Dejo link: https://www.scalawithcats.com/

sábado, 29 de enero de 2022

Concurrencia en Clojure - parte 7


Seguimos con concurrencia en Clojure

Ahora podemos modificar nuestro servicio web para usar sesiones. Primero, necesitamos una función que cree una nueva sesión:

(defn create-session []

  (let [snippets (repeatedly promise)

    translations (delay (map translate

                         (strings->sentences (map deref snippets))))]

      (new-session {:snippets snippets :translations translations})))


Seguimos usando una secuencia perezosa infinita de promesas para representar los fragmentos entrantes y un mapa sobre esa secuencia para representar las traducciones, pero ahora ambos están almacenados en una sesión.

A continuación, debemos modificar accept-snippet y get-translation para buscar :snippets o :translations dentro de una sesión:

(defn accept-snippet [session n text]

  (deliver (nth (:snippets session) n) text))

(defn get-translation [session n]

  @(nth @(:translations session) n))


Finalmente, definimos las rutas que vinculan estas funciones a las URI:

(defroutes app-routes
  (POST "/session/create" []
    (response (str (create-session))))

  (context "/session/:session-id" [session-id]
    (let [session (get-session (edn/read-string session-id))]
      (routes
        (PUT "/snippet/:n" [n :as {:keys [body]}]
          (accept-snippet session (edn/read-string n) (slurp body))
          (response "OK"))

        (GET "/translation/:n" [n]
          (response (get-translation session (edn/read-string n))))))))

Esto nos brinda un servicio web que hace un uso juicioso de los datos mutables pero aún se siente principalmente funcional.


jueves, 27 de enero de 2022

Concurrencia en Clojure - parte 6



Vamos a crear un servicio web que maneje múltiples transcripciones introduciendo el concepto de una sesión. Cada sesión tiene un identificador numérico único, que se genera de la siguiente manera:

(def last-session-id (atom 0))

(defn next-session-id []

(swap! last-session-id inc))

Esto usa un átomo, last-session-id , que se incrementa cada vez que queremos una nueva ID de sesión. Como resultado, cada vez que se llama a next-session-id, devuelve un número uno más alto que el anterior:

server.core=> (in-ns 'server.session)
#<Namespace server.session>
server.session=> (next-session-id)
1
server.session=> (next-session-id)
2
server.session=> (next-session-id)
3

Vamos a hacer un seguimiento de las sesiones activas con otro átomo llamado sesiones que contiene un mapa de ID de sesión y valores de sesión:

(def sessions (atom {}))

(defn new-session [initial]

  (let [session-id (next-session-id)]

    (swap! sessions assoc session-id initial)

     session-id))


(defn get-session [id]

   (@sessions id))

Creamos una nueva sesión pasando un valor inicial a new-session, que obtiene una nueva ID de sesión y la agrega a las sesiones llamando a swap!. Recuper una sesión con get-session es una simple cuestión de buscarlo por su ID.

Si no vamos a aumentar continuamente la cantidad de memoria que usamos, necesitaremos alguna forma de eliminar las sesiones cuando ya no estén en uso. Nosotros podría hacer esto explícitamente (quizás con una función de eliminación de sesión), pero dado que estamos escribiendo un servicio web en el que no necesariamente podemos confiar en que los clientes limpien correctamente, vamos a implementar la caducidad de la sesión (expiry) en su lugar. Esto requiere un pequeño cambio en el código anterior:

(def sessions (atom {}))

(defn now []

  (System/currentTimeMillis))


(defn new-session [initial]

  (let [session-id (next-session-id)

         session (assoc initial :last-referenced (atom (now)))]

    (swap! sessions assoc session-id session)

    session-id))


(defn get-session [id]

  (let [session (@sessions id)]

     (reset! (:last-referenced session) (now))

     session))


Hemos agregado una función llamada now que devuelve la hora actual. Cuando new-session crea una sesión, agrega una entrada :last-referenced a la sesión, otro átomo que contiene la hora actual. Esto se actualiza con reset! cada vez que get-session accede a la sesión.

Ahora que cada sesión tiene una entrada :last-referenced, podemos caducar sesiones verificando periódicamente si alguna no ha sido referenciada por más de un cierto período de tiempo:


(defn session-expiry-time []

  (- (now) (* 10 60 1000)))


(defn expired? [session]

  (< @(:last-referenced session) (session-expiry-time)))


(defn sweep-sessions []

  (swap! sessions #(remove-vals % expired?)))


(def session-sweeper

  (schedule {:min (range 0 60 5)} sweep-sessions))


Esto utiliza la biblioteca Schejulure para crear un barrido de sesiones, que se ejecuten una vez cada cinco minutos. Cada vez que se ejecuta, elimina cualquier sesión que haya expirado.

miércoles, 26 de enero de 2022

Concurrencia en Clojure - parte 5




Imagina que queremos tener un átomo que nunca tenga un valor negativo. Podemos garantizar esto por medio de una función de validación cuando creamos el átomo:

user=> (def non-negative (atom 0 :validator #(>= % 0)))

#'user/non-negative

user=> (reset! non-negative 42)

42

user=> (reset! non-negative -1)

IllegalStateException Invalid reference state

Un validador es una función que se llama cada vez que se intenta cambiar el valor del átomo. Si devuelve verdadero, el intento puede tener éxito, pero si devuelve falso, el intento se abandonará.

El validador se llama antes de que se haya cambiado el valor del átomo y, al igual que la función que se pasa a swap!. Por lo tanto, los validadores tampoco deben tener efectos secundarios.

Los átomos también pueden tener observadores asociados con ellos:

user=> (def a (atom 0))

#'user/a

user=> (add-watch a :print #(println "Changed from " %3 " to " %4))

#<Atom@542ab4b1: 0>

user=> (swap! a + 2)

Changed from 0 to 2

2

La clave se utiliza para identificar al observador (así, por ejemplo, si hay varios observadores, podemos eliminar uno específico proporcionando la clave correspondiente). La función watch se llama cada vez que cambia el valor del átomo. Se le dan cuatro argumentos: la clave que se le dio a add-watch, una referencia al átomo, el valor anterior y el nuevo valor.

En el código anterior, usamos la macro lectora #(...) nuevamente para definir una función anónima que imprime los valores antiguo (%3) y nuevo (%4) del átomo.

A diferencia de los validadores, las funciones de observación se llaman después de que el valor ha cambiado y solo se llamarán una vez, ¡sin importar la frecuencia con la que se intercambien! 

Por lo tanto, una función watch puede tener efectos secundarios. Que en el momento en que se llama a la función de observación, es posible que el valor del átomo ya haya cambiado nuevamente, por lo que las funciones de observación siempre deben usar los valores pasados como argumentos y nunca eliminar la referencia del átomo.