Translate

viernes, 29 de abril de 2022

Consumiendo API con Retrofit


Si has trabajado con microservicios o simplemente has necesitado consumir una API de terceros, notaste que este código no es muy bello y muchas veces se torna complejo.

Retrofit es un framework que viene para salvarnos. 

Veamos un ejemplo, queremos consultar info de los repositorios de github, la información que necesitamos es esta : (voy a trabajar en kotlin porque pinto, es similar en Java) 

import com.google.gson.annotations.SerializedName


data class Repo(

    @SerializedName("id")

    var id: Int? = null,

    @SerializedName("name")

    var name: String,

    @SerializedName("full_name")

    var fullName: String,

    @SerializedName("language")

    var language: String

) {}

Entonces lo que tengo que hacer es agregar estas dependencias a mi proyecto (uso gradle) :

// https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit

implementation("com.squareup.retrofit2:retrofit:2.9.0")

implementation("com.squareup.retrofit2:converter-gson:2.9.0")

y luego tengo que crear una interface que represente el servicio que voy a consumir : 


interface GitHubClient {

    @GET("users/{user}/repos")

    fun listRepos(@Path("user") user: String?): Call<List<Repo?>?>?

}

Y por último debo construir este cliente y usarlo : 


        val retrofit = Retrofit.Builder()

            .baseUrl("https://api.github.com/")

            .addConverterFactory(GsonConverterFactory.create())

            .build()


        val gitHubClient = retrofit.create(GitHubClient::class.java)

        val repos = gitHubClient.listRepos(userName)?.execute()?.body()


Y listo!!    

Dejo link : 

https://square.github.io/retrofit/

lunes, 25 de abril de 2022

Funtores en Cats parte 6

Veamos la implementación de funtores contravariantes e invariantes en Cats, proporcionados por las clases de tipos cats.Contravariant y cats.Invariant.

trait Contravariant[F[_]] {

def contramap[A, B](fa: F[A])(f: B => A): F[B]

}

trait Invariant[F[_]] {

def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]

}

Podemos invocar instancias de Contravariant utilizando el método Contravariant.apply. Cats proporciona instancias para tipos de datos que consumen parámetros, incluidos Eq, Show y Function1. 
Veamos un ejemplo:

import cats.Contravariant
import cats.Show
import cats.instances.string._

val showString = Show[String]
val showSymbol = Contravariant[Show].contramap(showString)((sym: Symbol) => s"'${sym.name}")
showSymbol.show(Symbol("dave"))
// res1: String = "'dave"

Más convenientemente, podemos usar cats.syntax.contravariant, que proporciona un método de extensión de contramap:

import cats.syntax.contravariant._ // for contramap

showString.contramap[Symbol](sym => s"'${sym.name}").show(Symbol("dave"))
// res2: String = "'dave"



viernes, 22 de abril de 2022

Testea tu arquitectura java con ArchUnit


ArchUnit es una biblioteca gratuita, simple y extensible para verificar la arquitectura de su código Java utilizando cualquier framework de testing. Es decir, ArchUnit puede verificar dependencias entre paquetes y clases, capas y segmentos, verificar dependencias cíclicas y más. Lo hace analizando el código de bytes Java, importando todas las clases en una estructura de código Java.

Veamos un ejemplo, queremos poner unas reglas a nuestras interfaces : 


import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;


@AnalyzeClasses(locations = InterfaceRulesTest.RelevantExampleClasses.class)

public class InterfaceRulesTest {


    @ArchTest

    static final ArchRule interfaces_should_not_have_names_ending_with_the_word_interface =

            noClasses().that().areInterfaces().should().haveNameMatching(".*Interface");


    @ArchTest

    static final ArchRule interfaces_should_not_have_simple_class_names_containing_the_word_interface =

            noClasses().that().areInterfaces().should().haveSimpleNameContaining("Interface");


    @ArchTest

    static final ArchRule interfaces_must_not_be_placed_in_implementation_packages =

            noClasses().that().resideInAPackage("..impl..").should().beInterfaces();


    public static class RelevantExampleClasses implements LocationProvider {

        @Override

        public Set<Location> get(Class<?> testClass) {

            Set<Location> result = new HashSet<>();

            result.addAll(Locations.ofClass(SomeBusinessInterface.class));

            result.addAll(Locations.ofClass(SomeDao.class));

            result.addAll(Locations.ofClass(SomeInterfacePlacedInTheWrongPackage.class));

            return result;

        }

    }

}


