<![CDATA[JLG FullStack Ninja]]>https://jlgarcia.fulldev.ninja/https://jlgarcia.fulldev.ninja/favicon.pngJLG FullStack Ninjahttps://jlgarcia.fulldev.ninja/Jamify 1.0Mon, 08 Nov 2021 23:13:06 GMT60<![CDATA[Docker XIV - Kubernetes IX: Trabajando en Cloud]]>https://jlgarcia.fulldev.ninja/docker-xiii-kubernetes-viii-pensando-en-un-entorno-con-microservicios-usando-ingress-2/Ghost__Post__600ac23ff73ef66cb4cf74dcMon, 15 Feb 2021 09:30:00 GMT

Bien hasta ahora hemos estado haciendo todo en nuestro entorno local de pruebas, veamos como podemos trabajar con un entorno en cloud desde nuestro terminal con kubectl

Kubectl es una herramienta de linea de comandos para trabajar con cualquier tipo de cluster de kubernetes, en nuestro caso nos hemos creado un minientorno de pruebas usando minikube. Al hacer la instalación de minikube y kubectl automáticamente se configura para que estemos usando ese entorno por defecto (el de minikube) pero nosotros podemos cambiarlo en cualquier comento.

Para ver la configuración que tenemos actualmente podemos ejecutar el comando

kubectl config view

Esto nos debe mostrar algo similar a

Docker XIV - Kubernetes IX: Trabajando en Cloud

En esta configuración tenemos 4 cosas importantes:

  • clusters: Información de todos los cluster que tenemos configurados, con su certificado, su dirección o hostname  y el propio nombre que le hemos dado al cluster en cuestión
  • contexts: Todos los contextos que tenemos configurados. Esto puede parecer redundante, pero no es así, realmente nosotros trabajamos a través de los contextos y estos apuntan a un cluster en específico. Esto es así porque podemos tener distintos usuarios de acceso según el contexto y podemos querer configurar un namespace diferente del de por defecto (namespace es solo una separación lógica de objetos dentro de kubernetes)
  • current-context: El contexto seleccionado actualmente
  • users: Usuarios con sus nombres y sus métodos de acceso.

Estos serían los puntos relevantes que tenemos que tener en  cuenta a la hora de intentar conectarnos a otro cluster.

Como tal kubectl nos provee de los comandos necesarios para cambiar el contexto, ver el contexto actual, configurar nuevo cluster, cualquier cosa que necesitemos realmente. Por ejemplo:

kubectl config current-context
Para ver el contexto actual

Estos serían todos los comandos bajo config

Docker XIV - Kubernetes IX: Trabajando en Cloud

Visto más o menos que nos ofrece kubectl para seleccionar una configuración u otra veamos como hacer despliegue de kubernetes en Google

Hacia Google Cloud

Google cloud es el servicio cloud que mejor funciona con kubernetes (nació allí por lo que tiene sentido no?). En este sentido es el servicio con el que más rápido podremos configurar nuestro cluster de kubernetes, vamos a por ello:

Primero sería darnos de alta en google cloud, si no tenéis cuenta sería el momento de hacerlo. Una vez que tengamos nuestra cuenta lo que debemos hacer es instalar la linea de comandos de google (gcloud). Buscar en internet como realizar esta instalación en el caso de que no uséis brew para instalar paquetes, si lo tenéis solo es necesario hacer

brew install google-cloud-sdk

Para empezar, lo primero que tenemos que hacer una vez instalado es iniciar la configuración de nuestro servicio:

gcloud init

Con este estableceremos una configuración nueva o seleccionaremos una existente si ya la tenemos, aquí cada uno puede configurar lo que quiera y como quiera. Como tal yo tengo un proyecto creado que es: kuber-project-example

Si en algún caso os falla cualquier comando, puede ser por la versión que ha instalada brew del SDK, seguir estos pasos:

  • Cambiar versión de python de la console
export CLOUDSDK_PYTHON=python2
  • Actualizar los componentes
gcloud components update

Con esto ya os funcionaria todo (o debería). Continuemos:

Ahora lo que haremos será seleccionar ese proyecto como base para el resto de comandos

gcloud config set project kuber-project-example

Y también es necesaria tener una región de computo configurada

gcloud config set compute/zone us-west1-a

En este punto puede que os pida seleccionar una cuenta de facturación (por si te vuelves loco creando cosas jajajajaja). Esto lo más fácil es irse a la consola de google, irse al menú de facturación y seleccionar una de las cuentas (o crearla)

Docker XIV - Kubernetes IX: Trabajando en Cloud

Una vez seleccionada una cuenta en la consola de google, volvemos a ejecutar el comando, nos pedirá habilitar la API de "compute.googleapis.com", la habilitamos(tardará un poco) y ya nos confirmará que la configuración se ha actualizado o añadido

Docker XIV - Kubernetes IX: Trabajando en Cloud

A continuación creamos nuestro cluster de kubernetes que también puede que nos de error porque necesitamos habilitar el servicio de contenedores en nuestro proyecto

gcloud container clusters create ninja-cluster --num-nodes=1
Docker XIV - Kubernetes IX: Trabajando en Cloud

Por lo que si nos aparece este error habilitaremos ese servicio también

gcloud services enable container.googleapis.com

Y ya si todo va bien y no nos pide nada más ejecutamos de nuevo

gcloud container clusters create ninja-cluster --num-nodes=1
Docker XIV - Kubernetes IX: Trabajando en Cloud

Y ya cuando termine el proceso nos confirmará los datos pertinentes según lo que hayamos solicitado

Docker XIV - Kubernetes IX: Trabajando en Cloud

Ahora lo siguiente es configurar nuestro kubectl local para que apunte a este cluster.... que lo podíamos hacer a mano, pero en esta caso google nos provee de un comando mágico que nos añade la configuración a la kube config

gcloud container clusters get-credentials ninja-cluster

Básicamente es solicitarle las credenciales sobre un cluster en específico y listo

Docker XIV - Kubernetes IX: Trabajando en Cloud

Veamos la config

kubectl config view
Docker XIV - Kubernetes IX: Trabajando en Cloud

Como vemos ahora tenemos otro cluster, otro contexto y otro usuario y si nos fijamos en nuestro current-context ya no estamos en minikube si no en el de google

Veamos que tenemos ahora con nuestro comando para ver los nodos

kubectl get nodes
Docker XIV - Kubernetes IX: Trabajando en Cloud

Ahora tenemos nuestro nodo de cluster de kubernetes que le hemos indicado que cree. Probemos ahora a crear un deploy, usaremos el de ejemplo de kubernetes que es una imagen básica de google

kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0

Y comprobemos que nos ha creado

Docker XIV - Kubernetes IX: Trabajando en Cloud

Como tal podemos ejecutar en este caso todos los comandos que hemos ido viendo hasta ahora. Añadamos un service para poder acceder a nuestro deploy, en este caso vamos a añadir un service de tipo LoadBalancer (por fin jejejejje) que esto lo que hará será que google cloud nos creará un balanceador de carga y accederemos a nuestro pod desde él.

Una vez que tengamos ip externa

Docker XIV - Kubernetes IX: Trabajando en Cloud

Ya deberíamos poder acceder a nuestro pod, ojo en este caso tenemos que acceder por IP y al puerto 8080  (si habéis usado mi ejemplo porque el puerto del comando ha sido el 8080)

Docker XIV - Kubernetes IX: Trabajando en Cloud

En este caso podemos comprobar como de facil es trabajar con kubernetes en google cloud y la configuración no es complicada. Si queremos ver que realmente no es mentira podemos irnos a la consola de kubernetes y ver que es lo que tenemos creado

Docker XIV - Kubernetes IX: Trabajando en Cloud

Y ya para aseguraros de que no miento podemos ver que nos ha creado también un balanceador de carga apuntando a nuestro cluster

Docker XIV - Kubernetes IX: Trabajando en Cloud
Lista de balanceadores de carga
Docker XIV - Kubernetes IX: Trabajando en Cloud
Detalles del balanceador de carga

Creo que queda más o menos claro como trabajar con la cloud de google y kubernetes, que es realmente sencillo. Con esto ya podríamos crear infraestructuras tan complejas como quisiéramos que con unos cuantos comandos ya se encarga google de hacerte la magia.

Y hasta aquí la parte de google cloud (NO SE OS OLVIDE BORRAR LAS COSAS DE GOOGLEEEEEE CLOUUUUD JEJEJJEJEJE)

AWS

Para hacerlo en aws...... pues realmente es más fácil que lo veáis directamente de la documentación porque es realmente igual de trivial, lo único que nos ofrece 2 opciones:

  1. Usar eksctl: Es una herramienta de línea de comandos específica de Amazon EKS (servicio de kubernetes de AWS) que es bastante fácil de usar y a su vez nos da la opción de crear nuestro cluster con Fargate o no. Ver esta opción
  2. Usar el CLI de AWS y la consola de administración: Aquí se hace uso de CloudFormation otro servicio interesante de AWS que nos facilita la orquestación de algunas cosas, esta opción es algo más manual porque es necesario manejar algunos permisos de IAM y cosas así pero se ve algo más de que es lo que estamos montando. Ver esta opción

Ambas opciones realmente son sencillas y para probar son más que suficientes, recomiendo que intentar cualquiera de ellas y ver que es realmente lo que se monta en la consola de AWS (BORRARLOOOOOO que al final cobran por tener el espacio en uso indefinidamente tras el año gratuito)

Bueno y hasta aquí lo que veremos de Docker y Kubernetes por el momento, han sido un montón de posts al respecto espero que os hayan parecido interesantes, se que nos faltaría ver como ser haría con Azure pero realmente es una plataforma que no me gusta mucho (es personal ojo no digo que sea mala ni nada por el estilo) por lo que no lo veremos aquí ni lo comentare, seguro que existe muy buena documentación al respecto para quien esté interesado.

Clausuro esta serie de posts pero no descarto en ningún momento plantear más cosas sobre kubernetes y docker en un futuro cercano, sin mucho más que deciros NOS VEMOS EN EL SIGUIENTEEEEEEE un abraazoooooooo

]]>
<![CDATA[Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS]]>https://jlgarcia.fulldev.ninja/docker-xiii-kubernetes-viii-pensando-en-un-entorno-con-microservicios-usando-ingress/Ghost__Post__6004188da6c0f5058bff4dbdMon, 08 Feb 2021 09:37:00 GMTDocker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Hasta ahora hemos planteado nuestro entorno pensando solo en una o varías aplicaciones que realmente funcionan de manera autonóma, es decir, si pensamos en nuestro POD con el fork del juego de SUPER MARIO vemos como realmente no tiene comunicación con nada más y en un primer momento no la necesita (o nosotros a la hora de usarlo tampoco).

Ahora propongo otro escenario, como pudiera ser que tenemos un pequeño portal donde el usuario necesita hacer login antes de poder acceder a nuestro juego. Si lo planteamos como si realmente fueran servicios distintos tendriamos por ejemplo:

  • Lo que es el front, que no sería más que una miniweb con un login y a lo mejor una lista de posibles juegos.
  • Puede que un microservicio que gestionaría los usuarios y nos permitiría hacer login o un microservicio que fuera realmente la API con la lista de juegos disponibles
  • Y por dejarlo simple un microservicio que sería el fork de nuestro juego de SUPER MARIO y otro que puede ser un fork del SONIC por ejemplo.

Es posible que este escenario no tenga ningún sentido ojo, por el tema de la autenticación y demás, es solo para que entendamos que tendríamos varios microservicios distintos que gestionar. Esto con los objetos y configuraciones que hemos visto hasta ahora puede que lo pudieramos conseguir si tenemos un objeto de tipo SERVICE:NODEPORT por cada microservicio y el microservicio de front sepa donde esta todo (superfeo) o a lo mejor hacerlo con subdominios y con DNS o cosas así (se me están ocurriendo sobre la marcha pueden no tener sentido). El caso es que a no ser que lo gestionaramos en un servicio cloud que nos pudiera hacer el enrutamiento no tenemos una forma clara de hacerlo. Bueno pues para esto tenemos en kubernetes el objeto de tipo INGRESS.

Este objeto lo que hace es definir como se deben gestionar las peticiones que vienen desde fuera de nuestro cluster, es decir, hacia donde debe redireccionar cada petición. Veamos una diagrama de la documentación oficial donde creo yo que se explica bastante bien cual es la tarea de nuestro INGRESS

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Este diagrama que vemos resume un escenario típico que tendríamos en producción:

  1. El cliente envía una petición que lo primero que hace es llegar al balanceador de carga de nuestro cloud. Este balanceador de carga está manejado por un INGRESS CONTROLLER que es el que se encarga de asegurarse de que se cumplan las reglas que definimos en nuestro objeto INGRESS
  2. Según las reglas que hayamos definido nuestro INGRESS CONTROLLER decide a que servicio (objeto de tipo SERVICE) de nuestro cluster debe enviar la petición en cuestión
  3. Por último este service envía la petición al primero de sus PODS que este disponible

Un detalle que no hemos dejado claro todavía es que las reglas que definimos en el objeto de tipo INGRESS no son manejadas realmente por kubernetes, si no que necesitamos de un controlador externo (INGRESS CONTROLLER para que las gestione). Como tal existen opciones de configuración de ingress disponibles para NGINX, HAPROXY, TRAEFIK... para la mayoría de los servicios de proxy, uno de los más típicos es el de nginx. En un entorno en cloud seria necesario tener un POD con nginx que sería nuestro INGRESS CONTROLLER.

Comentar que el entorno completo en cloud con su loadbalancer, su ingress y demás no lo veremos de momento, en la documentación oficial viene todo lo necesario creo yo, por ahora con motivos puramente de prueba o académicos usaremos la opción que nos provee minikube para tener nuestro propio INGRESS CONTROLLER.

Veamos lo primero un ejemplo de objeto tipo INGRESS, como no parece existir de momento comando dry-run para verlo, nos tocará copiar un ejemplo como este

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mario-ingress
spec:
  rules:
  - host: mariogame.fulldev.ninja
    http:
      paths:
      - path: /mario
        pathType: Prefix
        backend:
          service:
            name: mario-service
            port:
              number: 8000
  defaultBackend:
    service:
      name: default-service
      port:
        number: 8001

Creo que es bastante intuitivo lo que hace cada cosa pero hablemos un poco en detalle de algunos puntos:

  • apiVersion: networking.k8s.io/v1: Aquí si nos fijamos tenemos un cambio respecto a lo que soliamos indicar en el resto de objetos, en este caso debemos especificar que pertenece a la api de networking de kubernetes.
  • spec: Como siempre dentro de spec tenemos siempre la definición de lo que queremos hacer.
  • rules: Dentro de rules como su propio nombre indica es donde definiremos las reglas que queremos usar dentro de nuestro ingress
    • host: hostname al cual enviará el cliente las peticiones
    • http -> paths: Estos serán los paths que usará nuestro ingress controller para redireccionar la petición a un servicio o a otro.
    • pathType: Como se filtrarán los paths, el caso más típico es el de Prefix que buscará coincidencias en el path completo separandolo por /
    • backend -> service: El servicio que debe responder al path indicado
  • defaultBackend: Aquí definimos el servicio por defecto en caso de que no se encontraran coincidencias en el resto de reglas

Como siempre existen muchas más reglas pero estas son las básicas y que me han parecido relevantes para empezar (vuelvo a recomendar leer la documentación oficial)

Una vez visto lo básico, veamos como configurar un ingress controller en minikube (recordemos que en un entorno cloud tenemos que tener nuestro propio pod que haga de controller).
Para ello lo primero es hablar de los addons de minikube. Minikube dispone de ciertos extras que podemos habilitar según nos insterese, uno de ellos es el ingress controller, este ingress que nos provee minikube es una propia intancia de nginx preparada para este uso y que en este caso no podremos ver como uno de los pods que hemos estado viendo hasta ahora (en un momento veremos lo que pasa)

OJO esta parte solo funciona si has iniciado minikube con algún gestor de máquinas virtuales como virtualbox doc oficial. Ver al final del post el apartodo INICIAR MINIKUBE CON VIRTUALBOX o también es posible jugar con este playground

Primero veamos la lista de addons, para ello solo es necesario que ejecutemos este comando:

minikube addons list

Esto nos mostrará una lista de los addons disponibles indicándonos si están habilitados o no (algunos estarán habilitados por defecto según nuestra instalación)

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Si os fijáis uno de los addons que aparecen es el de ingress, habilitemoslo

minikube addons enable ingress

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Con esto ya tendríamos habilitado nuestro ingress controller con nginx, pero como he comentado no nos aparece como un pod normal en este entorno.

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Minikube en este caso lo crea como un pod propio del sistema de minikube por lo que para verlo tendríamos que listar los pods que pertenecen a minikube, para ello

kubectl get pods -n kube-system

Y esto nos listará todos los pods que va creando minikube

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Si nos fijamos tenemos uno en específico que se llama nginx-controller.

Bien con esto ya tendríamos nuestro ingress controller, ahora vamos a trabajar con los mismos ejemplos que la documentación oficial (el de mario da algunos problemas con los direccionamientos y por no entrenernos), primero el deploy

example-deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web
    spec:
      containers:
      - image: gcr.io/google-samples/hello-app:1.0
        name: hello-app
        resources: {}
status: {}

El service

mario-service.yaml

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: web
  type: NodePort
status:
  loadBalancer: {}

Hacemos los create pertinentes que ya hemos visto anteriormente

kubectl create -f example-deploy.yaml
kubectl create -f example-service.yaml

Y por último preparamos nuestro ingress

example-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 8080

Y creamos nuestro ingress

kubectl create -f example-ingress.yaml

Esto nos creará nuestro ingress tardará un poco en tenerlo todo (fijaos en el ADDRESS)

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Como véis tenemos nuestro ingress creado, un detalle para que no nos alarmemos es el warning que aparece. Nosotros en nuestra definición de ingress hemos definido el uso de la api de kubernetes v1, lo que pasa es que el comando get ingress comprueba la existencia de ingress desde la primera versión que tiene disponible, por eso el aviso, no quiere decir que nuestra definición de ingress esté realmente mal.

Bien ahora que ya tenemos nuestro ingress desplegado veamos como podemos acceder a el. La primera intención puede ser intentar acceder por la IP, pero si lo intentamos veremos un 404 de nginx. Esto no es del todo malo, comprobamos que tenemos comunicación con nuestro ingress controller

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Para el que no sepa realmente como funcionan los proxys como nginx, lo que hacen normalmente es esperar que el acceso se produzca con un nombre de host específico, es posible que tengamos varios hostname distintos bajo el mismo nginx. En nuestro mismo caso podemos querer tener otro hostname para otro servicio que bye-world.info

Entonces lo que tenemos que hacer es modificar nuestra definición local de redirecciones IP/HOSTNAME, en mi caso lo tengo que hacer en el fichero /etc/hosts

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Una vez modificado si accedemos al hostname en cuestión veremos como accedemos sin problema al servicio pertinente

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Con esto tendríamos funcionando un ingress controller que puede redireccionar multiples peticiones a distintos servicios basandonse en el nombre del hosts o un path (en nuestro hemos puesto / pero podría ser cualquier otro)

Con esto habríamos visto lo básico para trabajar con ingress, el resto ya es revisar la documentación y ver las distintas opciones que nos ofrece kubernetes para cumplir con nuestras necesidades, sin mucho más terminamos este post, nos vemos en el siguiente un abrazooooorrrr

INICIAR MINIKUBE CON VIRTUALBOX

Esta es una sección para los que hayan estado jugando con el propio driver de docker (el que nos provee docker-desktop) para probar kubernetes que al final llega un punto en el que algunas cosas no funcionan de ninguna forma (depende de las versiones, van incluyendo poco a poco funcionalidades compatibles).

Lo primero que tenemos que hacer es borrar minikube de nuestro entorno, recomiendo borrar todo y hacer una instalación limpia para ello lo primero es:

minikube delete --all --purge

Esto nos borra todo lo que tenemos de minikube

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

A continuación instalamos virtualbox -> Downlaads

Una vez instalado todo lo que hacemos es arrancar minikube con el driver de virtualbox

minikube start --driver=virtualbox

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Esto tardara un rato hasta crea las máquinas virtuales pertinentes

Docker XIII - Kubernetes VIII: Pensando en un entorno con microservicios usando INGRESS

Con esto ya tendriamos arrancado nuestro minikube con un cluster de maquines virtuales. Well done!!!

]]>
<![CDATA[Docker XII - Kubernetes VII: Escalando PODS]]>https://jlgarcia.fulldev.ninja/docker-xii-kubernetes-vii-multiplicando-pods/Ghost__Post__6003341fa6c0f5058bff4d9eMon, 01 Feb 2021 09:39:00 GMTDocker XII - Kubernetes VII: Escalando PODS

Hasta ahora en los post anteriores hemos estado trabajando con solo un POD o una instancia de cada POD. Pero para realmente tener ese escenario no necesitamos kubernetes podiamos hacerlo igual con docker o con docker-conpose. Veamos ahora realmente algo de lo que nos ofrece realmente kubernetes que es la posibilidad de gestionar multiples instancias de un mismo POD.

Esto lo hace kubernetes posible haciendo uso de los objetos con tipo Deployment, es un objeto muy similar al POD con el que hemos estado trabajando solo que nos permite indicarle cuantas instancias queremos de un mismo pod. Para ver un ejemplo podemos hacer, como siempre, uso de un comando dry-run:

kubectl create deployment mario-deploy --image=pengbai/docker-supermario:latest --port=8080 --dry-run=client -o yaml > mario-deployment.yaml

Esto nos creará un documento similar a este:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: mario-deploy
  name: mario-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mario-deploy
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: mario-deploy
    spec:
      containers:
      - image: pengbai/docker-supermario:latest
        name: docker-supermario
        ports:
        - containerPort: 8080
        resources: {}
status: {}

Veamos un poco que es lo que tenemos aquí:

  • metadata: Ya hemos visto en el resto de posts que es esto. Contiene labels identificativos que nos pueden ser utiles para hacer referencia desde otros objetos del cluster de kubernetes. Y también tiene su name para identificar este objeto como tal.

Dentro del spec, que recordemos que es donde realmente se define la magia tenemos nuevo:

  • replicas: Creo que es autoexplicativo. Podemos indicar la cantidad de instancias que queremos de un tipo de POD.
  • selector: Al igual que pasaba con el objeto de tipo service, dentro de este selector indicamos los labels que tienen los pods que debe gestionar este objeto de tipo deployment.
  • template: Aquí es donde definimos el POD en cuestión que tiene que usar como plantilla, es decir, usando un objeto de tipo deployment ya no es necesario tener por otro lado un objeto de tipo POD, puede estar definido dentro de esta template (si los comparamos la definión debe ser muy similar)

Una vez repasadas más o menos las diferencias veamos que pasa, cambiemos la cantidad de replicas a 2 y ejecutemos el típico create

Docker XII - Kubernetes VII: Escalando PODS

Si comprobamos los pods que tenemos ahora veremos algo distinto

Docker XII - Kubernetes VII: Escalando PODS

Vemos como ahora ahora los pods se crean de otra manera, contienen el nombre del objeto deploy y además una conjunto alfanumerico, eso convierte el nombre del pod un identificador único. Además de comprobar los pods, ahora podemos usar otro comando específico para deployments

kubectl get deployments

Docker XII - Kubernetes VII: Escalando PODS

Que como podemos comprar nos muestra cuando pods tiene el deployment, cuandos estan activos y cuantos estan actualizados con la última versión (ya hablaremos de esto). Si ahora nosotros eliminamos uno de los pods, veremos como automáticamente nos crea uno nuevo.

Docker XII - Kubernetes VII: Escalando PODS

Kubernetes, como siempre, en su ciclo de vida (llamada bucle de reconciliación) comprueba que el estado de los objetos que están en funcionamiento cumple con sus definiones/plantillas y si no, pues actua en consecuencia, en este caso con la creación de un nuevo POD usando la plantilla que tiene el objeto de tipo deployment.

Pero claro ahora mismo hemos definido de antemano el número de replicas que queremos de un mismo POD dentro de la plantilla de deploy, pero... ¿y si tengo que escalar mi aplicación ahora mismo entando en producción? pues para ello tenemos varias opciones:

  • Editar el estado actual de nuestro deploy en la API
kubectl edit deploy mario-deploy

Nos aparecerá la definición que está actualmente en la API de kubernetes, lo que podemos hacer es editar el número de réplicas y esto nos debería escalar el número de instancias de pods

  • Otra forma es usando el comando scale
kubectl scale deployment mario-deploy --replicas=3

En cualquiera de los 2 casos si comprobamos los pods que tenemos deberían haber aumentado

Docker XII - Kubernetes VII: Escalando PODS

Y lo mismo podemos hacer para reducirlos, si disminuimos el número de replicas nos eliminará el número de instancias activas

kubectl scale deployment mario-deploy --replicas=1

Comprobamos que empieza con la eliminación de 2 de las instancias y al final nos deja solo con una

Docker XII - Kubernetes VII: Escalando PODS

Actualizando versiones de nuestra aplicación y rollback

Todo lo que hemos visto en este post está muy bien, pero normalmente ninguna aplicación se mantiene en la misma versión para siempre, como tal, es necesario actualizarla en algún momento, en esta sección veremos como cambiar la versión de nuestra imagen y también como hacer rollback en caso de que algo falle. Todo esto es realmente sencillo gracias a kubernetes.

Primero de todo vamos a seleccionar una imagen que tenga varias versiones, para no complicarnos creando nosotros un contenedor específico me he decantado por usar la imagen oficial de NGINX, como se puede ver entre los tags de versiones tenemos dos que son fáciles de diferencias stable y latest

Docker XII - Kubernetes VII: Escalando PODS

Primero creemos nuestro deployment con la imagen stable, para ello crearemos un fichero yaml con estos datos

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx-deploy
  name: nginx-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-deploy
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx-deploy
    spec:
      containers:
      - image: nginx:stable 
        name: nginx-pod 
        ports:
        - containerPort: 80
        resources: {}
status: {}

Como véis tenemos 2 replicas de la versión nginx:stable, ejecutemos el comando crear y comprobemos que pasa (recomiendo eliminar todo lo que hicimos anteriormente, recordar con kubectl delete ....)

kubectl create -f nginx-deploy.yaml

Docker XII - Kubernetes VII: Escalando PODS

Ya tenemos nuestros 2 pods que hemos definido en el deploy, ahora comprobemos la versión con la que se han creado con el comando describe

kubectl describe deploy nginx-deploy

Docker XII - Kubernetes VII: Escalando PODS

Si nos fijamos tenemos dentro de containers la imagen seleccionada que es nginx:stable, ahora veamos las opciones para actualizar de versión

  1. Usar el comando apply
    Tenemos la posibilidad de actualizar la configuración actual de un objeto en kubernetes usando el comando apply, modificando nuestro fichero de deploy. Cambiemos la versión en nuestro fichero
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx-deploy
  name: nginx-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-deploy
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx-deploy
    spec:
      containers:
      - image: nginx:latest 
        name: nginx-pod 
        ports:
        - containerPort: 80
        resources: {}
status: {}

Y ejecutemos el comando

kubectl apply -f nginx-deploy.yaml

Si ahora ejecutamos nuestros comandos get pods y describe veremos como tenemos otra versión ya desplegada.

Docker XII - Kubernetes VII: Escalando PODS

  1. Es posible, al igual que hicimos antes, usar el comando edit, para modificar el estado actual que está en la api de kubernetes (en teoría funciona pero a mi me da problemas, aviso por si acaso)
kubectl edit deploy nginx-deploy
  1. Tenemos la opción de modificar directamente la imagen de un deployment con la opción set
kubectl set image deployments/nginx-deploy nginx-pod=nginx:latest

Este comando indicamos que modifique la imagen dentro del deployment/nginx-deploy de los pods con nombre nginx-pod usando la imagen nginx-latest. Si nos fijamos en nuestra definición de nuestro objeto deployment el nombre que yo le he puesto es el de nginx-pod

Si comprobaramos nuestros ya típicos get pods y describe otra vez veríamos lo siguiente

Docker XII - Kubernetes VII: Escalando PODS

Otra vez de nuevo nos ha cambiado la versión correctamente.

Como tal tenemos otro comando para comprobar que la actualización ha ido correctamente que es el comando rollout

kubectl rollout status deployment nginx-deploy

Docker XII - Kubernetes VII: Escalando PODS

Este comando nos confirma que la actualización ha ido correctamente.

Haciendo rollback

Ya hemos visto como actualizar pero... ¿y si todo se ha roto al actualizar y queremos volver atras? Pues en este caso es tan sencillo como...

kubectl rollout undo deployment nginx-deploy

Et voilá

Docker XII - Kubernetes VII: Escalando PODS

Nuestra versión vuelve a ser la de nginx:stable. Creo que en este caso no tengo mucho más que decir, solo comentar que esto se podría automatizar con los healthchecks que vimos en post anteriores (revisar la doc oficial que yo no he entrado en tanto detalle de momento), esto han sido unos primeros pasos para ver como lo podemos hacer nosotros todo manualmente.

No me enrollo más, espero que os haya gustado este post con el escalado y el rollback de nuestras aplicaciones, sin mucho más nos vemos en el siguienteeeee un abrazoooor

]]>
<![CDATA[Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service]]>https://jlgarcia.fulldev.ninja/docker-xi-kubernetes-vi-service/Ghost__Post__5ffafa8ea6c0f5058bff4cc9Mon, 25 Jan 2021 09:57:00 GMTDocker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

En el post anterior ya incluimos el concepto de service, en este post explicaremos realmente lo que es.

Hasta ahora habíamos estado trabajando con objetos de tipo POD, si hacemos un poco de memoría o volvieramos a probar veriamos como no realmente no podíamos acceder realmente desde nuestro equipo a ningún contenedor a no ser que hicieramos algún mapeo externo de puertos o similar, pues bien para esto son los objetos de tipo de Service, estos objetos entre otras cosas nos permiten exponer hacía afuera los nodos del cluster que nosotros le indiquemos.

Conceptualmente puede ser algún similar a un nginx o por ejemplo si pensamos en cloud podría ser como un API Gateway de AWS (ojo que digo conceptualmente para que entendamos un poco cual es su función). Realmente el objetivo de un objeto service es proveer una dirección o forma estable de acceder a nuestros nodos del cluster, ya que cada vez que se levanten nuestros nodos pueden tener una dirección IP diferente, es más, usar un objeto service realmente no implica necesariamente que expongamos nuestros nodos, podemos simplemente crear una IP interna del cluster para acceder a un conjunto de nodos desde otros nodos, ya depende realmente de nuestras necesidades.

Veamos un ejemplo de configuración de un objeto de tipo service. Para ello lo más fácil es hacer lo mismo que hicimos cuando veiamos los pods, ejecutaremos un comando con el flag dry-run para que no haga nada.

kubectl expose pod/mario-test --port 8080 --type=NodePort --dry-run=client -o yaml > mario-service.yaml

Ya hablaremos sobre los tipos por el momento solo ejecutaremos el comando y abrimos el fichero que nos crea

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: mario-game
    run: mario-test
    tier: games
  name: mario-test
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: mario-game
    run: mario-test
    tier: games
  type: NodePort
status:
  loadBalancer: {}

Vemos como ahora la propiedad kind cambia y ahora es Service, pero hablemos un poco más en específico de lo que estamos viendo.

Primero de todo tenemos metadata, que como información relevante tiene:

  • labels: Conjunto de clave valor que en un principio es meramente informativo y que podemos incluir los que queramos. Esto según el caso puede servir como identificador, por ejemplo, en mi caso tiene los mismos labels que mi pod, en vuestro caso puede contener otros si habéis puesto algo distinto en los labels. Podéis hacer la prueba eliminando el pod actual, modificando los labels del fichero pod que estéis usando, creando el pod de nuevo y ejecutando el comando dry-run de nuevo para ver como cambia
  • name: El nombre con el que identificaremos el servicio

A continuación tenemos spec que al igual que con los pods es donde esta todo lo importante, es decir, lo que queremos crear realmente o que configuración debe llevar, veamoslo un poco en detalle:

  • selector: Empezamos por este porque nos ayuda a saber donde apunta realmente el service. Si nos fijamos en su contenido debe tener lo mismo que los labels que tiene nuestro pod. Esto es porque realmente esta propiedad es la que se usa para filtrar y encontrar los pods en cuestión. Pensemos que realmente todo esto esta pensado para tener varias instancias del mismo pod, entonces como las identifica el service? Hace uso de los labels que le hemos puesto al pod y con eso filtra o busca los contenedores en cuestión.
  • type: Como tal es posible crear 3 tipos de servicio.
    • Tipo ClusterIP: Nos proporcionaría una ip fija interna del cluster para todas las instancias de un mismo pod para que el resto de pods puedan hacer uso de ellos
    • Tipo NodePort: Expone hacia el exterior del cluster el conjunto de pods seleccionados.
    • Tipo LoadBalancer: Se usa realmente con kubernetes en producción en algún proveedor cloud que son capaces de hacer uso de esta configuración para integrarlo con sus balanceadores.
  • ports: Parte muy importante, en este punto definimos los puertos por los que se puede acceder a nuestro contenedor a través del servicio (bueno y también el protocolo como veis):
    • port: Puerto interno del cluster por el que el resto de pods pueden acceder a la aplicación que se despliegue en el pod (suele coincidir con targetPort)
    • targetPort: Puerto que expone el contenedor o pod de su servicio, este seria al puerto que redireccionará automáticamente el service cuando se accede desde fuera del cluster al pod (suele coincidir con port)
    • nodePort (opcional): Aunque no aparezca ahora mismo tenemos esta opción también que es el puerto que expondra el servicio para acceder al targetPort seleccionado y se usará cuando pongamos el type=NodePort como os podéis imaginar. Tiene un rango específico -> 30000-32767. A no ser que lo queramos tener identificado, podemos dejar que el servicio decida de manera random uno dentro de ese rango.

Estas serían las configuraciones principales o básicas sobre un Service, como siempre recomiendo ver la documentación para ver los detalles y más configuraciones que nos puedieran interesar -> Kubernetes Service

