Taller de Elixir #5 – Pattern Matching (La coincidencia de patrones)

Vamos a ver cómo el operador = en Elixir es en realidad un operador de coincidencia y cómo usarlo para establecer patrones dentro de las estructuras de datos. También, aprenderemos sobre el operador de pin ^ usado para acceder a valores previamente vinculados.

El operador match

Hemos usado el operador = un par de veces para asignar variables en Elixir:

iex> x = 1
1
iex> x
1

Pero en Elixir, el operador = en realidad se llama operador de coincidencia. Veamos por qué:

iex> x = 1
1
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1

Observe que 1 = x es una expresión válida y coincide porque los lados izquierdo y derecho son iguales a 1. Cuando los lados no coinciden, se genera un MatchError.

Una variable solo se puede asignar en el lado izquierdo del =.

iex> 1 = unknown
** (CompileError) iex:1: undefined function unknown/0

Como no hay una variable desconocida previamente definida, Elixir asumió que estaba intentando llamar a una función llamada unknown/0, pero esa función no existe.

Pattern Matching

El operador de coincidencia no solo se utiliza para comparar valores simples, sino que también es útil para desestructurar tipos de datos más complejos. Por ejemplo, podemos combinar patrones en tuplas:

iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"

Se producirá un error de coincidencia de patrón si los lados no pueden coincidir, por ejemplo, si las tuplas tienen tamaños diferentes:

iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}

Y también al comparar diferentes tipos:

iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]

Más interesante aún, podemos coincidir en valores específicos. El siguiente ejemplo afirma que el lado izquierdo solo coincidirá con el lado derecho cuando el lado derecho es una tupla que comienza con el átomo :ok:

iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13

iex> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}

Podemos emparejar patrones en listas:

iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1

Una lista también admite coincidencias en su propia cabeza y cola:

iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]

Similar a las funciones hd/1 y tl/1, no podemos hacer coincidir una lista vacía con un patrón de cabeza y cola:

iex> [head | tail] = []
** (MatchError) no match of right hand side value: []

La [cabeza | el formato tail] no solo se usa en la coincidencia de patrones, sino también para anteponer elementos a una lista:

iex> list = [1, 2, 3]
[1, 2, 3]
iex> [0 | list]
[0, 1, 2, 3]

La coincidencia de patrones permite a los desarrolladores desestructurar fácilmente los tipos de datos, como tuplas y listas.

El operador pin

Utilice el operador de pin ^ cuando desee comparar patrones con el valor de una variable existente en lugar de volver a vincular la variable:

iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

Como hemos asignado el valor de 1 a la variable x, este último ejemplo también podría haberse escrito como:

iex> {y, 1} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

Si una variable se menciona más de una vez en un patrón, todas las referencias deben unirse al mismo patrón:

iex> {x, x} = {1, 1}
{1, 1}
iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}

En algunos casos, no le importa un valor particular en un patrón. Es una práctica común vincular esos valores al guión bajo, _. Por ejemplo, si solo nos importa el encabezado de la lista, podemos asignar la cola para subrayar:

iex> [head | _] = [1, 2, 3]
[1, 2, 3]
iex> head
1

La variable _ es especial porque nunca se puede leer. Si intenta leer de él da un error de compilación:

iex> _
** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions

Aunque la coincidencia de patrones nos permite construir construcciones potentes, su uso es limitado. Por ejemplo, no puede realizar llamadas a funciones en el lado izquierdo de una coincidencia. El siguiente ejemplo no es válido:

iex> length([1, [2], 3]) = 3
** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match

Taller de Elixir #4 – Operadores Básicos

Hemos vistos que los operadores aritméticos +, -, *, / como, además de las funciones div/2 y rem/2 para la división entera y el resto.

También vimos ++ y — para manipular listas:

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, 2, 3] -- [2]
[1, 3]

La concatenación de cadenas se realiza con <>:

iex> "foo" <> "bar"
"foobar"

Aun que también es muy común el uso de:

iex> [foo, bar] = ["foo", "bar"]
["foo", "bar"]
iex> "#{foo}#{bar}"
"foobar"

Hay tres operadores booleanos: or, and y not. Estos operadores son estrictos en el sentido de que esperan algo que se evalúe como booleano (verdadero o falso) como primer argumento:

iex> true and true
true
iex> false or is_atom(:example)
true

Proporcionar un valor no booleano generará una excepción:

iex> 1 and true
** (BadBooleanError) expected a boolean on left-side of "and", got: 1

Or y and son operadores de cortocircuito. Solo ejecutan el lado derecho si el lado izquierdo no es suficiente para determinar el resultado:

iex> false and raise("This error will never be raised")
false
iex> true or raise("This error will never be raised")
true

Además de estos operadores booleanos, Elixir también proporciona ||, && y! que aceptan argumentos de cualquier tipo. Para estos operadores, todos los valores, excepto falso y nulo, se evaluarán como verdaderos:

# or
iex> 1 || true
1
iex> false || 11
11

# and
iex> nil && 13
nil
iex> true && 17
17

# !
iex> !true
false
iex> !1
false
iex> !nil
true

Como regla general, use y, o no y cuando esté esperando booleanos. Si alguno de los argumentos no es booleano, use &&, || y!

También podemos usar ==,! =, ===,! ==, <=,> =, como operadores de comparación:

iex> 1 == 1
true
iex> 1 != 2
true
iex> 1 < 2
true

La diferencia entre == y === es que este último es más estricto:

iex> 1 == 1.0
true
iex> 1 === 1.0
false

También podemos comparar dos tipos de datos diferentes:

iex> 1 < :atom
true

La razón por la que podemos comparar diferentes tipos de datos es el pragmatismo. Los algoritmos de clasificación no necesitan preocuparse por los diferentes tipos de datos para ordenar. El orden de clasificación general se define a continuación:

number < atom < reference < function < port < pid < tuple < map < list < bitstring

En realidad no necesita memorizar este pedido; es suficiente saber que existe este orden.

Taller de Elixir #3 – Tipos de datos

Los tipos básicos en elixir son enteros, flotantes, booleanos, átomos, cadenas, listas y tuplas.

iex> 1          # integer
iex> 0x1F       # integer
iex> 1.0        # float
iex> true       # boolean
iex> :atom      # atom / symbol
iex> "elixir"   # string
iex> [1, 2, 3]  # list
iex> {1, 2, 3}  # tuple

Aritmética básica

Las operaciones básicas son comunes:

iex> 1 + 2
3
iex> 2 - 1
1
iex> 5 * 5
25
iex> 10 / 2
5.0

Para que al dividir devuelva un entero en vez de un flotante se usa la función div/1 y para el modulo usamos rem/1.

iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1

Elixir le permite quitar los paréntesis al invocar funciones con nombre. Esta característica proporciona una sintaxis más limpia.

También admite anotaciones de acceso directo para ingresar números binarios, octales y hexadecimales:

iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31

Los números flotantes requieren un punto seguido de al menos un dígito y también admiten e para notación científica:

iex> 1.0
1.0
iex> 1.0e-10
1.0e-10

Las flotantes son de doble precisión de 64 bits.

La función “round” devuelve el número entero más cercano a un flotante y la función “trunc” para obtener la parte entera de un flotante.

iex> round(3.58)
4
iex> trunc(3.58)
3

Booleanos

Tenemos las palas claves true y false:

iex> true
true
iex> true == false
false

También tenemos la función “is_boolean”:

iex> is_boolean(true)
true
iex> is_boolean(1)
false

Atoms

Un átomo es una constante cuyo valor es su propio nombre. Algunos lenguajes llaman a estas símbolos. A menudo son útiles para enumerar valores distintos, como:

iex> :apple
:apple
iex> :orange
:orange
iex> :watermelon
:watermelon

Los átomos son iguales si sus nombres son iguales:

iex> :apple == :apple
true
iex> :apple == :orange
false

A menudo se utilizan para expresar el estado de una operación, mediante el uso de valores como :ok y :error.

Los booleanos verdadero y falso también son átomos:

iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true

Elixir te permite omitir el líder: para los átomos falso, verdadero y nulo.

Finalmente, Elixir tiene una construcción llamada alias que exploraremos más adelante. Los alias comienzan en mayúsculas y también son átomos:

iex> is_atom(Hello)
true

Strings

Las cadenas están delimitadas por comillas dobles, y están codificadas en UTF-8:

iex> "hellö #{:world}"
"hellö world"