También podríamos analizar que ningún servicio retorne un objeto del modelo o que estén bien definidas las dependencias o/y los nombres de las clases, etc. 

Veamos otro ejemplo : 

@AnalyzeClasses(packages = "com.tngtech.archunit.example.layers")

public class DaoRulesTest {

    @ArchTest

    static final ArchRule DAOs_must_reside_in_a_dao_package =

            classes().that().haveNameMatching(".*Dao").should().resideInAPackage("..dao..")

                    .as("DAOs should reside in a package '..dao..'");


    @ArchTest

    static final ArchRule entities_must_reside_in_a_domain_package =

            classes().that().areAnnotatedWith(Entity.class).should().resideInAPackage("..domain..")

                    .as("Entities should reside in a package '..domain..'");


    @ArchTest

    static final ArchRule only_DAOs_may_use_the_EntityManager =

            noClasses().that().resideOutsideOfPackage("..dao..")

                    .should().accessClassesThat().areAssignableTo(EntityManager.class)

                    .as("Only DAOs may use the " + EntityManager.class.getSimpleName());



    @ArchTest

    static final ArchRule DAOs_must_not_throw_SQLException =

            noMethods().that().areDeclaredInClassesThat().haveNameMatching(".*Dao")

                    .should().declareThrowableOfType(SQLException.class);

}

Y se pueden hacer muchas cosas más, es muy simple de usar. Agregamos la dependencia : 

dependencies {

    testImplementation 'com.tngtech.archunit:archunit:0.23.1'

}

o con maven : 

<dependency>

    <groupId>com.tngtech.archunit</groupId>

    <artifactId>archunit</artifactId>

    <version>0.23.1</version>

    <scope>test</scope>

</dependency>


Y a codear test!!!

También existe para .net (pero eso es tema para otro post) 

Dejo link : https://www.archunit.org/



miércoles, 20 de abril de 2022

Funtores en Cats parte 5

Podemos pensar en el método map de Functor como "agregar" una transformación a una cadena. Ahora vamos a ver otras dos type class, una que representa anteponer operaciones a una cadena y otra que representa la construcción de una cadena bidireccional de operaciones. Estos se denominan funtores contravariantes e invariantes respectivamente.

La primera de nuestras clases de tipos, el funtor contravariante, proporciona una operación llamada contramapa que representa "anteponer" una operación a una cadena. 

El método contramap solo tiene sentido para tipos de datos que representan transformaciones. Por ejemplo, no podemos definir un contramapa para una opción porque no hay forma de retroalimentar un valor en un Option[B] a través de una función A => B. Sin embargo, podemos definir un contramapa para la clase de tipo Imprimible :

trait Printable[A] {

  def format(value: A): String

}

Un Printable[A] representa una transformación de A a String. Su método contramapa acepta una función func de tipo B => A y crea un nuevo Imprimible[B]:

trait Printable[A] {
  def format(value: A): String
  def contramap[B](func: B => A): Printable[B] = ???
}

def format[A](value: A)(implicit p: Printable[A]): String = p.format(value)

Los funtores invariantes implementan un método llamado imap que es informalmente equivalente a una combinación de map y contramap. Si map genera nuevas instancias de clase de tipo agregando una función a una cadena y contramap las genera anteponiendo una operación a una cadena, imap las genera a través de un par de transformaciones bidireccionales.

Los ejemplos más intuitivos de esto son una clase de tipo que representa la codificación y decodificación como algún tipo de datos, como el formato Play JSON y el códec de scodec. Podemos crear nuestro propio códec mejorando Imprimible para admitir la codificación y decodificación hacia/desde una cadena:

trait Codec[A] {
def encode(value: A): String
def decode(value: String): A
def imap[B](dec: A => B, enc: B => A): Codec[B] = ???
}
def encode[A](value: A)(implicit c: Codec[A]): String =
c.encode(value)
def decode[A](value: String)(implicit c: Codec[A]): A =
c.decode(value)

Si tenemos un Codec[A] y un par de funciones A => B y B => A, el método imap crea un Codec[B]:
Como ejemplo de caso de uso, imagina que tenemos un Codec[String] básico, cuyos métodos de codificación y decodificación simplemente devuelven el valor que se les pasa:

implicit val stringCodec: Codec[String] =
new Codec[String] {
def encode(value: String): String = value
def decode(value: String): String = value
}

Podemos construir muchos códecs útiles para otros tipos construyendo a partir de stringCodec usando imap:

implicit val intCodec: Codec[Int] =
stringCodec.imap(_.toInt, _.toString)
implicit val booleanCodec: Codec[Boolean] =
stringCodec.imap(_.toBoolean, _.toString)

lunes, 18 de abril de 2022

Funtores en Cats parte 4

Podemos definir un funtor simplemente definiendo su método map. Aquí hay un ejemplo de un Functor para Option, aunque tal cosa ya existe en cats.instances. La implementación es trivial: simplemente llamamos al método de mapa de Option:


implicit val optionFunctor: Functor[Option] = new Functor[Option] {

   def map[A, B](value: Option[A])(func: A => B): Option[B] = value.map(func)

}

A veces necesitamos inyectar dependencias en nuestras instancias. Por ejemplo, si tuviéramos que definir un Functor personalizado para Future (otro ejemplo hipotético: Cats proporciona uno en cats.instances.future), tendríamos que tener en cuenta el parámetro ExecutionContext implícito en future.map. No podemos agregar parámetros adicionales a functor.map, por lo que debemos tener en cuenta la dependencia cuando creamos la instancia:

import scala.concurrent.{Future, ExecutionContext}

implicit def futureFunctor(implicit ec: ExecutionContext): Functor[Future] = new Functor[Future] {
  def map[A, B](value: Future[A])(func: A => B): Future[B] = value.map(func)
}

Cada vez que llamamos a un Functor for Future, ya sea directamente usando Functor.apply o indirectamente a través del método de extensión map, el compilador ubicará futureFunctor por resolución implícita y buscará recursivamente un ExecutionContext en el sitio de la llamada. Así es como se vería la expansión:

// We write this:
Functor[Future]

// The compiler expands to this first:
Functor[Future](futureFunctor)

// And then to this:
Functor[Future](futureFunctor(executionContext))

viernes, 15 de abril de 2022

Funtores en Cats parte 3

El método principal proporcionado por la sintaxis de Functor es map. Es difícil demostrar esto con Opciones y Listas, ya que tienen sus propios métodos de mapa integrados y el compilador de Scala siempre preferirá un método integrado a un método de extensión. Resolveremos esto con dos ejemplos. Primero veamos el map sobre funciones. El tipo Function1 de Scala no tiene un método de map (se llama andThen en su lugar), por lo que no hay conflictos de nombres:

import cats.instances.function._ // for Functor

import cats.syntax.functor._ // for map

val func1 = (a: Int) => a + 1

val func2 = (a: Int) => a * 2

val func3 = (a: Int) => s"${a}!"

val func4 = func1.map(func2).map(func3)

func4(123)

// res3: String = "248!"

Veamos otro ejemplo. Esta vez vamos a abstraernos de los funtores para no trabajar con ningún tipo concreto en particular. Podemos escribir un método que aplique una ecuación a un número sin importar en qué contexto del funtor se encuentre:

def doMath[F[_]](start: F[Int])(implicit functor: Functor[F]): F[Int] = start.map(n => n + 1 * 2)

import cats.instances.option._ // for Functor
import cats.instances.list._  // for Functor

doMath(Option(20))
// res4: Option[Int] = Some(22)
doMath(List(1, 2, 3))
// res5: List[Int] = List(3, 4, 5)

Para ilustrar cómo funciona esto, echemos un vistazo a la definición del método map en cats.syntax.functor. Aquí hay una versión simplificada del código:

implicit class FunctorOps[F[_], A](src: F[A]) {
  def map[B](func: A => B)(implicit functor: Functor[F]): F[B] = functor.map(src)(func)
}

El compilador puede usar este método de extensión para insertar un método map donde no haya un map integrado disponible:

foo.map(value => value + 1)

Suponiendo que foo no tiene un método de map incorporado, el compilador detecta el error potencial y envuelve la expresión en un FunctorOps para corregir el código:

new FunctorOps(foo).map(value => value + 1)

El método map de FunctorOps requiere un Functor implícito como parámetro. Esto significa que este código solo se compilará si tenemos un Funtor para F en el alcance. Si no lo hacemos, obtenemos un error del compilador:

final case class Box[A](value: A)
val box = Box[Int](123)
box.map(value => value + 1)
// error: value map is not a member of repl.Session.App0.Box[Int]
// box.map(value => value + 1)
// ^^^^^^^

El método as también está disponible como sintaxis.

List(1, 2, 3).as("As")
// res7: List[String] = List("As", "As", "As")



jueves, 14 de abril de 2022

EBOOK - Shifting Left for Application Security

Me llego este mail y quiero compartirlo con ustedes:

 

https://interact.f5.com/rs/653-SMC-783/images/EB - Shifting Left for Application Security - 760x284.png

Hi Emanuel,

Security is a paramount concern for developers, operations and security engineers, and company CISOs alike. Security remains crucial from the earliest phases of application design and development through deployment and support. In this report, author Peter Conrad explains why "shifting left" is important and how you can apply this practice in your organization.

In this eBook you will learn:

  • Why you need to adopt DevSecOps and place security at the core of application development and deployment
  • How shifting left embeds security throughout the entire software lifecycle
  • About traditional tools that can help protect clusters, containers, and microservices
  • How a European bank, an energy company, and a telecommunications company successfully shifted left

Funtores en Cats parte 2

La clase de tipo de funtor es cats.Functor. Obtenemos instancias usando el método Functor.apply estándar de un objeto complementario. Como es habitual, las instancias predeterminadas se organizan por tipo en el paquete cats.instances:

import cats.Functor

import cats.instances.list._

 // for Functor

import cats.instances.option._ // for Functor

val list1 = List(1, 2, 3)

// list1: List[Int] = List(1, 2, 3)

val list2 = Functor[List].map(list1)(_ * 2)

// list2: List[Int] = List(2, 4, 6)

val option1 = Option(123)

// option1: Option[Int] = Some(123)

val option2 = Functor[Option].map(option1)(_.toString)

// option2: Option[String] = Some("123")


Functor proporciona un método llamado lift, que convierte una función de tipo A => B en una que opera sobre un funtor y tiene tipo F[A] => F[B]:

val func = (x: Int) => x + 1
// func: Int => Int = <function1>
val liftedFunc = Functor[Option].lift(func)
// liftedFunc: Option[Int] => Option[Int] = cats.Functor$$Lambda$7972
/0x000000084250f840@195657fa
liftedFunc(Option(1))
// res1: Option[Int] = Some(2)

El método as es el otro método que es probable que utilice. Reemplaza con value dentro del Functor con el valor dado.

Functor[List].as(list1, "As")
// res2: List[String] = List("As", "As", "As")

miércoles, 13 de abril de 2022

Libros gratuitos de java code geeks

 

Download IT Guides!

 

Java NIO Programming Cookbook

java.nio (NIO stands for non-blocking I/O) is a collection of Java programming language APIs that offer features for intensive I/O operations. It was introduced with the J2SE 1.4 release...

 
 

IntelliJ IDEA Handbook

IntelliJ IDEA is a Java integrated development environment (IDE) for developing computer software. It is developed by JetBrains, and is available as an Apache 2 Licensed community...

 
 

Amazon S3 Tutorial

Amazon S3 (Simple Storage Service) is a web service offered by Amazon Web Services. Amazon S3 provides storage through web services interfaces (REST, SOAP, and BitTorrent). Amazon...

 
 

JUnit Programming Cookbook

JUnit is a unit testing framework to write repeatable tests. JUnit has been important in the development of test-driven development, and is one of a family of unit testing frameworks...

 

lunes, 11 de abril de 2022

Funtores en Cats

Formalmente, un funtor es un tipo F[A] con un map de tipo (A => B) => F[B]. 

Cats codifica Functor como una clase de tipos, cats.Functor, por lo que el método se ve un poco diferente. Acepta la F[A] inicial como parámetro junto con la función de transformación. Aquí hay una versión simplificada de la definición:

package cats

trait Functor[F[_]] {

   def map[A, B](fa: F[A])(f: A => B): F[B]

}

Los funtores garantizan la misma semántica ya sea que secuenciamos muchas operaciones pequeñas una por una o las combinemos en una función más grande antes de llamar a map. Para garantizar que esto sea así, deben cumplirse las siguientes leyes: Identidad: llamar a map con la función de identidad es lo mismo que no hacer nada: 

fa.map(a => a) == fa

Composición: llamar a map con dos funciones f y g es lo mismo que llamar a map con f y luego con g:

fa.map(g(f(_))) == fa.map(f).map(g)




jueves, 7 de abril de 2022

Funtores parte 3

Seguimos con Funtores 

Si Future no es referencialmente transparente, tal vez deberíamos buscar otro tipo de datos similar que lo sea. Deberías reconocer a este...

Functions (?!)

Resulta que las funciones de un solo argumento también son funtores. Para ver esto, tenemos que modificar un poco los tipos. Una función A => B tiene dos parámetros de tipo:
el tipo de parámetro A y el tipo de resultado B. Para forzarlos a la forma correcta, podemos fijar el tipo de parámetro y dejar que el tipo de resultado varíe:

• empezar con X => A ;
• proporcionar una función A => B;
• recuperar X => B.

Si hacemos un alias X => A como MyFunc[A], vemos el mismo patrón de tipos que vimos con los otros ejemplos :

• empezar con MyFunc[A];
• proporcionar una función A => B;
• recuperar MyFunc[B].

En otras palabras, "mapear" sobre una Función1 es la composición de funciones:

import cats.instances.function._ // for Functor
import cats.syntax.functor._

 // for map
val func1: Int => Double = (x: Int) => x.toDouble
val func2: Double => Double = (y: Double) => y * 2

(func1 map func2)(1)
 // composition using map
// res3: Double = 2.0
// composition using map

(func1 andThen func2)(1) // composition using andThen
// res4: Double = 2.0 // composition using andThen

func2(func1(1))
 // composition written out by hand
// res5: Double = 2.0

¿Cómo se relaciona esto con nuestro patrón general de secuenciación de operaciones? Si lo pensamos bien, la composición de funciones es secuenciación. Comenzamos con una función que realiza una sola operación y cada vez que usamos map agregamos otra operación a la cadena. Llamar a map en realidad no ejecuta ninguna de las operaciones, pero si podemos pasar un argumento a la función final, todas las operaciones se ejecutan en secuencia. Podemos pensar en esto como operaciones de cola perezosas similares a Future:

val func =
  ((x: Int) => x.toDouble).
  map(x => x + 1).
  map(x => x * 2).
  map(x => s"${x}!")

func(123)
// res6: String = "248.0!"



miércoles, 6 de abril de 2022

Funtores parte 2

Seguimos con Funtores 

Los métodos de map de List, Option y Either aplican funciones de forma eager (es decir no lazy). Sin embargo, la idea de secuenciar cálculos es más general que esto. Investiguemos el comportamiento de algunos otros funtores que aplican el patrón de diferentes maneras.

Futures : Future es un funtor que secuencia cálculos asincrónicos poniéndolos en cola y aplicándolos a medida que se completan sus predecesores. La firma de tipo de su método de map, tiene la misma forma que las firmas anteriores. Sin embargo, el comportamiento es muy diferente. Cuando trabajamos con un Futuro no tenemos garantías sobre su estado interno. El cálculo envuelto puede estar en curso, completo o rechazado. Si el futuro está completo, nuestra función de map se puede llamar inmediatamente. De lo contrario, algún grupo de subprocesos subyacente pone en cola la llamada de función y vuelve a ella más tarde. No sabemos cuándo se llamará a nuestras funciones, pero sí sabemos en qué orden se llamarán. De esta manera, Future proporciona el mismo comportamiento de secuencia que se ve en List, Option y Either:

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

val future: Future[String] =
    Future(123).
    map(n => n + 1).
    map(n => n * 2).
    map(n => s"${n}!")

Await.result(future, 1.second)
// res2: String = "248!"

Tenga en cuenta que los futuros de Scala no son un gran ejemplo de programación funcional pura porque no son referencialmente transparentes. Future siempre calcula y almacena en caché un resultado y no hay forma de que modifiquemos este comportamiento. Esto significa que podemos obtener resultados impredecibles cuando usamos Future para envolver cálculos de efectos secundarios. Por ejemplo:

import scala.util.Random
val future1 = {
  // Initialize Random with a fixed seed:
  val r = new Random(0L)
  // nextInt has the side-effect of moving to
  // the next random number in the sequence:
  val x = Future(r.nextInt)

  for {
    a <- x
    b <- x
  } yield (a, b)
}

val future2 = {
  val r = new Random(0L)
  for {
    a <- Future(r.nextInt)
    b <- Future(r.nextInt)
  } yield (a, b)
}