Veamos ahora como funciona todo esto, pero antes de continuar comentar que lo que se explica aquí puede dar algún problema o ser distinto según la configuración con la que tengáis levantado minikube o vuestro servicio de kubernetes que estéis usando, en mi caso el ejemplo para el blog lo estoy haciendo en un mac con Docker Desktop por lo que es necesario hacer algún extra para que funcione el Service.

Primero de todo creemos el servicio:

kubectl create -f mario-service.yaml

Y una vez creado veamos que tenemos

kubectl get svc

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

Como vemos tenemos dos servicios el de kubernetes que es el de por defecto, (de tipo ClusterIP y es el servicio que comunica con el server master de kubernetes) y el que acabamos de crear mario-test de tipo NodePort, es decir, que "técnicamente" deberiamos poder acceder desde nuestra máquina al contenedor en cuestión al que apunta usando el puerto que ha seleccionado kubernetes automáticamente, en mi caso el 32142

Si intentamos acceder a través del navegador con localhost:32142 deberíamos poder ver el fork de Mario

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

He comentado antes el "técnicamente" porque es posible (como es mi caso con el entorno de ejemplo) que aún asi no podamos acceder, ahora veremos porque.

Kubernetes como tal no está pensado para funcionar en un portatil o en el equipo típico de sobremesa, es decir, si estas usando la aplicación "Docker Desktop" para jugar con kubernetes (en lugar de simplemente tener el Docker Engine) esto no te va a funcionar, es decir, si no has creado una máquina virtual para esto o no estas usando un servidor real de kubernetes seguirás sin poder acceder. Para poder acceder tendremos que hacer uso de un port-forward. REPITO ESTO NO SE TIENE QUE HACER EN UN ENTORNO EN PRODUCCIÓN A NO SER QUE SEPAS LO QUE HACES, LO HACEMOS POR NECESIDAD EN NUESTRO ENTORNO DE PRUEBAS PARA ACCEDER AL CONTENEDOR

Para este caso no es necesario que tengamos un service creado ni nada (pongo esta opción solo para que podamos acceder a nuestro contenedor, no tiene que ver con nuestro uso de services). Lo que haremos será ejecutar

kubectl port-forward mario-test 8000:8080

Con este comando simplemente hacemos una redirección de nuestro puerto local 8000 al puerto 8080 del pod mario-test. Al ejecutar este comando deberíamos ver algo similar a esto

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

Y si entramos con un navegador a localhost:8000 veremos como nos aparece la aplicación y en la consola (donde debe permanecer en ejecución el port-forward) esta gestionando conexiones del puerto 8000

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

Tras el inciso para mostrar como acceder en el caso de que no tengas un entorno similar al de producción de kubernetes, continuamos un poco hablando de los tipos de Service

En el ejemplo anterior hemos visto como sería un service de tipo NodePort, he querido mostrar este ejemplo porque es similar al de ClusterIP pero nos permite comprobar el acceso sin complicarnos creando más pods ni nada así.
Si pensamos en el tipo ClusterIP, nos quedaremos con que nos permite acceder a un conjunto de pods usando una misma dirección IP o nombre de host desde dentro del propio cluster para que puedan tener comunicación entre sí, sobre todo cuando tenemos intención de crear más de una instancia de un mismo pod (que sería para lo que usamos kubernetes realmente creo yo jejejeje). Hago incapié en esto del hostname, como tal el nombre del service es también el hostname que podremos usar para acceder al conjunto de pods dentro del cluster.

Si, por ejemplo, eliminamos el servicio actual

kubectl delete svc mario-test

Cambiamos el fichero del service indicandole que es de tipo ClusterIP

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: mario-game
    run: mario-test
    tier: games
  name: mario-service
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: mario-game
    run: mario-test
    tier: games
  type: ClusterIP <-- cambiamos esta linea
status:
  loadBalancer: {}

Y ejecutamos de nuevo el create

kubectl create -f mario-service.yaml

Veremos como haciendo un get de los servicios tenemos otro de tipo ClusterIP. Como detalle fijarse en que ahora en puertos solo nos aparece uno, es decir, solo nos aparece el puerto interno del cluster, es decir, al que redireccionariamos si usáramos el tipo NodePort.

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

No me extiendo en esta parte porque no tiene mucho más, si quisierais probarlo solo es necesario crear otro pod, acceder al contenedor y lanzar una petición, como ejemplo rápido

Ejecutamos y nos adjuntamos a la consola

kubectl run --rm -i --tty alpine-pod --image=alpine --restart=Never -- sh

Con esto le indicamos que queremos un pod con el nombre alpine-pod, que use la imagen de docker de alpine. Los extras como el rm que se elimine al terminar la ejecución y el último relevante es el sh para acceder a la consola.

Si todo funciona os aparecera un promt para ejecutar comandos

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

Ejecutamos

wget mario-service:8080 

Y tenemos que se ha bajado el index.html de nuestro contenedor

Docker XI - Kubernetes VI: Exponiendo aplicaciones al exterior con el Objeto Service

(para salir con ejecutar exit vale ;) y veremos como nos aviso de que elimina el pod )

Es una prueba muy rápida pero creo que es suficiente para entender el funcionamiento de un service de tipo ClusterIP, aquí tenemos que plantearnos que en un entorno en producción lo más probable es que tengamos mas de un contenedor del mismo tipo de POD.

Nos faltaría por ver el tipo loadBalancer pero como esta relacionado más con entornos cloud lo veremos más adelante, sin mucho más se termina este post nos vemos en el siguiente un abrazooooor

]]>
<![CDATA[Docker X - Kubernetes V: Salud de nuestros PODS]]>https://jlgarcia.fulldev.ninja/docker-x-kubernetes-v-salud-de-nuestros-pods/Ghost__Post__5ffae3e3a6c0f5058bff4cbaTue, 19 Jan 2021 09:19:00 GMTDocker X - Kubernetes V: Salud de nuestros PODS

En este posts veremos 2 configuraciónes básicas para manejar el estado de salud de nuestros pods. Con estas configuraciones kubernetes de manera automática puede por ejemplo reiniciar un pod o no mandarle tráfico.

Para esto tenemos estas 2 propiedades:

  • livenessProbe: Si esta sonda falla kubernetes puede por ejemplo reiniciar el pod. Es el típico health que solemos tener en otros servicios. Como tal se puede configurar que tiene que hacer kubernetes cuando la sonda falla, como por ejemplo cuantos errores debe dar antes de reiniciar y cosas así
  • readinessProbe: Hasta que esta sonda no funcione kubernetes no enviaría tráfico al POD (no lo añadiría al objeto service que veremos más adelante lo que es) y en el caso de que empiece a devolver errores dejaría de enviarle tráfico (lo quitaría del objeto service)

Para ver como funciona esto, lo primero eliminaremos el pod actual si lo tenemos todavía activo

kubectl delete pods mario-test

Y modificaremos el fichero del POD añadiendo estas propiedades. Primero con la de liveness

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mario-test
  name: mario-test
spec:
  containers:
  - image: pengbai/docker-supermario:latest
    name: mario-test
    ports:
    - containerPort: 8080
      name: game // <- Esto es nuevo
    resources: 
      requests:
        cpu: 50m 
        memory: 64M
      limits:
        cpu: 50m
        memory: 64M
    livenessProbe:
      httpGet:
        path: /fake
        port: game

  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Antes de hablar de la configuraciónd de la sonda comentar que hemos añadido un nombre al puerto en cuestión del contenedor para que nos sea más fácil hacer referencia a él y no tener que cambiarlo en un montón de sitios si de repente cambiamos el puerto.

Además del nombre del puerto hemos añadido la propiedad livenessProbe, dentro de esta propiedad podemos incluir comandos, peticiones a http y otras cosas que nos pueden resultar útiles para realizar la comprobación del estado de salud (al final del post pondré el link de la documentación para ampliar información). En nuestro caso hemos añadido una petición get a un path /fake que realmente no existe, para que veamos que ocurre.

Guardamos y creamos el pod como hasta ahora

kubectl create -f mario.yaml

Y veamos que pasa si ejecutamos nuestro comando get pods

kubectl get pods

Docker X - Kubernetes V: Salud de nuestros PODS

En un principio parecería que todo esta correcto pero si esperamos veremos como se producen cambios, normalmente a los 30 segundos empezaremos a ver diferencias

Docker X - Kubernetes V: Salud de nuestros PODS

Vemos como se ha reiniciado una vez y si esperamos 5 loops tendremos este error y también ha cambiado el número de contenedores READY a 0

Docker X - Kubernetes V: Salud de nuestros PODS

Por último si comprobamos el describe del pod en cuestión podremos ver el registro de los eventos que se han ido sucediendo

Docker X - Kubernetes V: Salud de nuestros PODS

Este sería el caso usando livenessProbe, como tal hemos visto como ha intentando reiniciar cada 30 segundos hasta que ha confirmado que no funciona. Si analizamos el describe un poco más arriba veremos como en la configuración por defecto se comprueba cada 10 segundos con un máximo de 3 fallos hasta que reinicia (estas son serian algunas de los campos que podemos configurar)

Docker X - Kubernetes V: Salud de nuestros PODS

Hagamos la prueba ahora usando readinessProbe

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mario-test
  name: mario-test
spec:
  containers:
  - image: pengbai/docker-supermario:latest
    name: mario-test
    ports:
    - containerPort: 8080
      name: game
    resources: 
      requests:
        cpu: 50m 
        memory: 64M
      limits:
        cpu: 50m
        memory: 64M
    readinessProbe:
      httpGet:
        path: /fake
        port: game

  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Este caso si ejecutamos el get pods podemos ver como ya al inicio cambia un poco, nunca tenemos el pod en READY, si nos fijamos estamos siempre en 0/1

Docker X - Kubernetes V: Salud de nuestros PODS

Y trás algunos reinicios, si esperamos 3 minutos tenemos un error

Docker X - Kubernetes V: Salud de nuestros PODS

Por último veamos que tenemos en nuestro describe. Primero podemos ver la configuración por defecto (la misma que con liveness):

Docker X - Kubernetes V: Salud de nuestros PODS

Y si nos vamos a los eventos que han ido sucediendo tenemos

Docker X - Kubernetes V: Salud de nuestros PODS

Vemos como ha intendado varias veces acceder a el y no ha sido capaz devolviendonos el error que ya hemos visto.

Estas han sido dos configuraciones básicas que podemos hacer, en la documentación oficial tenemos más información sobre la configuración, a que afecta realmente cada caso y otro probe más que es el de startup, recomiendo encarecidamente leerla y hacer más pruebas para ver las posibilidades que nos ofrecen los checks de estado de salud sobre nuestros pods.

Y hasta aquí este post, nos vemos en el siguiente un abrazoooorrrrr

]]>
<![CDATA[Docker IX - Kubernetes IV: Definiendo recursos de hardware]]>https://jlgarcia.fulldev.ninja/docker-ix-kubernetes-iv-definiendo-recursos/Ghost__Post__5ff96d70a6c0f5058bff4c9dMon, 11 Jan 2021 09:32:00 GMTDocker IX - Kubernetes IV: Definiendo recursos de hardware

En el post anterior vimos una configuración muy básica de los pods, pero como os imaginaréis tenemos más posibilidades interesantes que estaria bien que tuvieramos en cuenta. Una de ellas es la configuración/limitación de recursos.

Cuando usamos pods pero no indicamos ningún límite de recursos de hardware, nos encontrariamos con estos 2 escenarios:

  • Tener unos límites definidos por defecto dentro del cluster de kubernetes
  • Usar los recursos sin límite (no recomendado para nada, si sabemos de sobra que no usaremos en ningun caso todo el hardware disponible nos podemos arriesgar, pero si tenemos intención de aumentar contenedores en el futuro esto puede convertirse en un problema importante)

Lo ideal es siempre definir unos recursos específicos para cada elemento, así kubernetes puede distribuir los contenedores y los recursos de manera mucho más eficiente.
Para ello podemos/tenemos que añadir dos nuevas propiedades en nuestros pods, veamos un ejemplo.

Como usaremos el mismo pod que hemos estado usando actualmente lo primero que haremos será borrar los pods que tenemos actualmente, para ello es suficiente con ejecutar el siguiente comando por cada pod que queramos eliminar:

// para obtener los pods actuales
kubectl get pods 

// para eliminar un post específico
kubectl delete pods nombreDelContainer

Docker IX - Kubernetes IV: Definiendo recursos de hardware

Como vemos en la imagen la consola nos confirma su eliminación.
Ahora añadamos las opciones para limitar o específicar los recursos:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mario-test
  name: mario-test
spec:
  containers:
  - image: pengbai/docker-supermario:latest
    name: mario-test
    ports:
    - containerPort: 8080
    resources: 
      requests:
        cpu: 50m 
        memory: 64M
      limits:
        cpu: 50m
        memory: 64M
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Si nos fijamos en nuestro fichero pod hemos añadido nuevas propiedades dentro de resources, hemos añadido:

  • requests: Con esta opción le indicamos a kubernetes el mínimo de recursos que debe tener nuestro contenedor. Esto es muy útil para que kubernetes lo situe donde más le convenga.
  • limits: Como su nombre indica este es el límite que le estamos indicando a kubernetes que puede usar nuestro contenedor. IMPORTANTE: si el contenedor se pasa de recursos es posible que kubernetes decida parar el contenedor

Para ver en detalle las opciones de CPU y MEMORIA recomienda leerse la documentación al respecto Assign cpu resource y Manage resources containers

En nuestro ejemplo le estamos limitando al uso de *50 milliCPU * y 64Mb, si por ejemplo le indicaramos algo que no tuviera disponible nuestro cluster, kubernetes lo que haría sería esperar a tener esos recursos disponibles para arrancar el contenedor en cuestión.

Ahora arranquemos nuestro pod:

kubectl create -f mario.yml

Y si hacemos un describe de nuestro pod

kubectl describe pods mario-test

Docker IX - Kubernetes IV: Definiendo recursos de hardware

Podemos ver como todo ha funcionado correctamente y nuestro pod tiene configurado sus recursos.

Ahora cambiemos la configuración para que veamos que pasa cuando le ponemos una cantidad de recursos que no puede asumir. Por ejemplo en mi caso le ponemos que use 64Gb (que mi maquina actual no los tiene)

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mario-test
  name: mario-test
spec:
  containers:
  - image: pengbai/docker-supermario:latest
    name: mario-test
    ports:
    - containerPort: 8080
    resources: 
      requests:
        cpu: 50m 
        memory: 64G
      limits:
        cpu: 50m
        memory: 64G
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Si ahora borramos el pod que tenemos y creamos este nuevo veamos lo que pasa:

Docker IX - Kubernetes IV: Definiendo recursos de hardware

Nuestro pod se queda con un estado pending permanente, y si vemos la descripción del pod nos indicara el estado y la información de porque no se esta ejecutando, en este caso por falta de memoria

Docker IX - Kubernetes IV: Definiendo recursos de hardware

Como vemos con estas simples propiedades dentro de los pods podemos limitar o indicar los recursos que nuestro contenedor o pod usará (con requests) y la cantidad de hardware que queremos que use como máximo (con limits).

Y hasta aquí lo referente a lo básico de sobre los recursos de nuestros pods, recomiendo leer la documentación para poder hacer un selección más específica de los recursos que queremos que usen nuestros contenedores, sin mucho más nos vemos en el siguiente, un abrazooooooooo

]]>
<![CDATA[JS Algoritmos y Estructuras de datos VII: La recursividad III - Resolviendo problemas II]]>https://jlgarcia.fulldev.ninja/js-algoritmos-y-estructuras-de-datos-vii-la-recursividad-iii/Ghost__Post__5fd65ddfa6c0f5058bff49a1Mon, 28 Dec 2020 09:30:00 GMTJS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

Bien en el post anterior vimos la primera de las opciones para compensar el problema del maximum call stack que nos podía dar la recursividad. En este post veremos algunas otras opciones para tener en nuestro arsenal, que cumplen (más o menos) con la resolución de nuestro problema aunque alguna sea bastante lenta y tengan el handicap de que debemos controlar la asincronía.

Usando setTimeout

En este caso vamos a jugar con el conocido setTimeout, en este punto es mejor que estemos familiarizados con el event-loop (recomiendo leer estos posts)

Usaremos este ejemplo, cambia un poco lo que teniamos antes, pero es por intentar seguir con el mismo concepto del sumatorio

let accum = 0
let number = 5

function addTo () {
	if (number === 0) { 
		console.log(accum)
		return
	}
	
	setTimeout(() => {
		accum = number + accum
		number--
		addTo()
	}, 0)
}


function finish () {
  console.log('Finished')
}

addTo()
finish()

Como vemos tenemos nuestra función addTo que si el número no es 0 pasa por un setTimeout con 0. Esto aunque no podamos verlo en la aplicación que lo incluye dentro de la task queue lo que hace es incluir el método en la timers queue y se ejecutaría según el ciclo de vida habitual de node cuando pase por los timers resueltos.

Veamos un poco lo que pasa cuando ejecutamos esto dentro de jv9000 (OJO que esto no funciona en la web tengo un fork modificado para permitir esto y otras cosas)

  1. Incluye en el call stack la primera ejecución de addTo
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

  2. Como number no es 0 se ejecuta el setTimeout, es decir, incluye la función anónima dentro de la timers queue, que para nosotros en nuestro visualizador esta dentro de la task queue
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

  3. Importante, el paso anterior ha pasado como a un segundo plano dentro del event loop, por lo que para nosotros ha terminado la ejecución de addTo por lo que ya la podemos sacar del call stack
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

  4. El loop continua y le toca a la función finish.
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II
    Importante en este punto darse cuenta de que ha entrado la función finish al call stack se ha ejecutado y ha salido, sin que todavía hayamos tocado lo que tenemos dentro de task queue

  5. Una vez ejecutada la función finish ya le toca el turno a comprobar task, al ver que tenemos algo lo pasa al call stack y lo ejecuta
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

  6. Al ejecutarlo dentro del call stack, lo que tenemos de nuevo es otra ejecución de addTo, que repite la operación incluye en la task queue la función anónima del timeout
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

Y el proceso se repite n veces hasta que se cumple el number === 0
JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

Donde terminaría nuestra ejecución. Como vemos aquí tenemos otro caso donde evitamos que la cola de llamadas se llene.

Usando setImmediate

Usando setImmediate es similar al setTimeout pero tiene menos pasos, en el caso anterior nuestra ejecución pasa por incluirse en la cola de task, comprobar el tiempo, ver que ha terminado incluirlo en el call stack y ejecutarlo. Al final son bastantes pasos. Sin embargo el setImmediate tiene su propia cola y se ejecutan al momento tras los eventos de I/O por lo que puede ser más rápido en un entorno recursivo. Veamos rápidamente la diferencia:

let accum = 0
let number = 5

function addTo () {
	if (number === 0) { 
		console.log(accum)
		return
	}
	
	setImmediate(() => {
		accum = number + accum
		number--
		addTo()
	})
}


function finish () {
  console.log('Finished')
}

addTo()
finish()
  1. Al igual que antes se ejecuta la primera vez addTo
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II
  2. Al igual que antes pasa al finish, pero fijemonos que no tenemos en la cola de task, es decir, no estamos pendientes de que realmente termine nada.
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II
  3. De repente, si continuaramos ejecutando vemos como ha desaparecido todo y de repente aparece un método anónimo en nuestro call stack, que no es otro que la ejecución de lo que tenemos dentro del setImmediate
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II
  4. Y al igual que antes, este ejecutaría de nuevo nuestra función addTo
    JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

Este proceso se repetiría, veriamos como entra anonymous, luego addTo, termina addTo y desaparece, termina anonymous y desaparece, y en el siguiente ciclo todo se repite, hasta que coincide con number===0 que hace que se termine
JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

Como vemos es bastante similar al proceso con setTimeout pero con menos pasos, esto no quiere decir que tenga que ser siempre más rápido, a fin de cuentas, ambos pasan por el event loop casi de la misma forma.

Usando process.nextTick

Por último veremos como será nuestra solución usando process.nextTick(). En este caso todo es igual que con setImmediate:

let accum = 0
let number = 5

function addTo () {
	if (number === 0) { 
		console.log(accum)
		return
	}
	
	process.nextTick(() => {
		accum = number + accum
		number--
		addTo()
	})
}


function finish () {
  console.log('Finished')
}

addTo()
finish()

En este ejemplo me ahorraré las imágenes ya que visualmente sigue el mismo proceso que setImmediate:

  1. Se ejecuta la primera vez addTo
  2. A continuación finish
  3. Anonymous (lo que está dentro de nextTick)
  4. addTo....
  5. y el proceso se repite

Usando process.nextTick, tenemos otra solución pero en este caso, el beneficio es la velocidad, si echamos un vistazo a como se procesan las colas:
JS Algoritmos y  Estructuras de datos VII: La recursividad  III - Resolviendo problemas II

Podremos ver como nextTick se ejecuta tras cada hito dentro del loop, lo que significa que no tenemos que repetir el ciclo de vida del loop para volver a encontrarnos con el nextTick, como ocurre con los timers o el setImmediate.

Con esto de la recursión realmente lo que quería era mostrar un poco como solucionar problemas de maximum call stack y que se viera un poco que ocurre por dentro del event loop que no está de más tenerlo a veces en cuenta cuando tenemos ciertos problemas.

En siguientes posts de esta serie empezaremos con los algoritmos de búsqueda. Otro tema bastante interesante creo yo. Sin más, nos vemos en el siguiente un abrazoooooo

]]>
<![CDATA[JS Algoritmos y Estructuras de datos VI: La recursividad II - Resolviendo problemas I]]>https://jlgarcia.fulldev.ninja/js-algoritmos-y-estructuras-de-datos-vi-la-recursividad-y-como-resolver-el-problema/Ghost__Post__5fbc0b8ba6c0f5058bff47faMon, 21 Dec 2020 10:00:00 GMTJS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Ya vimos en el post anterior cual es el problema principal que podemos tener en JavaScript a la hora de usar la recursividad. En un primer momento lo más probable es que nunca nos encontremos con este problema a no ser que trabajemos con una gran cantidad de datos, pero aún así, creo que ver como se soluciona este problema puede hacernos entender algunas cosas del funcionamiento de Javascript y como podemos usarlas para cualquier otro problema que nos encontremos.

Para resolver el problema de "Maximum call stack..." existen varias formas, veremos la mayoría aunque alguna puede que no me guste mucho.

Vamos con la primera Trampoline

Trampoline

Esta se fue extendiendo como una buena posibilidad para resolver el problema, pero personalmente a mí no me convence demasiado, creo que nos salimos un poco de la pureza del concepto de la recursividad pensando en su planteamiento más funcional. Veamos el código en cuestión:

const trampoline = (fn) => (...args) => {
  let result = fn(...args)
  while (typeof result === 'function') {
    result = result()
  }
  return result
}

function addTo(number, accum = 0) {
	if (number === 0) {
		return accum
	}
	return () => addTo(number - 1, number + accum)
}

function finish () {
  console.log('Finished')
}

console.log(trampoline(addTo)(5))
finish()

Más de uno se hará una idea de lo que realmente estamos haciendo en este caso y como cambia con respecto al ejemplo del post anterior sobre recursividad.
En este ejemplo tenemos una función addTo que no se llama a si misma, si no que, según el caso llama a una función anónima que la vuelve a llamar. Pero claro no podemos llamar solo a la función addTo ya que en la primera ejecución simplemente devolvería una función que no se ejecuta y terminaría nuestro programa. Lo que hacemos es crear una función trampoline que encapsularía cualquier otra donde queramos usar la recursividad. Esta función lo que hace es controlar si la respuesta es una función que debemos que espera ejecutarse o no, en resumen, trampoline es una función que se encarga o de devolver un resultado si se ha cumplido nuestro caso base o de ejecutar de nuevo la función, veamos ahora lo que pasa en nuestro call stack.

  1. Se ejecuta trampoline
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

En cuanto a ejecutar, me refiero a, entre en el call stack y se ejecuta directamente.
Esto es una función con currificación, si nos fijamos lo que le pasamos como parámetro a trampoline es la función que queremos ejecutar, es decir, addTo.

  1. Se ejecuta trampoline(addTo)(5)
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Entra trampoline(addTo)(5) al call stack y comienza a ejecutarse, aquí como tenemos un bucle while donde se ejecutan otros métodos, veremos como el call stack crece

  1. Se ejecuta por primera vez addTo con el valor 5
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Como vemos estamos dentro de trampoline(addTo)(5) ejecutando addTo(5, 0)
Como el valor no es cero retorna la función anónima que ejecutaria de nuevo addTo
JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

  1. Se ejecuta la función anónima que a su vez ejecuta addTo(4, 5)
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Esto devuelve de nuevo una función anónima, pero como tal su ejecución termina por lo que addTo sale del call stack
JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Y lo mismo sucede con la función anónima que su ejecución ha terminado y sale del call stack
JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

  1. Como el resultado de todo esto sigue siendo una función nuestro trampoline vuelve a ejecutar la función anónima que ha recibido
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Donde a su vez se ejecuta addTo(3, 9)
JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

  1. Este proceso se repite hasta que el parámetro number === 0 donde termina la ejecución de la función anónima dentro de trampoline
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

  2. Se ejecuta el resultado de console.log(trampoline(addTo)(5)) que nos muestra 15
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

  3. Se ejecuta finish()
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Entra dentro del call stack

  1. Ejecuta el console.log dentro de la función finish
    JS Algoritmos y  Estructuras de datos VI: La recursividad  II - Resolviendo problemas  I

Y la ejecución de nuestro programa o script termina

Como vemos esto es un truquillo que hace que no saturemos el call stack de ejecuciones pendientes, que cubre la necesidad que tenemos aunque como digo no es algo que me convenza mucho.

Esta es una de las formas que tenemos para evitar el Maximum call stack, en los siguientes post veremos otras posibles soluciones, algunas más sencillas otras más complejas, pero nos serviran para entender del todo como funciona el call stack.

Nos vemos en el siguiente un abrazoooooo

]]>
<![CDATA[Docker VIII - Kubernetes III: Introducción a los PODS]]>https://jlgarcia.fulldev.ninja/docker-vi-kubernetes-iii-pods/Ghost__Post__5fafdfdca6c0f5058bff45a7Mon, 14 Dec 2020 10:00:00 GMTDocker VIII - Kubernetes III: Introducción a los PODS

En este post hablaremos que son los pods y como podemos trabajar con ellos.
En el anterior post ya usamos un pod aunque realmente todavía no sabíamos nada de ellos. Si recordamos un poco cuando hicimos el run donde le indicabamos el nombre de la imagen que queriamos desplegar, lo que hizo por debajo kubernetes fué crear algo bajo el namespace pod/mario-test, ¿verdad?... pues bien para resumir lo que sería realmente un pod es una aplicación o servicio que puede tener uno o más contenedores y/o volúmenes de disco. Estos contenedores o aplicaciones están funcionando bajo el mismo namespace de networking, es decir, el conjunto de contenedores tiene la misma IP pero internamente dentro del POD los contenedores son capaces de comunicarse entre sí solo usando localhost: (es como si estuvieramos usando docker-compose con varios contenedores). La ip que reciben es privada y solo se puede acceder a través de otros PODS. Técnicamente hablando esto se conoce como que el pod esta funcionando dentro de su propio cgroup.

Un POD es la unidad mínima de recursos que podemos tener dentro de kubernetes, es decir, al menos necesitaremos un POD para poder ejecutar un contenedor. Cuando se crea un pod, kubernetes lo asigna a uno de los nodos del cluster que tengamos dependiendo de los recursos disponibles y los deseados. En kubernetes cada Pod tiene su propio ciclo de vida y podemos escalarlo o hacer lo que queramos con ellos por lo que se suele desplegar un POD por aplicación.

La declaración de un POD tiene un estilo similar a cuando trabajamos con recetas de docker-compose, es un yaml con un estilo similar a

apiVersion: v1
kind: Pod
metadata:
  name: example-app
  labels:
    app: example-app
    version: v1
    role: backend
spec:
  containers:
  - name: java
    image: companyname/java
    ports:
    - containerPort: 443
    volumeMounts:
    - mountPath: /volumes/logs
      name: logs
  - name: logger
    image: companyname/logger:v1.2.3
    ports:
    - containerPort: 9999
    volumeMounts:
    - mountPath: /logs
      name: logs
  - name: monitoring
    image: companyname/monitoring:v4.5.6
    ports:
    - containerPort: 1234

Donde como vemos tenemos la versión de la api, el tipo(pod obviamente), metadatos y spec donde declaramos los containers y su configuración deseada (esos campos son los mínimos necesarios). OJO esto es una forma declarativa de decirle a Kubernetes que es lo que queremos, luego el en su ciclo de vida, cuando toque comparará el estado actual con el deseado y hará los cambios necesarios para cumplir con lo solicitado, como por ejemplo en el caso de que no existan los contenedores los creará al igual que vimos en el post anterior.

Esto es realmente lo que hace kubectl cuando le enviamos el comando en el post anterior:

kubectl run mario-test --image=pengbai/docker-supermario:latest --port=8080

Usemos un truco para poder ver que es lo que realmente hace el comando, si ejecutamos este comando:

kubectl run mario-test --image=pengbai/docker-supermario:latest --port=8080 --dry-run=client -o yaml

Deberiamos ver algo como esto:

Docker VIII - Kubernetes III: Introducción a los PODS

Esto es lo que realmente esta enviando el comando, donde a modo de resumen rápido tenemos lo siguiente:

  • apiVersion: versión de la api de kubernetes
  • kind: como sabemos estamos ahora mismo trabajando con Pods
  • metadata.creationTimestamp: Como realmente no ha sido creado no nos aparece ningún timestamp (supongamos que esta es la especificación que mantiene kubernetes para comparar con la que le mandemos nosotros por eso tiene timestamp)
  • metadata.labels: Aquí podemos incluir todo tipo de información útil, como el entorno, al final lo utilizariamos para identificar de alguna manera este POD, su finalidad....
  • name: Identificador único del POD
  • spec: Donde realmente se encuentra toda la información de los contenedores o lo que sea que este incluido en este pod

Para terminar de comprobar que no estoy mintiendo hagamos lo siguiente, vamos a crear un fichero yaml con esta configuración y lanzarsela a kubernetes a ver que pasa. Para ello lo primero creamos el fichero (OJO con el nombre que tiene un 2):

kubectl run mario-test-2 --image=pengbai/docker-supermario:latest --port=8080 --dry-run=client -o yaml > mario.yaml

Esto nos creará un fichero con la configuración que acabamos de ver, podemos comprobarlo con:

cat mario.yaml

Ahora le indicamos a kubernetes que cree lo que dice el fichero:

kubectl create -f mario.yaml

Nos indica que ya esta creado
Docker VIII - Kubernetes III: Introducción a los PODS

Aunque realmente esa es la respuesta de kubernetes al comando, realmente lo creará cuando este en la fase de reconciliación de su ciclo de vida y vea que algo no cuadra entre lo que tiene y lo que se supone que debe tener según nuestros comandos.

Si ahora ejecutamos:

kubectl get pods

Podemos ver como tenemos otro contenedor creado con el nombre que le hemos indicado

Docker VIII - Kubernetes III: Introducción a los PODS

Si por algún motivo no sabemos realmente que es lo que tiene un pod en cuestión podemos ver toda la información relevante usando el comando:

kubectl describe pod mario-test

Con esto veremos toda la información posible:

Docker VIII - Kubernetes III: Introducción a los PODS

Como el nombre, el nodo donde se encuentra, su estadom la IP..... incluso si nos vamos al final de lo que nos ha mostrado la consola podremos ver un listado de eventos con lo que ha ido pasando con este pod

Docker VIII - Kubernetes III: Introducción a los PODS

Bueno y hasta aquí este post de introducción a los PODS, en el siguiente podremos ver algo vas información y configuraciones más avanzadas, nos vemos un abrazoooooor

]]>
<![CDATA[JS Algoritmos y Estructuras de datos V: La recursividad y su pequeño problema en Javascript]]>https://jlgarcia.fulldev.ninja/js-algorithm-and-data-structures-recursion/Ghost__Post__5c18a5012dd6610fd828cacdMon, 07 Dec 2020 09:33:00 GMTJS Algoritmos y  Estructuras de datos V: La recursividad y su pequeño problema en Javascript

Hablemos ahora de la Recursividad. A la gran mayoría por lo menos nos tiene que sonar ¿no?... por lo menos nos habrán dicho que es muy chula, que mola usarla, que porque no lo pruebas... pues bien he de coincidir con casi cualquier cosa buena que se diga sobre este concepto, aunque claro lo suyo es usarlo cuando toque y tenga sentido (pero eso es otra historia).

Por ahora veremos que es esto de la recursividad, para que nos sirve y cual es el problema que nos generará a la larga (y como resolverlo) esto en nuestro querido Javascript.

Recursividad

No me voy a extender en explicaciones técnicas en este caso (profundizaré más en el post de la línea de javascript funcional), por lo que, en palabras simples, es la capacidad de una función de llamarse a si misma. Esto es más un concepto de programación funcional, pero debido a que las funciones en javascript se pueden tratar como cualquier otro objeto (ciudadano de primera clase), es decir, que se puede pasar por parámetro, retornar dentro de otra función.... y todo lo que se nos ocurra, podemos hacer uso de la recursión también.

Algunos ejemplos de su uso podrian ser: Recorrer un sistema de ficheros (abrir primera carpeta, recorrer lo que hay dentro y si encuentra una carpeta la abre y recorre...y asi sucesivamente), se suele ver en la típica resolución de problemas sobre los números de Fibonacci (que es y ejemplos aquí) o en general cualquier problema que requiera repetir lógica sobre un mismo elemento (y no necesitemos o no tenga sentido funcional que usemos un bucle de los habituales)

Veamos cuales son los detalles de una función recursiva. Ya hemos comentado que en la teoría se supone que es una función que se llama a si misma, como por ejemplo

function addTo (number) {
	return number + addTo(number - 1)
}

addTo(10)

