Translate

martes, 28 de junio de 2022

Mónadas en Cats parte 13

Cats proporciona una class type adicional llamada MonadError que abstrae los tipos de datos similares a los que se usan para el manejo de errores. MonadError proporciona operaciones adicionales para generar y manejar errores.

package cats

trait MonadError[F[_], E] extends Monad[F] {

// Lift an error into the `F` context:

def raiseError[A](e: E): F[A]

// Handle an error, potentially recovering from it:

def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]

// Handle all errors, recovering from them:

def handleError[A](fa: F[A])(f: E => A): F[A]

// Test an instance of `F`,

// failing if the predicate is not satisfied:

def ensure[A](fa: F[A])(e: E)(f: A => Boolean): F[A]

}


MonadError se define en términos de dos parámetros de tipo:

• F es el tipo de la mónada;

• E es el tipo de error contenido en F.

Para demostrar cómo encajan estos parámetros, aquí hay un ejemplo en el que instanciamos la clase de tipo Either:


import cats.MonadError

import cats.instances.either._ // for MonadError

type ErrorOr[A] = Either[String, A]

val monadError = MonadError[ErrorOr, String]


Los dos métodos más importantes de MonadError son raiseError y handleErrorWith. raiseError es como el método puro para Monad excepto que crea una instancia que representa una falla:


val success = monadError.pure(42)

// success: ErrorOr[Int] = Right(42)

val failure = monadError.raiseError("Badness")

// failure: ErrorOr[Nothing] = Left("Badness")


handleErrorWith es el complemento de raiseError. Nos permite consumir un error y (posiblemente) convertirlo en un éxito, similar al método de recuperación de Future:


monadError.handleErrorWith(failure) {

case "Badness" =>

monadError.pure("It's ok")

case _ =>

monadError.raiseError("It's not ok")

}

// res0: ErrorOr[String] = Right("It's ok")


Si sabemos que podemos manejar todos los errores posibles, podemos usar handleWith.


monadError.handleError(failure) {

case "Badness" => 42

case _ => -1

}

// res1: ErrorOr[Int] = Right(42)


Hay otro método útil llamado ensure que implementa un comportamiento similar al de un filtro. Probamos el valor de una mónada exitosa con un predicado y especificamos un error para generar si el predicado devuelve falso:


monadError.ensure(success)("Number too low!")(_ > 1000)

// res2: ErrorOr[Int] = Left("Number too low!")


Cats proporciona instancias de MonadError para numerosos tipos de datos, incluidos Either, Future y Try. La instancia de Either se puede personalizar para cualquier tipo de error, mientras que las instancias de Future y Try, siempre representan errores como Throwables:


import scala.util.Try

import cats.instances.try_._ // for MonadError

val exn: Throwable = new RuntimeException("It's all gone wrong")

exn.raiseError[Try, Int]

// res6: Try[Int] = Failure(java.lang.RuntimeException: It's all gone wrong)

sábado, 25 de junio de 2022

Resultado de la encuesta 2022 de StackOverflow

StackOverflow hace una encuesta cada año a los desarrolladores y es un buen termómetro para ver como va evolucionando la industria: 

Cosas a destacar o que me llamaron la atención:

Lenguajes de programación más usados: Javascript, Python, Typescript y Java. Acá me llamo la atención que Typescript le gane a java. 


Base de datos: Mysql, postgresql, SQLite, mongodb. SQLite le gana a mongo, eso es raro.


Más amados y más odiados aparezca Delphi


Y por último entre los lenguajes mejor pagos, hay 6 lenguajes funcionales (no me llamo la atención pero lo quiero marcar) :  

Y te llamo la atención algo más? 

Dejo link: 

https://survey.stackoverflow.co/2022/?utm_source=so-owned&utm_medium=announcement-banner&utm_campaign=dev-survey-2022&utm_content=results

jueves, 23 de junio de 2022

Agregar código java a un proyecto Clojure en Leiningen


 Quiero hacer unas clases en java y utilizarlas en mis funciones clojure. Es muy fácil, tengo que agregar   :java-source-paths a mi proyecto y ya toma las clases.  

Veamos un ejemplo