val result1 = Await.result(future1, 1.second)
// result1: (Int, Int) = (-1155484576, -1155484576)
val result2 = Await.result(future2, 1.second)
// result2: (Int, Int) = (-1155484576, -723955400)

Idealmente, nos gustaría que resultado1 y resultado2 contuvieran el mismo valor. Sin embargo, el cálculo de future1 llama a nextInt una vez y el cálculo de future2 lo llama dos veces. Porque nextInt devuelve un resultado diferente cada vez que obtenemos un resultado diferente en cada caso.

Este tipo de discrepancia hace que sea difícil razonar acerca de los programas que involucran Futuros y efectos secundarios. También hay otros aspectos problemáticos del comportamiento de Future, como la forma en que siempre inicia los cálculos inmediatamente en lugar de permitir que el usuario dicte cuándo debe ejecutarse el programa. 

Cuando miramos Cats Effect, veremos que el tipo IO resuelve estos problemas. Si Future no es referencialmente transparente, tal vez deberíamos buscar otro tipo de datos similar que lo sea. Pero de él hablaremos el proximo post. 

viernes, 1 de abril de 2022

Funtores

Los funtores son una abstracción que nos permite representar secuencias de operaciones dentro de un contexto como una Lista, un Option o cualquiera de miles de otras posibilidades. Los funtores por sí solos no son tan útiles, pero los casos especiales de funtores, como las mónadas y los funtores aplicativos, son algunas de las abstracciones más utilizadas.

Informalmente, un funtor es cualquier cosa con un método map. Probablemente conozca muchos tipos que tienen esto: Option, List, y Either, por nombrar algunos. Por lo general, nos encontramos primero con el map cuando iteramos sobre Listas. Sin embargo, para entender los funtores necesitamos pensar en el método de otra manera. En lugar de recorrer la lista, deberíamos pensar en ella como transformar todos los valores dentro de una sola vez. Especificamos la función que se va a aplicar y el map se asegura de que se aplique a cada elemento. Los valores cambian pero la estructura de la lista (el número de elementos y su orden) permanece igual:

List(1, 2, 3).map(n => n + 1)

// res0: List[Int] = List(2, 3, 4)

De manera similar, cuando asignamos una opción, transformamos los contenidos pero dejamos el contexto Some o None sin cambios. El mismo principio se aplica a Either con sus contextos Left y Right. Esta noción general de transformación, junto con el patrón común de firmas de tipo, es lo que conecta el comportamiento del mapa en diferentes tipos de datos.



Debido a que map deja la estructura del contexto sin cambios, podemos llamarlo repetidamente para secuenciar múltiples cálculos sobre el contenido de una estructura de datos inicial:

List(1, 2, 3).
map(n => n + 1).
map(n => n * 2).
map(n => s"${n}!")
// res1: List[String] = List("4!", "6!", "8!")

Deberíamos pensar en el map no como un patrón de iteración, sino como una forma de secuenciar cálculos sobre valores ignorando algunas complicaciones dictadas por el tipo de datos relevante:



[eBook] Cloud Native DevOps with Kubernetes, 2nd Edition

 

  • https://interact.f5.com/rs/653-SMC-783/images/EBK - Cloud Native DevOps with Kubernetes 2nd Edition - 760x284.png

    Hi Emanuel,

    Kubernetes is the operating system of the cloud-native world, providing a reliable and scalable platform for running containerized workloads. In this comprehensive eBook, cloud experts Justin Domingus and John Arundel show your development and operations staff what Kubernetes can do – and what you can do with it.

    This updated second edition, provided with compliments of F5 NGINX, guides you through the growing Kubernetes ecosystem and provides practical solutions to everyday problems using tools currently in use in the software industry. You'll walk through a sample containerized application running in Kubernetes step-by-step, from the development environment through the continuous deployment pipeline, with patterns you can use for your own applications. Make your development teams lean, fast, and effective by adopting Kubernetes and DevOps principles.

    In this eBook you will learn:

    • About containers and Kubernetes from first principles – no experience necessary – and how to design your own cloud‑native services and infrastructure
    • How to run your own clusters, or choose a managed Kubernetes service from Amazon, Google, and others, while managing resource usage and the container lifecycle
    • How to optimize clusters for cost, performance, resilience, capacity, and scalability while using the best tools for developing, testing, and deploying your applications
    • How to apply the latest industry practices for observability, monitoring, and security in production