Las cadenas pueden tener saltos de línea en ellas. Puedes presentarlos usando secuencias de escape:

ex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"

Se puede imprimir una cadena usando la función IO.puts/1 desde el módulo IO:

iex> IO.puts "hello\nworld"
hello
world
:ok

Podemos ver que la función IO.puts/1 devuelve el átomo :ok después de imprimir.

Las cadenas están representadas internamente por secuencias contiguas de bytes conocidas como binarios:

iex> is_binary("hellö")
true

Para saber el número de bytes en una cadena:

iex> byte_size("hellö")
6

Observamos que el número de bytes en esa cadena es 6, a pesar de que tiene 5 caracteres. Esto se debe a que el carácter “ö” toma 2 bytes para ser representado en UTF-8. Podemos obtener la longitud real de la cadena, en función del número de caracteres, utilizando la función String.length/1:

iex> String.length("hellö")
5

El String module  contiene un montón de funciones para operar:

iex> String.upcase("hellö")
"HELLÖ"

Funciones anónimas

Estas funciones nos permiten almacenar y pasar código ejecutable como si fuera un entero o una cadena. Están delimitados por las palabras clave fn y end:

iex> add = fn a, b -> a + b end
<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true

Las funciones anónimas también se identifican por la cantidad de argumentos que reciben. Podemos verificar si una función usando is_function / 2:

iex> is_function(add, 2)
true

iex> is_function(add, 1)
false

Definamos una nueva función anónima que usa la función agregar anónimo que hemos definido previamente:

iex> double = fn a -> add.(a, a) end
<6.71889879/1 in :erl_eval.expr/5>
iex> double.(2)
4

Una variable asignada dentro de una función no afecta a su entorno:

iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42

Listas

Se usan corchetes para especificar una lista de valores. Los valores pueden ser de cualquier tipo:

iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3

Se pueden concatenar o restar dos listas utilizando los operadores ++ / 2 y – / 2 respectivamente:

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]

Los operadores de listas nunca modifican la lista existente. Concatenar o eliminar elementos de una lista devuelve una nueva lista. Decimos que las estructuras de datos de Elixir son inmutables. Una ventaja de la inmutabilidad es que conduce a un código más claro. Puede pasar libremente los datos con la garantía de que nadie los mutará en la memoria, solo los transformará.

La cabeza es el primer elemento de una lista y la cola es el resto de la lista. Se pueden recuperar con las funciones hd/1 y tl/1. Asignemos una lista a una variable y recuperemos su cabeza y cola:

iex> list = [1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]

Obtener la cabeza o la cola de una lista vacía arroja un error:

iex> hd []
** (ArgumentError) argument error

A veces creará una lista y devolverá un valor entre comillas simples. Por ejemplo:

iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'

Cuando Elixir ve una lista de números ASCII imprimibles, Elixir lo imprimirá como una lista de caracteres (literalmente, una lista de caracteres). Las listas de caracteres son bastante comunes al interactuar con el código Erlang existente. Siempre que vea un valor en IEx y no esté seguro de cuál es, puede usar el i/1 para recuperar información al respecto:

iex> i 'hello'
Term
  'hello'
Data type
  List
Description
  ...
Raw representation
  [104, 101, 108, 108, 111]
Reference modules
  List
Implemented protocols
  ...

Las representaciones con comillas simples y dobles no son equivalentes en Elixir, ya que están representadas por diferentes tipos:

iex> 'hello' == "hello"
false

Las comillas simples son charlists, las comillas dobles son cadenas.

Tuplas

Elixir usa llaves para definir tuplas. Al igual que las listas, las tuplas pueden contener cualquier valor:

iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2

Las tuplas almacenan elementos contiguos en la memoria. Esto significa que acceder a un elemento de tupla por índice u obtener el tamaño de la tupla es una operación rápida. Los índices comienzan desde cero:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2

También es posible poner un elemento en un índice particular en una tupla con put_elem/3:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> put_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}

Observamos que put_elem/3 devolve una nueva tupla. La tupla original almacenada en la variable tupla no se modificó. Al igual que las listas, las tuplas también son inmutables. Cada operación en una tupla devuelve una nueva tupla, nunca cambia la dada.

¿Listas o tuplas?