(defproject ejemplo "0.1.0-SNAPSHOT"

  :description "FIXME: write description"

  :url "http://example.com/FIXME"

  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"

            :url "https://www.eclipse.org/legal/epl-2.0/"}

  :dependencies [[org.clojure/clojure "1.10.3"],

                 ...                 ]

  :java-source-paths ["src-java"]

  :main com.assembly.ejemplo.core

  :aot :all

  :target-path "target/%s"

  :profiles {:uberjar {:aot :all

                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})


Y listo!!

martes, 21 de junio de 2022

Como chequear si un registro esta siendo cacheado por Hibernate en una aplicación Spring boot.


Necesitaba ver si un registro estaba siendo cacheado por hibernate es decir JPA, en una aplicación spring boot. 

Hice lo que cualquiera hubiera hecho, puse show-sql en true, corrí un test que busca 2 veces el registro y me fije en el log que no hubiera 2 selects. Fácil pero poco científico, estaría bueno hacer un test que pruebe esto, y bueno hice esto : 


@RunWith(SpringJUnit4ClassRunner::class)

@SpringBootTest(classes = [Application::class])

class EjemploRepositoryTest {


    @Autowired

    lateinit var ejemploRepository: EjemploRepository


    @Autowired

    lateinit var entityManagerFactory: EntityManagerFactory


    @Test

    fun `find a Ejemplo from Empty cache`() {

        val ejemplo= getEjemplo()

        val cache = entityManagerFactory.cache

        Assert.assertFalse(cache.contains(Ejemplo::class.java, ejemplo.id))

    }


    @Test

    fun `find a Ejemplo from cache`() {

        val ejemplo= getEjemplo()

        saveAndFind(ejemplo)


        val cache = entityManagerFactory.cache


        Assert.assertTrue(cache.contains(Ejemplo::class.java, ejemplo.id))

    }


    private fun saveAndFind(ejemplo: Ejemplo): Ejemplo{

        ejemploRepository.save(ejemplo)

        return ejemploRepository.findById(ejemplo.id).get()

    }


    private fun getEjemplo(): Ejemplo{

        return  ... //Aca construimos un ejemplo 

    }


}

Y listo!!

lunes, 20 de junio de 2022

Mónadas en Cats parte 12

Either se usa típicamente para implementar el manejo de errores rápidos. Secuenciamos los cálculos utilizando flatMap como de costumbre. Si un cálculo falla, los cálculos restantes no se ejecutan:

for {

a <- 1.asRight[String]

b <- 0.asRight[String]

c <- if(b == 0) "DIV0".asLeft[Int]

else (a / b).asRight[String]

} yield c * 100

// res21: Either[String, Int] = Left("DIV0")

Cuando usamos Either para el manejo de errores, necesitamos determinar qué tipo queremos usar para representar los errores. Podríamos usar Throwable para esto:


type Result[A] = Either[Throwable, A]


Esto nos da una semántica similar a scala.util.Try. El problema, sin embargo, es que Throwable es un tipo extremadamente amplio. No tenemos (casi) idea de qué tipo de error ocurrió.

Otro enfoque es definir un tipo de datos algebraicos para representar los errores que pueden ocurrir en nuestro programa:

object wrapper {

   sealed trait LoginError extends Product with Serializable

   final case class UserNotFound(username: String) extends LoginError

   final case class PasswordIncorrect(username: String) extends LoginError

   case object UnexpectedError extends LoginError

}; 

import wrapper._


case class User(username: String, password: String)

type LoginResult = Either[LoginError, User]


Este enfoque resuelve los problemas que vimos con Throwable. Nos da un conjunto fijo de tipos de errores esperados. También obtenemos la seguridad de la comprobación exhaustiva de cualquier coincidencia de patrones que hagamos:

// Choose error-handling behaviour based on type:

def handleError(error: LoginError): Unit =

    error match {

        case UserNotFound(u) => println(s"User not found: $u")

        case PasswordIncorrect(u) => println(s"Password incorrect: $u")

        case UnexpectedError => println(s"Unexpected error")

    }

val result1: LoginResult = User("dave", "passw0rd").asRight