La teoría de la función sería que queremos sumar los números desde 0 hasta el número dado. Visualmente tiene sentido, ¿no?, sumamos el número que recibimos primero que sería el 10 y luego llamamos a la misma función para que use el 9 y así sucesivamente. Pero claro si nos fijamos bien aquí tenemos un problema, si ejecutaramos esto tendriamos un bucle infinito porque en ningún momento le indicamos cuando tiene que parar. Lo que le falta a esta función es lo que se conoce como caso base.

El caso base es la unidad mínima que debe devolver una función recursiva, si pensamos en lo que queríamos hacer que era sumar del 0 al 10, en este caso nos falta indicarle que cuando llegue a 0 nos devuelva el número, este sería nuestro caso base, siempre debe tener un caso base.

function addTo(number) {
	if (number === 0) {
		return number
	}
	return number + addTo(number - 1)
}

addTo(10)

Si ahora ejecutamos esto todo iria bien y nos debería devolver 55. Esto es un caso muy sencillo para explicar que es la recursión pero veamos rápidamente que es lo que lo pasa por debajo.

Para ello lo primero es repasar que es el call stack. El call stack o pila de llamadas (lo defino a mi rollo por simplicicad) son las funciones que se tienen que ejecutar (con estructura LIFO) dentro de un Task del event loop. En el próximo post profundizaremos más en que es cada cosa y el flujo que sigue el eventloop, con lo que nos tenemos que quedar ahora mismo es que el call stack es el trabajo que tiene pendiente javascript para poder pasar a la siguiente tarea (es decir que nuestro thread esta bloqueado hasta que termine).

Para verlo bien vamos a usar una herramienta que me he encontrado (grandisima ayuda para entender esto visualmente ya lo veremos, agradecer a los creadores) que es jsv9000. Esta herramienta nos permite comprobar visualmente lo que pasa dentro de nuestro eventloop en cada una de sus Queues.

Lo que haremos será añadir el código siguiente y darle a run:

function addTo(number) {
	if (number === 0) {
		return number
	}
	return number + addTo(number - 1)
}

function finish () {
  console.log('Finished')
}

addTo(5)
finish()

Lo que hace la herramienta es indicarnos en que puesto está nuestro código y que ha ido pasando por dentro paso a paso, por lo que tenemos que ir dandole a step. Si le damos 6 veces (como le hemos indicado que hasta 5 y empezamos en el 0) vemos como el código lo único que ha hecho es incluir 6 llamadas en el call stack de la función de suma en la fase de evaluación

JS Algoritmos y  Estructuras de datos V: La recursividad y su pequeño problema en Javascript

Si le continuamos dando a step vemos como se va vaciando el call stack de la función y lo siguiente es el método finish que nos muestra el resultado del console.log. En este caso lo que vemos es que nuestra función recursiva va acumulando llamadas en el call stack hasta que llega al caso base y empieza a devolver datos y a vaciar. Si lo queremos ver más claro añadimos unos logs

function addTo(number) {
	if (number === 0) {
	  console.log(0)
		return number
	}
	const result = number + addTo(number - 1)
	console.log('result: ', result)
	return result
}

function finish () {
  console.log('Finished')
}

addTo(5)
finish()

Y podemos ver en acción realmente donde estamos

JS Algoritmos y  Estructuras de datos V: La recursividad y su pequeño problema en Javascript

Una vez entendido esto, ahora os planteo el gran problema que resolveremos en próximos posts. Cambiemos el código por lo siguiente pero no recomiendo ejecutarlo en la herramienta que tardaría muchisimo, ejecutarlo en la consola del navegador, en un fichero de node o donde más os guste

function addTo(number) {
	if (number === 0) {
		return number
	}
	return number + addTo(number - 1)
}

console.log(addTo(100000))

Fijaos el número que he puesto 100000, esto nos debe dar un error como el siguiente:

JS Algoritmos y  Estructuras de datos V: La recursividad y su pequeño problema en Javascript

Es decir hemos llegado al límite de acciones posibles dentro de la pila de llamadas, si nos planteamos lo que ocurria con 5, que teniamos 6 inclusiones el call stack, ahora tendríamos 100001 (se que este número es muy grande pero depende de lo que estemos haciendo o donde puede ser mucho menos, realmente insisto en que es un error muy habitual). Este problema viene dado porque javascript no es un lenguaje orientado a este tipo de funciones, pero siempre podemos encontrarle solución, en los próximos post veremos como podemos solucionar esto de muchas maneras, mientras tando plantear vosotros posibles soluciones. Nos vemos en el siguiente un abrazoooooor

]]>
<![CDATA[Docker VII - Kubernetes II: Lanzando nuestra primera imagen]]>https://jlgarcia.fulldev.ninja/docker-vi-kubernetes-ii-nuestra-primera-app-en-kubernetes/Ghost__Post__5fafd4b8a6c0f5058bff45a0Mon, 30 Nov 2020 09:33:00 GMTDocker VII - Kubernetes II:  Lanzando nuestra primera imagen

En este punto vamos a ver lo primero como seria lanzar o desplegar una aplicación usando kubernetes, para ello vamos a usar una imagen de docker pública (agradecer a los creadores, el contenido de esa imagen no tiene nada que ver conmigo) que contiene una versión de un juego de nuestro querido fontanero MARIO (Mas info). Para lanzarla lo que hacemos es:

kubectl run mario-test --image=pengbai/docker-supermario:latest --port=8080

En este comando lo que hacemos es indicarle a kubernetes que queremos arrancar un contenedor o un servicio con nombre mario-test con la imagen en cuestión que vemos en dockerhub y que esta aplicación escucha en el puerto 8080 (esto depende de cada imagen, en nuestro caso es ese, mirar la información de cada imagen para saber cual es el puerto que realmente escucha).
Al ejecutarse lo que hace es descargarse la imagen de docker hub y arrancarla.

Si todo va bien la respuesta seria algo similar a esto:
Docker VII - Kubernetes II:  Lanzando nuestra primera imagen

Podemos ver el estado de este contenedor con:

kubectl get pods

Docker VII - Kubernetes II:  Lanzando nuestra primera imagen

Por el momento no nos preocuparemos con que significa cada comando, lo que queremos hacer es comprobar como es empezar a trabajar con kubernetes.
Como vemos en la imagen tenemos un algo llamado pod con el nombre mario-test, este nombre es necesario para poder realmente acceder a nuestro contenedor.
En el comando que hemos lanzado para instanciar la imagen en cuestión, le hemos indicado el puerto por el que trabaja realmente el contenedor pero como tal nosotros no tenemos acceso a ese puerto, debemos hacer lo que se conoce como un port-forwarding para sincronizar un puerto de nuestra máquina con el puerto del contenedor, para ello ejecutaremos lo siguiente:

kubectl port-forward mario-test 8000:8080

Aqui lo que le indicamos es que sincronice nuestro puerto 8000 con el 8080 del contenedor o aplicación con el nombre mario-test. Si todo va bien veremos algo como:

Docker VII - Kubernetes II:  Lanzando nuestra primera imagen

Y si ahora nos vamos a nuestro navegador e intentamos acceder a localhost:8000, nos debería aparecer nuestro juego

Docker VII - Kubernetes II:  Lanzando nuestra primera imagen

Ojo tenemos que tener en cuenta que el comando de port-forward se queda ejecutandose hasta que lo paremos

Docker VII - Kubernetes II:  Lanzando nuestra primera imagen

Si nosotros paramos el comando en cuestión veremos como ya no podemos acceder a nuestra página con el juego de mario.

Este post ha sido solo un post introductorio para que veamos como seria desplegar una imagen básica usando kubernetes. En los próximos post entraremos más en detalle en como funciona todo esto y haremos muchas más cosas viendo todas las capacidades de kubernetes, un abrazooooo nos vemos en el siguiente.

]]>
<![CDATA[JS Algoritmos y estructuras de datos IV: Resolución de problemas-> Patrón Sliding Window]]>https://jlgarcia.fulldev.ninja/js-algoritmos-y-estructuras-de-datos-iii-patron-sliding-window/Ghost__Post__5fa84de4a6c0f5058bff43dbTue, 24 Nov 2020 09:03:00 GMTJS Algoritmos y estructuras de datos IV: Resolución de problemas-> Patrón Sliding Window

Este patrón como su nombre indica se basa en "mover una ventana", es decir, tratamos con los datos que estan dentro de esa ventana ignorando el resto, y vamos deslizando la ventana con los siguientes datos.
Veamoslo con un ejemplo. Dentro de un array queremos obtener la suma mayor entre "n" de esos números consecutivos, es decir, si por ejemplo queremos saber cual es la suma mayor de 2 números consecutivos de este array:

[1,4,2,6,3,9]

Tendremos que sumar:

  • 1 + 4
  • 4 + 2
  • 2 + 6
  • 6 + 3
  • 3 + 9

Por eso he comentado que es como mover una ventana o una caja donde solo entran 2 números y sumamos cada vez que cambia uno de los números.

Veamos el posible acercamiento mas habitual o sencillo (el que solemos hacer primero) aunque sería el más lento

// arr: el array a comprobar
// num: número de elementos en el subarray, de cuantos números queremos realizar el cálculo
function maxSubarraySum (arr, num){
    if (num > arr.length) return null;
    
    var max = -Infinity;
    for(let i = 0; i < arr.length - num + 1; i++){
        temp = 0;
        for (let j = 0; j < num; j++){
            temp += arr[i+j];
        }
        if (temp > max) {
            max = temp;
        }
    }
    return max;
}

Como vemos tenemos un "nested loop", y lo que hacemos es recorrer cada número en el primer loop y en el segundo sumamos los "n" números siguientes.

Es relativamente fácil pero tenemos una complejidad temporal de O(n^2)

Ahora veamos como sería usando el Sliding Pattern

function maxSubarraySum (arr, num){
    let maxSum = 0;
    let tempSum = 0;

    if (arr.length < num) return null;

    for (let i = 0; i < num; i++){
        maxSum += arr[i];
    }

    tempSum = maxSum;

    for (let i = num; i < arr.length; i++){
        const leftLeg = arr[i - num];
        const rightLeg = arr[i];
        tempSum = tempSum - leftLeg + rigthLeg;  
        maxSum = Math.max(tempSum, maxSum);
    }

    return maxSum;
}

Esto ya tiene algo más de "chicha", así que vayamos por partes

for (let i = 0; i < num; i++){
   maxSum += arr[i];
}
 tempSum = maxSum;

Sumamos los números que estarían inicialmente en la ventana, es decir desde "n" hasta 0 y lo establecemos en una variable que usaremos de comodín para ir moviéndonos (tempSum)

 for (let i = num; i < arr.length; i++){
     const leftLeg = arr[i - num];
     const rightLeg = arr[i];
     tempSum = tempSum - leftLeg + rigthLeg;   
     maxSum = Math.max(tempSum, maxSum);
 }

A continuación recorremos empezando por "n", es decir, después de sumar los "n" primeros números, y el paso sería elimino el primero y añado el siguiente, (para clarificar con el ejemplo de la ventana he creado leftLeg como limite o pata izquierda de la ventana y rightLeg como el derecho ) siendo más específico lo que hacemos es restarle a tempSum el primer número dentro de nuestra "ventana o cajón", que seria leftLeg y le añadimos el siguiente que esté fuera, es decir rightLef. Y vamos comparando con el valor anterior y metiendo el mayor dentro de la variable maxTemp

Siguiendo con el ejemplo de antes con este array, usando 2 valores consecutivos:

[1,4,2,6,3,9]

Los pasos serían:

  • En el primer loop, se suman los 2 primeros, con un resultado de 5 y se asigna a la variable tempSum.
  • En el segundo loop se le resta a tempSum el 1, se le suma 2, lo que nos daría 6 y así sucesivamente
  • 6 - 4 + 6
  • 8 - 2 + 3
  • 9 - 6 + 9
  • Lo que nos daría 12

Otro ejemplo algo más complicado.

En este caso la idea es encontrar el mínimo número de elementos necesarios del array que su suma sea igual o superior al número dado. Veamoslo con números primero, tenemos este array:

[2,1,6,5,4]

Y queremos saber cual es la menor cantidad de números de ese array que su suma sea igual o mayor que 9. En este caso serian necesarios 2 números (5+4).

Antes de ver el ejemplo, os cuento lo que haremos:

  1. En este caso empezaremos a sumar desde 0, creando nosotros la ventana, hasta que la suma sea mayor o igual que el número que buscamos.
  2. Cuando la suma sea mayor o igual veremos cuantos números hemos necesitado, lo apuntaremos y empezaremos a mover la ventana (recordar restando el primer número y sumando el siguiente)
function minSubArrayLen(nums, sum){
    let total = 0;
    let start= 0;
    let end = 0;

    let minLen = Infinity;

    while (start < nums.length) {
        //Si la ventana actual no es mayor o igual que el número dado movemos la ventana a la derecha, es decir ampliamos la ventana
        if (total < sum && end < nums.length){
            total += nums[end];
            end++;
        } 
        //Si la ventana actual suma al menos la suma dada entonces podemos reducir la ventana
        else if(total >= sum){
            minLen = Math.min(minLen, end - start);
            total -= nums[start];
            start++;
        } //Si el total actual es menor que lo que necesitamos pero hemos llegado al final, necesitamos el break para no un infinite loop
        else {
            break;
        }
    }

    return minLen === Infinity ? 0 : minLen;
    
}

Otro ejemplo más

En este caso queremos contar los carácteres del substring mas largo con carácteres distintos, es decir, buscamos un substring sin que se repita ninguna letra. Aquí usaremos una mezcla entre el frequency counter y el sliding pattern. Básicamente iremos anotando las letras por las que pasamos e ir moviendo la window según se repitan

function findLongestSubstring(str) {
  let longest = 0;
  let seen = {};
  let start = 0;
 
  for (let i = 0; i < str.length; i++) {
		let char = str[i];
    //Comprobamos si ya lo tenemos, y si existe, nuestro start seria el que tenemos anotado ya
    if (seen[char]) {
			start = Math.max(start, seen[char]);
    }
		// Sumamos uno para tener en cuenta el caracter actual
    longest = Math.max(longest, i - start + 1);
		// guardamos el índice con el siguiente carácter si se repite no contarlo, el nuevo esta dentro
		// de la ventana
		seen[char] = i + 1;
  }
  return longest;
}

Este ejemplo es algo más díficil de entender, creo que sobretodo el moviento de la ventana, por explicarlo un poco más supongamos que queremos saber la cantidad de caracteres máximo consecutivo donde no se repite ningún caracter de la palabra ninja (como no...), el resultado sería 4 pero veamos un poco el flujo hasta que cambia (tengamos en cuenta que i y start son las "patas" de la ventana):

  • Empieza siendo i 0 por lo que empezamos con la n, como no tenemos nada guardado todavía dentro de seen la variable start se queda con 0, longest sería 1 (tanto i como start son 0) y se guardaría seen.n = 1
  • A continuación la i del index es 1, lo que hace que la siguiente letra sea la i. No existe todavía dentro de seen por lo que start todavía seguiría siendo 0, longest pasaría a ser 2 (i es 1 y start 0) y guardamos seen.i = 2
  • En la siguiente iteración la i del index es 2, por lo que la siguiente letra es n. En este caso si que tenemos coincidencia dentro seen por lo que start pasaría a ser 1. Aquí es donde está la parte algo más complicada de ver al principio, este paso es el porqué de guardar cada letra con un +1 dentro de seen. Para explicarlo pensemos en el concepto de la ventana, antes de pasar por la modificación del start tenemos que el borde izquierdo (la posición actual del start) esta en la posición 0 y el borde derecho (la i) está en la posición 2, al encontrar coincidencía lo que tenemos que hacer es mover la posición del borde izquierdo para sacar la n de nuestro rango de la ventana por lo que para sacarla sería mover la posición actual del borde izquierdo a n + 1, es decir, movernos al siguiente carácter. Nuestra ventana ahora pasa de estar en nin a in. Hemos movido el start de 0 a 1 por lo que en este caso si la i es 2 entonces longest sigue siendo 2 y ahora como ya existe la n dentro seen pasamos a tener seen.n = 3
  • Ahora la i del index es 3, por lo que la siguiente letra es j, como no hemos pasado por ninguna nuestro start no cambia sigue en 1, longest ahora pasaría a ser 3 (i es 3, start es 1) y guardariamos seen.j = 4
  • Por último la i del index es 4, por lo que ya estariamos en la última letra que es la a, como no tenemos ninguna start sigue siendo 1, longest ahora pasaría a ser 4 (i es 4 y start es 1) y por último se guardaría seen.a = 5

En este punto terminaría la ejecución y nos devolvería longest que sería 4, es decir, inja. En este ejemplo lo más importante es ver como se mueve la ventana realmente, que lo que hace es ir creciendo hasta que encuentra una repetición y coloca su start a continuación de la repetición para comenzar de nuevo sin incluir ese carácter.

Creo que más o menos con estos ejemplos se entiende cual es el funcionamiento de este patrón de resolución de problemas que puede ser bastante útil si queremos sacar y comparar información entre elementos dentro de un mismo array.

Y hasta aquí este post nos vemos en el siguienteeeee, un abrazooooor

]]>
<![CDATA[Javascript Funcional III: Composición de funciones]]>https://jlgarcia.fulldev.ninja/javascript-funcional-iii-composicion-de-funciones/Ghost__Post__5fa7e206a6c0f5058bff43c9Fri, 20 Nov 2020 10:37:00 GMTJavascript Funcional III: Composición de funciones

Bien vamos con la última de las características que tiene la programación funcional antes de adentrarnos puramente en propiedades funcionales. En este caso vamos a ver en detalle que es eso de Composición de funciones.

Esto realmente no tiene mucha ciencia, el propio nombre nos indica de que va, se trata de aplicar funciones en cadena para lógicas más complejas. Esto se suele ver en ciertas librerias como composition, pipe, flow..., primero veremos como aplicar esto de forma nativa y ya veremos algún atajo con alguna libreria.

Continuemos con la teoria de uno de los ejemplos anteriores que es la de crear un player inicial para un juego:

const setName = (name) => (player) => {
  player.name = name
  return player
}

const setPowers = (player) => {
  const availablePowers = ['fly', 'invisibility', 'bulletproof', 'superstrength']
  player.powers = availablePowers[Math.floor(Math.random() * availablePowers.length)]
  return player
}

const setWeapon = (player) => {
  const availableWeapons = ['gun', 'katana', 'ninja star', 'grenade']
  player.weapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]
  return player 
}

const newPlayer = setWeapon(setPowers(setName('Ninja')({})))

console.log(newPlayer)

Si nos fijamos en el contenido de newPlayer, es donde tenemos nuestra composición de funciones, lo que se suele ver como:

f(g(x))

En este caso puede que sea facil de entender lo que estamos haciendo en este caso, pero si tuvieramos esta forma en soluciones más complejas puede que no nos enteremos de nada, por lo que veamos como ir mejorando nuestra teoria de la composición.

Lo primero que haremos será crear nuestro propio método compose:

const compose = (...fns) => x => {
  return fns.reduce((previous, current) => {
    return current(previous)
  }, x)
}

Este método compose lo que hace es recibir una cantidad n de funciones que debe aplicar sobre un valor inicial x. En este caso lo hace de izquierda a derecha, muchas veces se usa reduceRight para hacerlo de derecha a izquierda, depende de cuales sean los métodos a usar para la composición nos interesa más una dirección u otra (por defecto derecha a izquierda aunque a mí me gusta más izquierda a derecha).

Ahora con esta función compose solo tenemos que usarla:

const compose = (...fns) => x => {
  return fns.reduce((previous, current) => {
    return current(previous)
  }, x)
}

const setName = (name) => (player) => {
  player.name = name
  return player
}

const setPowers = (player) => {
  const availablePowers = ['fly', 'invisibility', 'bulletproof', 'superstrength']
  player.powers = availablePowers[Math.floor(Math.random() * availablePowers.length)]
  return player
}

const setWeapon = (player) => {
  const availableWeapons = ['gun', 'katana', 'ninja star', 'grenade']
  player.weapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]
  return player 
}

const newPlayer = setWeapon(setPowers(setName('Ninja')({})))
const otherPlayer = compose(setName('Ninja'), setPowers, setWeapon)({})

console.log(newPlayer)
console.log(otherPlayer)

Javascript Funcional III: Composición de funciones

Como podemos ver ambas funcionan correctamente y es un poco más sencillo de entender lo que estamos haciendo. Recuerdo que este ejemplo es un poco forzado es solo orientativo para ver la funcionalidad compose.

Veamos el ejemplo con Ramda

const R = require('ramda')

const setName = (name) => (player) => {
  player.name = name
  return player
}

const setPowers = (player) => {
  const availablePowers = ['fly', 'invisibility', 'bulletproof', 'superstrength']
  player.powers = availablePowers[Math.floor(Math.random() * availablePowers.length)]
  return player
}

const setWeapon = (player) => {
  const availableWeapons = ['gun', 'katana', 'ninja star', 'grenade']
  player.weapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]
  return player 
}

const newPlayer = setWeapon(setPowers(setName('Ninja')({})))
// Aquí en lugar de compose usamos el método en cuestión de R que es pipe
const otherPlayer = R.pipe(setName('Ninja'), setPowers, setWeapon)({})

console.log(newPlayer)
console.log(otherPlayer)

Solo tenemos que usar pipe para hacerlo de izquierda a derecha o compose para hacerlo de derecha a izquierda

Un detalle importante a tener en cuenta y que hacemos uso en el ejemplo, es lo que se conoce como programación tacita o point free, este concepto se refiere a la omisión de los parámetros de los métodos cuando lo usamos por ejemplo en una composición como esta que hemos visto, debido a que los valores de retorno de unos métodos son los de entrada de otros por lo que como el proceso se realiza automáticamente lo podemos omitir. En este caso que hemos visto siempre se retorna el mismo objeto, pero pudiera ser cualquier otro caso mientras que se cumpla este concepto:

funcA :: A -> B
funcB :: B -> C

// Esto lo podemos encadenar porque funcB espera como valor de entrada B que es lo que devuelve funcA
funcC:: funcA + funcB :: A -> B -> C

Creo que con esto se entiende más o menos lo básico sobre la composición, en los próximos capítulos veremos elementos más puramente funcionales como pueden ser los Funtores, una abrazooooo

]]>
<![CDATA[Docker VI - Kubernetes I: Intro e instalación]]>https://jlgarcia.fulldev.ninja/kubernetes-i-intro-e-instalacion/Ghost__Post__5fafcc29a6c0f5058bff4598Tue, 17 Nov 2020 10:03:00 GMTDocker VI - Kubernetes I: Intro e instalación

En general ya tenemos una idea de que es Kubernetes, seguro que lo hemos oido o leido en algún momento, en un resumen muy escueto, podemos decir que es una herramienta de instrumentación/orquestación de nodos de Docker, estos pueden estar en uno o varios servidores. Nos provee de una serie de herramientas para poder gestionar nuestros conjuntos de aplicaciones que están funcionando en varios contenedores Docker.

Instalación

En un primer momento comentar que realmente Kubertenes es un servicio que estará funcionando en algún proveedor cloud (AWS/Gooogle/Azure...), por lo que propiamente dicho no tendriamos una instalación. En esta sección veremos como instalar un entorno local de pruebas de kubernetes y las herramientas habituales de gestión de kubernetes (que estas si que se usan en producción)

CLI de gestión kubernetes

El servicio de Kubernetes como tal, dispone de una API para su gestión, es decir, que podríamos usar postman o curl para gestionar nuestro entorno de kubernetes, pero esto en mi opinión para el uso habitual de kubernetes no es necesario. Disponemos de un cli específico llamado kubectl que nos proporciona todo lo necesario para la gestión de cualquier entorno de kubernetes. (Mas info)

Basicamente para su instalación en MacOs por ejemplo solo necesitamos hacer:

brew install kubernetes-cli

Con esto se instalaria el cli que usaremos para trabajar.

Kubernetes en local

A continuación lo que haremos será instalar el servicio local que será nuestro kubernetes, para ello han creado una herramienta llamada minikube que hará las veces de server de kunernetes (Mas info).

Para instalarlo solo tenemos que ejecutar

brew install minikube

Esto instalará lo necesario para que minikube funcione.
A continuación lo que haremos será arrancar minikube para que funcione con kubectl. Para ello es suficiente con ejecutar:

minikube start

Esto iniciará y configurará el entorno de minikube y además hará que funcione con kubectl.
Podemos indicarle también la cantidad de memoria que queremos que use por defecto nuestro servicio de minikube pasandole:

minikube start --memory=4096

Esto haría que use 4Gb por defecto usa 2. Tiene más opciones como indicarle cantidad de CPU y demás. Si queréis ver más opciones lo ideal es mirar la documentación para verlas todas.

Probando que todo ha ido bien

Ahora ya tenemos lo básico para empezar a trabajar. Podemos comprobarlo ejecutando los comandos:

  • Ver la versión que tenemos tanto del cliente (lo que seria kubectl) como del servidor (en este caso minikube)
kubectl version

Docker VI - Kubernetes I: Intro e instalación

  • Ver los nodos que tenemos de kubernetes (donde podemos ver el rol que tiene cada uno, en este caso MASTER)
kubectl get nodes

Docker VI - Kubernetes I: Intro e instalación

  • Ver el estado kubernetes
minikube status

Docker VI - Kubernetes I: Intro e instalación

  • Información sobre el cluster de kubernetes
kubectl cluster-info

Docker VI - Kubernetes I: Intro e instalación

Con esto ya tendríamos lo básico para empezar a trabajar con kubernetes como si de un entorno de producción se tratara. En el próximo post empezaremos a mandarle instrucciones para ver como responde, un abrazoooooo

]]>
<![CDATA[Javascript Funcional II: Funciones puras, currying y aplicación parcial]]>https://jlgarcia.fulldev.ninja/javascript-funcional-ii/Ghost__Post__5fa6a809a6c0f5058bff43b6Fri, 13 Nov 2020 10:21:00 GMTJavascript Funcional II: Funciones puras, currying y aplicación parcial

Continuemos con nuestra serie de programación funcional, y en este caso seguiremos hablando de sus caracteristicas con Javascript.
En el post anterior hablamos de Inmutabilidad, una característica donde no se mutaban o modificaban los objetos si no que creabamos otros con los cambios aplicados.

Esto es básico por ejemplo para el caso de lo siguiente que queremos hablar que es Funciones puras

Funciones puras

Las funciones puras son aquellas donde los cambios que queramos hacer solo se producen dentro del scope de la función (en su interior básicamente), en ningún caso modifican variables externas. Si tenemos una función que no devuelve nada, que usa this o que no tiene argumentos podemos empezar a sospechar que no es pura. OJO aunque digamos que debemos cumplir este punto en la programación funcional muchas veces no es posible por ejemplo en funciones de IO donde por ejemplo trabajamos con bases de datos, pero en general debemos poder cumplir con este principio.
Veamos un ejemplo del problema y como lo podemos solucionar, seguiremos la teoría del ejemplo anterior

const oldNinjas = ['Ninja1', 'Ninja2']
const newNinja = 'Ninja3'

const addNinja = (actualNinjas, newNinja) => {
  const ninjas = actualNinjas
  ninjas.push(newNinja)
  return ninjas
}

const totalNinjas = addNinja(oldNinjas, newNinja)

console.log(totalNinjas) // result <--["Ninja1","Ninja2","Ninja3"]
console.log(oldNinjas) // result <--["Ninja1","Ninja2","Ninja3"]

Es un ejemplo muy forzado lo sé, pero creo que se puede captar el concepto, aunque le estemos enviando por parámetro los ninjas actuales al ser realmente una referencia modificamos el valor de oldNinjas queramos o no, en este caso vemos como nuestra función no es pura, tiene efectos colaterales que no deseamos. En este caso una posible solución podía ser esta:

const oldNinjas = ['Ninja1', 'Ninja2']
const newNinja = 'Ninja3'

const addNinja = (actualNinjas, newNinja) => {
  return actualNinjas.concat(newNinja)
}

const totalNinjas = addNinja(oldNinjas, newNinja)

console.log(totalNinjas) // result <--["Ninja1","Ninja2","Ninja3"]
console.log(oldNinjas) // result <--["Ninja1","Ninja2"]

Concat es un método de los arrays que realmente no modifica el objeto original, si no que devuelve uno nuevo, con esto cumpliriamos siempre con la funcionalidad en cuestión y no modificamos nada que realmente no queremos. Esta solo es una forma de cumplir con lo que estamos buscando sé que tenemos otras muchas opciones esto es solo a nivel de ejemplo.

Vamos a por la siguiente

Funciones de orden superior

Esto viene también de la mano de las funciones como ciudadanos de primera clase, es decir, las funciones se pueden tratar como cualquier otra variable u objeto del lenguaje y hacen uso de otras funciones.

Estas características estamos usandolas habitualmente en javascript aunque puede que no seamos conscientes del todo, por ejemplo cuando hacemos usos de callbacks o trabajamos con promesas ya estamos haciendo uso de estas características. Veamos un ejemplo sencillo que hace uso de las 2 características

function calculator (number, operation) {
  return operation (number)
}

const plus2 = (number) => {
  return number + 2
}

const squared = (number) => {
  return number**2
}

const twoPlusTwo = calculator(2, plus2)
const threeSquared = calculator(3, squared)

console.log(twoPlusTwo) // 4
console.log(threeSquared) // 9

Como vemos estamos pasando por parámetro las operaciones que queremos realizar y luego ya calculator hace uso de el directamente, creo que más o menos se entiende.

Veamos el siguiente punto.

Currificación y aplicación parcial

A menudo estos conceptos se separan porque puramente hablando no son lo mismo, pero hacen uso de la misma funcionalidad realmente lo que cambia es la manera de aplicarla.
Para hablar de esto (y aunque no quería meterme en tecnicismos) tengo que introducir el concepto de aridad. La aridad de una función básicamente es la cantidad de parámetros que tiene un método:

function calculator (number, operation) // <-- aridad 2

Bien viendo lo que es la aridad ahora veamos una funcionalidad muy util que tenemos con javascript, que viene de la mano de la de las funciones puras. Como hemos comentado ya, es posible que devolvamos funciones y las recibamos por parámetro ¿no?... pues en esto es en lo que se basa tanto la aplicación parcial como la currificación, veamos un ejemplo primero:

function calculator (number) {
  return function (operation) {
    return operation(number)
  }
}

const plus2 = (number) => {
  return number + 2
}

const squared = (number) => {
  return number**2
}

const baseNumber = calculator(2)

console.log(baseNumber) // function (operation) { return operation(number); }

const twoPlusTwo = baseNumber(plus2)
const twoSquared = baseNumber(squared)

console.log(twoPlusTwo) // 4
console.log(twoSquared) // 4

Lo importante ahora mismo es la función calculator, como vemos lo primero que hace la función es devolver otra función, si nos fijamos en el console.log de baseNumber vemos como realmente es una función que espera una operación y simplemente lo que tenemos es ya un número preparado para realizar la operación.

Visto esto veamos lo que es aplicación parcial con un ejemplo

const createPlayer = (name, health) => (weapon) => {
  return {
    name,
    health,
    weapon
  }
}

const playerBase = createPlayer('DevNinja', 1000)

const availableWeapons = ['SuperGun', 'Katana', 'Smoke Grenade']

const playerWeapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]

const player1 = playerBase(playerWeapon)

console.log(player1) // { name:"DevNinja", health:1000, weapon:"Katana" }

Vuelvo a comentar que esto es un poco forzado, pero nos vale para el concepto. Si nos fijamos ahora usamos arrow functions que son más comodas para hacer estas cosas.
Pues bien en este caso createPlayer lo primero recibe el nombre y el valor de salud y se queda "a la espera" de que el sistema le aplique un arma al jugador, esto es lo que seria la aplicación parcial, podemos definirlo como una ejecución de una función por pasos, pero como vemos en este caso tenemos una aridad inicial de 2 y luego de 1, esta situación de aridad es la diferencia principal entre aplicación parcial y currificación. La currificación tendría siempre aridad 1, es decir:

const createPlayer = (name) => (health) => (weapon) => {
  return {
    name,
    health,
    weapon
  }
}

const playerBase = createPlayer('DevNinja')

const playerHealh = Math.floor(Math.random() * 1000)
const playerWithInitialHealh = playerBase(playerHealh)

const availableWeapons = ['SuperGun', 'Katana', 'Smoke Grenade']
const playerWeapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]

const player1 = playerWithInitialHealh(playerWeapon)

console.log(player1) // { name:"DevNinja", health:428, weapon:"Smoke Grenade" }

Ahora tenemos que siempre trabajamos con una aridad de 1, lo que modulariza bastante el uso de nuestro método createPlayer, haciendo posible que hagamos cualquier operación entre cada paso de la función.

Bueno creo que más o menos se entienden estos conceptos, voy un poco rápido con esto porque existen multitud de ejemplos y explicaciones al respecto por internet, si algo no esta claro recomiendo investigar un poco más, en esta serie nos detendremos un poco en conceptos más complejos como los functores y las monadas que son algo más duros para entenderlos.

Nos vemos en el siguiente donde hablaremos de la Composición de funciones un abrazoooooo Ninjaaas

]]>
<![CDATA[JS Algoritmos y estructuras de datos III: Patrón Multiple Pointers]]>https://jlgarcia.fulldev.ninja/js-algoritmos-y-estructurdas/Ghost__Post__5c3e0fda2dd6610fd828cb5bMon, 09 Nov 2020 09:06:00 GMTJS Algoritmos y estructuras de datos III: Patrón Multiple Pointers

Continuemos con nuestra serie sobre algoritmos y estructuras de datos, en este caso vamos a ver el patrón Multiple pointers.

Multiple pointers pattern

Este patrón, pensando en arrays, se basa en crear puntos o valores que corresponden a una posición y moverse al principio, el medio o al final según ciertos condiciones. Este patrón es muy eficiente para reducir al mínimo posible la complejidad espacial.

Pensemos lo primero en un ejemplo donde poder usarlo, vamos a crear un método que dentro de un array ordenado nos devuelva la primera pareja que sume 0.

En un primer momento lo que nos vendría a la cabeza sería hacer 2 loops recorriendo dos veces todo:

function sumZero (arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length; j++) {
      if (arr[i] + arr[j] === 0) {
        return [arr[i], arr[j]]
      } 
    }
  }
}

console.log(sumZero([-2, -1, 0, 1, 2])) // [-2, 2]

Este algoritmo tiene una complejidad:

  • Temporal O(n^2)
  • Espacial O(1)

Que aunque en complejidad espacial no esta mal en temporal se nos puede ir de las manos.

Veamos una solución algo mejor usando este patrón, pero antes de continuar quiero recalcar que esto es posible porque el array esta ordenado, en general muchos de los algoritmos cuentan con esto para poder resolverse (más adelante veremos varios de los mejores patrones de ordenación). Vamos con la solución:

function sumZero (arr) {
  // Establecemos los puntos de inicio (multiple pointers u know)
  let left = 0
  let right = arr.length - 1

  while (left < right) { // para no pasarnos
    let sum = arr[left] + arr[right]

    if (sum === 0) {
      return [arr[left], arr[right]]
    } else if (sum > 0) {
      right--
    } else {
      left++
    }
  }
}

console.log(sumZero([-3, -2, -1, 0, 1, 2, 4])) // [-2, 2]

En esta solución lo que hacemos es ir moviendo nuestros puntos y comprobando si cumple la condición de ser igual a 0. Como es un array ordenado si la suma es mayor de 0 quiere decir que el valor es superior por la derecha del 0 (nuestra variable right) y si el valor es negativo quiere decir que el valor es superior por la izquierda (varible left). Según cual sea de los dos lo que hacemos es ir moviendo nuestros puntos hasta que en algun momento de 0 o hayamos recorrido todos los valores por entre ambos puntos.

Esta solución tiene una complejidad de:

  • Temporal O(n), solo recorremos una vez los valores.
  • Espacial O(1)

Veamos un ejemplo algo más dificil, en este caso queremos comprobar si un string es una subsecuencia de otro, o más bien si aparece como una secuencia de los carácteres de otro pero de manera ordenada, es decir, si word estaría dentro de hello world en orden (no es word pero el orden es primero w y ultimo d)

function isInString (string1, string2) {
  // Establecemos los puntos de inicio (multiple pointers u know)
  let s1 = 0
  let s2 = 0

  while (s2 < string2.length) { // para no pasarnos
    if (string1[s1] === string2[s2]) s1++
    if (s1 === string1.length) return true
    s2++
  }
  return false
}

console.log(isInString('ninja', 'new in jail')) // true si quitamos lo que sobra n__ in ja__

En este caso tenemos dos strings que debemos recorrer y queremos comprobar si el primero string1 es una subsecuencia del segundo string2. En este caso nuestros puntos los situamos al inicio de cada string y vamos recorriendo, si encontramos una coincidencia de la primera posición del string1 en la primera posición del string2, movemos la posición del string1 si no, movemos la posición del string2 y así sucesivamente una vez que hayamos recorrido todo el string1 quiere decir que ya tenemos coincidencia y devolvemos true, en el caso de que la posición de string2 sea mayor que el tamaño del string2 quiere decir que no tenemos coincidencias por lo que devolvemos false.

Este patrón como vemos es muy util para comparar de alguna forma ya sea un elemento o varios y no tener que recorrerlos todos n veces convirtiendo la complejidad temporal en O(n^x) siendo x la cantidad de veces (o la cantidad de elementos) que tenemos que recorrer un elemento (o varios) al completo. En este caso por ejemplo tenemos una complejidad de:

  • Temporal O(n + m). Dos strings que en el peor de los casos recorreremos enteros pero solo una vez cada uno.
  • Espacial O(1)

Hasta aquí lo referente a este patrón de resolución de problemas, nos vemos en el siguienteeee un abraazoooo

]]>
<![CDATA[Javascript Funcional I: Intro e Inmutabilidad]]> En informática, la programación funcional es un paradigma de programación declarativa basado en el uso de verdaderas funciones matemáticas. En este estilo de programación las funciones son ciudadanas de primera clase, porque sus expresiones pueden ser asignadas a variables com]]>https://jlgarcia.fulldev.ninja/javascript-funcional-intro/Ghost__Post__5fa68e1fa6c0f5058bff43acSat, 07 Nov 2020 13:57:05 GMTJavascript Funcional I: Intro e Inmutabilidad

Empezamos nueva serie donde explicaremos los conceptos básicos de la programación funcional orientados al mundo JavaScript.
Empecemos por lo básico, ¿qué es la programación funcional? Citando directamente a la wikipedia

En informática, la programación funcional es un paradigma de programación declarativa basado en el uso de verdaderas funciones matemáticas. En este estilo de programación las funciones son ciudadanas de primera clase, porque sus expresiones pueden ser asignadas a variables como se haría con cualquier otro valor; además de que pueden crearse funciones de orden superior.

Digamos que es un parádigma donde todo esta basado en algún tipo de función o teoría matemática (su centro fué el cálculo lambda) y nos obliga a realizar abstracciones que no son habituales en paradigmas más habituales como la programación orientada a objetos, pero esto no implica que tengamos que ser licenciados en matemáticas para porder usarlo y beneficiarnos de sus ventajas.

Las carácteristicas y ventajas que se suelen comentar sobre este paradigma:

  • En lugar de centrarse en el ¿cómo? se centra en el ¿qué? queremos hacer, lo que hace que nos abstraigamos un poco más del problema que queremos resolver.
  • Como su nombre indica está básado en el trabajo con funciones, practicamente todo es una función, y se tiende a la realización de funciones con un trabajo mínimo y específico, por lo que tendremos funciones pequeñas donde por ejemplo si estamos trabajando en un carrito de la compra existirian métodos como sumar precio o añadir descuento, y estos métodos devolverían siempre lo mismo siempre y cuando sus valores de entrada fueran los mismos. Muchos se preguntaran pero con esto ¿cómo contruimos toda la lógica de un carrito de la compra?... esto se basaría en la composición de funciones, es decir, cogemos esas funciones pequeñas que hemos creado y componemos una lógica que cumpla con nuestras necesidades. Siguiendo con el ejemplo del carrito si se añade algo al carrito de la compra tendriamos un método añadir producto que lo que podría hacer sería incluir producto al listado, sumar precio y aplicar descuento. Esto ya lo veremos más adelante.
  • Usa lo que se conoce como funciones puras, estás funciones se llaman así porque no tienen efectos colaterales, es decir, no cambián ni hacen nada fuera del propio método (incluso un console.log es considerado un efecto colateral), por eso siempre obtenemos el mismo resultado pasandole los mismos parámetros.
  • Al basarse en las funciones puras es un lenguaje muy apto para el paralelismo, ya que no modifica no accede a valores externos no tendremos problemas de race condition o casi cualquier otro que pudiera venir surgido de la concurrencia.
  • Las funciones son ciudadanos de primera clase, son funciones de orden superior. Ambos casos están relacionados, las funciones de orden superior se las llama asi cuando son funciones que actuan directamente sobre otras funciones y entedemos funciones como ciudadanos de primera clase cuando las funciones pueden ser tratadas como cualquier otro elemento del lenguaje, es decir se pueden pasar como parámetros o se pueden devolver funciones directamente como resultado.
  • Hace uso de lo que se conoce como recursividad, es decir, una función se puede llamar a si misma, por ejemplo para recorrer un árbol de directorios, podemos tener un método que sea abrir carpeta y según vaya recorriendo si el fichero en cuestión no es una carpeta lo devuelve y si es una carpeta se llama de nuevo a abrir carpeta y así hasta que se termina el arbol.
  • Todo es inmutable, es decir, no se modifica nada existente se crean elementos nuevos dentro de las propias funciones con los cambios necesarios.
  • Suele ser un código más expresivo
  • Es un código más facilmente testeable
  • Se tiende menos a la repetición de código, ya que todo está planteado como mini funciones con un trabajo en concreto, solo tenemos que reutilizarlas cuando toque o componer funciones que las usen.

Si me pongo podría seguir escribiendo características pero bueno en general creo que nos hacemos una idea de lo que os estoy vendiendo aquí y por mucho que siga hasta que no se vean ejemplos concretos no entenderemos lo que es esto realmente por lo que vamos a empezar hablando de Javascript e Inmutabilidad

Javascript funcional

Primero hablemos un poco del caso concreto de Javascript como lenguaje funcional. Para mi Javascript es de los mejores lenguajes que existen por su versatilidad y sus minimas obligaciones, es decir, podemos hacer lo que nos de la gana. Esto en general suele crear incovenientes porque no tenemos normas que seguir como puede ser establecer un tipo a las variables, como ya sabemos una variable ahora puede ser un número y más tarde convertirse en un string sin ni siquiera hacer un casting de tipos. Esta libertad viene de la mano de multitud de posibles errores si nos descuidamos con estas cosas (que no suelen pasar en otros lenguajes).
Pero gracias a esto podemos programar como más nos convenga o nos guste y combinar soluciones de distintos tipos para nuestro beneficio.
Como sabemos en Javascript practicamente todo es un tipo Object o parte de un tipo Object, incluso las funciones, esto nos permite usar las funciones como cualquier otro objeto: asignarlo a variables, pasarlas por parámetro, devolver funciones como resultado.... como hemos dicho al principio esto es lo básico para que un lenguaje sea funcional por lo que ya tenemos esta posibilidad, veamos como podemos cumplir con las carácteristicas básicas de un lenguaje funcional: Inmutabilidad, Funciones puras, Funciones de orden superior, Uso de currificación y composición de funciones

Inmutabilidad

Veamos lo primero el problema en cuestión

const oldNinjas = ['Ninja1', 'Ninja2']
const newNinja = 'Ninja3'

const totalNinjas = oldNinjas
totalNinjas.push(newNinja)

console.log(oldNinjas) <-- Result: [ "Ninja1", "Ninja2", "Ninja3" ]

Como vemos se ha modificado oldNinjas también, esto es funcionamiento interno de Javascript, digamos que lo que tenemos dentro de totalNinjas es una referencia en memoria a oldNinjas por eso hagamos lo que hagamos con totalNinjas se verá reproducido en oldNinjas, ojo que es un ejemplo un poco forzado para ver el caso concreto, con un oldNinjas.concat no tendriamos este problema.

Suponiendo que tenemos este problema tenemos varias opciones para resolverlo, la primera hacemos un freeze sobre oldNinjas para que no se pueda modificar hagamos lo que hagamos (nos daria error) y a continuación realizamos una copia del array:

const oldNinjas = Object.freeze(['Ninja1', 'Ninja2'])
const newNinja = 'Ninja3'

const totalNinjas = [...oldNinjas] // Copiamos
totalNinjas.push(newNinja)

console.log(totalNinjas) // result --> [ "Ninja1", "Ninja2", "Ninja3"]
console.log(oldNinjas) // result --> [ "Ninja1", "Ninja2"]

Esta sería una opción para cumplir con este principio, otra es usar alguna librería funcional que haga esto por nosotros, a mí la que más me gusta es Ramda, es muy eficiente y cumple más o menos bien con los requerimientos funcionales

const R = require('ramda')

const oldNinjas = Object.freeze(['Ninja1', 'Ninja2'])
const newNinja = 'Ninja3'

const totalNinjas = R.append(newNinja, oldNinjas)

console.log(totalNinjas) // result --> [ "Ninja1", "Ninja2", "Ninja3"]
console.log(oldNinjas) // result --> [ "Ninja1", "Ninja2"]

Esto es un ejemplo de como podemos cumplir con el principio de inmutabalidad necesario en la programación funcional.

Creo que por el momento es suficiente para un único post, continuaremos con los siguientes conceptos en proximos capítulos, cuadaaaos y un abrazooooooo

]]>
<![CDATA[MongoDB Index Ninja 2.0]]>https://jlgarcia.fulldev.ninja/mongodb-ninja-iv-index-ninja-2-0/Ghost__Post__5e19a48ee3717304bd77a170Sun, 12 Jan 2020 18:27:09 GMTMongoDB  Index Ninja 2.0

Antes de nada tenemos un repo con un script para insertar los documentos que usamos de ejemplo:

Mongo_index_explain

En general sabemos que es un índice de mongo pero hablemos un poco más en profundidad. Si no índicamos un índice cuando creamos una colección mongo, por defecto, nos creará un índice con el campo _id de tipo único (no se puede repetir el campo).

Cuando creamos un índice mongo crea otra especie de mini colección solo con los datos que le indicamos en el índice, ordenados de la manera que le indiquemos y apuntando al documento al que hacen referencia. Cada índice tiene una especie de firma, es decir, se le asigna un nombre para identificarlo usando los campos del índice y mongo recorre su lista de indices hasta que encuentra una coincidencia, es decir, si buscamos por dos campos intentará encontrar uno donde coincida la firma de esos campos y si no busca separando los campos hasta que encuentra una coincidencia, si no la encuentra continuará con una búsqueda sin índice.
Pero antes de ver esto vayamos paso a paso.
Mongo sigue la misma teoría que el resto de bases de datos con sus índices, usa lo que se conoce como Binary Tree Sort, es un tipo de algoritmo de búsqueda bastante eficiente para estas cosas Más info

Sintaxis básica de creación de índices

Antes de ver los tipos de índices la sintaxis básica de creación de índices es la siguiente

db.getCollection('index').createIndex({lastName: -1})

Siendo lastName el campo del que queremos el índice y -1 es la ordenación si lo queremos ascendente pondremos un 1, si lo queremos descendente -1

Más adelante veremos las opciones de creación de los índices.

Tipos de índices

Single field (un solo campo)

Como dice el nombre tenemos un tipo de índice donde lo creamos solo con un campo, es decir:

db.getCollection('index').createIndex({lastName: 1})

Veamos un ejemplo con la diferencia de tener un índice a no tenerlo. En la colección de prueba que tenemos vamos a realizar un búsqueda por lastName y veamos lo que tarda. Busquemos con el siguiente comando primero en la colección sin índice:

db.getCollection('noindex').find({ lastName: 'Master'}).explain("executionStats")

Esto nos da que nos ha traido 10 elemento en un tiempo de 4190 ms

MongoDB  Index Ninja 2.0

Ahora vamos a crear un índice en la colección para ello con este comando:

db.getCollection('index').createIndex({ lastName: 1 })

(más adelante veremos como ver los índices que tenemos y cosas así)

Ahora ejecutemos la misma búsqueda pero dentro de la colección con índice que acabamos de crear:

db.getCollection('index').find({ lastName: 'Master'}).explain("executionStats")

Esto nos da que nos ha traido 10 elementos en un brutal tiempo de 10ms la primera vez y las siguientes a 0ms

MongoDB  Index Ninja 2.0

Ahora se puede ver un poco la potencia que nos proveen los índices.

Veamos otro ejemplo que no va también, vamos a crear primero un índice de un campo con esto:

db.getCollection('index').createIndex({ city: -1 })

Tenemos una colección donde la gran mayoría de los 2000000 de elementos tienen la misma ciudad, por lo que ahora pasa una cosa curiosa, veamos la búsqueda con indice

db.getCollection('index').find({ city: 'Madrid'}).explain("executionStats")

Tarda unos 8873ms, ahora veamos la búsqueda normal

MongoDB  Index Ninja 2.0

db.getCollection('noindex').find({ city: 'Madrid'}).explain("executionStats")

Sorprendentemente tarda 3963ms mucho menos

MongoDB  Index Ninja 2.0

Pero porqué?? Bueno el algoritmo de búsqueda binaria que tienen los índices no es eficiente para este tipo de colecciones donde la mayoria de elementos son iguales, para que este algoritmo haga su magia necesitamos que los elementos a buscar sean la mayoría diferentes, por eso en este caso es más eficiente la búsqueda habitual donde recorre todos y descarta los que no necesita.

Si tenemos esta casuística donde un montón de campos son iguales lo más probable es que filtremos por algún campo más, es decir que creemos un índice de tipo compound (los veremos a continuación), pero si quisieramos realizar una busqueda sin índice, en mongo se conoce como búsqueda natural y aunque no se suele comentar esto mucho por los mares digitales, tenemos la posibilidad de forzarlo, solo tenemos que sugerirlo de esta manera:

db.getCollection('index').find({ city: 'Madrid'}).hint({ $natural : 1 }).explain("executionStats")

hint es uno de los extras que podemos indicar en las busquedas para sugerir un índice u otro según el nombre, podemos ponerlo de la misma manera que pusimos al crear el índice:

db.getCollection('index').find({ city: 'Madrid'}).hint({ city: -1 }).explain("executionStats")

O con el nombre de índice que se crea:

db.getCollection('index').find({ city: 'Madrid'}).hint("city_-1").explain("executionStats")

Los podemos ver por ejemplo desde robo3t

MongoDB  Index Ninja 2.0

Visto este caso especial pasemos a los índices compuestos donde podemos ver búsquedas eficientes por varios campos

Compound fields (compuesto....o básicamente varios campos)

Los siguientes índices son los que involucran a varios campos dentro de nuestras colecciones, estos siguen un orden según como los escribamos, la sintaxis básica sería:

db.getCollection('index').createIndex({ field1: 1 , field2: -1})

Lo que hace este índice es crear un índice partiendo principalmente del field1 con un orden ascendente y dentro de los field1 que coincidan los ordena según el field2 de manera descendente.

En nuestro caso crearemos un índice por ciudad e email, recordemos que en ciudad tenemos 1999000 veces la misma ciudad y antes ha tardado mogollón

db.getCollection('index').createIndex({ city: -1, email: 1 })

Ahora veamos la búsqueda sín indice:

db.getCollection('noindex').find({ city: 'Madrid', email: 'NinjaMaster@email.com'}).explain("executionStats")

Ha tardado 5112ms que no está nada mal pensando en la búsqueda anterior, pero ahora veamos la búsqueda con el índice

MongoDB  Index Ninja 2.0

db.getCollection('index').find({ city: 'Madrid', email: 'NinjaMaster@email.com'}).explain("executionStats")

Sorprendetemente ha tardado solo 12ms

MongoDB  Index Ninja 2.0

Cosas que tenemos que tener en cuenta de los índices compuestos:

  • Tienen un límite de 32 campos
  • En un principio el orden en que definamos el índice no influye demasiado (menos en los $fullText que usaría como filtro inicial para hacer el de texto) a la hora de buscar pero si a la hora de ordenar nosotros añadiendo un sort a continuación del find. Partiendo del índice que hemos definido anteriormente no seria lo mismo hacer esto:
db.getCollection('index').find({ city: 'Madrid', lastName: 'Master', email: 'NinjaMaster@email.com'}).sort({ city: -1, email: 1  }).explain('executionStats')

Que hacer esto:

db.getCollection('index').find({ city: 'Madrid', lastName: 'Master', email: 'NinjaMaster@email.com'}).sort({ email: 1, city: -1  }).explain('executionStats')

O esto:

db.getCollection('index').find({ city: 'Madrid', lastName: 'Master', email: 'NinjaMaster@email.com'}).sort({ city: 1, email: 1  }).explain('executionStats')

La primera usa el índice directamente y no tiene que hacer nada más:

MongoDB  Index Ninja 2.0

Las otras dos pasan del índice para hacer la ordenación, la segunda porque directamente ordenamo primero por email y la segunda porque no es ninguna de las opciones factibles en cuanto a dirección un índice usa solo dos tipos de ordenaciones: la misma con la que se ha definido y la que es directamente opuesta, es decir, si definimos un índice con 1,-1,1, usaría el índice para ordenar bajo esa definición o con -1,1-1

MongoDB  Index Ninja 2.0

Multikey Index (Indice con arrays en resumen)

Este tipo de índice lo crea automáticamente mongo al detectar un campo de tipo array, en un principio podemos pensar que no tiene mucho de especial, pero realmente tenemos que tener en cuenta que el índice creará una entrada por cada elemento del array. Supongamos que además de ciudades tenemos un array de poblaciones donde por ejemplo puede trabajar ese usuario, si alguien de Madrid tiene de poblaciones disponibles Coslada, Vicalvaro, Mordor.... nos creara:

Madrid + Coslada
Madrid + Vicalcaro
Madrid + Mordor
...


Esto es importante a la hora de controlar el tamaño del índice, los índices ocupan espacio.....por si no lo habías pensado y también consumen al rehacerse, cada vez que se escribe se vuelve a indexar

A tener en cuenta:

  • Solo podemos tener índices con uno de los valores de tipo array, si intentamos tener dos nos dirá que nos dediquemos a la pintura
  • Si tenemos un índice con array e intentamos insertar en otro de los campos del índice un array nos dirá otra vez que lo nuestro es el arte
  • Podemos crear índices con campos con arrays de documentos, que funcionarían igual que los arrays normales si le indicamos uno de los campos del documento
db.getCollection('index').createIndex({ 'tags.front': -1 })
db.getCollection('index').find({ 'tags.front': 'Vuejs' }).explain('executionStats')

MongoDB  Index Ninja 2.0

Si no le indicamos un campo en concreto nos creará un índice pero solo funcionara con documentos completos, no con campos específicos. Borramos el índice anterior y creamos este:

db.getCollection('index').createIndex({ 'tags': -1 })

Ahora realizamos la misma búsqueda anterior:

db.getCollection('index').find({ 'tags.front': 'Vuejs' }).explain('executionStats')

MongoDB  Index Ninja 2.0

Lo notamos con el tiempo que tarda pero podemos vemos como hace la búsqueda básica con COLLSCAN.
Sin embargo si búscamos un documento al completo:

db.getCollection('index').find({ 'tags': {
					front: 'Vuejs',
					back: 'Node'
				} }).explain('executionStats')

Vemos como tarda bastante menos y además nos indica que ha usado un índice

MongoDB  Index Ninja 2.0

Indice Full text(o campos con un montón de texto)

El índice de texto es un tipo de indice especial que nos mejora la búsqueda con un conjunto de palabras sobre un número n de campos, es decir, nos busca en todos los campos que tenga el índice. Este índice se puede aplicar en cualquier campo de tipo string o de array con valores de tipo string, donde podemos indicarlo el idioma en cuestión para que ignore palabras comunes como 'y, o, de' en español.
Por defecto se crea con idioma inglés por lo que en nuestro caso lo añadiremos a la hora de la creación del índice.

Para hacernos una idea de que tiene de especial, los índices habituales lo que hacen es crearlo basandose en esto que buscas está en estos documentos, sin embargo este tipo de índice lo que hace es darle la vuelta este documento tiene estas palabras

En este caso los ejemplos son un poco más complicados, las búsquedas especiales por un texto solo funcionan si tenemos un índice, por lo que , por ejemplo, en la colección noindex no podremos hacer esta búsqueda, lo más parecido sería una expresión regular con un find sobre los campos que quisieramos realizar la búsqueda y evidentemente tardará un poco más, veamos un ejemplo. Para la pruebas tener en cuenta que no seria lo mismo que un entorno de producción esto es solo para entender los conceptos, en un entorno real puede tardar más o menos la búsqueda sin índice:

db.getCollection('noindex').find({ firstName: /Juanchu/,
				alias: /Juanchu/,
				bio: /Juanchu/ }).explain("executionStats")

Esto nos da un tiempo de 4429ms que no está nada mal.
Creemos ahora un índice con los campos en formato texto:

db.getCollection('index').createIndex({ bio: 'text', firstName: 'text', alias: 'text' },  { default_language: "spanish" })

Esto tardará un montón, es un índice que tiene que comprobar todas las palabras que tienen nuestros campos de texto, y si no le ponemos el idioma bien....pues más todavía porque almacenará palabras innecesarias.

Ahora que ya tenemos el índice busquemos el mismo concepto:

db.getCollection('index').find({ $text: { $search: "Juanchu" } }).explain("executionStats")

Ahora vuelve a tardar 1ms, bastante mejor ¿no?. En un entorno con más campos se notaría más la diferencia

MongoDB  Index Ninja 2.0

Para que veamos lo que nos ha encontrado

MongoDB  Index Ninja 2.0

Con esto tenemos un ejemplo, veamos ahora que epecialidades tenemos con este tipo de índice:

  • SOLO ES POSIBLE TENER UN ÍNDICE DE TIPO TEXT, es decir, si queremos añadir campos de texto, tenemos que eliminar el que tuvieramos y hacerlo de nuevo.
  • Es CASE-INSENSITIVE
  • Se le pueden indicar pesos de importancia a cada campo, esto sumará las veces que aparece la palabra que buscamos en cada campo y documento y nos los devolverá según ese orden si se lo indicamos en la busqueda.
    Creación:
db.getCollection('index').createIndex(
   {
     alias: "text",
     bio: "text",
     firstName: "text"
   },
   {
     weights: {
       firstName: 10,
       alias: 5
     }
    },
    { default_language: "spanish" }
 )

Esto crearía un índice con los pesos:
* firstName 10
* alias 5
* bio 1 (by default)


db.getCollection('index').find(
   { $text: { $search: "Juanchu" } },
   { score: { $meta: "textScore" } }
).sort( { score: { $meta: "textScore" } } )

MongoDB  Index Ninja 2.0

  • Los Wildcard indices o índices creados según un patrón de posibles campos, aquí solo podemos ponerlo una vez y lo que haría sería un índice con todos los campos de tipo string (más adelante veremos esto de los wildcard)
  • Sí no queremos hacer que ignore las palabras tipo y, o, de que las usa como delimitadores para conjuntos, podemos índicarle language: "none"
  • No se puede indicar hint para sugerir un índice a la hora de realizar búsquedas tipo text
  • En los índices compuestos podemos tener varios campos de tipo text pero solo podemos tener el resto de campos tipo single, es decir, no podemos tener multikeys o geospatial
  • Crear este tipo de índice es muy costoso a nivel de recursos, tanto en ram como en espacio y puede hacer más lenta la inserción de nuevos campos que esten bajo ese índice.

Índice de texto multiidioma

Esta parte require su propio título ya que tiene un poco de miga. Podemos tener colecciones en multiples idiomas y para mejorar la búsqueda bajo estos campos podemos modificar un poco los campos del documento para que mongo haga su magia con las búsquedas, añadiendo solo la traducción pertinente según el idioma que le indiquemos en el índice.
Para esto tenemos que tener un campo language donde indiquemos el lenguaje en cuestión del documento o campo, así a la hora de hacer el índice mongo sabra mejor que tiene que hacer con ese campo. Un ejemplo de documento podría ser este:

{
   _id: 1,
   language: "portuguese",
   original: "A sorte protege os audazes.",
   translation:
     [
        {
           language: "english",
           quote: "Fortune favors the bold."
        },
        {
           language: "spanish",
           quote: "La suerte protege a los audaces."
        }
    ]
}

Si quisieramos tener otro campo como indicador de lenguaje lo podemos indicar a la hora de crear el indice:

db.quotes.createIndex( { quote : "text" },
                       { language_override: "idioma" } )

Wildcard Index (indices sin saber los campos...)

Esto no es muy dificil de entender, queremos crear índices donde el nombre de los campos puede ser dinámico, es decir, puede ir cambiando como puede ser un campo con metadatos que pueden ir variando, en este caso por ejemplo creariamos un índice así:

db.getCollection('index').createIndex( { "user_metadata.$**" : 1 } )

Esto nos crearía un indice teniendo en cuenta que dentro de user_metadata podemos tener de todo tipo de campos:

{
    user_metadata: {
        "clicks": 200,
        "views": 1000
    }
},
{
    user_metadata: {
        "images": 500,
        "favorite_tags": ["games", "dogs"]
    }
}

Si queremos crear un índice por todos los posibles campos, simplemente

db.getCollection('index').createIndex( { "$**" : 1 } )

Un índice de tipo wildcard recorrera todos los nested documents que se encuentre

También es posible indicar este tipo de índice en campos específicos

db.collection.createIndex(
  { "$**" : 1 },
  { "wildcardProjection" :
    { "user_metadata" : 1, "games.rewards_info" : 1 }
  }
)

Para hacerlo usamos wildcardProjection como indicador de lo que queremos hacer. Esto por ejemplo nos haría un índice con todo lo que estuviera dentro de esos dos campos.

También podemos excluir campos:

db.collection.createIndex(
  { "$**" : 1 },
  { "wildcardProjection" :
    { "user_metadata" : 0, "games.rewards_info" : 0 }
  }
)
  • Por defecto este tipo de índice omite el campo _id, si queremos añadirlo solo tenemos que indicarlo dentro de wildcardProjection
  • No es posible crear indices compuestos usando wildcard
  • No pueden ser indices únicos ni tener TTL
  • No pueden ser Geoespaciales o Hashed
  • Si queremos ordenar con este tipo de índice solo podemos hacerlo usando el campo mediante el que busquemos, es decir, si usamos user_metadata.images para buscar solo podremos hacer un sort en mongo usando ese campo.
  • No indexan campos vacios, es decir los ignoran y los guardan en el indice (son lo que se conoce como SPARSE)
  • No podemos hacer búsquedas haciendo coincidir un array al completo, solo campos sueltos
  • No podemos hacer búsquedas con un not equal null porque no tiene esos elementos y no comprende que hacemos.

HASHED INDEX

En estos índices lo que hace Mongo es convertir el valor del campo en un hash único y lo almacena. Este formato puede ser útil, en general para ahorrar espacio o para temas de sharding (comprime los documentos hijos en un hash), ya que en lugar de almacenar el campo almacena un hash. Luego Mongo por si mismo hace su magia y cada vez que busques va convirtiendo el directamente el valor de los campos, es decir, nosotros no tenemos que hacer nada.

db.getCollection('index').createIndex({ alias: 'hashed' })

Importante:

  • No admiten búsquedas por rango solo exactas
  • No admiten coumpound index

OTROS INDICES

  • 2D: Indices basados en coordenadas de planos 2d
  • 2Dsphere: Indices en formato Tierra, es decir, pone un plano en formato esfera para gestionar las coordenadas
  • geoHaystack: Indices especializados en planos 2d de tamaño pequeño

PROPIEDADES DE LOS INDICES

  • TTL: Propiedad de single field index donde indicamos un tiempo de vida al índice para que elimine los documentos de la colección, util para colecciones de logs y cosas así. El valor que indiquemos no se puede cambiar, para cambiarlo tenemos que borrar el indice y crearlo de nuevo
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )

También es posible indicar una hora de expiración si el propio campo es de tiempo

db.log_events.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )

Esto haría que este documento:

db.log_events.insert( {
   "expireAt": new Date('July 22, 2013 14:00:00'),
   "logEvent": 2,
   "logMessage": "Success!"
} )

Desapareciera a esa hora.

  • Unique: Campos o combinaciones de campos que no se pueden repetir
db.members.createIndex( { "user_id": 1 }, { unique: true } )

db.members.createIndex( { groupNumber: 1, lastname: 1, firstname: 1 }, { unique: true } )

Este índice si no existe un campo lo guardará como null y no se podrá repetir

  • Partial Index: Podemos crear indices que solo esten referidos cuando se haga un tipo de filtro, es decir, que solo se indexará algo si el documento cumple con el filtro que se le indica, es decir, si creamos este índice:
db.restaurants.createIndex(
   { cuisine: 1, name: 1 },
   { partialFilterExpression: { rating: { $gt: 5 } } }
)

Solo indexará cuando rating sea mayor que 5 (pero solo mayor que 5, no vale luego buscar por un mayor de 8), esto hace que consuma menos recursos el indice.

  • Case Insensitive: Podemos crear indices con case insensitive, indicando el locale y el strength que queremos que use para la parte insensitive
db.fruit.createIndex( { type: 1},
                      { collation: { locale: 'en', strength: 2 } } )

Para ver la diferencia en la comparación Pulsa aquí

  • SPARSE INDEX: Con la opción sparse le indicamos que no guarde en el índice los documentos que no contengan el campo que le indicamos.
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )

Tenemos que tener cuidado porque este tipo de indice puede no devolver todos los resultados, si usamos un filtro que contenga xmpp_id nos ignorará los que no lo tengan y si buscamos también por otro campo no nos aparecerá

  • Index intersection: Intersección de índices, resumiendo esta funcionalidad, si mongo cree que funciona puede hacer uso de dos indices para hacer una búsqueda. Por poner un ejemplo, si tenemos dos single index uno con ciudad y otro con población, si buscamos por ambos puede mezclar los indices para mejorar la búsqueda, pero solo la búsqueda no lo usaria por ejemplo si buscamos por ciudad y luego queremos hacer un sort por población.

  • background: Propiedad recomendada para que la colección no se bloquee mientras se rehacen los indices (aún así ojo con los campos que se tenga prevista un nivel alto de escritura)

db.addresses.createIndex( { "xmpp_id": 1 }, { background: true } )

Trabajando con indices

Ver indices de una colección

db.getCollection('index').getIndexes()

Esto nos muestra algo similar a esto:

[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "indexninja.index"
    },
    {
        "v" : 2,
        "key" : {
            "_fts" : "text",
            "_ftsx" : 1
        },
        "name" : "TextIndex",
        "ns" : "indexninja.index",
        "weights" : {
            "alias" : 5,
            "bio" : 1,
            "firstName" : 10
        },
        "default_language" : "english",
        "language_override" : "language",
        "textIndexVersion" : 3
    },
    {
        "v" : 2,
        "key" : {
            "lastName" : -1.0
        },
        "name" : "lastName_-1",
        "ns" : "indexninja.index"
    },

Con la type_version del indice, la key por la que se ha realizado, el name que tiene el indice y el ns, es decir, la colección a la que pertenece

Eliminar un índice

db.getCollection('index').dropIndex("lastName_-1")
db.getCollection('index').dropIndex( { lastName: -1 } )

Esto nos devolvería algo similar a esto:

/* 1 */
{
    "nIndexesWas" : 5,
    "ok" : 1.0
}

Donde nos índica el número de indices que coincidian con esa condición y el resultado

Eliminar todos(o varios indices)

Para eliminar todos los indices:

db.getCollection('index').dropIndexes()

Y a partir de la versión 4.2 de mongo podemos indicarle un array con los nombres de los indices que queremos eliminar

db.getCollection('index').dropIndexes(["TextIndex", "lastName_-1"])

Ver el uso actual de los indices