Las listas se almacenan en la memoria como listas vinculadas, lo que significa que cada elemento de una lista mantiene su valor y apunta al siguiente elemento hasta llegar al final de la lista. Esto significa que acceder a la longitud de una lista es una operación lineal: debemos recorrer toda la lista para determinar su tamaño.

Del mismo modo, el rendimiento de la concatenación de listas depende de la longitud de la lista de la izquierda:

iex> list = [1, 2, 3]

iex> [0] ++ list
[0, 1, 2, 3]

iex> list ++ [4]
[1, 2, 3, 4]

Las tuplas, por otro lado, se almacenan contiguamente en la memoria. Esto significa que obtener el tamaño de tupla o acceder a un elemento por índice es rápido. Sin embargo, actualizar o agregar elementos a las tuplas es costoso porque requiere crear una nueva tupla en la memoria:

iex> tuple = {:a, :b, :c, :d}
iex> put_elem(tuple, 2, :e)
{:a, :b, :e, :d}

Hay que tener en cuenta que esto solo se aplica a la tupla en sí, no a su contenido. Por ejemplo, cuando actualiza una tupla, todas las entradas se comparten entre la tupla antigua y la nueva, excepto la entrada que ha sido reemplazada. En otras palabras, las tuplas y las listas en Elixir son capaces de compartir sus contenidos. Esto reduce la cantidad de asignación de memoria que necesita realizar el idioma y solo es posible gracias a la semántica inmutable del idioma.

Esas características de rendimiento dictan el uso de esas estructuras de datos. Un caso de uso muy común para las tuplas es usarlas para devolver información adicional de una función. Por ejemplo, File.read/1 es una función que se puede usar para leer el contenido del archivo. Devuelve una tupla:

iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}

Si la ruta dada a File.read/1 existe, devuelve una tupla con el átomo :ok como el primer elemento y el contenido del archivo como el segundo. De lo contrario, devuelve una tupla con :error y la descripción del error.

La mayoría de las veces, Elixir te guiará para hacer lo correcto. Por ejemplo, hay una función elem/2 para acceder a un elemento de tupla, pero no hay un equivalente incorporado para las listas:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"

Hasta ahora hemos utilizado 4 funciones de conteo: byte_size/1 (para el número de bytes en una cadena), tuple_size/1 (para el tamaño de la tupla), length/1 (para la longitud de la lista) y String.length/1 ( para el número de grafemas en una cadena). Usamos byte_size para obtener el número de bytes en una cadena, una operación barata. Recuperar el número de caracteres Unicode, por otro lado, usa String.length, y puede ser costoso ya que depende de un recorrido de toda la cadena.

Elixir también proporciona puertos, referencias y PID como tipos de datos (generalmente utilizados en la comunicación de procesos).

Taller de Elixir #2 – Primeros pasos

Documentación

Modo Interactivo

Cuando tenemos instalado Elixir. Tendremos 3 ejecutables: iex, elixir, mix.

El primero que vamos a ver es iex o iex.bat en windows. Que significa Interactive Elixir. En este shell, podremos escribir cualquier expresión de Elixir y obtener su resultado.

Un ejemplo sencillo sería.

Erlang/OTP 21.0 [64-bit] [smp:2:2] [...]

Interactive Elixir (1.10.2) - press Ctrl+C to exit
iex(1)> 40 + 2
42
iex(2)> "hello" <> " world"
"hello world"

Para salir de iex, presione Ctrl + C dos veces.

Ejecutar scripts

Creamos un fichero llamado “simple.exs” y le añadimos la siguiente linea.

IO.puts "Hello world from Elixir"

Salvamos el fichero y ejecutamos en la consola.

$ elixir simple.exs
Hello world from Elixir

Crear un proyecto

Creemos nuestro primer proyecto con “mix new” desde la línea de comandos. Pasaremos el nombre del proyecto como argumento (kv, en este caso), y le diremos a Mix que nuestro módulo principal debería ser el KV en mayúsculas, en lugar del predeterminado, que habría sido Kv:

$ mix new kv --module KV

Lo que nos devolverá algo parecido a esto:

* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/kv.ex
* creating test
* creating test/test_helper.exs
* creating test/kv_test.exs

El fichero responsable de la compilación del proyecto (Opciones, dependencias.. etc) es mix.exs y se genera automáticamente.