// result1: LoginResult = Right(User("dave", "passw0rd"))

val result2: LoginResult = UserNotFound("dave").asLeft

// result2: LoginResult = Left(UserNotFound("dave"))

result1.fold(handleError, println)

// User(dave,passw0rd)

result2.fold(handleError, println)

// User not found: dave


sábado, 18 de junio de 2022

Crear un proyecto Spring boot, con Leiningen y Clojure


La idea es crear un proyecto spring boot, con Clojure y Leiningen. (ya lo dice el titulo)

Primero hacemos un proyecto hello word con Leiningen (ojo tienen que tener Leiningen instalado, esta bueno instalarlo con sdkman, así :  sdk install leiningen) 

lein new app ejemplo

Luego agregamos las dependencias de spring boot al proyecto, para que quede algo así: 

(defproject github "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.3"],
                 [org.springframework.boot/spring-boot-starter-web "2.6.7"],
                 [org.springframework.boot/spring-boot-configuration-processor "2.6.7"]]
  :main com.assembly.example.core
  :aot :all
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})

Ojo, tambien hay que agregar :aot y :all 

Ahora hacemos el Application, yo modifique el hello wold que vino por default, pero si lo hacen tengan cuidado de ponerlo en un paquete. 

(ns com.assembly.example.core
  (:import (org.springframework.boot SpringApplication))
  (:gen-class
    :name ^{org.springframework.boot.autoconfigure.SpringBootApplication []
            org.springframework.context.annotation.ComponentScan {:basePackages ["com.assembly.example"]}}
    com.assembly.example.core.Application
    :main true)
  )