db.getCollection('index').aggregate( [ { $indexStats: { } } ] )

Con esto podemos ver si se usan mucho o poco los indices actuales. Esto nos devuelve algo similar a esto:

/* 1 */
{
    "name" : "TextIndex",
    "key" : {
        "_fts" : "text",
        "_ftsx" : 1
    },
    "host" : "83165c61f9de:27017",
    "accesses" : {
        "ops" : NumberLong(2),
        "since" : ISODate("2020-01-12T11:18:17.642Z")
    }
}

/* 2 */
{
    "name" : "_id_",
    "key" : {
        "_id" : 1
    },
    "host" : "83165c61f9de:27017",
    "accesses" : {
        "ops" : NumberLong(0),
        "since" : ISODate("2020-01-12T10:22:38.796Z")
    }
}

También podemos ver que se ha usado en cada query que realizamos con el comando que estamos usando continuamente (además de mogollón de información relativa a la query)

db.getCollection('index').find({ city: 'Madrid', email:'NinjaMaster@email.com' }).explain("executionStats")

Y si por ejemplo queremos ver info extra cuando tenemos una busqueda con multiples condiciones, como un poco el orden de filtro que ha seguido con:

db.getCollection('index').find({ city: 'Madrid', email:'NinjaMaster@email.com' }).explain("allPlansExecution")

Esta query ademas de lo anterior nos devuelve:

"allPlansExecution" : [ 
            {
                "nReturned" : 5,
                "executionTimeMillisEstimate" : 0,
                "totalKeysExamined" : 5,
                "totalDocsExamined" : 5,
                "executionStages" : {
                    "stage" : "FETCH",
                    "nReturned" : 5,
                    "executionTimeMillisEstimate" : 0,
                    "works" : 6,
                    "advanced" : 5,
                    "needTime" : 0,
                    "needYield" : 0,
                    "saveState" : 0,
                    "restoreState" : 0,
                    "isEOF" : 1,
                    "docsExamined" : 5,
                    "alreadyHasObj" : 0,
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "nReturned" : 5,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 6,
                        "advanced" : 5,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "keyPattern" : {
                            "city" : -1.0,
                            "email" : 1.0
                        },
                        "indexName" : "city_-1_email_1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "city" : [],
                            "email" : []
                        },
                        "isUnique" : false,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "city" : [ 
                                "[\"Madrid\", \"Madrid\"]"
                            ],
                            "email" : [ 
                                "[\"NinjaMaster@email.com\", \"NinjaMaster@email.com\"]"
                            ]
                        },
                        "keysExamined" : 5,
                        "seeks" : 1,
                        "dupsTested" : 0,
                        "dupsDropped" : 0
                    }
                }
            }, 
            {
                "nReturned" : 0,
                "executionTimeMillisEstimate" : 0,
                "totalKeysExamined" : 6,
                "totalDocsExamined" : 6,
                "executionStages" : {
                    "stage" : "FETCH",
                    "filter" : {
                        "email" : {
                            "$eq" : "NinjaMaster@email.com"
                        }
                    },
                    "nReturned" : 0,
                    "executionTimeMillisEstimate" : 0,
                    "works" : 6,
                    "advanced" : 0,
                    "needTime" : 6,
                    "needYield" : 0,
                    "saveState" : 0,
                    "restoreState" : 0,
                    "isEOF" : 0,
                    "docsExamined" : 6,
                    "alreadyHasObj" : 0,
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "nReturned" : 6,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 6,
                        "advanced" : 6,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 0,
                        "keyPattern" : {
                            "city" : -1.0
                        },
                        "indexName" : "city_-1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "city" : []
                        },
                        "isUnique" : false,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "city" : [ 
                                "[\"Madrid\", \"Madrid\"]"
                            ]
                        },
                        "keysExamined" : 6,
                        "seeks" : 1,
                        "dupsTested" : 0,
                        "dupsDropped" : 0
                    }
                }
            }
        ]

Recomendable que los índices entren en la RAM, asi nos evitamos tener que tirar de disco para consultarlos

Para ver lo que ocupan los indices solo tenemos que lanzar el comando

db.getCollection('index').totalIndexSize()

El resultado está en bytes

Ver toda la info posible de una colección

db.getCollection('index').stats()
]]>
<![CDATA[JS Algoritmos y estructuras de datos II: Resolución de problemas. Patrón Frequency Counter]]>https://jlgarcia.fulldev.ninja/js-algorithms-and-data-structures/Ghost__Post__5c1154572dd6610fd828cab0Tue, 15 Jan 2019 17:48:01 GMTJS Algoritmos y estructuras de datos II: Resolución de problemas. Patrón Frequency Counter

Antes de empezar a ver algoritmos complejos o las estructuras de datos entremos un poco en el área de la resolución de problemas que, aunque no te lo creas, existen algunos patrones típicos que podemos usar para resolver problemas y que pueden ser bastante útiles en nuestro dia a dia o por lo menos a la hora de hacer entrevistas. Aquí veremos algunos de estos patrones para que nos hagamos una idea y luego simplemente sería ir buscando más. Empecemos.

Resolución de problemas

Antes de entrar a ver patrones, vamos a comentar un poco algunos conceptos importantes.
Empecemos por preguntarnos, ¿qué es un algoritmo?... Por ponerlo simple podemos definirlo como un proceso o serie de pasos que tenemos que seguir para completar una tarea específica o para resolver un problema (algo así podría valer ¿no?).
Y esto ¿por qué es importante?... si lo pensamos detenidamente los desarrolladores realmente lo que hacemos es resolver problemas (muchos de estos los generamos nosotros mismos pero eso es otro tema) y aunque en nuestro día a día realmente puede que no necesitemos usar procesos complejos para nuestro trabajo, siempre estamos haciendo uso de algoritmos y como casi siempre en programación, alguien ya se ha encontrado con el problema que tenemos actualmente y puede ser que exista una manera mejor o más eficiente para resolver un problema en el que, por ejemplo, estemos usando bucles anidados o cosas similares (ya hemos visto que eso es kaka). Básicamente podemos entender que los algoritmos son como nuestras bases para ser mejores programadores y resolutores de problemas. También que aunque en vuestro dia a dia al final no hagáis uso de esto, siempre os podrá venir bien para las entrevistas o para las pruebas técnicas, que ahora cada vez más están recurriendo a este tipo de pruebas y muchas contienen problemas que podéis resolver con este tipo de patrones.

Best Practices

Como en la mayoría de entornos a la hora de resolver problemas tenemos unas best practices reconocidas (que realmente no usamos) y que podemos usarlas en última instancia si no somos capaces de resolver el problema que tenemos entre manos. Esto sería lo que llamaríamos Idear un plan de resolución de problemas, luego tenemos la otra opción que es dominar los patrones de resolución de problemas y aplicarlos directamente (ya llegaremos a esto demomoento empecemos con nuestro plan).

Pasos básicos para resolver un problema

Estos serían los pasos básicos que podemos seguir para resolver un problema:
1. Entender realmente el problema
2. Revisar ejemplos concretos de este problema
3. Descomponer el problema en problemas más pequeños y asumibles
4. Resolver o simplificar
5. Revisar y refactorizar




1. Entender el problema
Para ver si realmente entendemos el problema podemos hacernos algunas preguntas básicas:
* ¿Puedo replantear el problema con mis propias palabras?
* ¿Cuáles son realmente los inputs?
* ¿Cuáles son los outputs que debería devolver la solución del problema?
* ¿La salida puede ser determinada con los elementos que recibe el problema?¿Tenemos toda la información que necesitamos para resolver el problema?
* ¿Como debo identificar los datos importantes que son parte del problema?





Lo primero seria resolver preguntas de este tipo para poder hacernos una idea de lo que necesitamos y donde tenemos las mayores dificultades con este problema.

2. Buscar ejemplos
Si no tenemos ejemplos con la entrada y los resultados que deberíamos obtener, podemos tener tests o historias de usuario, pero al final, necesitamos algún tipo de pista de que es lo que necesitamos conseguir.
A continuación podemos plantearnos empezar creando nosotros mismos ejemplos simples (podemos incluso simplificar un poco el problema ignorando algunas de sus condiciones) e ir avanzando añadiendo dificultad hasta tener toda la complejidad que el problema realmente tiene. OJO no se nos pueden olvidar ejemplos con entras vacías o erroneas.

3. Descomponer el problema
En este punto debemos escribir los pasos que necesitamos tomar para resolver el problema, y sí escribir aunque parezca una tontería esto nos ayuda a plantear la resolución del problema correctamente viendo posibles fallos en nuestro planteamiento antes de escribir una sola línea de código. Al escribir los pasos, los estamos descomponiendo en otros más pequeños que son más sencillos de enfrentar.

4. Resolver el problema/Simplificando el problema
Ahora llega el punto de realmente resolver el problema. A la hora de intentar resolverlo nos daremos cuenta donde realmente estará el núcleo de la difilcutad que tenemos para llegar a revolver el problema. En este punto lo que debemos hacer es ignorar esa dificultad, es decir, simplificamos el problema y lo resolvemos de esta manera. Una vez que esté resuelto volvemos a incorporar esa dificultad a nuestra solución.
Aunque no lo parezca, este proceso ayuda muchas veces a resolver nuestro problema.

5. Revisar y refactorizar
En este punto lo que tendríamos que hacer sería comprobar, lo primero, si nuestra solución realmente funciona en todos los casos y luego ya hacernos preguntas de este estilo:
- ¿Podemos llegar a la misma solución de otra manera?
- ¿Podemos usar este algoritmo para resolver otros problemas?
- ¿Podemos mejorar el rendimiento de nuestra solución?
- ¿Cómo han resuelto el problema otros desarrolladores?




Con un plan o estrategia similar a estos 5 puntos podemos acercarnos más a una solución a nuestro problema.

Y a continuación esta estrategia la complementaremos con nuestro conocimiento en algunos patrones típicos de resolución de problemas.

Common problem solving patterns

Existen multitud de patrones de resolución de problemas:
- Frequency Counter
- Multiple Pointers
- Sliding Window
- Divide and Conquer
- Dynamic Programming
- Greedy Algorithms
- ....






Aquí comentaremos algunos que están en el nivel correcto sencillez/utilidad pero en esta página GeeksForGeeks tenemos todos los patrones o algoritmos que podamos necesitar (es muy completa).

Vamos a empezar viendo el que se conoce como Frequency Counter

Patrón FREQUENCY COUNTER

Este patrón es relativamente sencillo y tiene una función muy específica "Contar las veces que algo se repite o aparece". En un primer momento podemos pensar en la utilidad de esto, veamoslo con un ejemplo.

Queremos un método que compruebe si un string es un anagrama de otro, es decir, queremos saber si ambos strings se pueden formar con las mismas letras. Una forma fácil es contar las veces que existe cada letra en ambos strings y comparar, veremos dos acercamientos posibles de este patrón

function validAnagram(string1, string2){
    
    //Comprobamos si el tamaño es el mismo,si no, terminamos antes de empezar
    if (string1.length !== string2.length){
        return false;
    }
    
    const frequency1 = {};
    const frequency2 = {};
    //Recorremos ambos strings y guardamos en un objeto siendo la letra la key y el valor el número de veces que aparece
    for (let char of string1){
        frequency1[char] = ++frequency1[char] || 1;
    }
    
    for (let char of string2){
        frequency2[char] = ++frequency2[char] || 1;
    }

    //Recorremos y comprobamos. Si en algún caso las cantidades no coinciden entonces no es un anagrama uno de otro
    for (let char in frequency1){
        
        if(frequency1[char] !== frequency2[char]){
            return false;
        }
    }
    return true;
  }

Esta es una solución válida, que excepto que tenemos 3 búcles for, no está mal. Veamos otro posible acercamiento en este caso son solo 2 búcles

function validAnagram(string1, string2){
    //Comprobamos si el tamaño es el mismo,si no, terminamos antes de empezar
    if (string1.length !== string2.length){
        return false;
    }
    const letters = {};
    
    for (let i = 0; i < string1.length; i++){
        letters[string1[i]] = ++letters[string1[i]] || 1;
    }
    
    for (let i = 0; i < string2.length; i++){
    //Si es 0 o no existe no es un anagrama.
        if(!letters[string2[i]]){
            return false;
        } else {
            letters[string2[i]] -= 1;
        }
    }
    return true;
}

En este caso tenemos otra solución que tiene un búcle for menos y también nos funciona. Por si a alguien no le queda claro lo que estamos haciendo es restando las coincidencias del segundo string con el primero dentro de nuestro objeto letters y en el caso de que intentemos acceder a un valor que ya es 0 o que no exista es señal de que no es un anagrama.

Bueno y hasta aquí el patrón de Frequency Count, básicamente contamos dentro de un objeto CLAVE-VALOR los elementos que nos interesen, sencillo ¿verdad?

Nos vemos en el siguiente, un abrazooooooorrrrrrrr.

]]>
<![CDATA[JS Algoritmos y estructuras de datos I: Big O]]>https://jlgarcia.fulldev.ninja/js-algoritmos-y-estructuras-de-datos-big-o/Ghost__Post__5c1a01ea2dd6610fd828cad3Sun, 04 Nov 2018 08:31:00 GMTJS Algoritmos y estructuras de datos I: Big O

Hablemos de algoritmos, eficiencia y esas cosas, es decir, hablemos de cosas 'chungas'.
Para medir la eficiencia de los algoritmos no usamos el tiempo propiamente dicho (como podríamos pensar en un primer momento), si lo midiéramos mediante algo temporal, como segundos, milisegundos o cosas así los datos no serían fiables, ya que dependen del hardware y de lo que esté haciendo en ese momento el ordenador.
En su lugar usamos 2 tipos de medidas:

  • Time complexity (complejidad temporal).
  • Space complexity (complejidad espacial) o también lo podríamos llamar Auxiliary Space complexity (luego hablaremos de esto).

Y para cualquiera de estos tipos de medida usamos lo que se conoce como Big O notation o notación de Landau. No vamos a entrar en la matemática específica de esto, si queréis saber más tenéis más información aquí. En nuestro caso valdrá con que tengamos este diagrama como referencia

JS Algoritmos y estructuras de datos I: Big O

Siendo O(1) lo mejor y O(n^2) lo peor. Con esto como base empecemos.

TIME COMPLEXITY

Al contrario de lo que indica su nombre no es una regla de medida temporal, lo que hace es medir la efeciencia basándose en el número de operaciones simples que realiza la función, entendiendo por operaciones simples: =, +, -, *, /, %, <, >.

Veamoslo mejor con algunos ejemplos simples:

function addUpTo(n) {
    let total = 0;
    for (let i= 0; i <= n; i++) {
        total += i;
    }

    return total;
}

Veamos cuantas operaciones tenemos aquí:

  1. let total=0: Primera asignación que ocurre solo 1 vez.
  2. let i=0: Segunda asignación que ocurre solo 1 vez.
    3 y 4. i <= n: Que se divide en 2 comparaciones 1 por el < y otra por el = y esto se haría n veces.
    5 y 6. i++: Esto es una suma y una asignación a i que se produce a su vez n veces.
    7 y 8. total += i: Y esto es igual que el anterior una suma y una asignación que se produce n veces.


En este caso tenemos 2 operaciones simples que se producen solo 1 vez y luego 6 operaciones que se producen n veces, es decir, que dependen de n, por lo tanto ¿cual creéis que será el TIME COMPLEXITY en este caso? Pensemos que son todo operaciones simples pero que dependen de n, por lo que en este caso nuestro valor será O(n).

Vayamos ahora con otro ejemplo:

function addUpToV2(n) {
    return n * (n + 1) / 2;
}

Es la misma función, es decir, hace lo mismo, nos devuelve la suma de todos los valores menores que n incluido n. Veamos las operaciones:

  1. n+1: Operación suma que se produce 1 vez.
  2. n * (resultado de lo anterior): Multiplicación del resultado anterior que se produce 1 vez.
  3. resultado anterior/2: División del resultado anterior que se produce solo 1 vez.

En este caso tenemos solo 3 operaciones pero además estas operaciones solo se producen una vez, es decir, no dependen de n, por lo tanto, en este caso el valor sería O(1) puesto que pasemos el número que pasemos a n siempre es constante la cantidad de operaciones que se realizarán.

Otro ejemplo:

function countUpAndDown(n){
    console.log("Hacia arriba...");
   
    for (let i = 0; i < n; i++) {
      console.log(i);
    }

    console.log("En la cima \n Bajando...");
    for (let j = n -1; j >= 0; j--) {
        console.log(j);
    }
    console.log("Bajamos!!");
  
}

En este caso tenemos 2 bucles que como ya hemos visto cada una sería O(n), es decir, O(2n). Es 2n porque depende 2 veces de n, pero al medir la complejidad siempre ignoramos los valores numéricos constantes por lo que volvemos a tener una función con un valor de 0(n).

Y ahora vamos con el último ejemplo con las medidas simples de medida (de momento no entramos a las medidas logarítmicas).

function printAllPairs (n){
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            console.log(i,j);
        }
    }
}

Aquí tenemos otro par de bucles, pero en este caso son 2 bucles anidados, es decir, uno dentro de otro, por lo que sería un n veces del bucle interior, por cada n veces del bucle superior, es decir, n*n o n^2, resumiendo en este caso tendríamos la peor medida de todas O(n^2).

Hasta aquí de momento en cuanto al Time Complexity, más adelante volveremos a ello, ahora vamos a por el Space Complexity

SPACE COMPLEXITY

Cuando hablamos de la complejidad espacial, nos referimos a cuanta memoria necesitaremos para ejecutar nuestra función. Al igual que con la time complexity, se usa para medir O, lo único que en este caso lo que diferenciamos será:

  • Valores primitivos: number, boolean, undefined y null. Estos valores son siempre constantes en cuanto a su tamaño por lo que ocuparán O(1).
  • Objetos y arrays: Que en este caso se miden en O(n) siendo n la cantidad de keys en los objetos y siendo n el tamaño de array.

En este caso la diferenciación es simple, si no tiene arrays u objetos será O(1), si tiene n arrays u objetos O(n) y si tiene arrays u objetos anidados entre sí sería O(n^x) siendo x la cantidad de anidación que tengamos, es decir, si tenemos un array de 3 dimensiones tendremos un O(n^3), si tenemos uno de 2 O(n^2).

Esto en cuanto a la parte simple del space complexity y del time complexity, evidentemente todo esto tiene algo más de chicha cuando entramos en los resultados intermedios de la gráfica (si, esos que contienen logaritmos). Hagamos un repaso de los logaritmos

LOGARITMOS

Hablemos ahora de los logaritmos, puesto que aunque no los hayamos visto todavía en algún ejemplo si que forman parte de las mediciones de eficiencia de los algoritmos. Si os fijáis en la gráfica inicial están presentes en las mediciones intermedias.

Pues veamos, ¿qué es un logaritmo? Para ponerlo simple es lo contrario que un exponente, es decir, ¿cuál es el resultado de 2^4? (2 elevado a 4)?
16
Por lo que entonces sería:
JS Algoritmos y estructuras de datos I: Big O


Una definición más específica sería:
Un logaritmo es la cantidad de veces que tengo que dividir un número por 2 (si es de base 2) hasta tener un valor menor o igual a 1. En nuestro caso serían 4 las veces que tenemos que dividir 16:

  1. 16 / 2 = 8
  2. 8 / 2 = 4
  3. 4 / 2 = 2
  4. 2 / 2 = 1

Y a la hora de hablar de logaritmos, por defecto siempre hablamos de logaritmos base 2, por lo que lo podemos omitir (y así es como lo vemos en las gráficas).

Bueno y hasta aquí lo básico, nos vemos en el siguiente, un abrazooorrrrr.

]]>
<![CDATA[Docker IV: Development Workflow]]>https://jlgarcia.fulldev.ninja/docker-iv-development-workflow/Ghost__Post__5be93e342dd6610fd828ca11Mon, 01 Oct 2018 10:33:00 GMTDocker IV: Development Workflow

La idea es ir empezando a ver como sería un entorno real de desarrollo con Docker teniendo:

  • Nuestro entorno de desarrollo: Un entorno donde creamos las cosas.
  • Entorno de test: Tras el desarrollo, pasamos nuestro código al entorno de test donde, como su propio nombre indica, probamos que todo funciona.
  • Entorno de producción: Tras pasar los tests pertinentes automáticamente el proyecto pasaría a producción.

Este workflow es lo que comunmente se conoce como Integración Continua, Continuous Integration o CI. Para este proceso usaremos herramientas o servicios como Github o Travis CI y desarrollaremos con ReactJS (no importa que no sepamos usarlos veremos lo necesario para entender lo que estamos haciendo).

Entorno de desarrollo

Empecemos por el primero de los entornos, el entorno de desarrollo. Lo que buscamos en este entorno es que nuestros cambios mientras estamos desarrollando se sincronicen automáticamente con el contenedor de desarrollo (esto en varios casos es innecesario pero lo veremos igualmente con los fines didácticos que nos ocupan).
Pensando en el objetivo de este entorno, ya hemos visto que si realizamos cambios en nuestro código tenemos que volver a realizar un build y luego arrancar un contenedor nuevo con la imagen que nos crearía el build anterior. Esto realmente no es lo que estamos buscando, es poco eficiente y tendríamos demasiadas imágenes de contenedor.

En docker existe una forma de solucionar esto, vamos a introducir algo que no hemos visto todavia: Los Volúmenes.
En docker un volumen no deja de ser una referencia al filesystem del contenedor y puede ser una referencia, como un mapeo (al igual que los puertos) de una carpeta local a una del contenedor, o un 'no lo toques' (que básicamente es mapea todo menos esto). Lo vamos a probar directamente, primero de todo vamos preparar un proyecto para trabajar con el, empecemos por crearnos un proyecto de ReactJS.

Para trabajar con react usaremos un paquete de npm que nos instala un proyecto básico inicial, lo instalamos con

npm -g install create-react-app

Una vez instalado ya podríamos crearnos un proyecto

create-react-app frontWeb

Esto nos creará un proyecto de react dentro de una carpeta llamada frontWeb. Para probar si funciona solo tenemos que entrar en la carpeta y ejecutar con:

npm start

Docker IV: Development Workflow

Bien ya tenemos nuestra web de react, vamos ahora con la parte del entorno de desarrollo de docker.
Para ejecutar nuestro entorno de desarrollo en un contenedor solo necesitamos que tenga node instalado, es decir, que es similar a lo que hemos creado anteriormente.

Antes de continuar comentar que aunque vamos a trabajar casi todo el rato con comandos de docker-compose todo lo que hagamos a partir de ahora se puede hacer con comandos docker run también, pero realmente son comandos muy largos y poco útiles a la larga. En caso de necesidad siempre podemos buscar cual es el flag del comando para ponerlo directamente sin usar un dockerfile.

Continuemos, volvamos al principio, hemos dicho que queríamos un entorno de desarrollo donde nuestro contenedor se actualice automáticamente según vayamos haciendo cambios en local, para ello vamos a hacer uso de lo que en docker se conoce como volumenes.
Lo primero nos crearemos dentro de nuestra carpeta de proyecto de react un fichero dockerfile pero esta vez pondremos:

Dockerfile.dev

Como es normal aunque no lo hayamos visto a docker se le puede indicar el fichero dockerfile a usar cuando hacemos un build, solo tenemos que usar el flag -f, por ejemplo (ojo al punto del final)

docker build -f ./Dockerfile.dev .

Lo mismo con los ficheros para docker-compose, por lo que realmente no tendremos ningún problema y podemos tener varios dockerfile distintos según nuestro entorno.
Sabiendo esto continuamos con nuestro fichero Dockerfile.dev

FROM node:alpine

WORKDIR '/app'

COPY package.json .
RUN npm install

COPY . .

CMD ["npm", "start"]

Misma teoria que anteriormente, copiamos el package.json primero por posibles cambios solo del resto y no tener que hacer otra vez el npm install todo el rato cada vez que hagamos un build.
Ya tenemos nuestro fichero dockerfile, ahora como la idea es usar docker-compose para todo necesitamos crear el fichero docker-compose.yml

version: '3'
services:
    web_react:
        build: 
            context: .
            dockerfile: Dockerfile.dev
        ports:
            - "3000:3000"

Antes de continuar, vemos que ahora donde tenemos puesto build ahora hemos añadido 2 propiedades:

  • context: Indicamos el contexto desde(path) desde el que trabajara el build del docker-compose.
  • dockerfile: Nombre del fichero dockerfile que queremos usar.

Continuemos, ahora vamos a hablar del concepto de Volume (por fin ;) ), primero añadamoslo al fichero

version: '3'
services:
    web_react:
        build: ./Dockerfile.dev
        ports:
            - "3000:3000"
        volumes:
            - /app/node_modules
            - .:/app

Si nos fijamos en lo que hemos puesto, tenemos realmente dos conceptos distintos dentro de volumes:

  • .:/app: Hablemos primero del segundo, este es similar al concepto de mapear puertos, básicamente le estamos indicando que mapee todo el contenido de la ruta actual de mi equipo local, al path /app del contenedor, lo que funcionaría similar a un acceso directo a los ficheros de la carpeta local de nuestro equipo.
  • /app/node_modules: Si nos fijamos en esta línea no tenemos ':', eso es porque aquí le estamos indicando que haga como un marcador de la carpeta node_modules del contenedor, es decir, usa la del contenedor, no la toques y dejala donde está (esto hace que la carpeta se mantenga aunque hagamos el paso anterior).

Ahora que ya sabemos lo que hemos puesto, viene la pregunta del ¿por qué?....bien, si pensamos en el proceso que hemos añadido en el Dockerfile.dev, tenemos una parte donde instalamos los paquetes que están indicados en el package.json, es decir, queremos que los descargues e instales de nuevo cuando hagamos un build de la imagen del contenedor, y no pasamos la carpeta local node_modules (que es donde instala las dependencias), que de hecho la vamos a eliminar para que veamos como funciona, si, eliminarla.

Ya que hablamos del Dockerfile.dev, alguno se puede preguntar si hacemos la refencia o linkado de nuestra carpeta local, ¿para qué hacemos el COPY?....bueno, esto es para prevenir creaciones para producción usando los mismos Dockerfiles, realmente en nuestro caso actual no lo necesitamos pero para producción siempre es mejor para evitar errores, ya que un contenedor en producción NO DEBE hacer referencias a carpetas locales de ningún sitio solo tiene que tener sus propios ficheros.

Ya tenemos todo ahora nos situamos en la ruta donde tenemos el fichero docker-compose.yml y ejecutamos:

docker-compose up

Si todo va bien deberíamos ver algo como esto en la consola
Docker IV: Development Workflow

Y si accedemos en el navegador a:

localhost:3000

Deberíamos ver

Docker IV: Development Workflow

Bien como tal ya tenemos todo funcionando, pero realmente lo que queremos es poder desarrollar en local y que se actualice el contenedor , ¿no?... pues vamos a probarlo.
Nos vamos a nuestra web de react, y dentro de la carpeta src modificamos el fichero App.js y ponemos lo que queramos:

 <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Hola ninjaaaaasssss
          </p> //<-- Esta es la linea que cambiamos
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>

Y una vez que guardemos el documento automáticamente se debería actualizar el navegador y mostrar

Docker IV: Development Workflow

¡¡¡PERFECTO!!!... ya tenemos nuestro entorno de desarrollo funcionando.

Empecemos con el entorno de TEST

TEST

Por defecto react viene ya configurado con 1 test para poder probar, solo tenemos que hacer

npm run test

o

npm test

y automáticamente nos pasaría los tests

Docker IV: Development Workflow

Bien, pues ahora queremos esto pero en un contenedor, como opción rápida podemos ejecutar una imagen creada con el mismo dockerfile pero cambiando el comando de arranque (esto es solo para que veais que funciona).

Por si no la tenemos creamos el build de la imagen, yo la voy a taggear para poder identificarla

docker build -f ./Dockerfile.dev . -t test_react

Esto nos devuelve una imagen con ese nombre

Docker IV: Development Workflow

Ya la tenemos, ahora solo vamos a realizar un run habitual cambiando el comando de arranque

docker run -it test_react npm test

Le he añadido -it para poder trabajar con la consola de test y como algo nuevo si nos fijamos hemos puesto cosas tras el nombre de la imagen, básicamente todo lo que ponemos a continuación del nombre de la imagen a ejecutar lo toma como comando de arranque para el contenedor. Si lo ejecutamos nos devuelve
Docker IV: Development Workflow

Otra opción es añadirlo a nuestro docker-compose añadiendo otro servicio pero con la misma teoría, compartiendo o mapeando ficheros entre el equipo local y el contenedor, teniendo 2 contenedores funcionando uno para las pruebas en desarrollo y otro probando los test. El problema de este acercamiento es que no tenemos control sobre la consola de test por lo que no podemos hacer mucho más que ver como pasan los tests cada vez que hacemos un cambio en los ficheros.
Vamos a probarlo, cambiamos nuestro fichero docker-compose

version: '3'
services:
    web_react:
        build:
            context: .
            dockerfile: Dockerfile.dev
        ports:
            - "3000:3000"
        volumes:
            - /app/node_modules
            - .:/app
    test_react:
        build:
            context: .
            dockerfile: Dockerfile.dev
        volumes:
            - /app/node_modules
            - .:/app
        command: ["npm","test"]

Como véis hemos añadido otro servicio (contenedor ya sabéis), en este caso se llama test_react, que usa el mismo dockerfile y mapea de la misma forma los volumenes. Despues de eso si que tiene cambios, hemos quitado el mapeo del puerto porque ya no lo necesitamos y como extra nuevo hemos añadido la propiedad command que básicamente lo que hace es cambiar el comando de inicio del contenedor.
A continuación si ejecutamos nuestro docker-compose up, nos crea dos contenedores y como podremos ver el log ambos funcionan correctamente, y si cambiamos algo en los ficheros se actualizan ambos, tanto el de desarrollo como el que pasa los tests.

Docker IV: Development Workflow

Ninguno de los casos es muy ideal pero son funcionales y puede que en algún caso nos pueda servir para algo, más adelante veremos un entorno de test más 'real' por el momento esto es más que suficiente, a continuación empezaremos a hablar un poco de PRODUCCIÓN

PRODUCCIÓN

Pasemos ahora a producción, la intención es crear un contenedor que nos devuelva nuestra aplicación ya preparada para producción, para el que no lo sepa, una app de react la preparamos para producción ejecutando el comando:

npm run build

Y este comando nos deja unos ficheros típicos de web (html, js y css), es decir, ficheros estáticos. Estos ficheros los deja en una carpeta llamada build dentro de nuestro proyecto.

Ahora necesitamos para producción un servidor web, los más utilizados son Apache o Nginx, aunque podríamos hacerlo con NodeJS, Go, Ruby, etc..... con casi todos los lenguajes tenemos alguna opción para hacerlo. En nuestro caso usaremos un contenedor con Nginx.

Si miramos la documentación del contenedor oficial podemos ver que los ficheros los sirve desde el path /usr/share/nginx/html. Sabiendo esto entonces básicamente lo que tendriamos que hacer sería copiar nuestros ficheros de producción en esa ruta del contenedor de nginx... pero claro se supone que no tenemos en local los archivos, veamos como podemos hacerlo con el entorno que tenemos ahora.

Nos vamos a crear un nuevo dockerfile con esto

FROM node:alpine as builder
WORKDIR '/app'
COPY package.json .
RUN npm install
COPY . .
RUN npm run build

FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html

Empecemos por la primera linea:

FROM node:alpine as builder

Introducimos algo nuevo en este punto, básicamente lo que estamos haciendo es indicarle al proceso que añada como una referencia al resultado del build de ese contenedor con nombre builder, pero puede ser cualquier otro. Esta referencia solo está disponible en el contexto de la ejecución de docker build.
El resto del primer contenedor es algo que ya hemos visto, vayamos con el segundo

FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html

Aquí empezamos con el segundo contenedor. Vemos como la instrucción COPY tiene algo nuevo

--from=builder

Como os podéis imaginar tiene que ver con la instrucción as builder del primer contenedor, aquí le estamos diciendo que del primer contenedor se copie la ruta /app/build y la pegue en /usr/share/nginx/html
Bien pues vamos a ejecutar nuestro nuevo build

docker build .

Una vez terminado

Docker IV: Development Workflow

Tenemos ya construido una imagen con supuestamente nginx y nuestra app en producción. Por último nos faltaría crear un contenedor con esa imagen, pues vamos a ello

docker run -p 3500:80 --name webpro idImagen

Como extra he añadido --name que lo que hace es taggearnos el contenedor con un nombre que podamos gestionar de manera más comoda que un ID numérico

Docker IV: Development Workflow

Se puede ver al principio el comando y a continuación un log (el de nginx) una vez que intentamos acceder a la página

Docker IV: Development Workflow

Y como podemos ver ya tenemos nuestro entorno para producción que básicamente es el build del dockerfile una vez que hemos terminado de desarrollar.

En próximos posts veremos una forma más profesional de hacer todo esto con Integración Continua gracias a TravisCI y GitHub.

]]>
<![CDATA[Docker III: Más complicado]]>https://jlgarcia.fulldev.ninja/docker-iii-mas-complicado/Ghost__Post__5be6b01a2dd6610fd828ca02Sat, 01 Sep 2018 10:32:00 GMTDocker III: Más complicado

Empecemos con cosas más complicadas, ahora vamos a preparar un entorno que se comunique entre sí y en el que podamos crear varios contenedores que se comuniquen entre con un solo comando.
Para poder hacer esto, empezaremos a ver algo que se llama docker-compose, esto son otra serie de comandos pertenecientes al entorno de Docker, en el que básicamente se busca un fichero docker-compose.yml con las instrucciones que se deben seguir.
Lo primero, en nuestra carpeta visits donde hemos creado anteriormente nuestro dockerfile con nuestra aplicación en NodeJS, nos creamos el fichero docker-compose.yml y vayamos poco a poco

version: '3'
services:
  • version: Hace referencia a la versión de la composición, esto le indica a docker, cuando ejecutamos comandos del CLI de docker-compose, si es versión más actual de docker-compose (a fecha de este texto es la 3).
  • services: Dentro del fichero, la propiedad services hace referencia a los contenedores, no a un servicios, dentro de este apartado indicaremos todos los contenedores que crearemos mediante esta composición.