defmodule KV.MixProject do
  use Mix.Project

  def project do
    [
      app: :kv,
      version: "0.1.0",
      elixir: "~> 1.9",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end

Ahora ya tenemos un proyecto. Se podría crea módulos y funciones. Para usarlo como librería de otra app. Pero queremos que se ejecute. Para ello modificamos el archivo mix.exs el apartado application. Tiene que ser algo parecido a esto.

  def application do
    [
      mod: {KV, []},
      extra_applications: [:logger]
    ]
  end

Eso hará que cuando ejecutemos la app nos llame a la función “start” del método “KV”. Nos vamos a fichero “lib/kv.ex” y cambiamos su contenido que es de ejemplo por:

defmodule KV do
  @moduledoc """
  Documentation for `KV`.
  """

  @doc """
    Run to start app.
  """
  def start(_type, _args) do
    IO.puts "starting"
    :timer.sleep(1000)
    Task.start(fn -> :timer.sleep(1000); IO.puts("done sleeping") end)
  end
end

Una vez realizado esto. Ejecutamos en consola:

$ mix run

Lo que nos devolverá el siguiente texto con un pequeño delay.

Compiling 1 file (.ex)
starting

Taller de Elixir #1 – Conceptos e instalación

Este taller estará basado en Elixir guides. Es básicamente una traducción del mismo con algunos cambios y apuntes.

¿Qué es?

Elixir es un lenguaje de programación funcionalconcurrente, de propósito general que se ejecuta sobre la máquina virtual de Erlang (BEAM). Elixir está escrito sobre Erlang y comparte las mismas abstracciones para desarrollar aplicaciones distribuidas y tolerantes a fallos. Elixir también proporciona un diseño extensible con herramientas productivas. Incluye soporte para metaprogramación en tiempo de compilación con macros y polimorfismo mediante protocolos.

Características

Instalación en Linux (Ubuntu)

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb 
sudo dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang
sudo apt-get install elixir

Instalación en Mac

brew install elixir (utilizando Homebrew)

Instalación en Windows

Descarga el instalador y sigue los pasos

Montar una instancia Nextcloud en Docker

nextcloud

En esta entrada vamos a explicar brevemente como montar una instancia de Nextcloud en Docker, Nextcloud es un potente servicio de software libre de cloud (nube), sirve para almacenar, sincronizar y compartir archivos principalmente aunque con sus plugins se pueden añadir muchas funcionalidades, reemplaza al viejo proyecto ownCloud.

Para ello utilizaremos la imagen oficial de Nextcloud que podemos encontrar en el hub de Docker, esto nos facilitará el despliegue y es más fácil que mantener una imagen propia, podemos encontrar distintas versiones de la imagen aunque nos basaremos en la última latest, además contamos con las últimas actualizaciones de seguridad.

Usaremos el programa docker-compose para el despliegue de la instancia, con el que configuramos los contenedores necesarios con nuestras opciones, en vez de hacerlo directamente desde la línea de comandos.

version: '2'

services:
  nextcloud:
    image: nextcloud
    restart: always
    container_name: nextcloud
    hostname: nextcloud
    environment:
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=xxx
      - MYSQL_HOST=mariadb-nextcloud
    depends_on:
      - mariadb-nextcloud
    expose:
      - 80
    links:
      - mariadb-nextcloud
    volumes:
      - ./nextcloud/html/custom_apps:/var/www/html/custom_apps
      - ./nextcloud/html/config:/var/www/html/config
      - ./nextcloud/html/data:/var/www/html/data
      - ./nextcloud/html/themes:/var/www/html/themes
    networks:
      - cloudnet:

  mariadb-nextcloud:
    image: mariadb
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    container_name: mariadb-nextcloud
    hostname: mariadb-nextcloud
    volumes:
      - ./nextcloud/mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=yyy
      - MYSQL_PASSWORD=xxx
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
    networks:
      - cloudnet:

networks:
  cloudnet:

Teniendo este archivo en una carpeta, con el comando docker-compose up -d nos bajaremos la última versión de las imágenes de Nextcloud y MariaDB desde el hub de Docker, y seguidamente se instanciarán ambos contenedores. Hemos preferido compartir los volúmenes de los contenedores con el sistema de archivos local para mantener la persistencia por si se elimina el contenedor, y configurar las imágenes con las variables de entorno necesarias para el correcto funcionamiento del servicio.

También hemos relacionado los contenedores (links) y expuesto el puerto 80/tcp, ya que nuestro servicio está detrás de un proxy nginx, del contenedor Nextcloud para utilizarlo aunque pueden incluirse los que se necesiten.

Una vez se levanten los contenedores podremos acceder al servicio por el puerto correspondiente y comenzar a configurar la instancia con las opciones que necesitemos.

Esperamos que esta entrada os haya gustado y os sirva para futuros despliegues, saludos

Tuneando PostgreSQL

PostgreSQL

Después de una larga ausencia en el blog, vamos a explicar una configuración para la base de datos PostgreSQL que pueda soportar altas cargas y aumentar la disponibilidad del servicio, comentaremos sus opciones y justificaremos los valores que decidimos aplicar en función de los recursos de la máquina.

Nuestra máquina es una servidor cloud que tiene 4 núcleos y 8G de memoria RAM, la máquina actualmente está compartida con contenedores docker de otros servicios por lo que en este contenedor vamos a consumir como máximo 3G de RAM y un pool de 250 conexiones, ya que el software que la usa (Elixir en este caso) es muy rápido y necesita de una alta disponibilidad, así mismo precisa de un alto número de conexiones concurrentes para no saturarla.

Opciones de configuración

Estas opciones las hemos sacado de la página https://pgtune.leopard.in.ua que nos ayudará a afinar la configuración en función de los valores de versión, núcleos, memoria y conexiones que necesitemos, hemos decidido usar el perfil online transaction processing system para nuestro caso.

# DB Version: 12
# OS Type: linux
# DB Type: oltp
# Total Memory (RAM): 3 GB
# CPUs num: 4
# Connections num: 250

max_connections = 250 # máximo número de conexiones concurrentes
shared_buffers = 768MB # cantidad de memoria dedicada a datos en caché
effective_cache_size = 2304MB # cantidad de memoria disponible para memoria intermedia en el disco
maintenance_work_mem = 192MB # cantidad de memoria usada para operaciones de mantenimiento
checkpoint_completion_target = 0.9 # permite escribir lentamente en la instancia: checkpoint_completion_target * checkpoint_timeout (5min)
wal_buffers = 16MB # pequeña memoria que sincroniza los datos, aumentándola se permiten inserciones más grandes
default_statistics_target = 100 # recolecta estadísticas de cada una de las tablas para decidir como se ejecutarán las consultas sobre ellas
random_page_cost = 1.1 # sugiere al optimizador cuanto tiempo le llevará al disco encontrar una página aleatoria de disco
effective_io_concurrency = 200 # número de operaciones de disco I/O concurrentes
work_mem = 1572kB # para operaciones complejas realiza ordenamientos más distendidos en memoria
min_wal_size = 2GB # cantidad mínima de la memoria usada para integridad de los datos
max_wal_size = 4GB # cantidad máxima de la memoria usada para integridad de los datos
max_worker_processes = 4 # cantidad máxima de procesos
max_parallel_workers_per_gather = 2 # cantidad máxima de subprocesos en paralelo por nodo, no puede exceder a max_parallel_workers
max_parallel_workers = 4 # cantidad máxima de subprocesos en paralelo

Con esta configuración el contenedor de PostgreSQL 12 está preparado para trabajar con 250 conexiones concurrentes, pudiendo gestionar correctamente la carga de las consultas que se realicen y mantener la estabilidad del sistema a lo largo del tiempo.

Esperamos que os haya gustado, nos vemos en la próxima entrada, saludos

Java con un poco de SpringBoot2

En esta entrada vamos a presentar un poco el framework SpringBoot2 para Java, es un software que como objetivo tiende a sustituir y eliminar los tediosos archivos XML del framework de Spring por las anotaciones, que básicamente son clases programadas que modifican el comportamiento de las clases, métodos y parámetros sobre los que están puestos, y se distinguen en el código por tener una @ delante del nombre de la anotación.

Tenemos decenas de anotaciones en el framework que nos van a ayudar a la hora de implementar un proyecto de forma muy rápida sin tener que reescribir el código necesario para que todo funcione, parece magia ya que todo es transparente para el programador, aunque nos obliga a formarnos, tiene una curva de aprendizaje muy rápida y sus funcionalidades pueden ayudarnos a la hora de integrar la lógica de la aplicación.

@anotaciones

@Autowired // Instancia una variable y sus @Beans
@Bean // Crea una instancia singleton
@SpringBootApplication // Instancia una aplicación SpringBoot2
@Controller // Clase controlador
@Service // Clase servicio
@Repository // Clase repositorio
@SpringBootTest // Instancia una bateria de pruebas
@RequesMapping // Prepara un método para un endpoint
@GetMapping // Mismo que el anterior HTTP GET
@PostMapping // Mismo que el anterior HTTP POST
@Async // Clase asíncrona
@EnableAsync // Instancia una aplicación asíncrona

Las anotaciones son muy potentes, nos permiten añadir funcionalidad al código sin tener que implementarla, podemos pasar parámetros de entrada en la anotación entre paréntesis () o varios entre llaves ({}) eso dependerá según para lo que esté programada la anotación.

@GetMapping({ "/", "/index" }) // dos parámetros
@PostMapping("/new") // un parámetro
@GetMapping("/{id}/edit") // podemos especificar parámetros enlazados con variables de entrada

Como vemos con estas herramientas fácilmente podríamos implementar una aplicación con el patrón MVC y añadir las funcionalidades necesarias como un simple CRUD

Spring Initializr para SpringBoot2

Para iniciar una aplicación en SpringBoot2 vamos a la web start.spring.io y seleccionamos el tipo de proyecto que vamos a desplegar y las librerías que vamos a utilizar

Una vez terminamos nos descargamos el proyecto y lo descomprimimos en el workspace para empezar a trabajar con el IDE que más os guste

Espero haber animado a usar este framework que cada día se utiliza más en aplicaciones, y ofrece características como la asincronía o la programación reactiva que no deja indiferente a la hora de poner aplicaciones en producción con altas cargas de peticiones.

Aquí os dejo un pom.xml de prueba

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Saludos

Modificadores de acceso en Java

Este post es un resumen de los modificadores de Java. Pensado para las personas que estén preparándose los exámenes de grado superior a modo de guía rápida.

Los modificadores de acceso permiten el encapsulamiento. El cual busca la forma controlar el acceso a los datos de un objeto o instancia.

Los modificadores dan seguridad a las aplicaciones limitando el acceso a diferentes atributos, métodos, constructores asegurando una “ruta” especificada acceder a la información.

Si nuestra aplicación es usada por programadores. Con modificadores de acceso aseguramos que un valor no será modificado incorrectamente. Para eso usaremos el acceso a los atributos con los métodos get y set.

  • Private

Este modificador solo nos permitirá acceder desde la misma clase. En caso de un método solo se podrá llamar internamente. Para los atributos, se crearan métodos públicos GETTERS y SETTERS.

package app.ejemplo;
public class Ejemplo1
{
	// Atributo y sus metodos
	private int atributo;

	public int getatributo()
	{
	  return atributo;
	}

	public void setAtributo(int atributo)
	{
	  this.atributo = atributo;
	}

	// Metodo
	private int metodo(){
	  //code;
	}
}
  • Default

Este acceso por defecto que permite que tanto la propia clase como las clases del mismo paquete.

package app.ejemplo;
public class Ejemplo2
{
    static int atributo = 0;
}
package app.ejemplo2;
public class Ejemplo2_1
{
    public static int getAtributo()
    {
            return Ejemplo2.atributo;
    }
}
  • Protected

Nos permite acceder a las propiedades de la clase padre, cuando estemos haciendo herencias. Fuera de las clases hijas es equivalente a private.

package app.ejemplo;
public class Ejemplo3
{
    protected static int atributo = 0;    
}
package app.ejemplo3_1;
import app.ejemplo3.Ejemplo3;
public class Ejemplo3_1 extends Ejemplo3
{
    public static void main(String[] args)
    {
        System.out.println(atributo)
    }
}
  • Public

Es el mas permisivo. Cualquier clase tendra acceso a el sin importar paquete o procedencia.

package app.ejemplo4;
public class Ejemplo4
{
    public static int atributo = 0;

    public static void metodo()
    {
        System.out.println("Metodo Publico");
    }
}
package otro.paquete;
import app.ejemplo4.Ejemplo4;

public class OtraExterna
{
    public static void main(String[] args)
    {
        System.out.println(Ejemplo4.atributo);
        Ejemplo4.metodo(); 
    }
}
  • La directiva Static

Static es una directiva no un modificador de acceso, pero por utilidad vamos desarrollarla un poco.

Una clase, método o campo declarado como estático puede ser accedido o invocado sin la necesidad de tener que instanciar un objeto de la clase. Uno de los ejemplos típicos de uso de métodos y variables estáticas es la clase java.lang.Math.

public class MathTest {
     public static void main(String[] args) {
        double floorOfPi = Math.floor(Math.PI);
        System.out.println(floorOfPi);
    }
}

Empezando con Docker

docker

¿Qué es Docker?

Docker es un proyecto de código abierto que automatiza el despliegue de aplicaciones dentro de contenedores de software, proporcionando una capa adicional de abstracción y automatización de virtualización de aplicaciones en múltiples sistemas operativos.

Docker utiliza características de aislamiento de recursos del kernel Linux, tales como cgroups y espacios de nombres (namespaces) para permitir que “contenedores” independientes se ejecuten dentro de una sola instancia de Linux, evitando la sobrecarga de iniciar y mantener máquinas virtuales.

¿Qué NO es Docker?

Docker no es como otros sistemas de virtualización completa de un sistema operativo, no emula el hardware necesario para arrancarlo, utiliza el de la máquina anfitriona compartiendo los recursos de la misma entre los contenedores.

En resumen Docker es una herramienta que nos da la capacidad de trabajar con contenedores (máquinas virtuales) que comparten un mismo Kernel.

Vamos a ver un poco las partes básicas de Docker para hacer funcionar un sistema, y luego explicaremos como podemos automatizarlo con docker-compose.

Contenedores

Es el espacio donde se almacenan los datos de la máquina virtual, contiene todos los archivos necesarios para funcionar independientemente del sistema anfitrión. Un contenedor es una instancia de una imagen.

Imagen

Es un prototipo de un contenedor, así como una clase es el prototipo de un objeto y este una instancia de la misma, un contenedor es una instancia de una imagen. Varios contenedores pueden compartir una imagen, pero una imagen solo se corresponde con un contenedor.

Volumen

Es un espacio de almacenamiento que comparte el contenedor con la máquina anfitriona para tener persistencia de los datos, los volúmenes pueden ser de varios tipos como los internos (que los gestiona Docker) o pueden usarse mayormente carpetas compartidas.

Red

Las interfaces de red pueden configurarse según se necesite, tenemos varias como host, que comparte con la máquina anfitriona, el bridge que genera un puente de red o null que no tiene red.

Automatizar Docker con docker-compose

Docker compose es una herramienta creada por Docker, que permite crear una pila de contenedores intercomunicados, partiendo de distintas imágenes.

Docker compose se basa en un fichero con extensión yml donde vamos a indicar que imagen queremos desplegar, cómo se va a configurar y de qué depende.

version: '3'
services:
  web:
    build: .
    ports:
    - "80:80"
    volumes:
    - ./html:/var/www/html
    networks:
      net:

networks:
  net:

En el anterior ejemplo vemos como se crea un contenedor que compila un Dockerfile de la carpeta local, que comparte el volumen de la carpeta local html y también crea la configuración de red.

Conclusión

Las principales ventajas de la virtualización basada en contenedores son:

  • Menos recursos, ahorro en costes. En una misma máquina pueden desplegarse más contenedores que máquinas virtuales tradicionales. Las exigencias en el proceso de inicio y espacio en disco son menores y más rápidas.
  • Gestión TI más fácil, aumento de la productividad TI. La creación de contenedores permite estandarizar los despliegues ya que son entornos repetibles para tareas de desarrollo, prueba y producción. La compatibilidad con todos los sistemas de implantación elimina un valioso tiempo de configuración. Son elementos totalmente portables. Con Docker la implementación se realiza en segundos.
  • Múltiples aplicaciones independientes en un mismo host. Cada aplicación se ejecuta en su contenedor o clúster de contenedores de forma independiente, sin entrar en conflicto con el resto de aplicaciones que aloje el host que ejecutarán mediante sus propios contenedores. Esto garantiza un entorno seguro y eficiente.

Cuando una aplicación ya no es necesaria, simplemente se elimina su contenedor sin dejar huella en el sistema donde se ejecutaba.