(defn -main []
  (SpringApplication/run (Class/forName "com.assembly.example.core.Application") (into-array String '()))
  )

Y ahora hacemos un endpoint, en este ejemplo voy a hacer un hola mundo : 

(ns com.assembly.exanple.controller.greet

  (:import (org.springframework.web.bind.annotation PathVariable RequestMapping RequestMethod RestController)

           (org.springframework.http ResponseEntity))

  )


(gen-class

  :name ^{RestController {}

          RequestMapping {:value ["/v1/greet"]}} com.assembly.exanple.controller.greet.GreeterEndPoint

  :methods [[^{RequestMapping {:value ["/hello"]

                               :method [RequestMethod/GET]}} sayHello [] Object ]

            [^{RequestMapping {:value ["/helloto/{name}"]

                               :method [RequestMethod/GET]}} sayHelloTo [^{PathVariable {:value "name"}} String] Object ]

            ]

  :state injected

  :init init

)


(defn -init

  "Initialize the class by setting the state to an empty map, which can be populated with injected dependencies."

  []

  [[] (atom {})])


(defn -sayHello

  [this]

  (ResponseEntity/ok "Hello"))


(defn -sayHelloTo

  [this name]

  (ResponseEntity/ok (str "Hello " name)))


Y listo, si corremos el main, vamos a levantar el tomcat embebido de spring y vamos a : 

http://localhost:8080/v1/greet/hello

y veremos como nos saluda spring boot con clojure!! 

viernes, 17 de junio de 2022

[FREE EBOOK] Understanding Databases (Free eBook)

 

Start Today

 

Free eBook:  Understanding Databases 


Deploy High-Performance Database Clusters in Modern Applications. Develop a high-level understanding of industry-standard databases, the design of database architectures, and different deployment methods in the cloud.


 
Download Now
Your Suggestions
Any ideas or suggestions? Shoot us an email at support@webcodegeeks.com

[O’Reilly Ebook] The Complete NGINX Cookbook – 2022 Edition

 

EBOOK

[O’Reilly Ebook] The Complete NGINX Cookbook – 2022 Edition

Hi Emanuel,

The NGINX Cookbook is the “go-to” guide to NGINX configurations, updated for 2022 with new chapters on optimizing container environments and using NGINX Instance Manager. Check out these brand-new recipes as well as updates for the most popular NGINX deployments: load balancing, security, cloud deployment, containers and microservices, service mesh, API gateway, automation, and more.

In this eBook you will learn:

  • Updated solutions for more than 100 real-world, modern application delivery use cases
  • How to secure your applications and APIs and mitigate DDoS and Layer 7 attacks
  • How to optimize and secure microservices and container environments
  • About using NGINX Instance Manager to automate your management of NGINX instances

[eBook] Managing Kubernetes Traffic with NGINX: A Practical Guide

 

EBOOK

https://interact.f5.com/rs/653-SMC-783/images/EBK - Managing Kubernetes Traffic with F5 NGINX - 760x284.png

Hi Emanuel,

Learn everything you need to deploy and manage a production-grade Kubernetes environment with NGINX Ingress Controller and NGINX Service Mesh. Our new eBook includes thorough explanations, diagrams, and code samples for a broad range of use cases, including traffic control and splitting, tracing and monitoring, and single sign-on.

In this eBook you will learn:

  • How to install and configure NGINX Ingress Controller and NGINX Service Mesh and how to migrate from the Kubernetes community's Ingress controller
  • Techniques for traffic control and splitting, including rate limiting, blue‑green deployment, and A/B testing
  • About integrating with third‑party tracing and monitoring tools like Jaeger, Prometheus, and AWS CloudWatch
  • How to protect Kubernetes apps with identity solutions and NGINX App Protect WAF

jueves, 16 de junio de 2022

Scripting en java


Supongamos que tenemos una lógica que cambia casi todo el tiempo, por ejemplo reglas de negocio que permitan calcular un impuesto, etc. 

Lo que podríamos hacer es tener en una variable un script que permita calcular esa lógica y ejecutarlo en java.  

Supongamos que al principio este impuesto es un porcentaje de un monto : 

String code="monto * 0.1"; 

Bindings bindings = new SimpleBindings();

bindings.put("monto", 1000);

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByExtension("js");

try {

  System.out.print(engine.eval(code, bindings));

} catch (ScriptException ex) {

  //catch statement

}


Luego esto puede cambiar y el impuesto volverse màs complejo, no importa porque si lo cambiamos el programa lo ejecuta : 

String code="monto * 0.01 +  (monto * 0.01) * 0.21 "; 

Bindings bindings = new SimpleBindings();

bindings.put("monto", 1000);

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByExtension("js");

try {

  System.out.print(engine.eval(code, bindings));

} catch (ScriptException ex) {

  //catch statement

}


Podemos modificar la logica sin volver a compilar, teniendo la potencia de un lenguaje script en nuestro querido java. 

En fin el scripting en java es una gran herramienta que no todos conocen. 


sábado, 11 de junio de 2022

Top de lenguajes en la JVM


Buscando que lenguajes están en el top 5 de más usados me encontré con esto en la wikipedia : 

As of April 2022, according to the TIOBE Index[1] of the top 100 programming languages, the top JVM languages are:

Lo que me sorprendio no fue la información sino que la wikipedia este tan actualizada. 

Otra cosa que me llamo la atención es que groovy le gane a scala o a clojure, pense que era diferente.

Y bueno, eso es todo, no me critiquen porque pegue algo en ingles en un blog en español, es de la wikipedia, no voy andar cambiando la fuente.  

Dejo link : https://en.wikipedia.org/wiki/List_of_JVM_languages

Mónadas en Cats parte 11

cats.syntax.either también agrega algunos métodos útiles para instancias de Either

Los usuarios de Scala 2.11 o 2.12 pueden usar orElse y getOrElse para extraer valores del lado derecho o devolver un valor predeterminado:

import cats.syntax.either._

"Error".asLeft[Int].getOrElse(0)

// res11: Int = 0

"Error".asLeft[Int].orElse(2.asRight[String])

// res12: Either[String, Int] = Right(2)


El método de aseguramiento nos permite verificar si el valor de la derecha satisface un predicado:


-1.asRight[String].ensure("Must be non-negative!")(_ > 0)

// res13: Either[String, Int] = Left("Must be non-negative!")


Los métodos recover y recoverWith con proporcionan un manejo de errores similar al de sus homónimos en Future:


"error".asLeft[Int].recover {

case _: String => -1

}

// res14: Either[String, Int] = Right(-1)

"error".asLeft[Int].recoverWith {

case _: String => Right(-1)

}

// res15: Either[String, Int] = Right(-1)


Hay métodos leftMap y bimap para complementar el map:


"foo".asLeft[Int].leftMap(_.reverse)

// res16: Either[String, Int] = Left("oof")

6.asRight[String].bimap(_.reverse, _ * 7)

// res17: Either[String, Int] = Right(42)

"bar".asLeft[Int].bimap(_.reverse, _ * 7)

// res18: Either[String, Int] = Left("rab")


El método swap nos permite intercambiar izquierda por derecha:


123.asRight[String]

// res19: Either[String, Int] = Right(123)

123.asRight[String].swap

// res20: Either[Int, String] = Left(123)


Finalmente, Cats agrega una serie de métodos de conversión: toOption, toList, toTry, toValidated, etc.



miércoles, 8 de junio de 2022

sttp: ¡el cliente Scala HTTP que siempre quisiste!


sttp es un cliente http, simple que permite manejar las respuestas. sttp proporciona interfaces tanto síncronas como asíncronas, procedimentales y funcionales.

Las implementaciones incluyen aquellas basadas en clientes akka-http, async-http-client, http4s, OkHttp y HTTP que se envían con Java. Se integran con Akka, Monix, fs2, cats-effect, scalaz y ZIO. Las versiones de Scala admitidas incluyen 2.11, 2.12, 2.13 y 3, Scala.JS y Scala Native.

Veamos un pequeño ejemplo: 

// In addition to the usual values brought into scope by `sttp.client3._`,

// the `quick` version also defines a default synchronous `backend`.

import sttp.client3.quick._

// Circe integration: `asJson` response description.

import sttp.client3.circe._


import io.circe.generic.auto._


// Case classes corresponding to the json returned by GitHub (just the 

// fields that interest us).

case class GitHubResponse(total_count: Int, items: List[GitHubItem])

case class GitHubItem(name: String, stargazers_count: Int)


val query = "language:scala"

val sort: Option[String] = Some("stars")


// Describing the request: specifying the method, uri and how to handle

// the response. The `query` parameter is automatically url-encoded

// `sort` will be unwrapped if `Some(_)`, and removed if `None`.

val request = basicRequest

  .get(uri"https://api.github.com/search/repositories?q=$query&sort=$sort")

  .response(asJson[GitHubResponse])

  

// As we are using the synchronous `HttpURLConnectionBackend`, `send()` will 

// return `Response[_]`. Async backends return e.g. `Future[Response[_]]`.

val response = request.send(backend)

// The body will be a `Left(_)` in case of a non-2xx response, or a json

// deserialization error. It will be `Right(_)` otherwise.

response.body match {

  case Left(error) => println(s"Error when executing request: $error")

  case Right(data) =>

    println(s"Found ${data.total_count} Scala projects.")

    println(s"Showing ${data.items.size} with most stars:")

    data.items.foreach { item =>

      println(s"  ${item.name} (${item.stargazers_count})")

    }

}

Dejo link : https://sttp.softwaremill.com/en/latest/


lunes, 6 de junio de 2022

Mónadas en Cats parte 10

Además de crear instancias de Left y Right directamente, también podemos importar los métodos de extensión asLeft y asRight desde cats.syntax.either :

import cats.syntax.either._ // for asRight

val a = 3.asRight[String]

// a: Either[String, Int] = Right(3)

val b = 4.asRight[String]

// b: Either[String, Int] = Right(4)

for {

x <- a

y <- b

} yield x*x + y*y

// res3: Either[String, Int] = Right(25)


Estos "constructores inteligentes" tienen ventajas sobre Left.apply y Right.apply porque devuelven resultados de tipo Either en lugar de Left y Right. Esto ayuda a evitar problemas de inferencia de tipo causados por la sobreflecha, como el problema del ejemplo a continuación:


def countPositive(nums: List[Int]) = nums.foldLeft(Right(0)) { (accumulator, num) =>

if(num > 0) {

accumulator.map(_ + 1)

} else {

Left("Negative. Stopping!")

}

}

// error: type mismatch;

//found

//required: scala.util.Right[Nothing,Int]

: scala.util.Either[Nothing,Int]

//accumulator.map(_ + 1)

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

// error: type mismatch;

//found

//required: scala.util.Right[Nothing,Int]

: scala.util.Left[String,Nothing]

//Left("Negative. Stopping!")

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


Este código falla al compilar por dos razones:

1. el compilador infiere el tipo del acumulador como Right en lugar de Either;

2. no especificamos parámetros de tipo para Right.apply, por lo que el compilador infiere que el parámetro izquierdo es Nothing.

Cambiar a asRight evita ambos problemas. asRight tiene un tipo de retorno de Either, y nos permite especificar completamente el tipo con solo un parámetro de tipo:


def countPositive(nums: List[Int]) = nums.foldLeft(0.asRight[String]) { (accumulator, num) =>

if(num > 0) {

accumulator.map(_ + 1)

} else {

Left("Negative. Stopping!")

}

}

countPositive(List(1, 2, 3))

// res5: Either[String, Int] = Right(3)

countPositive(List(1, -2, 3))

// res6: Either[String, Int] = Left("Negative. Stopping!")


cats.syntax.either agrega algunos métodos de extensión útiles para el objeto complementario Any. Los métodos catchOnly y catchNonFatal son excelentes para capturar excepciones como instancias de:


Either.catchOnly[NumberFormatException]("foo".toInt)

// res7: Either[NumberFormatException, Int] = Left(

// java.lang.NumberFormatException: For input string: "foo"

// )

Either.catchNonFatal(sys.error("Badness"))

// res8: Either[Throwable, Nothing] = Left(java.lang.RuntimeException:


También hay métodos para crear un Either a partir de otros tipos:


Either.fromTry(scala.util.Try("foo".toInt))

// res9: Either[Throwable, Int] = Left(

//

// )

java.lang.NumberFormatException: For input string: "foo"

Either.fromOption[String, Int](None, "Badness")

// res10: Either[String, Int] = Left("Badness")

viernes, 3 de junio de 2022

Crear un proyecto Spring boot, con Scala y Sbt



La idea es crear un proyecto spring boot, con scala y sbt. (ya lo dice el titulo)

Primero hacemos un proyecto hello word con sbt (ojo tienen que tener sbt instalado, esta bueno instalarlo con sdkman) 

sbt new scala/hello-world.g8

Luego, tenemos que agregar nuestra dependencia web de spring boot en build.sbt : 

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web

libraryDependencies += "org.springframework.boot" % "spring-boot-starter-web" % "2.6.7"

libraryDependencies += "org.springframework.boot" % "spring-boot-configuration-processor" % "2.6.7"


Deberían de modificar bastante el buld.sbt en mi caso me quedo así :


scalaVersion := "2.13.8"

organization := "com.miCompania"
name := "ejemplo"
version := "1.0"


libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1"

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-web" % "2.6.7"
libraryDependencies += "org.springframework.boot" % "spring-boot-configuration-processor" % "2.6.7"

Ahora lo que debemos de hacer es crear un paquete y mover el main a ese paquete para luego modificarlo de tal forma que llame a run de spring boot : 

package com.miCompania

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication


@SpringBootApplication
class Application

object Main extends App {
  SpringApplication.run(classOf[Application], args:_*)
}

Ojo, tienen que tener el main en un paquete sino no anda. 

Por ultimo hacemos nuestro endpoint, en este ejemplo voy a hacer un hola mundo, comun. Pero en este punto podemos utilizar todas las ventajas de spring boot : 

package com.assembly.endpoint

import org.springframework.http.ResponseEntity

import org.springframework.web.bind.annotation.{PathVariable, RequestMapping, RequestMethod, RestController}


@RestController

@RequestMapping(Array("/v1"))

class GreeterEndPoint {


  @RequestMapping(value=Array("/hello/{name}"), method = Array(RequestMethod.GET))

  def sayHello(@PathVariable(name = "name") name:String): ResponseEntity[String] =

    ResponseEntity.ok(s"Hello ${name}")

}


Lo puse en otro paquete, esto solo para que quede más ordenado, y listo!!!