version: '3'
services:
  redis-server:
    image: 'redis'
  node-app:
    build: .
    ports:
      - "4001:8081"

Dentro del apartado de services hemos indicado 2 contenedores: redis-server y node-app. Un detalle importante antes de continuar: TODOS LOS CONTENEDORES DENTRO DEL MISMO DOCKER-COMPOSE ESTÁN DENTRO DE LA MISMA RED, es decir, tienen comunicación entre ellos, y una forma de llamarse unos a otros es con el propio nombre que hemos indicado en el fichero de docker-compose.yml, por lo que si volvemos a nuestro fichero index.js donde tenemos nuestra aplicación de NodeJS, tenemos que hacer un pequeño cambio que antes habíamos ignorado, cuando creamos nuestro cliente de redis es necesario indicarle el host y el puerto del servidor que tiene el servicio de redis-server (siempre es necesario indicarlo menos cuando está en el mismo servidor que el cliente). Para indicarlo en este caso solo es necesario con:

const client = redis.createClient({
    host: 'redis-server',
    port: 6379
});

Si nos fijamos hemos puesto el mismo nombre que en nuestro docker-compose, realmente lo que esta sucediendo es que cuando la petición llega a la red interna de docker el está reconociendo el nombre y sabe donde ir a buscarlo.
Sabiendo esto y ya teniendo el cambio continuemos con nuestro docker-compose.yml, teniamos esto:

version: '3'
services:
  redis-server:
    image: 'redis'
  node-app:
    build: .
    ports:
      - "4001:8081"

Siguiendo con lo que nos faltaba:

  • image: Básicamente le estamos indicando que ese contenedor use la imagen de contenedor con nombre redis (ya sea nuestra o de la docker store)
  • build: Le indicamos que inicie el proceso de build con el fichero dockerfile que se encuentra en la ruta indicada.
  • ports: Puertos a mapear en este contenedor de nuestro equipo local.

Con esto ya tendríamos todo listo para que nuestro conjunto de contenedores funcionaran, vamos a probarlos. Para ello basta con ejecutar:

docker-compose up

Docker III: Más complicado

Como vemos tenemos los dos contenedores levantados con los nombres que le hemos indicado en el fichero docker-compose.yml. Si ahora accedemos a localhost:4001 (recordad que hemos cambiado el puerto al hacer el mapeo)

Docker III: Más complicado

Vemos como funciona perfectamente.

Comandos docker-compose

Veamos algunos comandos de docker-compose:

  • docker-compose up: Funciona como docker run, busca el fichero docker-compose up y ejecuta lo que aparece en el.
  • docker-compose up --build: Fuerza a rehacer los contenedores antes de ejecutarlos. Muy útil si hemos realizado algún cambio en nuestro código de aplicación.
  • docker-compose up -d: Arranca los contenedores en segundo plano.
  • docker-compose down: Para los contenedores y elimina tanto los contenedores como la red que crea entre ellos.
  • docker-compose start: Arranca los contenedores si están parados (OJO parados que no eliminados)
  • docker-compose stop: Para los contenedores y NO elimina nada.
  • docker-compose ps: Al igual que docker ps, vemos el estado de nuestra composición de contenedores.

TODOS LOS COMANDOS DE DOCKER COMPOSE NECESITAN EL FICHERO DE docker-compose.yml PARA FUNCIONAR

Reinicio automático de contenedores

Puede pasar que alguno de nuestros contenedores se detenga, por algún error por ejemplo, en este caso docker ya está preparado para automáticamente reiniciar los contenedores según como lo configuremos en nuestro docker-compose.yml. Tenemos estas opciones de reinicio:

  • "no": Es la configuración por defecto de los contenedores. No se reinician en ningún caso. Para poner explicitamente no es necesario ponerlo entre ""
  • always: Siempre que se pare, sea cual sea la razón, automáticamente se reinicia.
  • on-failure: Solo se reinicia cuando se pare con un error code. En general si el código de salida ha sido un 0, eso significa que se ha parado de manera controlada o porque ha terminado su ejecución correctamente, sin embargo, si el código con el termina es distinto de 0 entonces lo tomará como un error y reiniciará el contenedor.
  • unless-stopped: Siempre se reinicia a menos que lo hayamos parado nosotros manualmente.

Para configurar estas opciones solo tenemos que indicarselo en el fichero de la siguiente forma:

version: '3'
services:
  redis-server:
    image: 'redis'
  node-app:
    restart: always <--Con esta opción
    build: .
    ports:
      - "4001:8081"

Esto último era un extra que nos puede ser útil a la hora de configurar nuestro contenedor. Nos vemos en el siguienteeee, un abrazooor.

]]>
<![CDATA[Docker II: Creando contenedores]]>https://jlgarcia.fulldev.ninja/docker-ii-creando-contenedores/Ghost__Post__5be1df0c2dd6610fd828c9edWed, 01 Aug 2018 10:32:00 GMTDocker II: Creando contenedores

Ahora vamos a empezar a trabajar con lo que se conoce como Dockerfile, que básicamente es un fichero con la configuración que queremos para un contenedor.

Lo primero nos creamos un fichero llamado Dockerfile(sin extension)

# Indicamos una imagen base de la que partir. Usamos alpine que es una básica sin prácticamente nada
FROM alpine

#Descargamos e instalamos las dependencias que queramos. Usamos el copando apk que es un gestor de paquetes que viene dentro de la imagen de alpine
RUN apk add --update redis

# Le indicamos a la imagen que hacer o que comandos ejecutar cuando arranque
CMD ["redis-server"]

Nos situamos en la consola (o linea de comandos), en la carpeta donde tenemos el fichero y ejecutamos

docker build .

Esto nos ejecuta los comandos que tiene nuestro fichero. Para que entendamos el proceso, si miramos detenidamente el log de salida del comando veremos como en cada paso nos genera una imagen intermedia, ejecuta el comando del paso que le toque crea un snapshot para el siguiente paso y elimina la imagen anterior
Docker II: Creando contenedores

Por último vemos que nos pone Successfully build ....., ese ID que nos aparece seria el nombre de nuestra imagen creada, a continuación solo tenemos que ejecutar

docker run IDimagen

Y veremos como se ejecuta perfectamente nuestro contenedor
Docker II: Creando contenedores

Y si miramos que se esta ejecutando, veremos un nuevo contenedor con un nombre aleatorio que parte de nuestra imagen anteriormente creada
Docker II: Creando contenedores

Usando la Caché

Si rehusamos un Dockerfile o creamo uno nuevo igual que otro o con los mismo pasos iniciales, Docker usará su caché para no tener que repetir procesos que ya ha ejecutado, veamoslo con un ejemplo. Si añadimos otra dependencia a la instalación:

FROM alpine

RUN apk add --update redis
RUN apk add --update gcc
RUN apk add --update vim

CMD ["redis-server"]

Y ejecutamos de nuevo el comando build, veremos como en los pasos iniciales nos sale Using cache, en algunas lineas, concretamente en las que se han repetido. OJO solo aparece hasta que llega a la línea que ha cambiado del Dockerfile, una vez que encuentra algo distinto ya lo hace todo de nuevo (si cambiaramos el orden de los comandos RUN lo haría todo de nuevo)

Docker II: Creando contenedores

Añadir TAG a imagen

Si nos fijamos cuando se termina de construir la imagen de nuestro contenedor, nos pone un ID cualquiera, algo que es dificil de recordar. Es posible indicarle un nombre identificativo a nuestro contenedor, para ello basta con:

docker build -t aliasDockerHub/nombreImagen:latest .

Con el flag t indicamos que queremos taggear la imagen creada con un nombre, el cual es necesario que tenga la estructura que veis con el alias que tenemos en el marketplace de DockerHub

Crear Imagen a partir de un contenedor existente

La idea es configurar un contenedor como queramos y luego generar una imagen de ese contenedor para poder desplegarla. Es necesario indicarle cual el es comando de inicio

docker commit -c 'CMD["redis-server"]' idContenedor

Con este comando generaríamos una imagen que arrancaría con el comando redis-server. Este nos genera una imagen con un id gigantesco, pero para usarla no tenemos que copiar todo el id con una parte inicial que sea única y ya docker la busca.

Creando imagen propia 2.0

Hasta ahora hemos visto como crear una imagen usando otra como base e instalando algún paquete extra, pero....y ¿si queremos añadir algún desarrollo o fichero nuestro? Vamos a crear una mini aplicación en NodeJS que nos devuelva una página. Para ello vamos a crearnos una nueva carpeta para almacenar todos los ficheros de este contenedor, por ejemplo la llamaremos miniapp.
Dentro de esta carpeta lo primero nos creamos un fichero package.json que contendrá las dependencias y scripts de nuestro aplicación en NodeJS (quién no sepa de que estamos hablando puede revisarse los post de NodeJS donde lo explico todo con más detalle). Dentro de este fichero ponemos:

{
  "dependencies": {
    "express": "*"
  },
  "scripts": {
    "start": "node index.js"
  }
}

Aquí le estamos diciendo que tenemos como dependencias cualquier versión de express y un script de npm para arrancar el fichero index.js.
Ahora como os podéis imaginar tenemos que crear ese fichero, el index.js

const express = require('express');
 
const app = express();
 
app.get('/', (req, res) => {
  res.send('How are you doing');
});
 
app.listen(8080, () => {
  console.log('Listening on port 8080');
});

Ya tenemos nuestro proyecto de node preparado, ahora vamos a crear nuestro fichero Dockerfile para indicarle los pasos necesarios

#Imagen de la que partimos. En este caso usamos una imagen de node en su versión alpine (es decir con lo mínimo posible)
FROM node:alpine

#Comando nuevo. Con este comando copiamos los ficheros de un origen en nuestro equipo local a una ruta en el contenedor
COPY ./ ./
#Ejecutamos la instalación de las dependencias que hemos indicado en el package.json
RUN npm install

# Le indicamos como comando de arranque el script que hemos indicado en el package.json que básicamente inicia nuestra aplicación en Node
CMD ["npm","start"]

Ahora tenemos como comando nuevo COPY este comando copia contenido de una carpeta local (relativa a la ejecución del comando de docker build) a una carpeta en el propio contenedor.

Cambiando el directorio de ejecución en el contenedor

Con la configuración anterior, en el contenedor anterior todo se copia y se ejecuta en la raiz (/), eso en linux NO ESTÁ BIEEEEN, tenemos que indicarle un directorio en condiciones a nuestra aplicación o a nuestro ficheros. Para ello tenemos el comando WORKDIR:

WORKDIR pathDondeTrabajar

Básicamente cambia el path desde el que se está trabajando en el Dockerfile, una ruta posible para usar puede ser dentro de /usr/app (si no existe, nos crea la carpeta), por lo que quedaría así nuestro Dockerfile

#Imagen de la que partimos. En este caso usamos una imagen de node en su versión alpine (es decir con lo mínimo posible)
FROM node:alpine

# Directorio desde el que funcionarian las siguientes instrucciones
WORKDIR /usr/app

#Comando nuevo. Con este comando copiamos los ficheros de un origen en nuestro equipo local a una ruta en el contenedor
COPY ./ ./

#Ejecutamos la instalación de las dependencias que hemos indicado en el package.json
RUN npm install

# Le indicamos como comando de arranque el script que hemos indicado en el package.json que básicamente inicia nuestra aplicación en Node
CMD ["npm","start"]

Ahora si hicieramos un build del contenedor, arrancaramos una imagen y nos conectaramos con exec -it podríamos ver que nos conectamos directamente al directorio que hemos indicado como working directory....peeeeeero realmente todavía no funcionaria nuestra aplicación, veamos lo último para hacerla funcionar.

Mapear puertos locales a puertos de contenedor

El que tenga algunos conocimientos de node se habrá dado cuenta de que lo que hacemos es arrancar una aplicación en el puerto 8080, pero claro, es el puerto 8080 de nuestro contenedor no de nuestro equipo, tenemos que pensar que un contenedor es prácticamente como un equipo independiente, como si hablaramos de una máquina virtual.

Para poder hacer uso de un puerto del contenedor, tenemos que mapear un puerto local a uno del contenedor, no tienen porque ser iguales, pero si tiene que estar libre nuestro puerto local (hace algo similar a una redirección).
Este mapeo se hace a la hora de crear el contenedor no al construir la imagen, es decir, el mapeo de puertos no se hace en nuestra fase de docker build, si no en la fase de docker create/docker run de la siguiente manera:

docker run -p ptoLocal:ptoContenedor IDimagen

Docker II: Creando contenedores

Si ahora hacemos un docker ps, veremos que tenemos algo en la columna PORTS

Docker II: Creando contenedores

Con esto ya tendríamos nuestra app funcionando perfectamente, solo tenemos que entrar a localhost:5000 en nuestro navegador y voilá.

]]>
<![CDATA[Docker I: Comandos básicos]]>https://jlgarcia.fulldev.ninja/docker-basics-i/Ghost__Post__5be004332dd6610fd828c9e4Sun, 01 Jul 2018 10:32:00 GMTDocker I: Comandos básicos

Un contenedor Docker es en realidad un grupo de recursos que se reserva del hardware de nuestra máquina para una función específica. Este grupo de recursos funciona prácticamente como una máquina independiente.
En Mac y en Windows al instalar Docker, lo que realmente se instala es un máquina virtual Linux donde realmente se hará la gestión de nuestros contenedores.

Comandos básicos

docker run nombreDeContenedor

Este es el comando básico que aprendemos cuando empezamos a trabajar con Docker. Este comando hace lo siguiente:
1. Comprueba si tenemos en nuestro disco duro la imagen o el fichero de imagen que hemos solicitado.
2. Si no lo tenemos se va a buscarlo a la Docker Hub (marketplace con todos los contenedores públicos) y lo descarga.
3. Crea un contenedor a partir de la imagen que tenemos o teniamos ya en nuestro disco duro.
4. Inicia el contenedor pertinente ejecutando el comando de inicio que tenga configurado el contenedor.
5. Nos muestra un log con la salida de información que tenga configurado ese contenedor (o con los errores que nos haya generado)




docker create nombreDeContenedor

Esto serían solo los pasos 1,2 y 3, es decir, nos crea el contenedor pero no lo arranca.

docker start idDeContenedor

Esto nos inicializa un contenedor a partir del ID que nos ha generado al crearlo o el NOMBRE (OJO no es el nombre de la imagen si no el del propio contenedor). Con docker start no veríamos nada en la consola, esto simplemente arranca el contenedor en segundo plano (por decirlo de alguna manera)

docker start -a idDeContenedor

Igual que el anterior pero si nos muestra el log por la consola.

docker ps

Con docker ps vemos la información de los contenedores que tenemos activos actualmente

Docker I: Comandos básicos

Aquí podemos ver:

  • Container ID: El id único que tiene el contenedor que hemos creado
  • IMAGE: La imagen de la que parte el contenedor, es decir, el fichero de configuración a partir del cual se ha creado
  • COMMAND: El comando de inicio que ejecuta nuestro contenedor al iniciarse.
  • CREATED: Hace cuanto hemos creado el contenedor.
  • STATUS: El estado actual del contenedor, si esta arrancado, apagado o en error.
  • PORTS: En el caso de que tuvieramos algún puerto de nuestro equipo direccionado a alguno del contender aparecería aquí.
  • NAMES: Los nombres que tiene el contenedor, a partir del cual podemos llamarlo como si fuera su ID. Este nombre se crea automáticamente o lo podemos indicar nosotros a la hora de crear el contenedor (docker create o docker run)
docker ps --all

Nos muestra todos los contenedores que tenemos creados, tanto si están encendidos como si no.

Docker I: Comandos básicos

docker system prune

Elimina todos los contenedores parados, las redes sin usar, las imagenes descargadas....

docker stop Id/NameContainer

Envia la señal SIGTERM al contenedor para que se apague correctamente cerrando los procesos de la manera correcta.

docker kill id/NameContainer

Envía la señal SIGKILL al contenedor, que lo que hace es cerrar todo en plan guantelete del infinito.

Que hacer cuando un contenedor ya esta arrancado y quiero ejecutar un comando?

docker exec -it idContainer Comando
  • exec: Indicamos que ejecute un comando.
  • it: básicamente es que queremos que nos devuelva la consola con el comando que hemos ejecutado. Sin it ejecutaría el comando pero no veríamos que pasa.

Veamos un ejemplo con un contenedor de Redis

Docker I: Comandos básicos

Vemos que se esta ejecutando
Docker I: Comandos básicos

En este estado podemos ejecutar comandos y tomar control de la sesion
Docker I: Comandos básicos
Docker I: Comandos básicos

]]>
<![CDATA[jQuery Slideshow]]> Ghost__Post__5a8c07c98cda356bbe9bbe44Thu, 22 Feb 2018 13:10:51 GMTjQuery Slideshow

Vamos con un ejemplo de creación de un Slideshow muy simple en jQuery. Lo primero de todo es generarnos la estructura del proyecto, cada uno que la haga como quiera, yo tengo algo así
jQuery Slideshow

En mi caso voy a usar bootstrap, por comodidad, pero no es necesario, lo añado al index.html al igual que jQuery. También he creado mi propio fichero de estilo style.css y lo he añadido, quedando todo de esta manera:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Ninja_slider</title>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/style.css">
    
</head>
<body>
    
</body>
</html>

Lo siguiente es añadir donde estará el slider en el body de la página

<body>

    <div class="container" align="center">
        <h1>Ninja-Slider</h1>
        <hr>

        <div class="ninjaSlider"></div>

    </div>
    
</body>

Nuestro slider como os podéis imaginar estará en el div con la clase ninjaSlider, y esto es todo por el momento en el html, ahora vamos a nuestro css para darle un poco de estilo y que se vea algo en la página. Le daremos un tamaño y un color de fondo para ir viendo algo.

.ninjaSlider{
	background-color: blue;
	width: 600px;
	height: 300px;
	overflow: hidden;
	margin-bottom: 10px;
}

Quedando así
jQuery Slideshow

Bien ahora vamos a crear un fichero js donde meteremos toda la lógica de funcionamiento de nuestro slider. Tener en cuenta que la idea es hacerlo estilo plugin, es decir que este encapsulado para poder usarlo en cualquiera de nuestros proyectos.
Bien lo primero como hemos comentado es crearnos el fichero js y meterlo en la carpeta correspondiente, lo siguiente son las imágenes para que slider tenga algo que mostrar, entonces buscamos unas cuantas imágenes, yo me he descargado 4 y las metemos en la carpeta img.

Una vez que tenemos las imagenes ya podemos empezar, con el js. Creamos una función anónima que se ejecute nada más cargar el js en el HTML:

(function(){
    //Aqui dentro meteríamos todo el código.
})();

Vamos a empezar por algo sencillo, que es recibir las imagenes desde el html que es desde donde nos las mandarían si pensamos que este es un plugin. Estas imágenes las incluiremos en una lista, es decir, dentro de un ul de html.
Pero antes de hacer esto, pensemos...¿cómo hacemos para pasar los datos que necesitemos a nuestro plugin?, fácil, por parámetros le pasamos un objecto que tenga las modificaciones que queramos hacer.
Primero creemos un método, que será el que realmente configure e inicie nuestro slider

(function(){
    $.ninjaShow = function(config){
    
    }
})();

Aqui hemos añadido la función ninjaShow a jQuery, por lo que la podemos llamar desde cualquier sitio desde el que podamos llamar a jQuery. A esta función le pasamos por parámetro un objeto config que tendrá todos los cambios sobre la configuración por defecto que establezcamos.
Continuemos añadiendo lógica para nuestras imagenes, suponiendo que nos indiquen cuales son las imágenes a través del objeto config, podemos tener nuestras imagenes bajo la propiedad images dentro del objeto config. Hagamos una prueba para que veáis de lo que estoy hablando, nos vamos a nuestro index.html y añadimos lo siguiente al final (pero siempre dentro del body)

<!-- añadimos nuestro js al html -->
<script src="js/slider.js"></script>
<script>
	//Llamamos a nuestra función pasandole un objeto con la propiedad images
	$.ninjaShow({

        images:['img/ninja1.jpg','img/ninja2.jpg','img/ninja3.jpg','img/ninja4.jpg'],
	});

</script>
</body>

Y luego en nuestro fichero js, ponemos un log para ver que estamos recibiendo.

(function(){

    $.ninjaShow = function( config ){

        console.log(config);
    }

})();

Si guardamos y miramos la consola al ejecutar nuestro index.html, veremos algo como lo siguiente
jQuery Slideshow

Es decir, un objeto con la propiedad images y dentro de esta un array con la dirección donde estan nuestras imágenes.
Entonces ya tenemos nuestras imágenes, ahora creemos la lista con ellas.

(function(){

    $.ninjaShow = function( config ){
                //Comprobamos que nos pasan alguna imagen
            if (config.images.length == 0){
                alert("Necesitamos alguna imagen para mostrar");
                return;
            }

            //Abrimos la lista
            var imgList = "<ul>";

            //Recorremos el array y vamos añadiendo lineas con nuestra imagen como source
            config.images.map((img)=>{
                imgList += '<li><img src="'+ img +'"</li>';
            })
            //Cerramos la lista
            imgList += "</ul>";
            
            //Adjuntamos la lista al contenedor
            $(".ninjaSlider").append(imgList);
    }

})();

Si ejecutamos nuestro index.html veremos algo similar a esto:

jQuery Slideshow

Ahora mismo no vemos casi nada, ¿verdad?, vamos a modificar un poco el estilo para que veamos que realmente las cosas estan donde deben estar. Nos vamos a nuestro style.css y lo dejamos así

.ninjaSlider{
	background-color: blue;
	width: 600px;
	height: 300px;
	/* overflow: hidden; */
	margin-bottom: 10px;
}

.ninjaSlider ul{
	padding: 0;
	list-style-type: none;
	margin-left: 0px;
	display: flex;
}

Ahora si veríamos algo aunque solo sean las imágenes
jQuery Slideshow

Pero si miramos el código fuente de la página deberíamos ver algo como esto:
jQuery Slideshow

Ya tenemos nuestra lista de imágenes, y las vemos porque hemos cambiado la propiedad overflow de css, la configuración correcta sería descomentado la línea y dejandola así

overflow: hidden;

Pero la hemos comentado para explicar que es lo que vamos a hacer para ir cambiando de imágenes.
Si os fijáis las imágenes estan configuradas en una linea horizontal, una al lado de otra, lo que haremos será ir moviendo el interior del contenedor en esa linea horizontal, modificando el margin-left, es decir, supongamos que nuestro margin-left en nuestra primera imagen es 0 y tenemos imágenes con un ancho de 600, pues lo que haríamos para movernos entre las imágenes sería ir modificando el margin-left en conjuntos de 600, es decir, la siguiente imagen tendria un margen de -600, la siguiente de -1200... y el - es necesario porque nuestro slide se movera de izquierda a derecha y como os podréis imaginar para que funcione en este ejemplo es necesario que el ancho de las imágenes sea el mismo o muy parecido (podríamos manejar el ancho de cada li dentro de la lista pero prefiero dejar el código más claro).
Creo que está más o menos explicado el concepto de lo que queremos hacer, veamos como lo hacemos

 //Variable de ayuda para gestionar la posicion
 var position = 0;
 //Numero de imagenes para controlar
 var images = config.images.length;

 //intervalo en el que se cambia la imagen
 var interval = setInterval(function(){
   move();
 }, 1400);

 function move(){
//Cada vez que pase por aqui aumentamos la posicion       
   position++;

//Si la posicion es igual o mayor que la cantidad de imagenes
//ponemos position a 0
   if (position >= images ){
        position = 0
   }

//Por ultimo movemos el margen según la posición en grupos de 600
   $(".ninjaSlider ul").animate({
     marginLeft: position * -600
   },400);

Por último descomentamos el overflow:hidden en el fichero css y listo, ya lo tenemos

jQuery Slideshow

Ya tenemos nuestro slide funcionando, en este punto podríamos añadir más funcionalidad, que tuviera los puntitos típicos que te indican en que slide te encuentras y que puedes pulsar para moverte de uno a otro, o parar el slide por ejemplo. Eso lo haremos más adelante en algún post posterior ahora quería solo mostrar como podríamos hacer lo más básico de un slide, ahora lo ultimo que haremos será añadir un par de cosas al js para que se vaya pareciendo más a un plugin y podamos personalizarlo un poco más.

Si miramos el código detenidamente y pensamos un poco que cosas podrían ser susceptibles de cambiar, podrían ser:

  • El ancho básico de las imágenes para ir moviendo el slide.
  • La clase o id del slider (podriamos tener mas de uno por ejemplo)
  • El intervalo de transición de las imágenes.

Estos son algunos de los cambios que se me han ocurrido con el código que tenemos actualmente podríamos tener más seguramente. Por el momento vamos a preparar esos.
Para empezar tenemos que plantearnos cuales son obligatorios y cuales podrían tener una configuración por defecto. Las imágenes deben ser obligatorias, por eso hemos puesto el control antes que nos lanzaría una alerta si no se pasa ninguna imagen, y a lo mejor el nombre del id del slide y el resto podemos tener valores por defecto si no nos lo pasan por parámetro.
Para ellos basta con usar el método extend de jQuery sobre el objeto que se pasa por parámetro

$.ninjaShow = function( config ){
            
            config = $.extend({
                slideName: "",
                width: 600,
                timeTransition: 1400,
                images: []
            }, config);

Este método lo que hace es sobreescribir el objeto local, que sería el primero (el que está entre { }) con el que se pasa por parámetro, pero solo las propiedades con el mismo nombre, es decir, no elimina el objeto por otro, es como si cambiaramos las propiedades que coincidan por las nuevas que se pasan por parámetro.
Y ahora simplemente es cambiar los elementos que tenemos puestos a mano como ".ninjaslider" por la propiedad correspondiente y listo, ya tendríamos preparado el código como si fuera un plugin quedando así (dejo comentado el código)

//Función anonima
(function(){

    $.ninjaShow = function( config ){
            
            config = $.extend({
                slideName: "",
                width: 600,
                timeTransition: 1400,
                images: []
            }, config);


            //Comprobamos que nos pasan alguna imagen
            if (config.images.length == 0){
                alert("Necesitamos alguna imagen para mostrar");
                return;
            }
            
            //Comprobamos que nos han pasado un nombre para slider
            if (config.slideName == ""){
                alert("Falta el nombre o id donde estará el slider")
                return
            }

            //Abrimos la lista
            var imgList = "<ul>";

            //Recorremos el array y vamos añadiendo lineas con nuestra imagen como source
            config.images.map((img)=>{
                imgList += '<li><img src="'+ img +'"</li>';
            })
            //Cerramos la lista
            imgList += "</ul>";
            
            //Adjuntamos la lista al contenedor
            $(config.slideName).append(imgList);

            //Variable de ayuda para gestionar la posicion
            var position = 0;
            //Numero de imagenes para controlar
            var images = config.images.length;

            //intervalo en el que se cambia la imagen
            var interval = setInterval(function(){
                move();
            }, config.timeTransition);

            function move(){
                
                position++;

                if (position >= images ){
                    position = 0
                }

                $(config.slideName + " ul").animate({
                    marginLeft: position * - config.width
                },400);
            }
    }

})();

Como véis ahora tenemos config.algo por todas partes. Y para usarlo nos vamos a nuestro index.html y añadimos las propiedades necesarias:

<script>
	
	$.ninjaShow({
        slideName: ".ninjaSlider",
        ancho: 600,
        timeTransition: 1400,
        images:['img/ninja1.jpg','img/ninja3.jpg','img/ninja4.jpg'],
    });
</script>

Y podríamos como he comentado, crear más sliders simplemente creando el elemento, dandole un nombre y crear un nuevo ninjaShow

$.ninjaShow({
        slideName: ".ninjaSlider",
        ancho: 600,
        timeTransition: 1400,
        images:['img/ninja1.jpg','img/ninja3.jpg','img/ninja4.jpg'],
    });
    
$.ninjaShow({
        slideName: ".ninjaSlider2",
        ancho: 600,
        timeTransition: 1200,
        images:['img/ninja4.jpg','img/ninja1.jpg','img/ninja3.jpg'],
});

jQuery Slideshow

Como véis ahora es muy sencillo crear sliders básicos con este código. En algún post posterior mejoraremos este slider para que tenga más funcionalidades, esto es básicamente para que veamos un poco como podemos trabajar con jQuery para hacer todo lo que queramos con el DOM.

Sin mucho más nos vemos en el siguiente, un abrazooorrrr.

]]>
<![CDATA[jQuery Basics I]]> Leccion 01 React Superhero (II): Cambios entre componentes

En este post veremos como pasar datos o más bien como comunicarnos entre componentes. Para ello partiendo de lo que tenemos del post anterior:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <!-- Libreria para trabajar con React -->
    <script src="https://unpkg.com/react@latest/dist/react.js"></script>
    <!-- Libreria de react para trabajar con el Dom-->
    <script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
    <!--Preprocesador para traducir lo que escribimos a todos los navegadores ya que react funciona con ECS6 y puede que no 
    funcione en todos -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script type="text/babel">

        class SuperComponente extends React.Component{
          constructor(){
            super();
          }

          render(){
            return (
              <div>
                <h2>Hello with React</h2>
                <MiSegundoComponente/>
              </div>
            );
          }
        }

        class MiSegundoComponente extends React.Component{
          constructor(){
            super();
            this.state = {
              nombre: "Your Super Name"
            }
          }
          getName(input){
            this.setState({nombre: input.target.value});
          }


          render(){
            return (
              <div>
                <h2>REACT is in the HOUSE...YEAH!!!!</h2>
                <input onChange={this.getName.bind(this)}></input>
                <h4> { this.state.nombre } </h4>
              </div>
            )
          }
        }

        ReactDOM.render(
          <SuperComponente/>,
          document.getElementById('root')
        );

      </script>
   
  </head>
  <body>
      <div id="root"></div>    
  </body>
</html>

Vamos a crear otro componente que usaremos para hacer que un numero aumente, es decir vamos a sumarle uno, es un ejemplo muy simple pero suficiente para ver los conceptos que queremos, por el momento lo primero sera crear el componente con un botón y un elemento para mostrar el nùmero

class PlusOneNumber extends React.Component {
    render(){
            return (
              <div>
                 <p>0</p>
                 <button>AumentarNumero</button>
              </div>
              )
          }
}

Bien y lo ponemos en nuestro componente principal para que lo muestre, a continuación del que era MiSegundoComponente:

render(){
     return (
       <div>
         <h2>Hello with React</h2>
         <MiSegundoComponente/>
         <PlusOneNumber/>
       </div>
       );
  }

Bien ahora mismo ya se tendría que mostrar, pero esto como tal no hace nada ni el botón ni el 0, vamos a empezar a darle "vida" a nuestro componente en conjunto con el componente principal.
Lo primero que hacemos es crear un constructor con una propiedad en el componente principal, algo así:

class SuperComponente extends React.Component{
    constructor(){
         super();
            this.state = {
              number: 0
            }
          }

     render(){
       return (
         <div>
           <h2>Hello with React</h2>
           <MiSegundoComponente/>
           <PlusOneNumber/>
         </div>
        );
      }
}

Lo que tenemos dentro de this.state será lo que iremos cambiando cada vez que pulsemos el botón, es decir la idea es que el componente PlusOneNumber tome el valor de número desde el componente principal y le sume uno. ¿Pero como empezamos con esto?

Lo primero es pasar el valor desde el componente principal a nuestro componente que realiza la suma, para ello es tan simple como "pasarselo como una propiedad al componente":

<h2>Hello with React</h2>
<MiSegundoComponente/>
<PlusOneNumber number={this.state.number}/>

Con esto lo mandamos al componente, y para acceder a este número desde nuestro componente auxiliar:

class PlusOneNumber extends React.Component {
    render(){
      return (
        <div>
         <p>{this.props.number}</p>
         <button>AumentarNumero</button>
        </div>
      )
    }
}

Como veis es suficiente con acceder a las propiedades de this en lugar de a su estado. Si ahora cargáis de nuevo la página veréis que todo sigue "igual", o casi, la diferencia es que ya está cogiendo el número desde el componente principal.

Siguiente paso, Aumentar el número, para ello lanzo una pregunta, ¿creéis que ahora que tenemos acceso podemos modificar esa propiedad en nuestro componente? Y hablo directamente la propiedad no de hacer un +1 en conjunto del this.props.number, ni tampoco de asignarla a una propiedad de este componente y trabajar desde aquí, me refiero a directamente modificar esa propiedad.....Pues No, y ¿por qué?, pues porque realmente el dueño o propietario de ese objeto es el componente principal no nuestro componente que realiza la suma. Es como si en POO tuviéramos una variable de clase que para cambiarla necesitamos usar un método setter(más o menos) y con esta pista veamos que tenemos que hacer para crear nuestro "setter".

Primero creamos una función en nuestro componente principal que realizará la suma:

plusOne(){
    this.setState({number: this.state.number + 1 })
}

Como hemos comentado anteriormente usamos setState para cambiar valores y simplemente le sumamos uno.
A continuación queremos que el button haga algo, por lo que tendríamos que indicarle un onClick en algún sitio como suele ser en asuntos relacionados con JS y queremos que ese onClick de alguna manera use el método plusOne(), pero ¿como hacemos que nuestro componente auxiliar pueda usar ese método? Pues de la misma manera que hemos conseguido que reciba la propiedad número, lo único que en lugar de pasarle una propiedad o un estado le pasamos una función:

 <MiSegundoComponente/>
<PlusOneNumber number={this.state.number} funAumentar={this.plusOne.bind(this)}/>

Ahora la propiedad funAumentar contiene la función o método plusOne y recordemos que es necesario "enlazar"(bind) el contexto en el que trabajamos(el this vamos) para que todos tengan la información que necesitan, y por ultimo solo necesitamos decirle al button lo que tiene que hacer cuando lo pulsen en su propiedad onClick:

<p>{this.props.number}</p>
<button onClick={this.props.funAumentar}>AumentarNumero</button>

Y ya lo tenemos si ahora recargamos nuestra página tendremos lo siguiente, con un número que crece cada vez que lo pulsamos:
React Superhero (II): Cambios entre componentes
Y como podréis comprobar el componente del post anterior también funciona.
Para asentar un poco y confirmar que un componente realmente se comunica con otro, vamos a añadir lo siguiente en el componente principal:


 <h2>Hello with React</h2>
 <MiSegundoComponente/>
 <PlusOneNumber number={this.state.number} funAumentar={this.plusOne.bind(this)}/>
 <h4>{this.state.number}</h4>

Como veis he puesto directamente la propiedad number que tiene nuestro SuperCompomente, para que veaís que realmente si se esta modificando el número de este componente:
React Superhero (II): Cambios entre componentes
Bueno como veis es relativamente sencillo compartir información entre componentes, en los próximos post ya empezaremos a ponernos más serios.
Nos vemooosssss


]]>
<![CDATA[Don't stop the party: Node JS (I)]]> Node.js® es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8 de Chrome. Node.js usa un modelo de operaciones E/S sin bloqueo y orientado a eventos, que lo hace liviano y eficiente. El ecosistema de paquetes de Node.js, npm, es el ecosistema m]]>https://jlgarcia.fulldev.ninja/dont-stop-the-party-node-js-i/Ghost__Post__5a338158333e0f134c248f1eThu, 17 Aug 2017 08:19:00 GMTDon't stop the party: Node JS (I)

Comienzo otra nueva sección también dedicada al mundo JavaScript en este caso de NodeJS pero...

¿Que es NodeJS?

Bien en este caso la mejor explicación corta es la de la página oficial de NodeJS:

Node.js® es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8 de Chrome. Node.js usa un modelo de operaciones E/S sin bloqueo y orientado a eventos, que lo hace liviano y eficiente. El ecosistema de paquetes de Node.js, npm, es el ecosistema mas grande de librerías de código abierto en el mundo.

Para aclarar un poco NodeJS es la parte de backend de JavaScript y uno de sus puntos fuertes al ser no bloqueante (como dice arriba) es la concurrencia, es decir maneja peticiones sin detenerse o bloquear las que le llegan (tiene límites por supuesto) y esta orientado a eventos, que para el que no lo sepa este tipo de programación basa su funcionamiento en eventos o mensajes recibidos desde otros hilos, programas....desde donde sea.

Otro de sus puntos fuertes es npm, ¿y esto que es? bueno como hemos comentado arriba es el gestor de paquetes de código abierto más grande del mundo (orientados a JS), en el que podemos encontrar librerías de casi todo lo que se nos ocurra al ser open-source se beneficia de que cualquiera puede subir paquetes por lo que el límite es prácticamente la imaginación ;)

En un principio esta sección esta centrada solo en NodeJS pero en los primeros posts comentaré algunas características de JavaScript que nos ayudarán en el entendimiento del conjunto, de todas formas ya os adelanto que posiblemente haga alguna sección con pequeños trucos o ejemplos de uso de JS.
Hablaremos de Node no de JS pero si explicaremos lo necesario para entender lo que estamos haciendo.

Veamos un poco de que va esto de Node.

Instalación

No me voy a entretener en la instalación en la página oficial de Node viene muy bien explicado lo que tenemos que hacer en cada plataforma (en el caso de que sea distinto de descargar e instalar jejejeje)
Yo recomiendo instalar la versión LTS ya que es a la que van dando soporte a lo largo del tiempo, las nuevas pueden tener bugs o dar problemas, pero podéis probar si queréis jejeje.
La instalación nos provee también de npm por lo que ya lo tendríamos también disponible para usarlo. Más adelante iremos viendo como usar npm y que tenemos que tener en cuenta de su uso.

Pues sin mucho más vamos a empezar por ver algo de JavaScript.

Callbacks

En node todos los usos de entrada o salida deberían ser asíncronos (no deben bloquear el hilo principal que es el que gestiona ), y una gran parte de la ayuda la obtenemos de los callbacks. Y que son los callbacks, para explicarlo de forma sencilla podríamos decir que son funciones que estando en el ciclo de vida de otra se ejecutan al final, es decir, son funciones que se llaman al terminar otra y no antes(aprovechándonos muchas veces de la posibilidad de pasar funciones como parámetros), veamos un ejemplo de función asíncrona con callbacks, para ello nos ayudaremos de la función setTimeout que para el que no lo sepa nos da la posibilidad de ejecutar algo tras el periodo de tiempo que le indiquemos:

"use strict";

console.log('Empezamos');

function showAfter2Seconds(text, callbackFunc){
    setTimeout(()=>{
        console.log(text);

        callbackFunc();

    },2000);
}

showAfter2Seconds('Pasaron 2 segundos',()=>{
    console.log('Soy el callback');
});

console.log('Estoy fuera de la función')

Para ejecutarlo solo tenemos que usar el comando node seguido del nombre de nuestro fichero js:

node callbacks.js

Y tendriamos algo como esto:

Don't stop the party: Node JS (I)
Como vemos primero nos muestra 'Empezamos' y luego directamente vemos 'Estoy fuera de la función' pero...¿por qué?. Bien esto de debe a como funciona la asincronía en sí, cuando trabajamos con asincronía tenemos que pensar que tenemos más de 1 trabajador jugando con nuestro código, tenemos el principal(el que manda) el que siempre curra y luego tenemos a los que este llama para que trabajen en segundo plano mientras el continua, es decir, cuando nuestro worker principal se encuentra con código asíncrono avisa a un compi para que lo haga y el continúa por donde estaba sin detenerse, por eso nos retorna el log que esta al final, porque el hilo principal ha llegado allí antes de que nuesto worker en segundo plano terminara. Pero claro esto no es tan simple como usar una función como parámetro y ya está, no nos confundamos, usar una función como parámetro y llamarla cuando nos interese es solo una ventaja del lenguaje que nos ayuda a evitar lo que se conoce como race condition, y que se ejecute nuestro código como nosotros queremos, si nosotros quitamos el setTimeout y ponemos otro tipo de función que nos detenga la ejecución lo que haría el código seria pararse en ese punto, devolver 'Soy el callback' y a continuación nos mostraría nuestro log 'Estoy fuera de la función'. En este caso lo que sucede es que setTimeout es una función asíncrona de por sí, es decir, cuando nuestro hilo principal se encuentra con esta función llama a algún compi disponible para que la ejecute y ya el compi es el responsable de lo que pase dentro de setTimeout, puede que sea un poco lioso y yo me haya extendido mucho en este punto pero es realmente IMPORTANTE entender esto para poder trabajar bien con NodeJS. Node comunmente se usa con una librería llamada async, la cual nos abstrae un poco de tener que hacer funciones como la que hemos visto arriba para usar asincronia, la documentación oficial esta muy bien explicada por lo que creo que es suficiente con que miréis un poco de que va aquí.

Bueno para empezar creo que es suficiente nos vemosss en el siguiente Ninjaaaassssssss

]]>
<![CDATA[React Superhero (I): Cambios en un mismo componente]]> Hello World ]]>https://jlgarcia.fulldev.ninja/react-js-i-cambios-en-un-mismo-componente/Ghost__Post__5a338158333e0f134c248f1bMon, 14 Aug 2017 11:11:00 GMTReact Superhero (I): Cambios en un mismo componente

Vamos a empezar a ver donde está realmente la gracia de esta librería, lo que vimos en el post de introducción bien lo podíamos tener con un poco de código HTML de toda la vida. Ahora vamos a darle algo de dinamismo a nuestro código.
Teníamos este código al final de nuestro primer post:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <!-- Libreria para trabajar con React -->
    <script src="https://unpkg.com/react@latest/dist/react.js"></script>
    <!-- Libreria de react para trabajar con el Dom-->
    <script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
    <!--Preprocesador para traducir lo que escribimos a todos los navegadores ya que react funciona con ECS6 y puede que no 
    funcione en todos -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script type="text/babel">

        class SuperComponente extends React.Component{
          render(){
            return (
              <div>
                <h2>Hello with React</h2>
                <MiSegundoComponente/>
              </div>
            );
          }
        }

        class MiSegundoComponente extends React.Component{
          render(){
            return (
              <h2>REACT is in the HOUSE...YEAH!!!!</h2>            
            )
          }
        }

        ReactDOM.render(
          <SuperComponente/>,
          document.getElementById('root')
        );

      </script>
   
  </head>
  <body>
      <div id="root"></div>
  </body>
</html>

Bien tenemos 2 componentes y los renderizamos dentro del body en nuestro div con id root, pues bien, vamos a añadir/modificar lo siguiente en nuestro MiSegundoComponente:

class MiSegundoComponente extends React.Component{
    constructor(){
      super();
      this.name = {
         nombre: "Your Super Name"
      }
    }
    render(){
       return (
         <div>
           <h2>REACT is in the HOUSE...YEAH!!!!</h2>
           <h4> { this.name.nombre } </h4>
         </div>
        )
     }
}

Lo que hacemos es crear un método constructor (OJO es palabra reservada no es que lo haya puesto yo), por el momento siempre llamamos a super (esencial para que se ejecute), y hacemos que nuestra instancia del componente (nuestro amigo this) tenga una propiedad name, que es un objeto de clave=valor en este caso con una key nombre con un texto. Asimismo en la parte de render entre {}, llamamos a this, es decir, a la instancia de nuestro componente, pasándole la propiedad y el key que hemos creado. ESTO SERIA PROPIO DE JSX, este superpoder nos permite añadir JS en nuestro código HTML. Si ahora ejecutamos esto:
React Superhero (I): Cambios en un mismo componente
Vemos como nos añade el contenido de la key nombre, "pues muy bien, y esto pa que cansino??" diréis...vale realmente Your superName es un placeholder, la idea es que ese texto cambie en algún momento.

Para ello necesitamos "escuchar algo en algún sitio" y hacérselo saber a nuestra propiedad nombre. Pero... ¿como hacemos esto? Muy fácil, vamos por partes, primero como "escuchamos":

//Añadimos un input con la propiedad onChange
<input onChange={}/>
<h4> { this.name.nombre } </h4>

Y esto que hace, básicamente envía los eventos de cambio de ese elemento a lo que tengamos dentro de {} y ¿que debemos poner dentro? pues como es costumbre un método que creemos a través de nuestra instancia(this):

//Fuera de render
getName(input){
    console.log(input)
}

//Dentro de render
<input onChange={this.getName}></input>
<h4> { this.name.nombre } </h4>

Como véis tenemos un método getName que recibe algo por parámetro, y le pasamos dentro del onChange, nuestra instancia llamando al método getName. De momento le ponemos un console.log para que podamos ver que es lo que nos pasa cada vez que escribimos. Si guardamos y abrimos nuestro index.html, cada vez que escribamos algo en nuestro input, recibiremos algo del estilo a esto:
React Superhero (I): Cambios en un mismo componente
Es un objeto con nombre Proxy y mogollón de información. Es un estilo de elemento clásico de eventos(digo estilo), pero claro no es lo que realmente buscamos, como conseguimos el contenido de nuestro input:

getName(input){
    console.log(input.target.value)
}

Esto lo habréis tenido que ver alguna vez ya, simplemente buscamos el value del target de nuestro elemento, y ahora nuesto console.log nos ofrece, esta información según escribimos:
React Superhero (I): Cambios en un mismo componente
Bien ya tenemos lo que necesitamos, ahora completemos la función para que nos modifique nuestro placeholder.

getName(input){
  this.setState(this.name = {nombre: input.target.value});
}

Para modificar "el estado" de un objeto o elemento del DOM tenemos el método o propiedad setState, el cual modificaría solo lo que le pasemos entre ().
Teóricamente sería algo como esto, pero lo habéis probado? Si lo probáis veréis que no funciona,nos indica que this es undefined
React Superhero (I): Cambios en un mismo componente
pero.....¿por qué? Básicamente porque this no está disponible como propiedad u objeto de las funciones o métodos que nosotros mismos creamos, para hacer que funcione es necesario enlazar(bind) el this con el contexto de ejecución. Para ello solo seria necesario hacer el siguiente cambio:


<div>
    <h2>REACT is in the HOUSE...YEAH!!!!</h2>
    <input onChange={this.getName.bind(this)}></input>
    <h4> { this.name.nombre } </h4>
</div>

Como veis dentro de nuestro método render le hemos añadido bind(this), es decir, enlazame el this de render con mi método getName et voilá:

React Superhero (I): Cambios en un mismo componente
Realmente es todo muy sencillo ¿verdad?
Antes de terminar con este post me faltaría comentar otro concepto, fijaros en esta parte del código que casi no hemos comentado:

this.setState(this.name = {nombre: input.target.value});

Hemos hablado de la propiedad setState pero no mucho de lo que hay dentro, si miráis un poco el código esto es prácticamente lo mismo que tenemos dentro de nuestro método constructor(es un poco feo no?). Pues bien realmente la propiedad tiene un motivo por el que se llama setState() y es porque el método constructor ya está preparado para gestionar los cambios de estado, si cambiamos el nombre de la propiedad y en lugar de name ponemos state:

constructor(){
  super();
  this.state = {
     nombre: "Your Super Name"
  }
}

Veamos como tendría que quedar nuestro método getName:

getName(input){
  this.setState({nombre: input.target.value});
}

Y que no se nos olvide cambiar nuestro placeholder:

<input onChange={this.getName.bind(this)}></input>
<h4> { this.state.nombre } </h4>

Si lo probáis veréis que todo funciona correctamente y hemos quitado alguna cosa que no quedaba del todo bien, aún así puede que algún momento queramos usarlo por lo que solo tenemos que recordar que si en el constructor no lo hacemos todo dentro del this.state, a la hora de usarlo es necesario indicar la estructura completa del objeto o propiedad que queremos cambiar.
En el próximo post veremos como comunicarnos entre componentes y alguna cosa más. Nos vemooooooos

]]>
<![CDATA[React Superhero : Introducción]]>https://jlgarcia.fulldev.ninja/react-superhero-introduccion/Ghost__Post__5a338158333e0f134c248f1aMon, 07 Aug 2017 15:33:00 GMTReact Superhero : Introducción

Empiezo nueva sección en el blog (tenía muchísimas ganas de empezar con React).

Os cuento un poco que es esto:

  • Esta desarrollado por Facebook que aunque no lo parezca nos da algo de confianza en cuanto a su durabilidad a la larga y su estabilidad.
  • Se centra mucho en la parte de UI, es decir seria la V del MVC.
  • Es muy útil para construir grandes aplicaciones que cambian con el tiempo.
  • Gracias a su Virtual DOM el rendimiento no se ve severamente afectado cuando la información necesita actualizarse.
  • Se basa en componentes, haciéndolos reutilizables fácilmente.
  • Lo podemos integrar a cualquier API.
  • Y es JavaScript :)
  • Se puede integrar con Node, Ruby o cualquier otro lenguaje de backend.
  • Tiene framework para mobile (React Native, que lo venden como 'nativo'), que será lo siguiente que veremos cuando terminemos con el básico ;)
  • Usa JSX que es una especie de lenguaje tipo XML para crear nuestros componentes. Básicamente nos permite usar código HTML en variables de JS. Tenéis mas información sobre esto en la pagina oficial de React.

Suena bien no?? A mi me encanta:
React Superhero : Introducción

Para trabajar con React necesitamos agregar mínimo 3 librerías a nuestro proyecto, es decir a nuestro index.html

<!-- Libreria para trabajar con React -->
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<!-- Libreria de react para trabajar con el Dom-->
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<!--Preprocesador para traducir lo que escribimos a todos los navegadores ya que react funciona con ECS6 y puede que no funcione en todos -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

Bien con esto ya tenemos lo básico para trabajar con React. Como extra yo me descargado un plugin para Visual Studio Code para que reconozca el trabajo con babel en su momento. No es en absoluto necesario.

Pues bien antes de continuar hagamos una prueba para comprobar que todo funciona bien, si alguno ha trabajado o conoce un poco Angular vera como ciertos conceptos con parecidos.

Vamos a añadir una cabecera a nuestro fichero HTML usando React, para ello incluimos lo siguiente:

<script type="text/babel">
        ReactDOM.render(
          <h2>Hello with React</h2>,
          document.getElementById('root');
        );
</script>

Como vemos usamos en lugar de nuestro text/javascript típico, hemos puesto babel para que reconozca que tiene que usar babel. Y a continuación le indicamos que renderice en nuestro DOM el HTML que le pasamos, pero......¿donde? bien aquí es donde viene un concepto similar a angular. En Angular 1.5 por ejemplo teníamos nuestro ng-app:

<body ng-app="appName">

Donde le indicabamos donde empezaba a funcionar nuestra WebAPP, pues con React el concepto es parecido, si veis en el código superior hemos puesto que busque un elemento con el ID root(que podria ser cualquier otro nombre), que sera donde renderice el código, por lo que en algún sitio de nuestro código html tenemos que añadirlo jejejejje

<div id="root"></div>

Bien y si todo ha ido bien tendríamos que tener algo similar a esto:
React Superhero : Introducción
Perfecto, como prueba de concepto esta bien, pero realmente así no se trabaja con React, es un lenguaje basado en componentes por lo que vamos a empezar por crear nuestro primer componente.

Primer Componente

Nuestro primer componente solo para ver de una forma rápida y facil como se harían, lo crearemos en el mismo index.html, dentro de nuestra etiqueta script

<script type="text/babel">
class Supercomponente extends React.Component{
      render(){
          return <h2>Hello with React</h2>;
      }
}

Como veis hemos creado una clase Principal (Ojo el nombre tiene que empezar en mayúsculas mas abajo veremos porque, y normalmente usamos inglés para los nombres, solo he puesto ese superNombre para que quede claro), la cual extiende de React.Component. Luego simplemente creamos un método render que devuelve el mismo código que teníamos antes, pero... y como lo llamamos, bien aqui viene el porque de las mayúsculas al crear el componente.
Los componentes pasan a ser etiquetas al estilo HTML y las ponemos en mayúsculas para diferenciarlas de las etiquetas normales, quedando la llamada al componente de esta manera:

ReactDOM.render(
   <SuperComponente/>,
   document.getElementById('root')
);

Ponemos la etiqueta (importante cerrándola al final), y con esto debería funcionarnos igual que antes, y ya habríamos creado y usado nuestro primer componente.

Fácil verdad? Bien como importante recordar que un componente siempre tiene que tener un método render y que en las llamadas del ReactDOM, solo repito solo podemos pasarle un componente. Y ahora viene cuando nos deberíamos preguntar, ¿Y como hago para usar varios componentes unos con otros?¿No me digas que tenemos que hacer para todos un ReactDOM.render?......Pues no, no es necesario, realmente podemos integrar unos componentes con otros, pero a nivel de componentes, es decir, un componente puede tener y devolver otros componentes, vamos a crear otro componente y hagamos la prueba

class MiSegundoComponente extends React.Component{
       render(){
            return <h2>REACT is in the HOUSE...YEAH!!!!</h2>
       }
}

Ya tenemos otro componente y para llamarlo, lo hacemos desde el primero como si de otra etiqueta HTML normal se tratara, algo así no?:

class SuperComponente extends React.Component{
render(){
     return (
            <h2>Hello with React</h2>
            <MiSegundoComponente/>
        );
     }
}

Fijaros que en el return he metido el contenido entre (), pero hagamos la prueba.....upssss no carga nada y si abrimos la consola del navegador vemos lo siguiente:
React Superhero : Introducción
Bien he puesto este error a propósito para que lo tengamos en cuenta como importante, TODOS LOS COMPONENTES ES NECESARIO QUE ESTEN DENTRO DE UNA ETIQUETA PADRE, es decir tienen que ser elementos HTML por si solos. En este caso seria simplemente meterlo todo dentro de un div:

class SuperComponente extends React.Component{
  render(){
         return (
           <div>
             <h2>Hello with React</h2>
             <MiSegundoComponente/>
           </div>
        );
   }
 }

Y ahora ya si nos funcionaria:
React Superhero : Introducción

Como véis el uso de React es bastante fácil solo tenemos que tener unas pequeñas cosas en cuenta, como por ejemplo que los componentes tengan que estar encapsulados dentro de una etiqueta principal para que funcionen.

Y hasta aquí este post introductorio de React, os animo a seguir los siguientes si queréis aprender al mismo tiempo que yo a usar React, nos vemos en el siguiente.

]]>
<![CDATA[Go Go Power Ra....ah que no: Go Parte 5 - Maps]]>https://jlgarcia.fulldev.ninja/go-go-power-ra-ah-que-no-go-parte-5-maps/Ghost__Post__5a338158333e0f134c248f16Sun, 23 Jul 2017 08:57:00 GMTGo Go Power Ra....ah que no: Go Parte 5 - Maps

Espero que este post sea bastante rápido jejejejej.
En este caso hablaremos de los Maps o lo que en otros lenguajes conoceríamos como diccionario. En general siguen las mismas teorías que los arrays o los slices en cuanto a creación y a lo referente al rendimiento, los mapas crecen dinámicamente pero podemos indicarles el tamaño lo que optimizaría un poco su rendimiento.

Creación

Empecemos con la creación. Como ya hemos visto en Go tenemos siempre varias formas de crear los elementos:

  • Forma básica
//var nombreVariable map[tipoClave]tipoValor
var rangerRojo map[string]int
  • Declaración corta:
//Inicializando sin valores
rangerRojo := map[string]int{}

//Inicializando con valores. Obligatoria la coma ultima
rangerVerde := map[string]int{
 "Fuerza": 10000,
 "Resistencia": 5000,
}
  • Usando make
rangerVerde := make(map[string]int)
//Al igual que en los arrays podemos indicar el tamaño para optimizar rendimiento.
rangerNegro := make(map[string]int, 2)  

Usando Map

Ya hemos visto un poco como es la creación de Maps en golang, no es muy complicado no?, veamos ahora algunos detalles de su uso.

Cuando asignamos el valor, realmente este devuelve 2, el contenido que queremos sacar y si existe o no. Veamoslo con un ejemplo:

rangerRojo := map[string]int{
  "Fuerza": 10000,
}

rangerVerde := make(map[string]int)

power, exist := rangerRojo["Fuerza"]
fmt.Println("Valor: ", power, "Existe: ", exist)

power2, exist2 := rangerVerde["Fuerza"]
fmt.Println("Valor: ", power2, "Existe: ", exist2)

He usado dos formas de crear maps y el resultado de esto seria:

Go Go Power Ra....ah que no: Go Parte 5 - Maps
Como veis nos inicializa el valor como suele hacer Go y nos indica que no existe realmente.

Borrando contenido de un Map

En este caso Go se vuelve muy útil tiene una función propia para poder eliminar el elemento que queramos:

delete(rangerRojo,"Fuerza")

Probémoslo en un ejemplo más completo:

rangerRojo := map[string]int{
	"Fuerza":      10000,
	"Resistencia": 500,
}
fmt.Println(rangerRojo)

delete(rangerRojo, "Fuerza")

fmt.Println(rangerRojo)

Y como vemos elimina el contenido sin problemas:
Go Go Power Ra....ah que no: Go Parte 5 - Maps

Recorrer un MAP

Para recorrer un MAP usamos la misma teoría que para los arrays o slices: RANGE vamos directos al ejemplo ya que es bastante simple:

rangerRojo := map[string]interface{}{
  "Fuerza":       10000,
  "Resistencia":  500,
  "Inteligencia": "200",
  "Arma":         "Espada",
}

for key, value := range rangerRojo {
  fmt.Println("Clave: ", key, " Valor: ", value)
}

Tenemos un map que tiene como claves varios strings representando las características de nuestro ranger y como valor he introducido algo que no habiamos visto todavia INTERFACE{}, bueno como resumen haceros a la idea de que es un tipo especial que funciona como un genérico, es decir, que representa cualquier valor(por eso podemos tener valores tipo INT y tipo STRING) y al recorrerlo tenemos esto:
Go Go Power Ra....ah que no: Go Parte 5 - Maps
OJO a tener en cuenta, no tiene porque hacerlo en orden sobretodo si introducimos algo a posteriori, mirar un ejemplo:

rangerRojo := map[string]interface{}{
  "Fuerza":       10000,
  "Resistencia":  500,
  "Inteligencia": 150,
  "Arma":         "Espada",
}

rangerRojo["Maná"] = 150

for key, value := range rangerRojo {
  fmt.Println("Clave: ", key, " Valor: ", value)
}

He añadido otra propiedad a nuestro ranger y el resultado es:
Go Go Power Ra....ah que no: Go Parte 5 - Maps

Si lo ejecuto de nuevo:

Go Go Power Ra....ah que no: Go Parte 5 - Maps

Por último y como he adelantado en el ejemplo anterior:

Añadir elementos a nuestro MAP

Bueno como ya hemos visto esto es muy sencillo:

rangerRojo["Maná"] = 150

fmt.Println(rangerRojo)

Y nos muestra
Go Go Power Ra....ah que no: Go Parte 5 - Maps
Por el momento hemos terminado con los mapas, profundizaremos en el trabajo con ellos más adelante.
Sin mucho más nos vemos en la siguiente superNinjas :)


]]>
<![CDATA[Cloud Adventures: Android y Azure Active Directory Login]]>https://jlgarcia.fulldev.ninja/cloud-adventures-android-y-azure-active-directory-login/Ghost__Post__5a338158333e0f134c248f13Wed, 19 Jul 2017 19:39:07 GMTCloud Adventures: Android y Azure Active Directory Login

Estreno otra sección, en este caso estará centrado en problemas que me he ido encontrado con los servicios Cloud a la hora de desarrollar alguna aplicación nativa para Android o IOS.
En este caso le toca el turno a Azure, por ciertas necesidades he tenido que "pegarme" con el servicio para que me funcionara el Login con el servicio de Azure Active Directory.
Vuelve a ser por el problema de la documentación existente, que creo que es el único problema con los servicios Cloud, que la documentación o no esta clara o no esta actualizada.

Este escenario es muy simple, queremos hacer login, con una app Android, usando usuarios pertenecientes a Azure Active Directory, en un principio debería ser bastante fácil pero por lo menos en mi caso he tenido algún que otro problema.

Primero de todo:

Configurar el AppService para el login

Teóricamente bastante sencillo, vamos a la doc oficial:
Configure AAD in APPService

Y la seguimos, como tal hacemos todo y pensamos que todo esta bien, pero a la hora de probarlo falta alguna cosilla, que os voy adelantando por aqui:

Cloud Adventures: Android y Azure Active Directory Login

Como véis en la foto en las redirecciones URL permitidas necesitamos añadirle algo similar a lo que veis en la foto, básicamente es el nombre de tu appservice seguido de un extra:

appservicename://easyauth.callback

Esto es importante, lo que pongamos aqui lo tendremos que poner tambien en nuestra aplicación en algún momento, en el caso de Android, lo haremos en el manifest.

Siguiente:

Encontrar el SDK

Bueno esto realmente depende de que queramos hacer y de como tengamos montado el entorno en Azure, en mi caso he creado un Mobile AppService, por lo que en condiciones normales deberíamos trabajar con el SDK para ello.

Bien pues en este caso deberia ser bastante fácil, la documentación oficial esta bastante bien para esto:
Azure SDK Android

Como veis al inicio te lo dice, básicamente usa el compile pertinente y un par de cosas más y listo.

Y por último:

Código necesario para el login

Bien seguramente si habeis mirado los links que he puesto más arriba habréis visto que casi todo el código que necesitamos ya estaba, concretamente en este Azure SDK Android

Creamos una conexión de cliente en el onCreate:

MobileServiceClient mClient = new MobileServiceClient(
    "<MobileAppUrl>",       // Replace with the Site URL
    this);                  // Your application Context

Por si no lo tenéis claro MobileAppUrl es la url de vuestro APPService algo similar a:

https://nombreDeApp.azurewebsites.net

El resto es como viene en la documentación, pero en nuestro caso como provider de login de backend ponemos aad o podemos usar:

MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory

Un par de detalles muy importantes:

  • siempre que en la documentación pone algo de url_scheme se refiere a la url de nuestra app o al menos al nombre.
  • Tenemos que tener esto en el manifest.xml (con la URL de nuestra app)
<activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="nombreDeApp" android:host="easyauth.callback"/>
    </intent-filter>
</activity>

OJO con ese easyauth.callback es el que permitimos más arriba en las redirecciones y sin el nada funciona.

Como tal solo he remarcado las partes que me han parecido más importantes, la gran mayoria de las cosas funcionan bien siguiendo la información que aparece en los links que os he comentado más arriba.

En otro post comentare como obtener datos del usuario que ha realizado el login, ya que a simple vista no es muy intuitivo, hasta la próximaaaaaa.

]]>
<![CDATA[Go Go Power Ra....ah que no: Go Parte 4 - Arrays y Slices II]]>https://jlgarcia.fulldev.ninja/go-parte-4-arrays-y-slices-ii/Ghost__Post__5a338158333e0f134c248f12Tue, 18 Jul 2017 19:19:48 GMTGo Go Power Ra....ah que no: Go Parte 4 - Arrays y Slices II

En este post vamos a ir viendo como se podrian hacer las cosas mas habituales con los Arrays o Slices, como puede ser recorrerlos, veremos tambien como podemos pasar de uno a otro para la optimización, sin mucho mas empezamos.

Recorrer un array o slice

Empecemos por el caso más habitual y es la necesidad de recorrer el contenido de un array, en este punto voy a adelantar alguna otra cosa que no es solo de Array o Slices, pero creo que es mejor verlo en esta parte.

Para recorrer un array, tipicamente lo hacemos un bucle for, pues en este caso Go tiene un proceso muy similar específico (aunque se podria simular un for normal, haciendo el código a mano pero me parece innecesario, con el código lo vereis mejor)

rangers := []string{"Ranger Rosa", "Ranger Amarillo"}

for index, ranger := range rangers {
	fmt.Println("El indice o posicion es: ", index)
	fmt.Println("El Ranger de esa posicion es:", ranger)
}

Con un resultado similar a este:
Go Go Power Ra....ah que no: Go Parte 4 - Arrays y Slices II
Como vemos aqui tenemos un par de palabras reservadas que no habiamos usado: for y range, mas o menos os podéis imaginar la funcion que tiene cada una, básicamente esto quiere decir "Por cada índice y ranger dentro del rango de rangers....."

Bastante sencillo no?? Si os fijais tenemos la declaración corta de variables :=, lo que esta haciendo es asignar en cada vuelta, el índice y el valor en los nombres de variables que hemos puesto(podrian ser otros perfectamente).

Y si no queremos el índice?? Básicamente ignoramos el resultado:

for _, ranger := range rangers {
	fmt.Println("El Ranger de esa posicion es:", ranger)
}

El _ en Go es un valor especial(funciona igual que en Swift por ejemplo) con el que estamos indicando que ignore ese resultado que podemos obtener de una función o cualquier otro proceso como puede ser este que acabamos de ver de recorrer un array.

Separando un array

O mas bien crear un subarray con parte del array original, es decir, veamos como partir un array para extrar el rango de posiciones que nos interese.
En go tenemos la posibilidad de seleccionar un rango de X posiciones dentro de un array, de una manera bastante sencilla:

miniRangers := rangers[:2]
fmt.Println("Hemos seleccionado del inicio a la posicion 2 sin incluirla:")
fmt.Println(miniRangers)

miniRangers2 := rangers[2:4]
fmt.Println("Hemos seleccionado desde la posicion 2 hasta la 4 sin incluirla:")
fmt.Println(miniRangers2)

miniRangers3 := rangers[4:]
fmt.Println("Hemos seleccionado desde la 4 hasta el final")
fmt.Println(miniRangers3)

Go Go Power Ra....ah que no: Go Parte 4 - Arrays y Slices II

Tambien es posible asignar al mismo array o resumiendo cortar el array:

rangers := []string{"Ranger Rosa", "Ranger Amarillo", "Ranger Rojo", "Ranger Verde", "Ranger Negro"}

rangers = rangers[2:4]
fmt.Println("Hemos seleccionado del inicio a la posicion 2 sin incluirla:")
fmt.Println(rangers)

Go Go Power Ra....ah que no: Go Parte 4 - Arrays y Slices II

Tamaño y Capacidad

Estos ya los hemos visto:

fmt.Println(len(rangers))//Para el tamaño

fmt.Println(len(rangers))//Capacidad

Añadir

Tambien hemos visto como añadir, ya sea en un array hasta su tamaño designado y en un slice cuando no esta inicializado:

var rangersDesignados [2]string
rangersDesignados = append(rangersDesignados,"R.Rojo","R.Verde")

rangersSlices = make([]string,2,10)
append(rangersSlices,"R.Rojo","R.Verde")

Eliminar

Siento decir que aqui el lenguaje nos falla un poco, ya que no ofrece ningun método ya preparado para esto, como tal ya tenemos que jugar nosotros. En esta página:SliceTricks
Tenemos alguna forma de hacer las operaciones mas comunes de eliminar, cortar, etc de manera eficiente.

Copiar(Importante)

En un principio esto puede no parecer nada, estamos hablando de copiar un array o slice en otro......bueno pues no es tan simple, ¿recordáis lo que comentamos en el post anterior sobre tamaño y capacidad? Por refrescar rápidamente, el tamaño era la cantidad de elementos que tenia un slice o array y la capacidad la reserva de elementos que puede contener, es decir, la cantidad máxima de elementos que puede tener. Recordemos que Go lo que hace es reservar un espacio en memoria para almacenar una cantidad aproximada de datos, lo importante venia a la hora de aumentar, como tal el slice comentamos que al aumentar lo que hacia era reservarse el doble de la capacidad que tenia anteriormente. Esto con slices pequeños esta muy bien, pero ¿y si tenemos un slice de 1000 elementos? En cuanto añadamos 1 más se reservara un espacio de 2000.

Pues bien con este escenario tenemos la accion de copiar como solución, lo que hacemos es copiar un array en otro con la capacidad ajustada a lo que queramos, la forma de usarlo es simple:

copy(destino, origen)

Bastante simple no? Pues veamos un ejemplo y apliquemos un poco de lógica para optimizar el consumo:

//Creamos dos slices
slice := []int{1, 2, 3, 4}
copia := make([]int, 4)

copy(copia, slice)

fmt.Println(slice)
fmt.Println(copia)

Como vemos hemos creado un slice con la forma corta, con contenido, y otro vacio con un tamaño y capacidad de 4. Si imprimimos el contenido todo estaría normal, pero un detalle a tener en cuenta es que realmente copy tiene en cuenta la capacidad del slice de destino (en este caso copia), como le hemos puesto una capacidad de 4, nos copia todo el contenido. Pongamos un ejemplo si ponemos menos:

//Si cambiaramos copia con otra longitud
copia2 := make([]int, 2)
copy(copia2, slice)
fmt.Println(slice)
fmt.Println(copia2)
//Veriamos como solo nos copia 2 elementos, 

Lo mejor es controlar el tamaño(no la capacidad ojo), del slice origen y usar eso para crear el destino:

//para no equivocarnos lo ideal seria
copia3 := make([]int, len(slice))
copy(copia3, slice)

fmt.Println(slice)
fmt.Println(copia3)
//Con esto si cambia el tamaño del slice la copia tambien.

//Como buena practica, aunque segun el entorno, es añadir el doble del tamaño a la capacidad
copia4 := make([]int, len(slice), len(slice)*2) //O con la capacidad segun el caso
    
copy(copia4, slice)
fmt.Println(slice)
fmt.Println(copia4)

Bueno creo que por el momento vamos a dejar los arrays y slices para continuar con otras cosas, recordar mirar el link SliceTricks y en otro de mis post tengo algo mas de trabajo con arrays: Go Tricks I

]]>
<![CDATA[Go Tricks (I): Array contains]]>https://jlgarcia.fulldev.ninja/go-tricks-i-array-contains/Ghost__Post__5a338158333e0f134c248f14Thu, 13 Jul 2017 09:00:34 GMTGo Tricks (I):  Array contains

Comienzo sección nueva, en este caso son pequeños post que trataran sobre algún truco para trabajar con Go, como todo existen varias formas de hacer, estas serán solo algunas posibles no siempre serán las mejores pero seguro que funcionan ;)

En este primer post vamos a ver como comprobar si un elemento existe dentro de un array(o slice), lo veremos con strings pero puede ser con cualquier tipo(primitivo por lo menos)

Como sabemos no tenemos forma directa en Go de comprobar si algo existe dentro de un array por lo que debemos implementarlo nosotros.
Una forma de hacerlo es la habitual de recorrer el array e ir comprobando pero realmente tenemos un par de formas(mínimo) más que yo creo que pueden ser más eficientes, aunque siempre depende del caso concreto.

Primera:

Veamos una típica recorriendo el array

list := []string{"a", "z", "j", "f", "i", "v", "r", "c", "l", "p", "h", "w", "k", "e", "u", "s", "b"}

for _, v := range list {
 if v == "b" {					
      fmt.Println("Existe")
      break
 }
}

Método bastante sencillo, recorremos el array y comprobamos si es lo que buscamos, en cuanto lo encuetre salimos del for. Es bastante rápido la verdad.

Segunda:

list := []string{"a", "x", "z", "j", "f", "i", "v", "r", "c", "l", "p", "h", "w", "k", "e", "u", "s", "b"}

set := make(map[string]bool)

for _, v := range list {
   set[v] = true
}

fmt.Println(set["b"])

Tenemos una array de strings desordenado, lo que hacemos es crear un map(lo que seria un diccionario en otros lenguajes), de clave=valor donde la clave es el contenido del array, y como valor le he puesto true, para que cuando comprobemos que existe muestre un true(aunque eso pasaria con todos), es un poquito de azuquitar visual que hemos puesto. Realmente nos pasaria lo mismo si intentamos asignar el valor de forma corta, como tal recordar que los maps al asignarlos a otra variable nueva devuelven realmente 2 valores, el contenido del map y si existe o no.

Como veis bastante sencillo, tenemos un for si o si pero es bastante rápido. Fijaos que le he puesto el último valor con la idea de que al final haremos pruebas de cuanto tarda cada uno.

Tercera:

list := []string{"a", "x", "z", "j", "f", "i", "v", "r", "c", "l", "p", "h", "w", "k", "e", "u", "s", "b"}

sort.Strings(list)
i := sort.SearchStrings(list, "b")

fmt.Println(list[i] == "b")

Lo primero que hacemos es ordenar el array y luego nos valemos del método SearchStrings(tiene search de todo) que tiene el objeto sort, le pasamos el array y lo que queremos buscar y nos devuelve la posición en la que se encuentra. A continuación comprobamos si el array tiene en la posición recibida el string que buscamos y voilá.
OJO!! Hacemos la comprobación de list[i] == "b" porque el método SearchStrings devuelve la posición donde deberiamos insertar el elemento en el caso de que no exista para que este ordenado directamente al añadirlo

Esta forma a mi me gusta más pero en grandes cantidades de datos realmente tendriamos que probar su eficienca

Pruebas VERSUS

Vamos a ver la diferencia de lo que tarda cada uno, esto no es del todo válido ya que depende de muchos factores pero es solo por ver lo que podria ser un acercamiento.
Vamos a crear un bucle rápido con cada tipo, que nos muestre el tiempo que ha tardado, pondremos unas 50 ejecuciones de cada uno para ver un poco la media, pero siempre tener en cuenta que afecta la cantidad de código que tengamos un simple Print puede alterar los valores:

Primer método

j := 0
for {
  t1 := time.Now()
  list := []string{"a", "z", "j", "f", "i", "v", "r", "c", 
"l", "p", "h", "w", "k", "e", "u", "s", "b"}
	
for _, v := range list {
  if v == "b" {
    //Aqui podriamos hacer que muestre true o algo asi
    	break
    }
}

fmt.Println("Tiempo total: ", time.Since(t1))

if j == 50 {
  break
}
j++

Seguiremos la misma linea para los 3, creamos una especie de bucle while en el que cuando sea 50 la variable j se salga de la funcion.
Esta nos daria algo parecido a esto:
Go Tricks (I):  Array contains
La primera ejecución tarda un poco mas y luego se mantiene, muy rápido no??(el array es muy pequeño jejejejje)


Segundo método:

j := 0

for {
  t1 := time.Now()
  list := []string{"a", "z", "j", "f", "i", "v", "r", "c", "l", "p", "h", "w", "k", "e", "u", "s", "b"}

set := make(map[string]bool)
for _, v := range list {
   set[v] = true
}
_ = set["b"]

fmt.Println("Tiempo total: ", time.Since(t1))

if j == 50 {
  break
}
j++

Nos da unos tiempos de este estilo
Go Tricks (I):  Array contains
Bien aqui vemos que hemos bastante mas lentos en comparación, le he puesto el _ = set["b"] para simular que haciamos algo para comprobar.
Este método tiene una ventaja, y es que si queremos buscar algo más ya lo tenemos en el map set. Como desventaja es que posiblemente consumamos recursos innecesariamente.


Tercer Método:

j := 0

for {
  t1 := time.Now()
  list := []string{"a", "z", "j", "f", "i", "v", "r", "c", "l", "p", "h", "w", "k", "e", "u", "s", "b"}

  sort.Strings(list)
  i := sort.SearchStrings(list, "b")
  _ = (list[i] == "b")
  fmt.Println("Tiempo total: ", time.Since(t1))

if j == 50 {
  break
}
j++

Y con este ultimo obtenemos:
Go Tricks (I):  Array contains
Vemos que es más rápido que el segundo pero menos que el primero. Como tal con este método si tenemos que realizar otra búsqueda como ya estaria ordenado seria más rápida, he probado y salen unos tiempos como estos:
Go Tricks (I):  Array contains
Como veis llegamos a los nanosegundos, que no esta mal.



Al final todo esto es orientativo, siempre dependerá del entorno y la cantidad de elementos que tenga el array. Es bastante probable que la primera forma sea la más rápida en la mayoría de los entornos pero al final deberemos valorarlo en el momento. También es cierto que es más fácil crearnos un método de utilidad con la primera opción que con el resto, podríamos generalizar usando interface o cualquier cosa que se nos ocurra. Siendo esto bastante útil si pensamos en la concurrencia como Go funciona con copias de elementos(a no ser que usemos punteros) no tendríamos "condiciones de carrera"(race conditions) a la hora de acceder a los elementos.

Espero que os haya parecido interesante, nos vemos en el siguiente NINJA POST :)

P.D: Para el que no sepa lo que es el race condition, básicamente es la posibilidad de que se este accediendo al mismo elemento y al mismo tiempo, lo que generaría varios problemas(cosa que en Go si trabajamos con copias no tendríamos)

]]>
<![CDATA[Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I]]>https://jlgarcia.fulldev.ninja/go-go-power-ra-ah-que-no-go-parte-3-arrays-y-slices-i/Ghost__Post__5a338158333e0f134c248f0cTue, 11 Jul 2017 20:11:39 GMTGo Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

En Go tenemos dos tipos de arrays o arreglos:

  • Los que llaman arrays, son arreglos con una cantidad definida de elementos,es decir, tienen un tamaño fijo y no pueden crecer.

  • Los llamados slices, que son los arreglos o arrays más comunes, en los cuales podemos añadir los elementos que queramos.

Y porque tenemos dos tipos? Pues básicamente es por el consumo de recursos, los arrays definidos, solo se reservan los recursos que necesitan, los slices o llamemoslos arrays dinámicos tienen siempre reservado una cantidad mayor de recursos para poder crecer. Más abajo veremos como saber esta cantidad.

Arrays definidos

La forma de declararlos es bastante sencilla:

//supongamos que son 3
var rangers [3]string
rangers[0] = "Ranger Rojo"
rangers[1] = "Ranger Negro"
rangers[2] = "Ranger Amarillo"

//El uso es como todos los arrays
fmt.Println(rangers)
fmt.Println(rangers[0])
fmt.Println(rangers[1])
fmt.Println(rangers[2])

Si ejecutamos esto:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Hasta aqui todo parece normal, vamos a intentar añadir alguno mas:

rangers[3] = "Ranger Verde"

Vemos como el propio visual studio nos avisa:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I
La única forma que tendriamos de ampliar ese array, o más bien de trabajar con los datos que contiene, es hacer una copia del array en otro con más cantidad de elementos posibles(o que fuera un slice).

Más abajo veremos como hacer eso, ya que es común para ambos tipos de array.

SLICES

Continuemos con los slices, que serán posiblemente el tipo más usado, sobretodo al principio.
Tenemos varias formas de crear slices, cada una tiene sus peculiaridades.
La primera:

var rangers []string

rangers[0] = "Ranger Rosa"
fmt.Println(rangers)

En un principio todo parece normal, el compilador no se queja tampoco, cool, pero vamos a ejecutarlo:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I
Como veis tenemos un bonito Panic (para el que no se lo imagine es como muestra los errores) pero ¿porque?

Go al inicializar los slices si lo hacemos de esa forma los inicializa por defecto "vacios" pero no entendamos vacios como cualquier otro elemento que podemos asignarle un valor directamente, si no lo inicializa con "ausencia de todo" incluso de tamaño.
Pero esto no significa que no podamos usarlo, Go tiene un método append que nos permite añadir los elementos que necesitemos:

rangers = append(rangers, "Ranger Rosa", "Ranger Rojo")

fmt.Println(rangers)

Podriamos añadir todos los que queramos, y como vemos funciona perfectamente:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Antes de continuar con la siguiente forma de inicializar slices vamos a hablar de "Tamaño" y "Capacidad"

¿¿??

Si jejejeje, con los slices en aplicaciones complejas tendremos que tener en cuenta estas dos cosas, pensando en la optimización por supuesto.

  • Tamaño: Básicamente es el tamaño que tiene ocupado el slice, lo que en otros lenguajes consultaríamos con un array.length

  • Capacidad: Es la cantidad de recursos reservada para ese slice.

Es decir si pensamos en los slices como cajas, tenemos que un slice con una capacidad de 5 son 5 cajas, de las cuales 2 estan cerradas con algo dentro(tamaño 2) y otras 3 estan abiertas a la espera de tener algo dentro, pero estan ocupando su espacio en el suelo ya.

Ahora veamos la segunda forma de crear un slice:

rangers := make([]string,10)
//Aqui tendriamos un slice con 10 de tamaño y 10 de capacidad

rangers := make([]string,5,10)
//Y aqui tendriamos un slice con 5 de tamaño y 10 de capacidad

Veamos un ejemplo de código para ilustrar un poco mejor esto:

var rangers []string
fmt.Println("Sin inicializar, es decir tamaño 0")
fmt.Println(rangers)

rangers = append(rangers, "Ranger Rosa", "Ranger Rojo")

fmt.Println("Append al no inicializado")
fmt.Println(rangers)

rangers2 := make([]string, 10)

fmt.Println("Usando make para asignar tamaño y capacidad iguales")
fmt.Println(rangers2)

rangers3 := make([]string, 5, 10)

fmt.Println("Usando make para asignar tamaño y capacidad distintos")
fmt.Println(rangers3)

Si ejecutamos este código tendriamos un resultado como este:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I
Si os fijais tenemos bastantes diferencias en cuanto a la inicialización por defecto de cada uno de los slices, el primero realmente tendria un valor similar a null, en el segundo hemos inicializado ese null con dos elementos, en el tercero se ha aplicado la inicialización por defecto de go de strings(es decir "") sobre 10 elementos, en el ultimo esa misma inicialización sobre 5.

Ahora ya podemos usar lo que llamariamos asignación directa:

rangers2[2] = "Ranger Verde"
rangers3[3] = "Ranger Negro"

fmt.Println(rangers2)
fmt.Println(rangers3)

Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Bien hemos comprobado que podemos añadir ahora por lo menos dentro del "tamaño inicializado", si nos fijamos cada elemento tiene una posición definida dentro del slice. Veamos ahora lo que pasa si intentamos añadir en una ubicación por encima del tamaño(ojo no de la capacidad). Lo vamos a intentar on ranger3 que tiene una capacidad de 10 y un tamaño de 5:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Vemos como nos vuelve a dar el error de que nos hemos salido del rango permitido. Para añadir por encima de esto tenemos que usar el mismo método que antes con append

rangers3 = append(rangers3, "Ranger Rosa", "Ranger Rojo")
fmt.Println(rangers3)

Si ahora ejecutamos veremos como despues del espacio que teniamos antes ha añadido los dos rangers nuevos.
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I
OJO a lo de los espacios vamos a ver una ultima forma de inicializar Arrays y Slices y volveremos a este ejemplo para ver cosas sobre la capacidad y el tamaño.

Como última forma vamos a ver la inicialización directa de ambos casos:

//Array definido
rangers2 := [2]string{"Ranger Verde", "Ranger Blanco"}
//Slice
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
	
fmt.Println(rangers2)	
fmt.Println(rangers3)

Como tal no notamos ninguna diferencia a la hora de mostrarlos:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Atención si hacemos esto:

rangers2 := [3]string{"Ranger Verde", "Ranger Blanco"}

Nos añade,en este caso un string(podria ser cualquier tipo que hubieramos indicado) vacio en el elemento faltante.

Bien ahora vamos a probar a añadir:

rangers2 := [2]string{"Ranger Verde", "Ranger Blanco"}
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}

rangers2 = append(rangers2, "Ranger Negro")
rangers3 = append(rangers3, "Ranger Blanco")

Si vemos el array definido se queja:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Como tal sigue restringiendo su ampliación, lo contrario que el slice.
Ahora vamos a ver la diferencia real entre uno y otro.

Len y Cap

Go tiene como casi todos los lenguajes una forma de comprobar la cantidad de elementos que tiene un array,normalmente suele tener que ver con length, pues Go no iba a ser menos tenemos nuestro len:

rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}

fmt.Println(len(rangers3))

Esto nos devuelve un 2, cosa normal no?? Pero como tal antes hemos hablado de la capacidad también, en este caso Go tiene también una forma de ver la capacidad que tenemos reservada(básicamente es reserva en memoria)

rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}

fmt.Println("Tamaño",len(rangers3))
fmt.Println("Capacidad",cap(rangers3))

Esto nos devolveria:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Hasta aqui todo normal no?? Todo tiene sentido verdad?? Vale ahora vamos a añadirle algo para aumentarlo, recordemos que con capacidad 2 no podemos usar asignación directa para los siguientes elementos como tal tenemos que usar append, vamos a usar append y veamos que nos devuelve ahora:

rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}

rangers3 = append(rangers3, "Ranger Blanco")

fmt.Println("Tamaño", len(rangers3))
fmt.Println("Capacidad", cap(rangers3))

Para nuestra sorpresa al ejecutar vemos esto:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I
UPS!!! y que ha pasaaaaaooooo!!!????, es simple Go lo que hace es duplicar la capacidad anterior, por lo que si no tenemos en cuenta y tratamos con slices muy grandes, por ejemplo imaginemos un array de 100 elementos, si añadimos 1 mas para hacer 101 tendriamos una capacidad de 200 siendo realmente inecesario.
Veamoslo con un ejemplo añadiendo al código anterior los 2 rangers restantes:


rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}

rangers3 = append(rangers3, "Ranger Blanco")

fmt.Println("Tamaño", len(rangers3))
fmt.Println("Capacidad", cap(rangers3))

rangers3 = append(rangers3, "Ranger Rojo", "Ranger Negro")

fmt.Println("Tamaño", len(rangers3))
fmt.Println("Capacidad", cap(rangers3))

Vemos que tenemos de repente una capacidad de 8:
Go Go Power Ra....ah que no: Go Parte 3 - Arrays y Slices I

Creo que con esto mas o menos queda clara la diferencia entre los arrays definidos y los slices, hablando de consumo de memoria y del propio uso de uno u otro, siempre sera recomendable tener arrays definidos, veremos más adelante alguna forma de pasar de uno a otro.

En el próximo post veremos tambien como trabajar con ambos, para recorrerlos, por ejemplo, y varias cosas mas.

]]>
<![CDATA[Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)]]>https://jlgarcia.fulldev.ninja/go-go-power-ra-go-parte-2-el-lenguaje-variables-tipos-primitivos/Ghost__Post__5a338158333e0f134c248f0bWed, 28 Jun 2017 08:00:00 GMTGo Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)

Vamos a ir viendo como trabajar con el lenguaje, es decir, como usar variables, arrays, slices, estructuras...etc. He pensado que es mejor hacer post mas cortos, y asi público un poco más rápido, por lo que cada post realmente tratará unicamente de un tema.Sin entretenerme más empecemos a trabajar.

Variables y Constantes

Para los que no lo sepan, una variable es un elemento de un tipo específico(numeral, texto, etc) que usamos para gestionar y poder trabajar con ellos. Después de este resumen extraño, comento con un poco más de tecnicismos, realmente una variable es una dirección de memoria que reservamos donde almacenamos el valor que contiene la variable. Esto es importante entenderlo, en lenguajes como Go sobretodo, porque el lenguaje usa punteros (los veremos más adelante, pero resumiendo es como un acceso directo a esa dirección de memoria)

Vamos a empezar con los tipos primitivos o básicos que tiene el lenguaje(como tal podemos crear nuestros propios tipos, pero eso ya lo veremos), aunque más que ver los tipos voy a hablar de como trabajar con ellos.
Los tipos básicos en Go los tenéis o en la documentación oficial o más resumido en el tour oficial del lenguaje.

En Go las variables tienen un tipado estático, y ¿esto que quiere decir?, pues que a la hora de declararlas tenemos que indicar el tipo, el cual no cambia:

var power int
var rangerName string

Un detalle importante, en Go no existe nil, null, undefined ni nada similar, siempre, siempre, siempre las variables son iniciadas, si no lo son por nosotros, el lenguaje automáticamente le pone 0 o "", según el caso. Si intentamos imprimir eso:

package main

import "fmt"

func main() {

  var power int
  var rangerName string

  fmt.Println("Power ranger red power: ", power)
  fmt.Println("Power ranger red name: ", rangerName)

}

Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)
El int es 0 y el string esta vacio.

Es posible tambien declarar varias variables del mismo tipo en la misma linea

var power,speed int

Las variables es posible iniciarlas una vez que son declaradas(tambien si hemos declarado varias en la misma linea)

var power, speed int = 100,70

Si imprimimos con:

	fmt.Println("Power ranger red power: ", power)
	fmt.Println("Power ranger red speed: ", speed)

Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)
Como extra Go tiene otra forma bastante mas ágil de declarar variables e iniciarlas que infiere el tipo (un poco de azúcar sintáctico)

power := 100
speed := 70
#Tambien podemos declarar varias a la vez
stamina, swordsSkill := 50, 80
# Y es posible usar distintos tipos en la misma declaración
megazordSkill, rangerName := 1000,"Scott"
## Realmente tambien infiere el tipo si inicializamos al declarar una variable
var power = 100 seria igual que lo de arriba

Si os fijais en la ultima declaración tenemos un int y un string, pues si imprimimos:

fmt.Println("Power ranger red power: ", power)
fmt.Println("Power ranger red speed: ", speed)
fmt.Println("Power ranger red stamina: ", stamina)
fmt.Println("Power ranger red swordsSkill: ", swordsSkill)
fmt.Println("Power ranger red megazordSkill: ", megazordSkill)
fmt.Println("Power ranger red name: ", rangerName)

Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)

En cuanto a las constantes comentar que como gran parte de los lenguajes Go también las tiene, básicamente es cambiar la palabra var por const:

const hello = "Hello World"
const hello string = "Hello World"

Trabajar con distintos tipos

Go no traduce entre tipos como otros lenguajes, por lo que tenemos que hacerlo nosotros, si probamos con este ejemplo:

jump := 60
gravityForce := 9.8
fmt.Println(v / p * 2)

Tendriamos este error:
Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)

Como el error indica tenemos que igualar los tipos:

fmt.Println(float64(jump) / gravityForce * 2)

Esto ya no daria error y funcionaría sin problemas. No voy a especificar como transformar los tipos de todas las variables porque eso en la documentación esta bien explicado, y tenéis ejemplos. Tambien tenemos funciones para transformar de String a int y viceversa(esto veremos algun ejemplo mas adelante pero insisto en la documentación viene bastante bien y es realmente sencillo).
Para los que tenga bastante experiencia,comentar que es posible sobreescribir los métodos propios que transforman todo en String,es decir lo que usa fmt, y hacer lo que nosotros queramos(esto lo veremos mas adelante), es bastante útil cuando no podemos imprimir algunos datos o funciones directamente por algún motivo(por ejemplo con los struct)

Variables Privadas y Públicas

En Go existe el concepto de variables públicas y privadas, las privadas solo son visibles en el fichero donde estan.
La diferencia está en la declaración, si las declaramos con la primera letra en mayúsculas o en minusculas:

  • Minúsculas = Privada
  • Mayúsculas = Pública

Veamos un ejemplo:

Creemos una estructura de ficheros similar a esta donde tengamos el root del proyecto como comentamos en el post anterior:
Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)
Como vemos tenemos un fichero main.go como hemos tenido siempre, una carpeta rangerModel y dentro de esta un fichero rangerModel.go

En rangerModel.go ponemos lo siguiente:

package rangerModel

var power int
var speed int

Como vemos he puesto el nombre del paquete donde esta alojado para diferenciarlo del main.
En el main.go:

package main

import (
	"vuestroRootPath/rangerModel"
	"fmt"
)

func main() {
	power := rangerModel.power
	fmt.Println("Power ranger red power:", power)
}

Si guardamos ambos, vemos como nos da un error al instanciar el power del rangerModel:
Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)
Como tal no tenemos acceso a la variable o propiedad power de rangerModel.
Ahora vamos a cambiar el power por Power en el modelo, quedando de esta manera(de paso le ponemos un valor):


package rangerModel

var Power = 100
var speed int

Y lo mismo en el main:

func main() {
	power := rangerModel.Power
	fmt.Println("Power ranger red power:", power)
}

Ahora si imprimimos:
Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)
Es bastante sencillo como veis, comentar que no es recomendable hacer variables públicas(o exportar variables según el lenguaje de Go), por eso nos da esta recomendación al ponerlo en mayúsculas:
Go Go Power Ra....ah que no: Go Parte 2 - El Lenguaje (Variables-Tipos Primitivos)


Por el momento dejamos aquí las variables básicas, más adelante seguiremos trabajando con ellas.
Nos vemos en el siguiente

]]>
<![CDATA[Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion]]>https://jlgarcia.fulldev.ninja/go-go-power-ra-ah-que-no-go-parte1-intro-y-preparacion/Ghost__Post__5a338158333e0f134c248f06Sat, 03 Jun 2017 09:45:14 GMTGo Go Power Ra....ah que no: Go Parte1 - Intro y preparacion

Empiezo con una nueva sección, mi aventura con el lenguaje GO, el cual he descubierto que me encanta, es un lenguaje muy bueno y sencillo para tratar con temas de concurrencia y realmente hace un montón de cosas por si solo(como su paquete net que hace la vida mas fácil en cuanto a servidor Web se refiere), lo vamos a ir viendo poco a poco. Sin mucho más empecemos

Instalación

El lenguaje lo podemos escribir desde cualquier editor de texto pero es necesario instalar el compilador y realizar algunas configuraciones. Desde la propia página Go Download están los paquetes, y las instrucciones. También es posible instalarlo con Homebrew (si lo usamos).

brew install golang

Para la correcta instalación tenemos que tener en cuenta que es necesario configurar ciertas variables de entorno, teneis toda la información en:
Instalación GO

Haceros a la idea de que la documentación de Go es bastante completa por lo que en general encontraremos todo lo que necesitemos aquí:
Documentación GO

Solo como aclaración tener en cuenta lo que comenta la documentación sobre los variables de entorno, si ejecutamos algún intalador (MAC/Windows) no deberíamos tener que hacer nada, si algo no funciona comprobar que existe en la variable de entorno $PATH la referencia a Go y que es correcta:

Windows
c:\Go\bin
Mac
echo $PATH <- nos devuelve lo que tenemos ahora mismo en el path
Esto deberia devolvernos una cadena de texto muy larga que en algún punto deberia poner algo similar a esto
/usr/local/go/bin

Si lo hacemos en Linux o con cualquier source, lo mejor es extraer el contenido en las rutas recomendadas y configurar las variables de entorno como aparece en la documentación.

Si tenéis algún problema buscar en internet que hay mucha información sobre esto, aun así si continuáis con problemas ponerlo en los comentarios e intentamos resolverlo.

Antes de continuar un detalle de algunas carpetas del directorio de Go, por si quereis buscar algo:

bin <- binarios,  los que instalamos con go install
package o pkg <- Trabajar con los wrappers dentro de nuestro código. Encapsuladores de C.
src <- Todo nuestro código fuente y la librerías de terceros. Las puede descargar go al realizar los imports en nuestras clases, o las podemos poner nosotros. Aqui es donde suele buscar el origen de los imports.

Algunos detalles sobre GO

Go es un lenguaje compilado no interpretado, es decir, el código es compilado (transformado) a lenguaje máquina con lo que se genera un ejecutable que funcionará sin necesidad de nada más para la arquitectura para la que fue creado. Si hablamos de los errores, en este tipo de lenguajes deberíamos verlos en el proceso de compilación, pero veremos como con GO podemos probar nuestro código sin necesidad de compilar.

Un lenguaje interpretado es aquel en el que un tercero(normalmente llamado intérprete) lee nuestro código y lo traduce a código máquina, esto sucede cada vez que lo ejecutamos, y los errores los detectamos en este tiempo de ejecución.

Go es un lenguaje que se usa mucho para scripting en backend ya que es bastante rápido en ejecutar, sus librerías básicas cubren mogollón de escenarios y como he comentado el mismo script lo podriamos compilar para todos los S.O, aunque esto depende un poco de lo que haga jejejeje, no es tan mágico como suena. Personalmente me encanta por la parte de concurrencia y la parte de Web Server, que si mezclamos ambas cosas podemos hacer cosas muy curiosas y que consuman pocos recursos.
Como ejemplo pongo un caso de uso de la empresa Malwarebytes en el que gestionan 1 Millón que bajaron sus consumos en un porcentaje muy alto usando GO link. No se a vosotros pero a mi me parece muy interesante poder hacer cosas como esa.

Es un lenguaje con tipado estático, esto quiere decir que desde el principio al declarar las variables tenemos que indicarle el tipo que son. Aunque es cierto que GO tiene una forma de declaración y asignación de variables, en la que el lenguaje hace lo que se conoce como "inferencia de tipos", o lo que es lo mismo, el propio lenguaje define el tipo de la variable según el valor asignado(veremos mas adelante ambas formas)

Por último comentar que es un lenguaje que tiene características similares a C (como puede ser que usa punteros), compila directamente en ensamblador y un extra que es muy útil y que nos ahorra quebraderos de cabeza es que tiene recolector de basura es decir, no tenemos que controlar el estado de las variables o funciones para saber si se estan usando o no, ya lo hace Go por nosotros y aunque afecta un poco al rendimiento (realmente es muy poquito), es de agradecer a la hora de programar

Hola Mundo

Como ya he escrito bastante rollo, vamos a trabajar un pelín con go.
Antes de empezar primero comprobamos que tenemos bien instalado Go, para ello desde cualquier linea de comandos (da igual Windows/Mac/Linux) escribimos:

go version

Y deberíamos ver algo como esto:
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion
Si lo vemos todo va bien, si a alguien no le aparece o tiene problemas que lo vaya poniendo en los comentarios y vemos como lo solucionamos.

Ahora abrimos un fichero de texto escribimos siguiente y guardamos como main.go:

package main

import "fmt"

func main() {
   fmt.Println("Hello Power Rangers")
}

Con el fichero guardado, nos situamos en la ruta donde lo tengamos en nuestra linea de comandos y ejecutamos:

go run main.go

Esto nos deberia mostrar algo similar a esto en la consola:
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion
Voilá ya tenemos nuestro hola mundo. Ya iremos viendo lo que es cada cosa que hemos escrito, esto es solo para que escribamos un poco y veamos que funciona.

IDEs

Lo último que vamos a ver en este post, serán los IDE, que yo no se vosotros, pero a mi el modo Chuck Norris para programar (hacerlo directamente en un fichero de texto) no es lo que mas me convence, si tenemos posibilidades de que nos ayuden un pelín de alguna forma yo lo agradezco, pero como ya he comentado se puede hacer con cualquier editor de texto.

Para programar con Go tenemos varias opciones:

  • Podemos hacerlo en un fichero de texto(Chuck Norris/Bruce Lee/Yoda mode) como ya he comentado
  • Otra opción es usar Sublime Text una gran mayoria de vosotros ya lo conocereis, y como siempre pues tenemos plugins que la comunidad ha creado Ejemplo lo que le daria algunas vitaminas a nuestro editor para trabajar con Go.
  • Tambien es posible usar Atom. Sigue la misma teoría que Sublime, tenemos plugins que nos ayudan con Go Ejemplo
  • Los grandes de JetBrains están preparando un IDE muy completo (como siempre previo pago), y ahora tenemos disponibles algunos plugins para algunos de sus IDEs actuales.
  • Para hacer pruebas o si quereis para algunas cosas de las que vayamos viendo podemos usar el PLAYGROUND de Go SuperPlayground, super util para compartir código.
  • Como última opción y la que más gusta es Visual Studio Code, para mi es un acercamiento a las aplicaciones de JetBrains (que para mi son de lo mejor que hay). Como es la que uso veamos un poco su configuración.

Visual Studio Code

Con cualquier IDE y como programadores, nos pueden interesar mínimo 2 cosas para agilizar un poco nuestro trabajo:

  • Que nos indique errores cuanto antes
  • Y poder hacer debug de nuestro código para poder resolver mejor los problemas que nos vayamos encontrando.

Pues estas dos cosas y algún extra como las sugerencias son las que nos ofrece este IDE con algunas configuraciones.

Lo primero es instalar el plugin de Go desde la aplicación. Para ello tenemos que buscar la ventana de extensiones, creo que en todas ellas esta en el menu superior de la aplicación en la pestaña ver o view, seleccionamos extensiones o extensions
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion
Y en la ventana que nos aparece buscamos Go
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion
Y entre todas las extensiones que nos aparecen buscamos la de lukehoban y la instalamos.
Una vez que termine nos pedirá reiniciar la aplicación, y una vez que reinicie lo mejor es crear un fichero nuevo y ponerle extension .go.




Hacemos esta para que el propio visual studio nos complete la instalación, ya que cuando guardemos el fichero nos aparecerán algunos mensajes como este:
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion
Básicamente en todos los que nos pregunte tenemos que indicarle Install All

También es posible que nos indique que configuremos el GOPATH para que os hagáis una idea ese seria el Workspace desde donde trabajaremos, si os sale eso en Doc-GOPATH o en Test Install GO tenéis info para que identifiquéis bien lo que es y podáis configurarlo correctamente.

Lo segundo es configurar para poder hacer debug, lo más fácil es que miréis en la documentación del creador Derekparker-delve, aquí según vuestro sistema operativo tenéis las instrucciones. En general suele valer con hacer

go get github.com/derekparker/delve/cmd/dlv

Aunque depende un poco de la versión de sistema, como he comentado lo mejor es que miréis la documentación.
Y una vez que lo tengáis instalado ya podremos ir poniendo lo que se conoce como puntos de interrupción para controlar lo que hace nuestro código.
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion
Con hacer click un par de veces al lado del número de linea es suficiente. Y para comprobar que funciona pulsamos sobre F5 o nos vamos al menú Depurar y pulsamos Iniciar depuración
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion



Y entonces nuestro IDE cambiará al modo debug que lo que nos hará será parar la ejecución del código donde tengamos los puntos y podemos ver como están las variables o lo que tengamos en el código.
Go Go Power Ra....ah que no: Go Parte1 - Intro y preparacion

Como veis en la imagen variables a la izquierda, pila de llamadas un poco más abajo, puntos de interrupción.... todo muy útil sobretodo cuando tenemos problemas con nuestro código.

Y con esto finalizo este post(por fiiiin!!! ) y ya en los siguientes empezaremos con lo bueno.
Si tenéis dudas o problemas ponerlo en los comentarios.
Ciaaaao

]]>