From 019562ea9243d95290db700d23a274ace831ac70 Mon Sep 17 00:00:00 2001 From: ivanrodricalleja Date: Thu, 19 Mar 2020 20:01:17 +0100 Subject: [PATCH] Migrate blogs --- .../2017-01-08-hemos-tenido-un-sueno/es.md | 38 ++ .../blogs/2017-08-08-bot-flow-and-luis/es.md | 304 ++++++++++ .../blogs/2017-08-10-first-vs-single/es.md | 129 ++++ .../blogs/2017-08-14-atributos-en-unity/es.md | 164 ++++++ .../2017-08-22-micropost-f12-en-vs/es.md | 107 ++++ .../es.md | 125 ++++ .../es.md | 41 ++ .../es.md | 205 +++++++ .../es.md | 474 +++++++++++++++ .../es.md | 280 +++++++++ content/blogs/2017-11-07-version-ie/es.md | 172 ++++++ .../es.md | 326 +++++++++++ .../es.md | 89 +++ .../blogs/2017-11-30-codemotion-2017/es.md | 312 ++++++++++ content/blogs/2017-11-31-scary-code/es.md | 371 ++++++++++++ .../es.md | 186 ++++++ .../es.md | 113 ++++ content/blogs/2018-01-09-atributos-en-c/es.md | 292 +++++++++ .../es.md | 118 ++++ .../es.md | 222 +++++++ .../2018-02-13-hoc-vs-render-props/es.md | 358 ++++++++++++ .../es.md | 65 +++ .../es.md | 301 ++++++++++ .../es.md | 302 ++++++++++ content/blogs/2018-04-12-pull-requests/es.md | 201 +++++++ .../2018-04-19-el-buen-informatico/es.md | 50 ++ .../es.md | 160 +++++ .../2018-05-31-sinergia-del-equipo/es.md | 46 ++ .../2018-07-18-nuevo-react-boilerplate/es.md | 88 +++ content/blogs/2018-09-20-destructuring/es.md | 439 ++++++++++++++ .../es.md | 35 ++ .../2018-10-04-grpc-protocol-buffers/es.md | 381 ++++++++++++ .../es.md | 182 ++++++ .../es.md | 285 +++++++++ .../es.md | 424 ++++++++++++++ .../blogs/2019-06-12-tutorial-git-flow/es.md | 393 +++++++++++++ .../es.md | 290 +++++++++ .../es.md | 105 ++++ .../blogs/2019-08-14-jugando-con-svelte/es.md | 406 +++++++++++++ .../es.md | 461 +++++++++++++++ .../es.md | 66 +++ .../es.md | 58 ++ .../es.md | 149 +++++ .../es.md | 320 ++++++++++ .../es.md | 552 ++++++++++++++++++ 45 files changed, 10185 insertions(+) create mode 100644 content/blogs/2017-01-08-hemos-tenido-un-sueno/es.md create mode 100644 content/blogs/2017-08-08-bot-flow-and-luis/es.md create mode 100644 content/blogs/2017-08-10-first-vs-single/es.md create mode 100644 content/blogs/2017-08-14-atributos-en-unity/es.md create mode 100644 content/blogs/2017-08-22-micropost-f12-en-vs/es.md create mode 100644 content/blogs/2017-08-24-rambling-javascript-1-let-y-const/es.md create mode 100644 content/blogs/2017-08-29-micro-post-organizando-las-referencias-en-c/es.md create mode 100644 content/blogs/2017-08-31-rambling-javascript-2-fat-arrow-functions/es.md create mode 100644 content/blogs/2017-10-31-generando-un-sprite-svg-en-aplicacion-react-performance-y-accesibilidad/es.md create mode 100644 content/blogs/2017-11-07-operadores-de-conversion-en-c/es.md create mode 100644 content/blogs/2017-11-07-version-ie/es.md create mode 100644 content/blogs/2017-11-16-rambling-javascript-3-arrays/es.md create mode 100644 content/blogs/2017-11-24-sobrecarga-de-constructores-en-c/es.md create mode 100644 content/blogs/2017-11-30-codemotion-2017/es.md create mode 100644 content/blogs/2017-11-31-scary-code/es.md create mode 100644 content/blogs/2017-12-07-visual-studio-team-services-desde-terminal/es.md create mode 100644 content/blogs/2017-12-14-estudiemos-japones-juntos/es.md create mode 100644 content/blogs/2018-01-09-atributos-en-c/es.md create mode 100644 content/blogs/2018-01-22-tips-tricks-diseno-para-desarrolladores/es.md create mode 100644 content/blogs/2018-02-06-templates-string-in-javascript/es.md create mode 100644 content/blogs/2018-02-13-hoc-vs-render-props/es.md create mode 100644 content/blogs/2018-02-22-tips-para-mejorar-bases-de-datos-sql-server/es.md create mode 100644 content/blogs/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/es.md create mode 100644 content/blogs/2018-04-02-react-context-api-y-portals/es.md create mode 100644 content/blogs/2018-04-12-pull-requests/es.md create mode 100644 content/blogs/2018-04-19-el-buen-informatico/es.md create mode 100644 content/blogs/2018-05-17-diferencis-entre-graphql-rest/es.md create mode 100644 content/blogs/2018-05-31-sinergia-del-equipo/es.md create mode 100644 content/blogs/2018-07-18-nuevo-react-boilerplate/es.md create mode 100644 content/blogs/2018-09-20-destructuring/es.md create mode 100644 content/blogs/2018-10-02-yo-he-venido-aqui-a-equivocarme/es.md create mode 100644 content/blogs/2018-10-04-grpc-protocol-buffers/es.md create mode 100644 content/blogs/2019-01-31-no-mas-condicionales-anidados-abraza-el-patron-estrategia/es.md create mode 100644 content/blogs/2019-05-27-no-renderices-mas-de-la-cuenta/es.md create mode 100644 content/blogs/2019-06-10-rambling-javascript-6-spread/es.md create mode 100644 content/blogs/2019-06-12-tutorial-git-flow/es.md create mode 100644 content/blogs/2019-07-06-contenerizacion-de-aplicaciones-en-docker/es.md create mode 100644 content/blogs/2019-07-17-domotizando-nuestra-casa-con-home-assistant/es.md create mode 100644 content/blogs/2019-08-14-jugando-con-svelte/es.md create mode 100644 content/blogs/2019-08-22-contruye-un-server-graphql-con-prisma/es.md create mode 100644 content/blogs/2019-09-04-dominios-seguros-a-medida-con-github-pages/es.md create mode 100644 content/blogs/2019-10-09-typescript-types-vs-interfaces/es.md create mode 100644 content/blogs/2019-11-13-camara-de-seguridad-con-raspberry-pi/es.md create mode 100644 content/blogs/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/es.md create mode 100644 content/blogs/2020-03-18-react-data-fetching-con-suspense/es.md diff --git a/content/blogs/2017-01-08-hemos-tenido-un-sueno/es.md b/content/blogs/2017-01-08-hemos-tenido-un-sueno/es.md new file mode 100644 index 0000000..06a1b52 --- /dev/null +++ b/content/blogs/2017-01-08-hemos-tenido-un-sueno/es.md @@ -0,0 +1,38 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-01-08-hemos-tenido-un-sueno/header.jpg +navigation: True +title: "Hemos tenido un sueño" +date: 2017-01-08 12:00:00 +tags: nocountryforgeeks +class: post-template +subclass: 'post' +author: aclopez +--- + + +"La mejor forma de predecir el futuro es implementarlo" [Alan Kay](https://hipertextual.com/2011/10/historia-de-la-tecnologia-alan-kay-padre-legitimo-de-la-computacion-personal) + +Hemos tenido un sueño. Hemos tenido un sueño en el cual teníamos todo el tiempo para poder aprender todos los lenguajes. Un sueño en el que quedan atrás todos los prejuicios, en donde la gente siempre mira hacia delante. Un sueño en el cual no existen _Global.asax_ ni _WebConfig_, un sueño donde _Webpack_ genera todos los ```js``` ordenados, un sueño donde la programación funcional va entrando en nuestro código. Un sueño donde había equipo y no gente trabajando junta. Un sueño donde el pair programming surge de forma espontánea. Un sueño donde las _Pull Request_ llegaban a más de 4.000, llenas de comentarios positivos. Un sueño donde nadie piensa eso de _"Aquí se viene con to aprendío"_, donde se valoran las cosas bien hechas, no las cosas funcionales. Un sueño donde el programador busca esa excelencia en su código de manera natural. Un sueño donde escribir código es un arte. + +Y así es como nace __No Country For Geeks__. Tenemos un sueño y queremos compartirlo. Queremos programar con _C#_, con _Node_, ver el mundo de las APIs con la perspectiva de _GraphQL_, adentrarnos en _React_ y _Webpack_, _Clojure.js_, que nuestros bots hablen como personas, descubrir _Cognitives Services_ con _LUIS_, _Watson_, y lo más importante, compartir el mundo que vamos aprendiendo. Porque nosotros somos esos __eternos aprendices__ que están detrás de la pantalla de ordenador picando código, desaprendiendo y aprendiendo constantemente del mundo de los bits, la programación y el trabajo en equipo. + +![Padawan](/assets/images/posts/2017-01-08-hemos-tenido-un-sueno/starwars.jpg) + +Nuestra única aspiración al compartir nuestro código es que en el mundo haya algunos programadores más que puedan decir: + +> Soy un programador a pesar de tener que volver a ser un aprendiz cada día. + +Y estaríamos aún más satisfechos si algunos de ellos fueran más lejos y dijeran: + +> Soy un programador y me encanta tener que volver a ser un aprendiz cada día. + +Para ello podéis seguirnos en este blog y nuestras redes sociales: + +* Blog: [http://www.nocountryforgeeks.com/](http://www.nocountryforgeeks.com/) +* Twitter: [https://twitter.com/nocountry4geeks](https://twitter.com/nocountry4geeks) +* Instagram: [https://www.instagram.com/nocountryforgeeks](https://www.instagram.com/nocountryforgeeks/) +* Facebook: [https://www.facebook.com/463217730714163/](https://www.facebook.com/463217730714163/) + +> ¡Bienvenidos! diff --git a/content/blogs/2017-08-08-bot-flow-and-luis/es.md b/content/blogs/2017-08-08-bot-flow-and-luis/es.md new file mode 100644 index 0000000..b835959 --- /dev/null +++ b/content/blogs/2017-08-08-bot-flow-and-luis/es.md @@ -0,0 +1,304 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-08-code-flow-and-luis/header.jpg +navigation: True +title: "Bot Flow & LUIS" +date: 2017-08-08 12:00:00 +tags: bot luis bot-framework +class: post-template +subclass: 'post' +author: elenaps +--- + + +## Introducción + +Aunque ya hace como un año que se presentó **Bot Framework** en su **Microsoft Build2016**, es en estos últimos meses cuando hemos percibido un notable crecimiento en el interés de diversos clientes en adquirir su propio bot-chat. + +Este servicial, incansable y humanizado bot conversacional hace que las empresas se froten las manos: un sólo asistente puede ser capaz de solventar dudas a multitud de clientes a través de diferentes canales de comunicación, tienen disponibilidad absoluta y suponen un coste muy bajo. Por supuesto, la realidad difiere bastante de lo que parece. Todavía es necesario un humano cuando se requiere de un servicio algo más personalizado. + +La evolución de la inteligencia artificial también está teniendo muy buena aceptación en la comunidad de desarrolladores. Estos asistentes personalizados pueden desarrollarse a través de diferentes tecnologías, captando así un mayor número de desarrolladores que se encuentran cómodos dentro de su especialidad. Como ya se comentó en la pasada Build2016, Bot Framework ofrece unos servicios y un SDK para desarrollar nuestro bot tanto en C# como en Node.js. + +Con esta entrada nace una serie de posts dedicados a cómo crear nuestro propio bot conversacional desde cero: + +1. Diseño del flujo de los diálogos y su entrenamiento con LUIS +2. Bot Framework y cómo desarrollar un bot conversacional con el SDK para **Node.js**. Lo haremos de esta manera puesto que nuestro compañero [Francisco Olmedo](https://geeks.ms/windowsplatform/author/folmedo/) ya nos explicó cómo hacerlo en C# : [Construyendo un bot (parte 1)](https://geeks.ms/windowsplatform/2016/04/27/construyendo-un-bot-parte-1/). +3. Desplegar el bot sobre cualquiera de los canales de comunicación disponibles como: Skype, Facebook, Teams, Slack, Cortana, etc. + +## A tener en cuenta +Puntos importantes a tener en cuenta antes de empezar a diseñar el flujo para nuestro bot: + +* Qué información queremos ofrecer +* Cuáles serán los limites dentro del flujo +* Cómo será la interacción entre el usuario y el bot +* Cuál será el rol de nuestro bot + +Me gustaría comentar estos dos últimos puntos ya que creo que van bastante ligados entre sí. No debemos olvidar que el bot tiene ciertas limitaciones, y si no queremos que los usuarios se cansen y abandonen la conversación, éste tendrá que dar respuestas concretas y coherentes. + +Además, deberemos analizar en profundidad la identidad que podría adoptar. +Una buena posibilidad es que dé respuestas y sugerencias en un tono divertido e ingenioso. El usuario obtendrá una experiencia positiva y la sensación de estar teniendo una conversación lo más fiel posible a la que tendría con un humano… divertido y afable 😊. +En cualquier caso, esto queda a criterio de cada creador. + +Por el momento, en este post nos centraremos en los **flujos conversacionales y cómo trasladarlos a LUIS**. + +## Muy brevemente, ¿Qué es LUIS? +Aunque en el siguiente [enlace](https://docs.microsoft.com/es-es/azure/cognitive-services/LUIS/Home) podéis leeros toda la documentación, me gustaría explicar muy por encima cómo funciona LUIS. + +L.U.I.S **(Language Understanding Intelligent Service)** es un servicio cognitivo creado por Microsoft con el fin de reconocer el lenguaje natural. Se trata de una herramienta que analiza e interpreta las peticiónes de los usuarios. Sustrae ciertos datos e intenciones, que hemos configurado previamente, y nos devuelve una valoración (***score***), que analizaremos posteriormente. + +Si el resultado no es el deseado, LUIS nos ofrece la posibilidad de entrenarlo. Este entrenamiento consiste en añadir y probar una serie de acciones concretas las cuales le ayudarán a comprender qué ha querido decir el usuario. A mayor entrenamiento, mayor será la comprensión. + +Un típico ejemplo que nos ayudará a familiarizarnos y entender la diferencia que hay entre *utterances, intents y entities:* +* ***Utterance***: Es la frase del usuario. ***Ej.: Quiero comprar una camisa.*** +* ***Intent*** : Es la intención que desea el usuario a través de la frase, el verbo. ***Ej.: Comprar.*** +* ***Entity*** : Sobre qué quiere aplicarse esa intención. ***Ej.: Camisa.*** + +## Diseñando el flujo de un bot +Cuando nos enfrentamos por primera vez al diseño del flujo de los diálogos de un bot conversacional, solemos cometer el mismo error: obtener primero la entidad que quiere el usuario y posteriormente la intención sobre esa entidad. +Para que los diálogos fluyan adecuadamente y nuestro modelo de LUIS funcione de manera eficiente, el flujo deberá hacerse a la inversa. + +**¿Por qué debemos hacerlo de esta manera?** Porque el JSON recibido nos devuelve la intención con el mayor *score*. Una vez que tenemos dicha intención guardada, simplemente tendremos que aplicarlo con las entidades que nos devuelven. + +A continuación, vamos a analizar un diagrama diseñado a partir del flujo conversacional de este blog: + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/flowbotgeeksteam.jpg) + +El **flujo del diálogo inicial** sería el siguiente: + +Dependiendo del canal de comunicación utilizado, la primera interacción entre el bot y usuario estará restringida. Por ejemplo, ***Teams*** no permite que sea el bot quien inicie el diálogo. +Por ello, lo más habitual es que sea el usuario quien lance su *utterance* primero. +En cuanto el usuario haga su petición, el bot lo manda al diálogo inicial para analizarlo, ¿qué está preguntando? ¿qué quiere? + +LUIS irá comprobando con cada uno de los *intents* cuál es el que tiene mayor *score*, y sobre dicha base, si obtenemos un *score* ganador significa que ha entendido lo que el usuario quiere. Si recibe todos los valores del *score* muy bajos, se mandará al cajón de sastre, que es el ***intent NONE***. Aquí, según nuestro flujo, se le preguntará al usuario si puede reformular su petición porque no le hemos entendido y le mandaremos al inicio de nuevo a la espera de su respuesta. + +Debemos evitar los bucles infinitos, por ello, en el ***intent NONE*** se puede apreciar que en el segundo intento fallido, el bot le hará una sugerencia (***Intent HELP***) donde, dependiendo de la *entity* seleccionada por el usuario, el bot le redirigirá hacia un *intent* u otro. + +## [LUIS.ai](luis.ai) + +Necesitamos tener una cuenta en luis.ai para poder empezar. En cuanto tengamos la cuenta, vamos a seleccionar a **My apps**, en el menú principal, y nos crearemos una nueva aplicación de LUIS configurándolo con un nombre, descripción e idioma. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/createnewapp.jpg) + +## Intents + +Con la app creada, nos dirigiremos al menú lateral del Dashboard para comenzar a añadir *intents*. Personalmente, prefiero crear todos los *intents* con sendas *utterances*. Así tengo una visión global de cómo puede ir quedando nuestro modelo. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/createfirstintent.jpg) + +El resultado sería algo así: + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/intents.jpg) + +Es recomendable que se vaya entrenando a medida que vamos creando los *intents* y las *utterances*, de esta manera será más sencillo controlar el *score* en cada entrenamiento. El entrenamiento lo encontrarás en el menú lateral como _**"Test your application"**_. + +Como puede observarse en la siguiente imagen, esta *utterance "hola!"* está respondiendo con el máximo *score* posible y, además, con bastante diferencia entre este primer *score* y el siguiente. Gracias a esta gran diferencia entre *scores* evitaremos que los *intents* se pisen entre ellos. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/firsttraining.jpg) + +La mejor forma de recibir un *score* alto por cada *intent*, es la de generar *utterances* con un patrón muy similar por cada uno de ellos. Puede resultar algo tedioso añadir tantas *utterances* tan similares, pero la creación de un buen modelo de LUIS nos evitará muchos quebraderos de cabeza en la fase de desarrollo. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/firstutterancestrained.jpg) + +## Entities + +Existen diferentes tipos de *entities*. Según cómo sea la complejidad de nuestro modelo de LUIS, pondremos uno u otro. A continuación voy a comentar los tipos que hemos ido utilizado en nuestro modelo. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/firstentitylist.jpg) + + +### Entity List + +Por ejemplo, en las categorías hemos usado una *entity* de tipo **lista**. Esta *entity* nos ofrece la posibilidad de añadir una lista con diferentes valores canónicos y, a cada valor canónico añadirle una serie de sinónimos. La probabilidad de que reconozca la *entity* será muy superior en comparación a un tipo **simple**, que no admite más que un solo valor. + +En la siguiente imagen, como usuario, si le pido al bot que me de los posts que tenga sobre *LUIS*, nuestro bot recibirá el mismo *score* y la misma *entity* que si le pido que me de los posts que tenga sobre *Language Understanding Intelligent Service*. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/entitykindcanonical.jpg) + +Ahora que tenemos una *entity* tipo lista configurada, es la hora de añadir más *utterances* para entrenar a nuestro modelo de LUIS. En la siguiente imagen hemos añadido dos *utterances* más. A través del siguiente comboBox, podremos ver las etiquetas que reconoce LUIS. + +Si os fijáis, las opciones de visualización son las siguientes: + +* **Tokens**: Muestra entre corchetes el texto que detecta como valor canónico. + +* **Entities**: Esta vez mostrará entre corchetes el nombre de su *entity*. + +* **Composite entities**: Esta visualización de etiquetas la veremos más adelante, pero aquí muestra unas *entities* compuestas. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/viewentitylabel.jpg) + +### Entity Pre-built + +Antes de explicar las *entities Composite*, voy a comentar otro tipo de *entities* muy útiles. Son las *entities Pre-built* que ofrece LUIS. Son *entities* base que ya han sido previamente creadas. Si váis al siguiente [enlace](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/pre-builtentities) veréis todas las que están disponibles por cada idioma. + +Para estre proyecto vamos a utilizar únicamente la *Pre-built* de *number*. Me hubiese gustado añadirle la *Pre-built* de *Datetime*, pero ya han sacado la *v2* y aún no está disponible en español. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/entityprebuild.jpg) + +### Entity Composite + +Puesto que aún no podemos utilizar la *Pre-built* de *Datetimev2*, y queremos que nuestros usuarios puedan solicitar post por fechas, vamos a crearnos nuestra *entity DateTime* personalizada. + +Es una *entity* bastante compleja, ya que queremos que el filtro busque por fecha, día de la semana, por mes, por año, o incluso en un lapso concreto. + +Para que LUIS lo reconozca como una sola *entity*, vamos a crearnos una *entity Composite* compuesta de otras *entities* de tipo lista más la *Pre-built number* que hemos añadido en el anterior paso. + +Gracias a este tipo de *entity*, si el usuario pide los post que se publicaron en la **tercera semana de enero**, LUIS nos devolverá: + +Una única *entity composite DateTimeComposite* que contiene una *entity Pre-built number* **(tercera)** **+** *entity DateTime* con un valor canónico de *"Week"* **(semana)** **+** *entity DateTimeMonth* con un valor canónico de *"January"* **(enero)**. + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/entitycomposite.jpg) + +Como hemos visto antes, una vez tengamos creadas todas las *entities* que componen esta *Composite*, tendremos que añadir *utterances* y entrenar a nuestro modelo de LUIS. + +Cuando añadimos una *utterance*, normalmente LUIS solo reconoce las *entities*. Para indicar que es una *Composite entity* tendremos que pulsar sobre la *entity* detectada en la *utterance*, borrar todas las etiquetas que te muestra y añadir nuestra *Composite* (normalmente te sugiere que la añadas). + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/removelabelstocomposite.jpg) + +El resultado de una *entity Composite* sería el siguiente, en sus tres visualizaciones: + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/utterancecompositesample.jpg) + +## ¿Cómo publicar la App? + +Nos iremos a *Publish App*, y rellenaremos los campos que nos piden. Necesitaremos de un *Endpoint Key* para poder publicar la aplicación. +Es importante que haya sido entrenado antes de publicarla. + +En cuanto lo publiquemos por primera vez, se generará un Endpoint url para hacer nuestras llamadas GET . + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/publish.jpg) + +## Postman + +Vamos a hacer unas pruebas a ver qué nos devuelve el modelo recién creado. Según el siguiente ejemplo: + +> *Dame los posts de cSharp* + +**Resultado:** Un *intent post* con un 0,9 de *score* y con una *entity Category* de un valor canónico *"CSharp"* + +![](/assets/images/posts/2017-08-08-code-flow-and-luis/getutterance.jpg) + +Ahora vamos a ver cómo nos devolvería la información de una ***entity composite***: + +> *¿Podrías mostrarme los posts de febrero del año pasado?* + +**Resultado:** Un *intent post* con un 0,98 de *score*. Además, nos devuelve una ***entity composite*** con un 0,88 de *score* compuesta de tres *entities* : *Datetime* (con un valor canónico *"Year"*), *DatetimePeriod* (con un valor canónico *"Before"*) y *DatetimeMonth* (con un valor canónico *"February"*). + +```json +{ + "query": "podrías mostrarme los posts de febrero del año pasado", + "topScoringIntent": { + "intent": "posts", + "score": 0.987381637 + }, + "intents": [ + { + "intent": "posts", + "score": 0.987381637 + }, + { + "intent": "help", + "score": 0.0315034 + }, + { + "intent": "team", + "score": 0.02026676 + }, + { + "intent": "members", + "score": 0.0175812021 + }, + { + "intent": "farewells", + "score": 0.00512256334 + }, + { + "intent": "greetings", + "score": 0.00164088979 + }, + { + "intent": "None", + "score": 0.00149371813 + } + ], + "compositeEntities": [ + { + "parentType": "DateTimeComposite", + "value": "febrero del año pasado", + "children": [ + { + "type": "DateTime", + "value": "año" + }, + { + "type": "DateTimePeriod", + "value": "pasado" + }, + { + "type": "DateTimeMonth", + "value": "febrero" + } + ] + } + ], + "entities": [ + { + "entity": "febrero del año pasado", + "type": "DateTimeComposite", + "startIndex": 30, + "endIndex": 51, + "score": 0.8830154 + }, + { + "entity": "año", + "type": "DateTime", + "startIndex": 42, + "endIndex": 44, + "resolution": { + "values": [ + "Year" + ] + } + }, + { + "entity": "pasado", + "type": "DateTimePeriod", + "startIndex": 46, + "endIndex": 51, + "resolution": { + "values": [ + "Before" + ] + } + }, + { + "entity": "febrero", + "type": "DateTimeMonth", + "startIndex": 30, + "endIndex": 36, + "resolution": { + "values": [ + "February" + ] + } + } + ] + +} +``` + +En este punto me despido hasta el próximo post, donde veremos cómo desarrollar un bot conversacional en ***Node.js*** utilizando nuestro modelo de Luis. + +## Documentación + +Voy a dejar los modelos completos en nuestro [GitHub](./content/GeeksWptBot.InitialDialog.json). + +[Documentación L.U.I.S (Language Understanding Intelligent Service)](https://docs.microsoft.com/es-es/azure/cognitive-services/LUIS/Home) + +[Crear una cuenta luis.ai](luis.ai) + +[Prebuilt entities disponibles por idiomas](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/pre-builtentities) + +[Documentación BotFramework](https://dev.botframework.com/) + +[Postman](https://www.getpostman.com/) diff --git a/content/blogs/2017-08-10-first-vs-single/es.md b/content/blogs/2017-08-10-first-vs-single/es.md new file mode 100644 index 0000000..0177a2c --- /dev/null +++ b/content/blogs/2017-08-10-first-vs-single/es.md @@ -0,0 +1,129 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-10-first-vs-single/header.jpg +navigation: True +title: "First vs Single" +date: 2017-08-10 12:00:00 +tags: csharp +class: post-template +subclass: 'post' +author: maktub82 +--- + +¡Hola a todos! Linq se ha convertido en nuestro mejor amigo cuando trabajamos con colecciones de datos. Pese a la comodidad que nos ofrece, Linq no es solo azúcar sintáctico: es mucho más, pero ¿siempre usamos Linq correctamente? + +Hoy vamos a comparar `Single` y `First` para ver las diferencias y si los usamos correctamente. + +**Nota:** Para hacer la comparativa vamos a utilizar tanto en el método `Single` como en el `First` la sobrecarga que recibe un predicado con una condición para buscar el elemento. + +## Principales diferencias + +La principal diferencia es que **`Single` lanzará una excepción si existe más de un ítem en la colección que cumpla la condición del predicado**. + +Esto ya nos está indicando que **`Single`, por norma general, realizará más iteraciones sobre la colección** porque no le basta con encontrar un ítem que cumpla el predicado, sino que tiene que seguir hasta encontrar otro ítem que cumpla la condición, para lanzar la excepción, o hasta llegar al final de la colección. + +**`First` únicamente devuelve el primer ítem que encuentre que cumple con el predicado.** En ese momento dejará de iterar sobre los elementos de la colección. + +Por suerte Linq es Open Source y lo tenemos disponible en [GitHub]( https://github.com/dotnet/corefx/tree/master/src/System.Linq/src/System/Linq) para poder ver las *entrañas* y comprender mejor cómo funciona. Vamos a ver la implementación de estos dos métodos. + +## First + +He extraído parte del código de la familia de métodos de extensión de [`First`]( https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/First.cs) de GitHub para poder analizarlo mejor. + + +```cs +foreach (TSource element in source) +{ + if (predicate(element)) + { + found = true; + return element; + } +} +``` + +La parte del `First` que se encarga de encontrar el primer ítem que cumpla con el predicado no es más que un `foreach` que evalúa el predicado y en caso de cumplirse devuelve el ítem. + +Por tanto, en cuando haya encontrado el ítem dejará de iterar sobre la colección y devolverá el resultado. Por otro lado, si no encuentra el ítem lanzará una excepción en el caso de `First` o `default(T)` en el caso de `FirstOrDefault`. + +## Single + +El código de [`Single`](https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Single.cs) es un poco más complejo. Para empezar, trabajamos con el `Enumerator` que nos va a permitir navegar por la colección. + +```cs +using (IEnumerator e = source.GetEnumerator()) +{ + while (e.MoveNext()) + { + TSource result = e.Current; + if (predicate(result)) + { + while (e.MoveNext()) + { + if (predicate(e.Current)) + { + throw Error.MoreThanOneMatch(); + } + } + + return result; + } + } +} +``` + +**Nota:** El método `e.MoveNext()` avanzará en la colección y devolverá `true` en caso de que haya podido realizarse y `false` en caso contrario, es decir, no quedan ítems en la colección. + +Por tanto, lo que tenemos es un *mientras queden ítems en la colección* que evalúa el predicado a ver si se cumple. En el caso de que se cumpla entramos en otro *mientras queden ítems en la colección* que de nuevo evalúa el predicado. En este caso si se cumple lanzamos una excepción, ya que hemos encontrado más de un ítem. + +En caso de no haber encontrado otro elemento que cumpla el predicado devolvemos el que ya habíamos encontrado. + +## ¿Cuál utilizar? + +Como hemos podido comprobar `First`, por normal general, ejecuta menos iteraciones sobre la colección. + +En un caso habitual en el que el predicado sólo lo cumpla un ítem, `First` recorrerá la colección hasta que lo encuentre, mientras que `Single` recorrerá toda la colección para asegurar que solo un ítem cumple el predicado. + +Es evidente que **si no tenemos necesidad de comprobar que el predicado solo lo cumple un elemento la mejor opción es utilizar `First`** que va a iterar sobre la lista sólo hasta que encuentre el ítem. + +## Bonus Track: Find + +Existe otro método, disponible para las listas, llamado `Find` que también acepta un predicado. Podemos consultar el [código de `List`]( https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Collections/Generic/List.cs) en Github. + +```cs +public T Find(Predicate match) + +... + +for (int i = 0; i < _size; i++) +{ + if (match(_items[i])) + { + return _items[i]; + } +} +return default(T); +``` + +Como vemos, **`Find` utiliza un `for` para recorrer la colección, a diferencia de `First` que utilizaba un `foreach`**. Por tanto, podemos concluir que **`Find` es más rápido recorriendo los ítems**, pero no podemos utilizarlo en todo tipo de colecciones. + +**Nota:** Además, si `Find` no encuentra ningún ítem que cumpla con el predicado, devolverá `default(T)`, teniendo el mismo comportamiento que `FirstOrDefault`. + +## Conclusiones + +Hemos comprobado que es mejor utilizar `First` que `Single` salvo que queramos asegurarnos que no existen más ítems que cumplen el predicado. Además, lo hemos comprobado leyendo el código de Linq. + +Es interesante tener disponible el código de Linq porque nos ayuda a comprender mejor los diferentes métodos de extensión que nos ofrece para trabajar con las colecciones. + +Escribí este post para investigar y sopesar si era verdad que no estaba utilizando `Single` en ocasiones que tendría que haberlo utilizado y creo que la respuesta es sí. En algunos desarrollos tendría que haberlo añadido y, si es el caso, controlar la excepción que lanza para ver cómo actuar al respecto. + +## Referencias + +* [Código]( https://github.com/dotnet/corefx/tree/master/src/System.Linq/src/System/Linq) de Linq. + +* [Código]( https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Collections/Generic/List.cs) de List. + +Free Vector Graphics by [Vecteezy.com](https://vecteezy.com) + +¡Nos vemos en el futuro! diff --git a/content/blogs/2017-08-14-atributos-en-unity/es.md b/content/blogs/2017-08-14-atributos-en-unity/es.md new file mode 100644 index 0000000..daf9f2b --- /dev/null +++ b/content/blogs/2017-08-14-atributos-en-unity/es.md @@ -0,0 +1,164 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-14-atributos-en-unity/header.jpg +navigation: True +title: "Atributos en Unity" +date: 2017-08-14 12:00:00 +tags: csharp +class: post-template +subclass: 'post' +author: carlosklsone +--- + +Buenas a tod@s! Vamos a aprender cómo personalizar las propiedades que se muestran en el inspector de un *Script* mediante atributos. + +Como sabemos, en *Unity*, todas las propiedades que añadimos públicas a un *Script* se muestran automáticamente en el inspector con una forma predeterminada, los '*int*', los '*float*' y los '*string*' por ejemplo, se muestran con una caja de texto, en la que podemos modificar el valor mediante el teclado. Vemos en la siguiente imagen el ejemplo que voy a seguir, con el *Script* de '*Player.cs*'. + +```csharp +public class Player : MonoBehaviour +{ + #region Public Fields + + public float Speed = 0.0f; + public float JumpPower = 0.0f; + public float MaxSpeed = 0.0f; + public int Lives = 0; + public float MaxHealth = 0.0f; + + #endregion + + #region Private Fields + + private float _health; + private bool _grounded; + private bool _jump; + private bool _attack; + + #endregion +} +``` + +![Attributes](/assets/images/posts/2017-08-14-atributos-en-unity/attributes01.jpg) + + +## Atributos + +Hay diferentes atributos que nos ofrecen tanto **Unity** como librerias propias de **.NET**. Aquí vamos a ver algunos que considero útiles a la hora de visualizar las propiedades en el inspector. Para saber todo lo que ofrece *Unity* podéis visitar este [link](https://docs.unity3d.com/ScriptReference/AddComponentMenu.html), donde hay un listado completo con todos los atributos. + +### Atributo Header + +El primero de ellos es el atributo '*Header*', que sirve para agrupar o categorizar propiedades. En nuestro ejemplo podríamos categorizar nuestras propiedades por control de *Player* y por salud del *Player*, por ejemplo. Para que el atributo surja efecto habría que añadirlo delante del grupo de propiedades que queramos categorizar con esta linea: `[Header("Text")]`. En nuestro ejemplo quedaría así: + +```csharp +[Header("Control Settings")] +public float Speed = 0.0f; +public float JumpPower = 0.0f; +public float MaxSpeed = 0.0f; + +[Header("Health Settings")] +public int Lives = 0; +public float MaxHealth = 0.0f; +``` + +![Attributes](/assets/images/posts/2017-08-14-atributos-en-unity/attributes02.jpg) + +**Nota:** estos atributos, solo funcionan en propiedades públicas como es lógico, más adelante veremos como mostrar una propiedad privada en el inspector. + +### Atributo Range + +El segundo que os voy a mostrar es el atributo '*Range*', que nos va a facilitar el uso en el inspector de números enteros y flotantes, mediante una '*slider*', y con la posibilidad de ponerle un mínimo y un máximo. Su nomenclatura sería así: `[Range(min, max)]`. Podríamos añadirlo a cualquier '*int*' o '*float*' que tengamos, pero en nuestro ejemplo vamos añadirlo a la velocidad, a las vidas y a la salud máxima del personaje. + +```csharp +[Header("Control Settings")] +[Range(1.0f, 10.0f)] +public float Speed = 0.0f; +public float JumpPower = 0.0f; +public float MaxSpeed = 0.0f; + +[Header("Health Settings")] +[Range(1, 5)] +public int Lives = 0; +[Range(50.0f, 100.0f)] +public float MaxHealth = 0.0f; +``` + +![Attributes](/assets/images/posts/2017-08-14-atributos-en-unity/attributes03.jpg) + +### Atributos HideInInspector y SerializeField + +Estos dos atributos sirven para ocultar variables públicas y para mostrar privadas en el inspector respectivamente. Muy útiles, ya que no siempre queremos que una propiedad pública se modifique en el editor, pero si desde otro *Script* por código. Al igual pasa con las propiedades privadas, no siempre queremos que se tenga acceso desde fuera del *Script*, pero si nos es cómodo poder modificarlas desde el inspector. + +En nuestro ejemplo, la propiedad velocidad máxima es pública, y no queremos que se muestre en el inspector, ya que esta propiedad solo se modificará *in game*, cogiendo algún *power up*, por ejemplo. Solo tendríamos que poner encima de la propiedad la siguiente linea `[HideInInspector]`. + +Y en el caso de la propiedad privada de salud, que indica la salud actual del personaje, no queremos que se tenga acceso fuera de nuestro *Script*, pero queremos ir haciendo pruebas en el editor, subiendo y bajando la salud actual del personaje, pues solo necesitaríamos añadir `[SerializeField]` encima de la salud. + +```csharp +#region Public Fields + +[Header("Control Settings")] +[Range(1.0f, 10.0f)] +public float Speed = 0.0f; +public float JumpPower = 0.0f; +[HideInInspector] +public float MaxSpeed = 0.0f; + +[Header("Health Settings")] +[Range(1, 5)] +public int Lives = 0; +[Range(50.0f, 100.0f)] +public float MaxHealth = 0.0f; + +#endregion + +#region Private Fields + +[SerializeField] +private float _health; +private bool _grounded; +private bool _jump; +private bool _attack; + +#endregion +``` + +![Attributes](/assets/images/posts/2017-08-14-atributos-en-unity/attributes04.jpg) + +### Atributo Tooltip + +Este atributo es muy útil para trabajar en equipo, si estás trabajando con más compañeros en un componente, para ayudar a los diseñadores a la hora de trabajar con los componentes en el editor o si estás creando un componente para otros usuarios. Se define de esta manera: `[Tooltip("Description")]`. En nuestro ejemplo se lo añado a la propiedad Speed: + +```csharp +[Header("Control Settings")] +[Range(1.0f, 10.0f)] +[Tooltip("Player Speed. Value between 1 and 10.")] +public float Speed = 0.0f; +public float JumpPower = 0.0f; +[HideInInspector] +public float MaxSpeed = 0.0f; +``` + +![Attributes](/assets/images/posts/2017-08-14-atributos-en-unity/attributes05.jpg) + +### Bonus: Atributo RequireComponent + +Este atributo ya no se aplica a una propiedad como los anteriores, si no a un *Script* o *Componente*. Y cuando se añade de esta forma `[RequireComponent(typeof(Type))]` delante de la declaración de la clase, hace que todo **GameObject** que lleve el componente '*Player.cs*', va a llevar obligatoriamente un componente del tipo que hemos definido. **Para que surja efecto, debemos de eliminar el componente Player.cs del GameObject si ya lo habiamos incluido**. Lo tenemos que volver a incluir y automaticamente se incluirá también el tipo definido. En nuestro ejemplo vamos a añadir un *Componente* de tipo *Rigidbody2D*: + +```csharp +[RequireComponent(typeof(Rigidbody2D))] +public class Player : MonoBehaviour +{ + ... +} +``` + +![Attributes](/assets/images/posts/2017-08-14-atributos-en-unity/attributes06.jpg) + +Hasta aquí los atributos que más útiles me han sido hasta ahora. Hay muchos más, como veréis en el link anterior, podéis probar por vuestra cuenta y ver si os son útiles o no a la hora de vuestros desarrollos. + +Podéis bajar el código del ejemplo desde [GitHub](https://github.com/NoCountryForGeeks/Blog/tree/feature/atributosUnity/carlos/atributos-unity/content) + +¡Hasta la Próxima! + +@CarlosKlsOne +[![Twitter Follow](https://img.shields.io/twitter/follow/carlosklsone.svg?style=social&label=Follow)](https://twitter.com/carlosklsone) \ No newline at end of file diff --git a/content/blogs/2017-08-22-micropost-f12-en-vs/es.md b/content/blogs/2017-08-22-micropost-f12-en-vs/es.md new file mode 100644 index 0000000..ac1ec3a --- /dev/null +++ b/content/blogs/2017-08-22-micropost-f12-en-vs/es.md @@ -0,0 +1,107 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-22-micropost-f12-en-vs/header.jpg +navigation: True +title: "[Micropost] F12 en Visual Studio" +date: 2017-08-22 12:00:00 +tags: micropost +class: post-template +subclass: 'post' +author: maktub82 +--- + +¡Hola a todos! Visual Studio tiene una gran cantidad de atajos de teclado. Hoy vengo a hablar del **F12** porque no todo el mundo sabe el partido que se le puede sacar a este atajo. + +## Código de ejemplo + +Para ver la funcionalidad que nos ofrece el atajo F12 vamos a trabajar sobre un pequeño código de ejemplo en el que tenemos una clase y su interfaz. + +```csharp +interface IPost +{ + void Publish(); + void Schedule(DateTime publishDate); +} + +class Post : IPost +{ + public void Publish() + { + Console.WriteLine($"Published post"); + } + + public void Schedule(DateTime publishDate) + { + Console.WriteLine($"Scheduled post: {publishDate.ToString()}"); + } +} + +class Program +{ + static void Main(string[] args) + { + var postA = new Post(); + IPost postB = new Post(); + + postA.Publish(); + postB.Schedule(DateTime.Now.AddDays(1)); + } +} +``` + +## Go To Definition + +En general **el atajo F12 nos permite navegar a la definición del ítem sobre el que estemos situados**. Por ejemplo, estando sobre una variable nos lleva a dónde está declarada. Pero vamos a ver más en detalle alguna de la funcionalidad que ofrece. + +**Nota:** los atajos que vamos a ver funcionan en Visual Studio 2017. No podemos asegurarlo para versiones anteriores. + +## Navegación al método + +El atajo **F12 nos sirve para navegar a diferentes partes del código: Go to Definition**. + +Si colocamos el cursor sobre un método y pulsamos F12 nos llevara a la definición de ese método. + +**Nota:** Si la referencia con la que estamos trabajando es **una interfaz nos llevará a la firma del método y no a la implementación**. + +De este modo al colocar el cursor sobre `Publish()` de la variable `postA` y pulsar F12 nos llevará a la implementación en la clase `Post`. + +Por el contrario si pulsamos F12 sobre `Schedule(...)` de `postB` navegaremos a la definición del método en la interfaz ya que `postB` es una referencia de `IPost`. + +### Ctrl + F12 + +**La mayoría de veces trabajamos con referencias a interfaces** ya que hacemos uso de la inyección de dependencias. + +Por eso el atajo F12 no siempre es tan útil porque **muchas veces queremos navegar a la implementación** de nuestro método. + +Para solucionar esto tenemos el comando **Ctrl + F12 que siempre navega a la implementación de método**. + +Por tanto si estando sobre `Schedule(...)` de `postB` navegaríamos a la implementación de `Schedule` en la clase `Post`. + +**Nota:** En caso de tener **más de una implementación de una interfaz, Visual Studio nos muestra una lista con todas ellas** para elegir a cual queremos navegar. + +## Navegar a un tipo + +Otra de las opciones que nos ofrece **el atajo F12 es la de navegar a un tipo**. Al igual que pasa con la navegación a un método si colocamos el cursor sobre un tipo o interfaz nos navega a dónde está definida. + +Si pulsamos F12 sobre `Post` navegaremos a la definición de la clase y lo mismo sucede si lo hacemos sobre `IPost` que nos navegará a la interfaz. + +### var + +Esta atajo es mucho más potente ya que nos permite navegar al tipo que infiere `var`. + +Por tanto, en nuestro ejemplo, **si nos colocamos sobre `var` y pulsamos F12 navegaremos a la clase `Post`**. + +**Importante:** Hay que colocar el cursor sobre la palabra reservada `var`. + +### Ctrl + F12 + +Al igual que pasaba con los métodos, al estar sobre una interfaz y pulsar **Ctrl + F12** nos navegará a la implementación de la interfaz, en este caso a `Post` estando sobre `IPost`. + +## Más atajos + +Esto es lo más interesante del los atajos **F12** y **Ctrl + F12**. Como hemos comentado al principio, Visual Studio tiene muchos otros atajos que nos ayudan en el día a día a trabajar más fácilmente. + +Podéis dejar en comentarios qué atajos de teclado os parecen más interesantes y los veremos en siguientes *Microposts*. + +Un saludo y ¡nos vemos en el futuro! diff --git a/content/blogs/2017-08-24-rambling-javascript-1-let-y-const/es.md b/content/blogs/2017-08-24-rambling-javascript-1-let-y-const/es.md new file mode 100644 index 0000000..50b82a3 --- /dev/null +++ b/content/blogs/2017-08-24-rambling-javascript-1-let-y-const/es.md @@ -0,0 +1,125 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-24-rambling-javascript-1-let-y-const/header.png +navigation: True +title: "Rambling javascript 1: let y const" +date: 2017-08-24 12:00:00 +tags: javascript rambling-javascript +class: post-template +subclass: "post" +author: franmolmedo +--- + +La idea con esta serie de entradas es ir comentando las nuevas características que se han ido introduciendo en el lenguaje. A partir de ES6 (o EcmaScript 2015) se han introducido importantes cambios de sintaxis o añadidos que pueden ser muy útiles a la hora de generar un código más estructurado, entendible y fácilmente mantenible. + +## Preparativos + +No queremos entrar a comentar la historia del lenguaje y como ha ido evolucionando a lo largo del tiempo (al final dejaré una serie de enlaces donde lo detallan perfectamente), pero sí es conveniente señalar el punto donde nos encontramos y cómo se plantea el futuro. Actualmente la versión del lenguaje soportada por la gran mayoría de navegadores es ES5 (que data del año 2009). En los últimos tiempos se ha abandonado la idea de crear grandes revisiones e ir actualizando el lenguaje paulativamente con nuevos añadidos. De ahí que hayan surgido ES2015, ES2016 o incluso ES2017. Esto permite que el lenguaje vaya avanzando de forma más dinámica y que los cambios lleguen antes a los desarrolladores. +La contrapartida a ello es el soporte que los navegadores dan a todos estos cambios. Por ello, con el fin de asegurar que tu código pueda ejecutarse de forma adecuada en la totalidad de entornos, se hace necesario un proceso previo de transpilación, que consiste en convertir el código escrito en ES2015 o posteriores, a código ES5. Esto se puede llevar a cabo utilizando alguna de las herramientas destinadas a esta tarea, siendo la más famosa de todas ellas [Babel](https://babeljs.io/). +De hecho, recomiendo utilizar la [herramienta](https://babeljs.io/repl/) de edición en vivo que proporcionan, donde se nos permite elegir la versión del lenguaje que vamos a utilizar para escribir el código (entre es2015, es2016, es2017, preset de react e incluso código con las últimas novedades: stage-0 en adelante), para poder ejecutar los ejemplos que iremos añadiendo tanto en ésta como en sucesivas entradas. Además de ver el resultado de ejecución del mismo, esta herramienta nos muestra el resultado de convertir dicho código a ES5, que sería realmente el que ejecutaría nuestro navegador. + +Entre las caracterísitcas que se han incluído en EcmaScript2015 podemos destacar la forma de declarar nuestras variables y constantes, la existencia de clases, módulos, las fat arrow functions, los generadores, el uso de template strings... Iremos comentado muchas de estas nuevas características en detalle. Para esta primera entrada nos centraremos en la nueva forma de declarar variables y constantes + +## Let y Const + +Son dos nuevas palabras reservadas que se introducen en es2015 y que complementan a var, que ya se usaba en es5. Nos proporcionan una idea del carácter inmutable o no (aunque lo vamos a matizar posteriormente) del valor al que referencian. Importante el hecho de que const exige que el valor se le asigne inmediatamente. + +Un aspecto interesante a comentar es que tienen un scope a nivel de bloque, entendiendo por bloque como aquel fragmento de código que encontramos entre dos llaves. Lo vemos +en el siguiente ejemplo: + +```javascript +if (true) { + var age = 30; +} + +console.log(age); +``` + +Por consola obtenemos el valor de 30. + +Sin embargo, si modificamos el código anterior de la siguiente forma: + +```javascript +if (true) { + let age = 30; +} + +console.log(age); +``` + +El resultado sería que la variable age no estaría definida. Esto ocurre por lo que hemos comentado, que las variables definidas utilizando let sólo tienen como scope el bloque +de código en el que se definen. + +Esto se pone de manifiesto, igualmente, en el siguiente código: + +```javascript +let age = 21; + +if (true) { + let age = 30; + console.log("first: " + age); +} + +console.log("second: " + age); +``` + +Donde el valor de la variable age se corresponde al que se asigna en su bloque correspondiente. Así mismo, se enmascara el valor de la declaración externa si la variable interna coincide en nombre. + +Vamos a hacer ahora un inciso acerca de la palabra reservada const. Revisemos el siguiente ejemplo: + +```javascript +const PERSONS = ["pedro", "carlos"]; +console.log(PERSONS); +PERSONS.push("pablo"); +console.log(PERSONS); +``` + +Este código es perfectamente correcto. ¿Pero cómo, si hemos modificado el valor de PERSONS declarado como const?. La razón es que const crea variables inmutables pero no valores inmutables. +Es decir, en este caso nuestra const PERSONS siempre tiene el mismo valor (apunta al array que le indicamos), pero el array en sí mismo puede cambiar. Otro caso sería que a PERSONS, le asignaramos un array diferente, entonces sí nos produciría un error: + +```javascript +const PERSONS = ["fran", "manuel", "antonio"]; + +if (true) { + const PERSONS = ["pedro", "carlos"]; + console.log(PERSONS); + PERSONS.push("pablo"); + console.log(PERSONS); + PERSONS = []; +} + +console.log(PERSONS); +``` + +Entonces, ¿cómo aseguramos que el valor sea inmutable? Podemos recurrir a `Object.freeze()`, teniendo siempre en cuenta que lo que hace inmutable son las propiedades de su argumento, no los objetos almacenados en dichas propiedades. Lo ilustramos con el siguiente ejemplo: + +```javascript +const obj = Object.freeze({ foo: {} }); +obj.foo.bar = 10; +console.log(obj); +``` + +Este código sería perfectamente correcto, mientras que el siguiente: + +```javascript +const obj = Object.freeze({ foo: {} }); +obj.bar = 3; +console.log(obj); +``` + +No lo sería, teniendo el siguiente error: `Can't add property bar, object is not extensible`. + +### Conclusiones + +¿Cuáles de las formas para declarar una variable debemos usar? + +- Por defecto tu elección debería ser const, a no ser que tengas la necesidad de reasignarle el valor más adelante. +- En ese último caso, deberás de usar let. +- Nunca deberías usar var. La razón es clara. Si declaramos una variable usando var, podemos hablar de constantes o variables, con lo que perdemos significado e información simplemente por el hecho de usarla. +- Ten en cuenta lo comentado sobre la inmutabilidad, a la hora de modificar o no los valores de tus variables o añadir nuevas propiedades a tus objetos. + +## Links + +- [A brief history of javascript by auth0](https://auth0.com/blog/a-brief-history-of-javascript/) +- [Javascript language resources](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Language_Resources) diff --git a/content/blogs/2017-08-29-micro-post-organizando-las-referencias-en-c/es.md b/content/blogs/2017-08-29-micro-post-organizando-las-referencias-en-c/es.md new file mode 100644 index 0000000..1409bc4 --- /dev/null +++ b/content/blogs/2017-08-29-micro-post-organizando-las-referencias-en-c/es.md @@ -0,0 +1,41 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-29-micro-post-organizando-las-referencias-en-tus-clases/header.jpg +navigation: True +title: "[Micro-Post] Organizando las referencias en C#" +date: 2017-08-29 12:00:00 +tags: csharp visualstudio micropost +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Hay muchas formas de mejorar nuestro código, algunas personas abogan por tener un código con una funcionalidad concreta y fiable, otros por un código limpio, y por qué no, otros simplemente por un código que funcione y punto. + +> Available in English [here](https://www.danielmartingonzalez.com/micro-post-organizing-the-references-in-your-classes/). + +En **No Country For Geeks** creemos que el código tiene que ser arte, y no nos referimos a un cuadro abstracto que cada persona interprete de una manera; hablamos de una sinfonía en el que cada variable, cada método, cada línea, sea un compuesto de funcionalidad, belleza, simpleza y elegancia que alcancen la excelencia en lo posible. + +Entre estas definiciones, entra la organización. Y algo muy sencillo de mejorar es la organización de las referencias *using* en nuestro código. + +![](/assets/images/posts/2017-08-29-micro-post-organizando-las-referencias-en-tus-clases/bad-organization.png) + +Lo adecuado en estos casos es que las referencias se organicen siguiendo un **orden alfabético**, de este modo de un vistazo se puede buscar fácilmente la referencia en la que estamos interesados. +Por otro lado, algo opcional, pero que está considerado como una buena práctica, es que las referencias *System* aparezcan antes que cualquier otra referencia, y de nuevo, ordenadas alfabéticamente. + +Con **Visual Studio**, toda esta organización se puede hacer de una manera muy sencilla. + +1. Para que las referencias *System* aparezcan al inicio de la lista de referencias, basta con dirigirnos a *Tools -> Options* y en la nueva ventana que se nos despliega, buscar la opción *Text Editor -> C# -> Advanced* y activar el check: *"Place 'System' directives first when sorting usings"*. + + ![](/assets/images/posts/2017-08-29-micro-post-organizando-las-referencias-en-tus-clases/system-options.png) + +2. Una vez estamos en una clase en la que queramos organizar las referencias, basta con utilizar un atajo de teclado poco conocido pero muy útil: *Ctrl+R, Ctrl+G*. Al hacerlo, veremos cómo se limpian las referencias no utilizadas en nuestra clase, las referencias a *System* se dispondrán en primera instancia en el listado, y todas las referencias se organizarán por orden alfabético. + + ![](/assets/images/posts/2017-08-29-micro-post-organizando-las-referencias-en-tus-clases/good-organization.png) + +De este modo hemos organizado todas nuestras referencias por clase de una manera muy cómoda y rápida. + +Algo que me gusta hacer cuando modifico una clase es presionar *Ctrl+R, Ctrl+G* (organizar referencias), *Ctrl+K, Ctrl+D* (dar formato al documento), y finalmente *Ctrl+S* para guardar el documento. Con esto quedaría todo nuestro código organizado y limpio de una manera muy rápida. + +Nota: Dependiendo de la configuración de **Visual Studio**, es posible que el atajo de teclado para dar formato al documento sea *Ctrl+E, Ctrl+D*. diff --git a/content/blogs/2017-08-31-rambling-javascript-2-fat-arrow-functions/es.md b/content/blogs/2017-08-31-rambling-javascript-2-fat-arrow-functions/es.md new file mode 100644 index 0000000..7d5e395 --- /dev/null +++ b/content/blogs/2017-08-31-rambling-javascript-2-fat-arrow-functions/es.md @@ -0,0 +1,205 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-08-31-rambling-javascript-2-fat-arrow-functions/header.png +navigation: True +title: "Rambling javascript 2: Fat arrow functions" +date: 2017-08-31 12:00:00 +tags: javascript rambling-javascript +class: post-template +subclass: "post" +author: franmolmedo +--- + +Continuamos nuestro camino indagando sobre diversos aspectos de JavaScript. En este caso vamos a comentar sobre uno de los añadidos más relevantes que se hicieron con _ES6_ (ó _EcmaScript 2015_). Estamos hablando de las _Fat arrow functions_. Con su uso conseguiremos un código más legible y compacto. +Sin embargo, es conveniente hacer hincapié en algunos puntos que pueden pasarse de largo a la hora de trabajar con ellas. Comenzamos. + +## Sintaxis + +Tomaremos como base un ejemplo básico de función escrita en _ES5_. En este caso la función `add` tiene como argumentos dos números y nos devuelve la suma. + +```javascript +const add = function(a, b) { + return a + b; +}; +``` + +Código totalmente correcto, pero quizás un pelín verboso para lo que realmente hace, que es la suma de dos números. De ahí partimos con las _fat arrow function_. En un primer momento podemos sustituir la palabra reservada `function` por el símbolo siguiente `=>`, conocido como _fat arrow_, quedando la expresión de la siguiente forma: + +```javascript +const add = (a, b) => { + return a + b; +}; +``` + +Hemos reducido algunos caracteres, pero la mejora tampoco es demasiado sustancial. Sin embargo, podemos seguir trabajando en la expresión. El siguiente aspecto en el que podemos dar cuenta, es que nuestra función consta de una única expresión. En casos como éste, podemos igualmente quitar la palabra reservada `return` e, incluso, quitar las llaves delimitadora de la función. Con ello, la palabra reservada `return` quedaría implícita en la única expresión de la que constaría nuestra función. Por lo tanto: + +```javascript +const add = (a, b) => a + b; +``` + +Creo que se intuye que extendiendo su uso conseguiremos un código más claro y limpio. + +El siguiente ejemplo también nos permite presentar un nuevo detalle relativo a la sintaxis: + +![Ejemplo sintaxis](/assets/images/posts/2017-08-31-rambling-javascript-2-fat-arrow-functions/basicExample.gif) + +Efectivamente, también hay puntos que aclarar sobre la sintaxis relativo a los argumentos: + +- En funciones sin argumentos, o con dos o más, es necesario rodearlos con paréntesis: + +```javascript +const sayHelloWorld = () => "Hello World"; + +const sayHelloWorldTwo = (place, hour) => `Hello ${place} at ${hour}`; +``` + +- Sin embargo, cuando tengamos un sólo argumento, los paréntesis son opcionales, tal y como hemos visto en el ejemplo previo. + +## Uso en casos reales + +Todo lo que se ha comentado está bien, pero realmente, y con los ejemplos vistos, tampoco parece un cambio demasiado relevante. +Sin embargo, cuando nos vamos a casos un poco más realistas, es donde empezamos a observar su valía. El ejemplo más claro en el que es muy útil su uso es en los predicados que utilizamos en las funciones para tratar con colecciones. +Mostramos el típico ejemplo de un array de números, y queremos extraer aquellos cuyo valor sea mayor o igual que 5: + +```javascript +const numbers = [10, 2, 3, 4, 5, 8]; + +const result = numbers.filter(function(number) { + return number >= 5; +}); +``` + +En este caso, utilizando _fat arrow functions_, nuestro ejemplo quedaría de la siguiente forma: + +```javascript +const numbers = [10, 2, 3, 4, 5, 8]; + +const result = numbers.filter(number => number >= 5); +``` + +La mejora queda clara en cuanto a legibilidad. Imaginemos su uso en callbacks, donde ayudaran, sino a reducir, sí a mejorar, el temido [callback hell](http://callbackhell.com/) en javascript. + +## Comportamiento de `this` + +Todo lo comentado tiene su valor, pero donde realmente está el factor diferenciador de las fat arrow function respecto al empleo de las funciones tradicionales es en el cambio de comportamiento que ha sufrido `this`. Cualquier persona que haya trabajado con un código JavaScript relativamente complejo, saliendo de los ejemplos básicos, habrá obtenido comportamientos no esperables de `this`. ¿Cuántos hemos hecho uso de alguno de los siguientes _workarounds_?: + +- Guardarnos el contexto en una variable para poder utilizarlo en el callback. Todos hemos hecho alguna vez: + +```javascript +function test() { + var self = this; + callOtherFunction(function(callbackResponse) { + console.log(this); + console.log(self); + }); +} +``` + +- Utilizar el `bind(this)`, a la hora de definir una función. + +Con el uso de las _fat arrow functions_ esto ya no será necesario, ya que, por defecto, `this` se va a referir al contexto donde la función haya sido declarada. Lo vemos con el ejemplo siguiente: + +```javascript +const testObject = { + numbers: [10, 20, 30, 40, 50], + title: "Double of", + double: function() { + return this.numbers.map(function(number) { + return `${this.title} ${number} is ${2 * number}`; + }); + } +}; + +console.log(testObject.double()); +``` + +Si ejecutamos el ejemplo obtenemos lo siguiente: + +```javascript +[ + "undefined 10 is 20", + "undefined 20 is 40", + "undefined 30 is 60", + "undefined 40 is 80", + "undefined 50 is 100" +]; +``` + +¿Qué está ocurriendo? Pues lo que todos sabíamos de antemano, el `this` dentro de la función que se ejecuta en el _map_ no es el que cabría esperarse (el objeto _testObject_, sino que es el contexto global, donde, lógicamente, no está definido _title_). Esto lo resolveríamos en _ES5_ con una de las formas siguientes: + +```javascript +const testObject = { + numbers: [10, 20, 30, 40, 50], + title: "Double of", + double: function() { + const self = this; + return this.numbers.map(function(number) { + return `${self.title} ${number} is ${2 * number}`; + }); + } +}; + +console.log(testObject.double()); +``` + +```javascript +const testObject = { + numbers: [10, 20, 30, 40, 50], + title: "Double of", + double: function() { + return this.numbers.map( + function(number) { + return `${this.title} ${number} is ${2 * number}`; + }.bind(this) + ); + } +}; + +console.log(testObject.double()); +``` + +Mientras que, usando _fat arrow function_, el ejemplo quedaría: + +```javascript +const testObject = { + numbers: [10, 20, 30, 40, 50], + title: "Double of", + double: function() { + return this.numbers.map( + number => `${this.title} ${number} is ${2 * number}` + ); + } +}; + +console.log(testObject.double()); +``` + +En todos los casos el resultado sería el esperado: + +```javascript +[ + "Double of 10 is 20", + "Double of 20 is 40", + "Double of 30 is 60", + "Double of 40 is 80", + "Double of 50 is 100" +]; +``` + +### Conclusiones + +En este caso no hay mucho que comentar: + +- Sintaxis más sencilla. +- Código más legible. +- Facilidad a la hora de escribir callbacks o predicados. +- Quita problemas al usar `this` + +Todo son ventajas. + +En la siguiente entrega hablaremos de los operadores rest / spread y de los valores por defecto que podemos indicar a nuestras funciones. + +## Links + +- [Practica ES6](http://jsbin.com/?console,output) diff --git a/content/blogs/2017-10-31-generando-un-sprite-svg-en-aplicacion-react-performance-y-accesibilidad/es.md b/content/blogs/2017-10-31-generando-un-sprite-svg-en-aplicacion-react-performance-y-accesibilidad/es.md new file mode 100644 index 0000000..3aa7234 --- /dev/null +++ b/content/blogs/2017-10-31-generando-un-sprite-svg-en-aplicacion-react-performance-y-accesibilidad/es.md @@ -0,0 +1,474 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/CoverPage.jpg +navigation: True +title: "Generando un sprite SVG en aplicación React. Performance y Accesibilidad" +date: 2017-10-31 12:00:00 +tags: javascript react +class: post-template +subclass: "post" +author: ivanrodri +--- + +Con la llegada del estándar **HTML5** ha resurgido el formato **SVG** en el desarrollo web. **HTML5** reconoce el formato **SVG** como un estándar, lo que ha obligado a los navegadores a soportarlo de manera nativa. + +## ¿Qué es SVG? + +SVG **(Scalable Vector Graphics)** es un formato vectorial que nos permite crear vectores escalables, esto nos permite agrandar y disminuir el tamaño de nuestras imágenes sin perder calidad. Una de las grandes ventajas de este formato es que tiene un tamaño reducido independientemente del tamaño de la imagen, este formato se exporta como XML. + +SVG nos permite usar tres tipos de vectores gráficos + +- Formas (líneas, círculos, polígonos, etc) +- Imágenes +- Texto + +## Cómo usar un SVG en la web + +En la web podemos usar **SVG** escribiendo directamente en el **HTML** las etiques que representen las formas **(rect, cicle, line, polygon, etc)** o podemos usar ficheros **.svg** exportados desde editores gráficos como **Illustrator, Inkscape o sketch.** + +**Hay que tener en cuenta que no todas las maneras de usar un SVG los tratan como un gráfico vectorial y vamos a perder las ventajas que esto nos ofrece.** + +**Como imagen** + +```html +circle +``` + +**Como iframe** + +```html + +``` + +**Como objeto** + +```html + +``` + +**Embebido** + +```html + +``` + +**Como fondo** + +```html +
+ +.circle { width: 200px; height: 200px; background: url('circle.svg'); } +``` + +**Como SVG** + +```html + + + +``` + +Las única manera que lo mantiene como un gráfico vectorial es la etiqueta `` + +## Ventajas de usar SVG + +- Podemos cambiar el color y el tamaño directamente con CSS incluso solo cambiar el color de partes específicas +- Reducimos el peso de imágenes a la hora de descargar +- Podemos ahorrar peticiones al servidor si generamos un Sprite SVG +- No perderemos calidad + +## Sprite SVG + +Un **sprite SVG** consiste en coger todos los **SVG** que contiene nuestra web y crear un único fichero **SVG** que los contenga a todos. Esto nos permite ahorrarnos múltiples llamadas al servidor lo que podría provocar bloqueo de llamadas por parte del navegador y ralentizaría nuestra carga de la web. + +Para generar un **sprite SVG** podemos realizarlo a mano (es un trabajo tedioso y en el que podemos equivocarnos) o usar tareas **gulp, grunt o webpack** para crearlos, estas tareas nos permiten configurar los plugins con los cuales podemos eliminar cosas innecesarias de nuestros **SVG** y que ocuparían espacio innecesario en el **sprite**. + +![Sprite SVG](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/SpriteSVGProcess.jpg) + +## Generando un sprite SVG con Webpack en mi aplicación React + +En este punto vamos a ver como generar un **sprite SVG** con **Webpack** en una aplicación **React**, esta configuración **Webpack** también serviría para aplicaciones con **Angular 2, Vue, etc** + +En mi caso voy a partir de una configuración **Webpack** básica con el loader de **Babel** para el **JavaScript** y el plugin de **React hot loader** para la recarga del proyecto cuando haya cambios. + +```javascript +const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const path = require("path"); + +module.exports = { + entry: [ + "./app", + "react-hot-loader/patch", + "webpack-dev-server/client?http://localhost:8080", + "webpack/hot/only-dev-server" + ], + output: { + path: path.join(__dirname, "./build"), + filename: "[name].[hash].js" + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + loader: "babel-loader", + exclude: /node_modules/, + options: { + cacheDirectory: true + } + } + ] + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), + new HtmlWebpackPlugin({ template: "./index.template.html" }) + ], + devServer: { + historyApiFallback: true, + open: true, + hot: true, + stats: "errors-only", + overlay: { + errors: true, + warnings: true + }, + headers: { + "Access-Control-Allow-Origin": "*" + }, + watchOptions: { + poll: true, + ignored: [/node_modules/] + } + } +}; +``` + +Para poder generar nuestro **sprite SVG** vamos a necesitar un loader que se encargue de ello, yo en mi caso voy a usar **svg-sprite-loader** en el siguiente [enlace](https://github.com/kisenka/svg-sprite-loader) podéis ver toda la documentación. + +Para instalarlo: + +``` +npm install svg-sprite-loader -D +# via yarn +yarn add svg-sprite-loader -D +``` + +Una vez instalado volveremos a nuestra configuración de **Webpack** y añadiremos la configuración necesaria para usar este loader, añadiendo una regla y el plugin correspondiente. + +Añadiremos la siguiente regla + +```javascript +{ + test: /\.svg$/, + loader: 'svg-sprite-loader', + options: { + extract: true, + spriteFilename: './svgSprite/images.svg' + } +} + +``` + +Como vemos, el **sprite SVG** nos lo va a servir en la ruta **./svgSprite** en el fichero **images.svg** + +También añadiremos el correspondiente plugin que nos obliga a usar el loader anterior. + +```javascript +new SpriteLoaderPlugin(); +``` + +Nuestro **webpack.config.js** quedaria de la siguiente manera + +```javascript +const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const SpriteLoaderPlugin = require("svg-sprite-loader/plugin"); +const path = require("path"); + +module.exports = { + entry: [ + "./app", + "react-hot-loader/patch", + "webpack-dev-server/client?http://localhost:8080", + "webpack/hot/only-dev-server" + ], + output: { + path: path.join(__dirname, "./build"), + filename: "[name].[hash].js" + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + loader: "babel-loader", + exclude: /node_modules/, + options: { + cacheDirectory: true + } + }, + { + test: /\.svg$/, + loader: "svg-sprite-loader", + options: { + extract: true, + spriteFilename: "./svgSprite/images.svg" + } + } + ] + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), + new HtmlWebpackPlugin({ template: "./index.template.html" }), + new SpriteLoaderPlugin() + ], + devServer: { + historyApiFallback: true, + open: true, + hot: true, + stats: "errors-only", + overlay: { + errors: true, + warnings: true + }, + headers: { + "Access-Control-Allow-Origin": "*" + }, + watchOptions: { + poll: true, + ignored: [/node_modules/] + } + } +}; +``` + +Cuando arranquemos nuestra aplicación este loader se encargara de generar un **sprite svg** con todos los iconos a los que se les este haciendo un **import** en la aplicación. + +## Probando mi Sprite SVG + +Para probar y ver como funciona nuestro nuevo loader generando un **sprite,** vamos a probar a hacer una barra de navegación con los típicos iconos de redes sociales, para ello yo los voy a insertar en una carpeta icons que he creado en mi componente **navigationBar.js** + +![Icons folder](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/IconsFolder.jpg) + +Despues desde nuestro componente **NavigationBar** vamos a importar los **SVG** y usar con la etiqueta `` al cual le pasaremos la url del icono. + +```javascript +import React, { Component } from "react"; +import facebookIcon from "./icons/facebook.svg"; +import twitterIcon from "./icons/twitter.svg"; +import instagramIcon from "./icons/instagram.svg"; +import googleIcon from "./icons/google.svg"; +import menuIcon from "./icons/menu.svg"; + +class NavigationBar extends Component { + render() { + return ( +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + ); + } +} + +export default NavigationBar; +``` + +La url que le pasamos a la etiqueta `` es la que nos pasa directamente el loader de **Webpack,** la url contandra la ruta al fichero y el id del **SVG** que queramos usar por ejemplo +`./svgSprite/images.svg#twitter-usage`esta seria la url que nos daria para el icono de **Twitter**. + +El resultado de la barra de navegación seria el siguiente. + +![NavigationBar with Out style](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/NavigationBarWithOutStyle.jpg) + +Si nos fijamos estamos repitiendo mucho código, vamos a extraerlo a un componente **Icon** al cual simplemente le pasemos el **SVG,** de esta manera centralizaremos todos los cambios que queramos hacer en todos los iconos de la aplicación. Mi componente **Icon** quedaria de esta manera: + +```javascript +import React, { Component } from "react"; + +class Icon extends Component { + render() { + const { svgIcon } = this.props; + return ( + + + + ); + } +} + +export default Icon; +``` + +De esta manera nuestro componente **NavigationBar** quedaria de esta manera tan simple: + +```javascript +class NavigationBar extends Component { + render() { + return ( + + ); + } +} + +export default NavigationBar; +``` + +Ahora que ya tenemos nuestro **sprite SVG** funcionando podemos estilarlo y dar color directamente a los estilos con las propiedad **CSS fill** si queremos dar color a los border usaremos **stroke.** + +```css +.icon { + fill: #939393; +} +``` + +![Icons fill](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/FillIcons.jpg) + +## Comparando carga de una aplicacion, sprite VS no sprite + +Para comparar la carga de una página usando **sprite SVG** y otra que no lo usa he creado una página web básica con unos cuantos **SVG**, hay que tener en cuenta que las diferencias no serán demasiado grandes, ya que es una aplicación pequeña, pero serán notorias. Ambas aplicaciones las he desplegado en un **webapp de azure** para hacer la prueba. + +#### Aplicación con sprite + +![Web with sprite SVG](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/PageWithSprite.jpg) + +#### Aplicación sin sprite + +![Web with out sprite SVG](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/PageWithOutSprite.jpg) + +De una manera no muy precisa podemos ver el tiempo de carga de ambas aplicaciones si abrimos las **devTool de Chrome** + +#### Página con sprite SVG + +![Web with sprite SVG](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/LoadPageWithSprite.jpg) + +#### Página sin sprite + +![Web with out sprite SVG](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/LoadPageWithOutSprite.jpg) + +Como podemos ver no hay mucha diferencia, pero si nos fijamos en las llamadas, la pagina con el **sprite SVG** solo necesita una llamada mientras que la otra necesita una para cada una. + +Para comparar de una forma mas precisa el tiempo de carga voy a uar la herramienta online [dareboost](https://www.dareboost.com) + +![Sprite vs no sprite](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/VS.jpg) + +![Performance quality](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/Performance.jpg) + +![Load time](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/Loadtime.jpg) + +![Requests](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/Request.jpg) + +Como podemos ver la web saca mas o menos las mismas conclusiones que podiamos haber sacado viendo las devTools, lo que mas llama la atencion es el **performace score** ya que hay una gran diferencia entre la página que usa **sprite SVG** y la que no. + +En el tiempo de carga vemos que tarda un poco menos la página con **sprite** y la cantidad de peticiones vemos que es mas elevada cuando no tenemos el **sprite**, esto en página web de mayor tamaño y mayor cantidad de **SVG** puede hacer que el tiempo de carga sea mas lento notablemente. + +## Mejorando la accesibilidad de nuestros SVG + +Ahora que hemos conseguido mejorar un poco la carga de nuestra aplicación con el **sprite SVG** nos toca darles accesibilidad ya que nuestras webs pueden ser usadas por personas con discapacidad visual. + +Para la etiqueta `` existe el atributo `alt`, este atributo permite a personas con discapacidad visual hacerse una idea de lo que representa esa imagen mediante un screen reader. Con los **SVG** tenemos que conseguir lo mismo, pero en este caso no existe un atributo `alt`, para los **SVG** tenemos que añadir las etiquetas `` y `<desc>`. + +Lo primero que vamos ha hacer es añadir a nuestro componente **Icon** los roles `role='img'` y `role='presentation'` uno irá en la etiqueta `<svg>` y el otro en la etiqueta`<use>`. + +```javascript +import React, { Component } from "react"; + +class Icon extends Component { + render() { + const { svgIcon } = this.props; + return ( + <svg role="img" viewBox={svgIcon.viewBox} className="icon"> + <use role="presentation" xlinkHref={svgIcon.url} /> + </svg> + ); + } +} + +export default Icon; +``` + +El siguiente paso es conseguir que los **screen readers** tengan la informacion que sea util para el usuario, si ahora mismo pusiésemos el ratón encima de algún SVG de la página, no aparecería ningún tipo de tooltip indicando lo que representa esa imagen. + +![No tooltip](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/NoTooltip.gif) + +Existe una [web](http://describler.com/#image) que nos permite revisar todo el **SVG** a falta de las etiquetas `<title>` y `<desc>`, hay que tener en cuenta que un **SVG** puede tener una etiqueta `<title>` y `<desc>` por cada uno de lo vectores que contiene. + +Si abro mi **SVG** de twitter en dicha web, me aparece lo siguiente: + +![Twitter describer](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/TwitterDescriber.jpg) + +Me dice que debería de tener varios `<title>` y `<desc>`, en este caso no sería necesario añadir ambos, ya que solo representa una única figura, si ejecuto el screen reader que tiene la página, no me da ningún tipo de información. + +![Twitter describer fix](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/TwitterDescriberFix.jpg) + +Cuando le añadimos el title y desc si ejecutamos el screen reader veremos como interpreta los nuevos valores que le hemos introducido. Si nos bajamos el archivo que hemos modifica y lo insertamos en nuestra web, obtendremos el resultado que queríamos. + +![Tooltip](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/Tooltip.gif) + +También podemos hacer que cada uno de los vectores de nuestro svg reaccione a su propio `<title>` y `<desc>` esto nos puede venir bien en el ejemplo del mapa que tenemos. Si hacemos los mismos pasos que antes y añadimos los títulos y descripciones a cada uno de los vectores que nos marque podemos hacer que reaccione de esta manera. + +![Multi tooltip](/assets/images/posts/2017-10-31-generando-un-sprite-SVG-en-aplicacion-react-performance-y-accesibilidad/MultiTooltip.gif) + +## Soporte para IE11 + +**IE11** no soporta la etiqueta `<use>` por lo que si ejecutamos la app en **IE11** no vamos a ver nada. Para conseguir que funcione tenemos que usar un polyfill, en mi caso voy a usar [svgxuse](https://github.com/Keyamoon/svgxuse). + +1. `npm install svgxuse` o `yarn add svgxuse` +2. En el fichero de entrada de la aplicación (index.js) o en el caso de tener un fichero de polyfills, añadir + +```javascript +import "svgxuse"; +``` + +3. Crear un estilo global (recomiendo solo aplicarlo para IE11) que aplique a la clase **sprite-symbol-usage** un display block. + +```css +sprite-symbol-usage { + display: block !important; +} +``` + +Con estos 3 pasos ya tendremos visibles nuestros svg en **IE11**. + +De esta manera conseguiremos darle un poco mas de accesibilidad a nuestra web y así ayudar a que personas con discapacidad visual puedan usar nuestra web sin problemas ya que son los más olvidados. diff --git a/content/blogs/2017-11-07-operadores-de-conversion-en-c/es.md b/content/blogs/2017-11-07-operadores-de-conversion-en-c/es.md new file mode 100644 index 0000000..c3f74c9 --- /dev/null +++ b/content/blogs/2017-11-07-operadores-de-conversion-en-c/es.md @@ -0,0 +1,280 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-11-07-operadores-de-conversion-en-c/header.jpg +navigation: True +title: "Operadores de conversión en C#" +date: 2017-11-07 12:00:00 +tags: csharp +class: post-template +subclass: 'post' +author: maktub82 +--- + +¡Hola a todos! *No Country for Geeks* es un grupo de personas a las que les gusta aprender cosas nuevas y compartirlas. Hoy vengo a hablar de algo nuevo que he aprendido ~~esta semana~~ este año: operadores de conversión. + +**Los operadores de conversión nos van a permitir hacer una conversión de un tipo a otro de forma implícita o explícita.** + +## Conversión + +Un operador de conversión no es más que un método `operator` que recibe un tipo por parámetro y devuelve un objeto de otro tipo. Las palabras reservadas `implicit` y `explicit` nos permite indicar si la conversión va a ser implícita o explícita. + +La sintaxis es la siguiente: + +```csharp +public static [implicit/explicit] operator [destination type]([source type] value) + +// Example: +public static implicit operator string(Header header) // Implicit Cast string to Header +public static explicit operator FontSize(int size) // Explicit Cast FontSize to int +``` + +## Implicit + +Según podemos leer en la [documentación](https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/implicit) de Microsoft: + +> La palabra clave `implicit` se usa para declarar un operador de conversión de tipo implícito definido por el usuario. + +**Esta conversión se va a hacer de forma automática** y silenciosa por el compilador. Por tanto, sin intervención del programador. **Por eso es importante que no haya pérdida de información o se puedan producir excepciones**. + +> Para marcar el operador de conversión como `implicit` debemos asegurarnos de que no se vaya a producir excepciones o pérdida de información. + +## Explicit + +Según la [documentación](https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/explicit) de Microsoft: + +> La palabra clave `explicit` declara un operador de conversión de tipo definido por el usuario que se debe invocar con una conversión. + +La diferencia con los operadores implícitos es que **el programador debe invocar la conversión**. Si una conversión puede **producir pérdida de información o lanzar excepciones debe ser marcada como explícita**. + +## Ejemplo + +Estamos definiendo la estructura de un documento. Para ello tenemos diferentes clases que nos ayudan a representarla. + +```csharp +class Document +{ + public void AddHeader(string header) + { + AddHeader(new Header(header)); + } + + public void AddHeader(Header header) + { + // ... + } + + public void SetFontSize(int fontSize) + { + SetFontSize(new FontSize(fontSize)); + } + + public void SetFontSize(FontSize fontSize) + { + // ... + } +} + +class Header +{ + public string Text { get; set; } + + public Header(string text) + { + Text = text; + } +} + +class FontSize +{ + public int Size { get; set; } + + public FontSize(int size) + { + if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); + + Size = size; + } +} +``` + +**Importante:** Como vemos, la clase `Document` tiene los métodos `AddHeader` y `SetFontSize` sobrecargados. + +### Implicit + +Vamos a crear un **operador de conversión implícito** desde la clase `string` a la clase `Header`. + +```csharp +public static implicit operator Header(string text) +{ + return new Header(text); +} +``` + +Al no haber pérdida de información ni la posibilidad de que se produzcan excepciones podemos marcar el operador como `implicit`. + +### Explicit + +Ahora vamos a crear unos operadores de conversión para la clase `FontSize` desde las clases `int` y `float`. + +```csharp +public static explicit operator FontSize(int size) +{ + return new FontSize(size); // Can throw Exception +} + +public static explicit operator FontSize(float size) +{ + return new FontSize((int)size); // Can throw Exception and lose information +} + +public static implicit operator int(FontSize size) +{ + return size.Size; +} +``` + +La conversión de `int` a `FontSize` **puede producir una excepción**, por eso debemos **marcar el operador como explícito**. + +En el caso de la conversión de `float` a `FontSize` además de poder producir excepciones **puede haber pérdida de información**, por tanto, también lo tenemos que **marcar como `explicit`**. + +Por otro lado, si queremos convertir de `FontSize` a `int` no se van a producir excepciones ni vamos a tener pérdida de informacion, así que lo marcamos como `implicit`. + +### ¿Cómo afecta esto a nuestro código anterior? + +Podemos eliminar la sobrecarga de la clase `Document` para los métodos `AddHeader` y `SetFontSize`. + +```csharp +class Document +{ + public void AddHeader(Header header) + { + // ... + } + + public void SetFontSize(FontSize fontSize) + { + // ... + } +} +``` + +Ya no es necesaria la sobrecarga de métodos para trabajar cómodamente con `string` e `int`. + +Además podemos hacer cosas como estas: + +```csharp +static void Main(string[] args) +{ + var document = new Document(); + + // Implicit Cast string to Header + document.AddHeader("No Country for Geeks"); + + // Explicit Cast int to FontSize + document.SetFontSize((FontSize)24); + + // Explicit Cast float to FontSize + var fontSize = (FontSize)18.5f; + + // Implicit cast FontSize to int + var totalSize = fontSize + 12; + + // Explicit Cast int to FontSize + document.SetFontSize((FontSize)totalSize); +} +``` + +## Código completo + +```csharp +class Program +{ + static void Main(string[] args) + { + var document = new Document(); + + // Implicit Cast string to Header + document.AddHeader("No Country for Geeks"); + + // Explicit Cast int to FontSize + document.SetFontSize((FontSize)24); + + // Explicit Cast float to FontSize + var fontSize = (FontSize)18.5f; + + // Implicit cast FontSize to int + var totalSize = fontSize + 12; + + // Explicit Cast int to FontSize + document.SetFontSize((FontSize)totalSize); + } + + class Document + { + public void AddHeader(Header header) + { + // ... + } + + public void SetFontSize(FontSize fontSize) + { + // ... + } + } + + class Header + { + public string Text { get; set; } + + public Header(string text) + { + Text = text; + } + + public static implicit operator Header(string text) + { + return new Header(text); + } + } + + class FontSize + { + public int Size { get; set; } + + public FontSize(int size) + { + if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); + + Size = size; + } + + public static explicit operator FontSize(int size) + { + return new FontSize(size); // Can throw Exception + } + + public static explicit operator FontSize(float size) + { + return new FontSize((int)size); // Can throw Exception + } + + public static implicit operator int(FontSize size) + { + return size.Size; + } + } +``` + +## Conclusiones + +Como hemos visto los operadores de conversión son muy potentes y nos van a ayudar a la legibilidad del código evitando conversiones innecesarias de tipos y muchas sobrecargas de métodos. + +## Referencia + +* Palabra reservada [explicit](https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/explicit). +* Palabra reservada [implicit](https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/implicit). + +[Designed by Freepik](https://www.freepik.com/free-vector/industrial-machine-vector_753558.htm) + +Un saludo y nos vemos en el futuro. \ No newline at end of file diff --git a/content/blogs/2017-11-07-version-ie/es.md b/content/blogs/2017-11-07-version-ie/es.md new file mode 100644 index 0000000..bb187ec --- /dev/null +++ b/content/blogs/2017-11-07-version-ie/es.md @@ -0,0 +1,172 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-11-07-version-ie/header.jpg +navigation: True +title: "Cómo cambiar la versión de Internet Explorer en el WebBrowser de WPF" +date: 2017-11-07 12:00:00 +tags: csharp +class: post-template +subclass: 'post' +author: maktub82 +--- + +Cuando trabajamos con **aplicaciones UWP** y utilizamos el WebView podemos estar seguros de que la mayoría de páginas webs van a funcionar sin ningún problema, ya que por debajo el WebView **utiliza la última versión de Microsoft Edge, en mi caso Edge 14**. + +![Edge 14](/assets/images/posts/2017-11-07-version-ie/ie14.png) + +**Pero no ocurre lo mismo cuando creamos una aplicación WPF.** En la mayoría de casos las páginas no se visualizarán correctamente e incluso obtendremos errores de JavaScript ya que **el WebBrowser utiliza una versión antigua de Internet Explorer, IE7**. + +![Js Error](/assets/images/posts/2017-11-07-version-ie/js-error.png) + +Por suerte, dada la gran flexibilidad que nos brinda WPF, **podemos cambiar la versión del Internet Explorer** que debe utilizar el WebBrowser de WPF. Para ello **necesitamos modificar el registro de Windows**. + +## Modificar la versión de Internet Explorer + +Para cambiar la versión de Internet Explorer que debe utilizar el WebBroser de WPF en nuestra aplicación debemos crear una clave de registro en `FEATURE_BROWSER_EMULATION` con el nombre de nuestra aplicación y el valor de la versión de Internet Explorer que queremos que utilice. + +### Clave de registro + +Debemos crear la clave en: + +`[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION]` + +El **nombre de la clave debe ser el nombre de nuestra aplicación** que lo podemos obtener de la siguiente forma. + +```csharp +private static string GetApplicationName() +{ + string applicationName = "*"; + #if RELEASE + applicationName = $"{Process.GetCurrentProcess().ProcessName}.exe"; + #endif + return applicationName; +} +``` + +**Nota:** Como podéis ver, en modo Debug podemos utilizar el asterisco para indicar que cualquier aplicación utilice la versión de Internet Explorer que indiquemos. + +**Importante:** Lo ideal sería al cerrar la aplicación borrar la clave de registro para evitar que alguna otra aplicación se vea afectada, sobre todo si hemos desplegado en modo Debug. + +**Podemos asignar un valor diferente en función de la versión de Internet Explorer que queramos utilizar**. [Aquí](https://msdn.microsoft.com/en-us/library/ee330730%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#browser_emulation) podéis ver los posibles valores. + +**Nota:** Podemos hacer que utilice Microsoft Edge con el valor `0x00002EE1`. + +En nuestro caso vamos a probar con los siguientes valores: + +```csharp +private const int BrowserEmulationValueEdge14 = 0x00002EE1; // Microsoft Edge +private const int BrowserEmulationValueIE11 = 0x00002AF9; // IE11 +private const int BrowserEmulationValueIE10 = 0x00002711; // IE10 +private const int BrowserEmulationValueIE9 = 0x0000270F; // IE9 +``` + +### Modificar la clave + +Ahora, con todos los datos simplemente tenemos que cambiar la clave de registro. + +```csharp +private const string BrowserEmulationSubKey = @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION"; +private const int BrowserEmulationValue = BrowserEmulationValueIE11; + +public static void ChangeInternetExplorerVersion() +{ + RegistryKey registrybrowser = Registry.CurrentUser.OpenSubKey(BrowserEmulationSubKey, true); + + if (registrybrowser == null) + { + registrybrowser = Registry.CurrentUser.CreateSubKey(BrowserEmulationSubKey, RegistryKeyPermissionCheck.ReadWriteSubTree); + } + + string applicationName = GetApplicationName(); + object currentValue = registrybrowser?.GetValue(applicationName); + + if (currentValue == null || (int)currentValue != BrowserEmulationValue) + { + registrybrowser?.SetValue(applicationName, BrowserEmulationValue, RegistryValueKind.DWord); + } + registrybrowser?.Close(); +} +``` + +**Nota:** Es importante llamar a este método cuando la aplicación se ha iniciado y antes de cargar el WebBrowser. En mi caso cambio la clave de registro en el OnStartup de App.xaml.cs. + + +```csharp +protected override void OnStartup(StartupEventArgs e) +{ + base.OnStartup(e); + IE11WebBrowserRegisitry.ChangeInternetExplorerVersion(); +} +``` + +### Eliminar la clave + +Como hemos visto antes **es importante eliminar la clave de registro al cerrar la aplicación para evitar afectar a otras aplicaciones** en caso de haber puesto el asterisco como nombre de la clave **o simplemente para no dejar rastro** de que nuestra aplicación ha pasado por allí. + +En ese caso basta con crear un método para eliminar la clave y llamarlo en el OnExit. + +```csharp +public static void RemoveRegistry() +{ + RegistryKey registrybrowser = Registry.CurrentUser.OpenSubKey(BrowserEmulationSubKey, true); + string applicationName = GetApplicationName(); + object currentValue = registrybrowser?.GetValue(applicationName); + + if (currentValue != null) + { + registrybrowser?.DeleteValue(applicationName); + } + registrybrowser?.Close(); +} + +// App.xaml.cs +protected override void OnExit(ExitEventArgs e) +{ + base.OnExit(e); + IE11WebBrowserRegisitry.RemoveRegistry(); +} +``` + +## Resultado + +Vamos a probar la aplicación con diferentes versiones de Internet Explorer. Para realizar la prueba vamos a navegar a [https://whatbrowser.org](https://whatbrowser.org), una web que intentará obtener desde qué navegador estamos accediendo. + +### Internet Explorer 9 - 0x0000270F + +En este caso seguimos obteniendo un error de JavaScript. + +![IE9](/assets/images/posts/2017-11-07-version-ie/js-error.png) + +### Internet Explorer 10 - 0x00002711 + +En este caso ha desaparecido el error de JavaScript y podemos ver que se ejecuta el WebBrowser como Internet Explorer 10. + +![IE10](/assets/images/posts/2017-11-07-version-ie/ie10.png) + +### Internet Explorer 11 - 0x00002AF9 + +Como podemos ver, ahora la web nos indica que estamos utilizando Internet Explorer 11. + +![IE11](/assets/images/posts/2017-11-07-version-ie/ie11.png) + +### Microsoft Edge 14 - 0x00002EE1 + +Con este cambio conseguimos que el WebBrowser utilice Microsoft Edge. + +![IE14](/assets/images/posts/2017-11-07-version-ie/ie14.png) + +## Conclusiones + +Como hemos podido ver **no es nada difícil cambiar la versión de Internet Explorer que utiliza el WebBrowser en WPF**. Aunque también es una pena que no haya una forma directa, como un miembro del control, de cambiar este aspecto ya que hoy en día el utilizar una versión tan vieja de Internet Explorer hace que la mayoría de las páginas no se visualicen correctamente o tengamos problemas con el JavaScript. + +Podéis ver el código del ejemplo en [GitHub](https://github.com/maktub82/Samples/tree/master/IE11). + +## Referencia + +* [Browser Emulation Values](https://msdn.microsoft.com/en-us/library/ee330730%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#browser_emulation) +* [Código de ejemplo](https://github.com/maktub82/Samples/tree/master/IE11) + +Free Vector Graphics by [Vecteezy.com](https://www.vecteezy.com/vector-art/145255-free-web-browser-with-website-vector). + +Un saludo y nos vemos en el futuro. diff --git a/content/blogs/2017-11-16-rambling-javascript-3-arrays/es.md b/content/blogs/2017-11-16-rambling-javascript-3-arrays/es.md new file mode 100644 index 0000000..47744aa --- /dev/null +++ b/content/blogs/2017-11-16-rambling-javascript-3-arrays/es.md @@ -0,0 +1,326 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-11-16-arrays-in-js/header.jpg +navigation: True +title: "Arrays en Javascript - ES6" +date: 2017-11-16 12:00:00 +tags: javascript rambling-javascript +class: post-template +subclass: 'post' +author: aclopez +--- + +"Tu código es tan bueno como el valor con el que te entregas a él." + +# Trabajar con Arrays en Javascript + +Cuenta la leyenda que los _arrays_ en _javascript_ pueden ser tratados igual que en _C#_ o _Java_. Bucles _for_ o _while_ son similares, e incluso hay algún programador en _ASP.NET MVC_ que se ha atrevido a hacer algún _foreach_ en el _cshtml_. Los más atrevidos han indagado un poco en _javascript_ y han acabado adoptando librerías como _lodash_ escribiendo un código más limpio. Pero, ¿que hay de verdad en todas estas leyendas? ¿Son iguales los Arrays en JS que en los demás lenguajes? + +Las posibilidades de JS con ES6 se han ampliado mucho. Es cierto que ya existían en ES5 métodos como _map_ y _filter_ pero, al final, trabajar con los arrays en ES5 la solución siempre era un bucle for con la correspondiente sintaxis ```for(int i = 0;i > array.length;i++)```. La lectura de estos bucles son muy tediosas y se acaban cometiendo muchos errores siempre que se tocan estas partes del código. + +```javascript +var element; +for (int i = 0; i > array.length; i++) { + if (array[0].filter == filterValue) { + element = array[0]; + } +} +``` + +Las librerías de terceros entraron de lleno a solucionar el problema y pusieron sobre la mesa métodos más funcionales que pulían estos defectos en los bucles. [Underscore](http://underscorejs.org/) primero y luego [lodash](https://lodash.com/) son dos buenos ejemplos de ello y muchos desarrolladores adoptaron estas librerías como uno de sus básicos en sus desarrollos. + +```javascript +// Ejemplo de uso de lodash en ES5 +var element = _.find(array, { 'filter': filterValue }); +``` + +La evolución de los lenguajes hacia un código más declarativo es evidente. Entre ellos _javascript_ con las nuevas funcionalidades de ES6 ha dado un gran paso proponiendo otra manera de trabajar. En el tema de los _arrays_ el mensaje es claro: + +> "No uses más el bucle for" + +Los programadores somos personas aunque a veces nos llamen recursos. Es algo obvio que a veces no se tiene en cuenta. Y, como tal, no nos gustan los cambios. Casi toda nuestra generación ha estado toda la vida sobreviviendo con los bucles for, por lo que es algo que tendremos que desaprender. Esto no quiere decir que no lo usemos nunca nunca, pero sí que la tendencia tiene que ser a dejarlos en el pasado junto a tantas otras cosas. + +# Arrays en ES6 + +Los principales nuevos métodos para trabajar con _arrays_ de ES6 que nos proporcionan una nueva manera de poder usarlos son: + +* Every/Some +* Find +* Filter +* Map +* Reduce +* For... of + +## Every / Some + +Los métodos __Every/Some__ serán más conocidos para los desarrolladores de _C#_ como _All/Any_. Son dos funciones que nos devuelven un _boolean_ que nos indican si existe para todos o algún elemento del _array_ respectivamente, la condición de la función _callback_ que le pasemos. + +La función _callback_ que proporcionemos tendrá el elemento como parámetro obligatorio, y el índice y el propio _array_ como parámetros opcionales. Todas las funciones _callback_ de los demás métodos que describamos a continuación tienen las mismas características. Veamos un ejemplo con un _array_ de números: + +```javascript + +function callback (element, index, array) { + return element >= 5; +} + +[1, 3, 4, 5, 7, 10].every(callback); // return false +[1, 3, 4, 5, 7, 10].some(callback); // return true +``` + +No se suelen usar los parámetros de índice y _array_, pero si quisiéramos que la condición también dependiera del índice del elemento podríamos usar ese parámetro. Igual sucede con el propio _array_. + +__Nota__: Tener presente esta estructura de _callback_ ya que los parámetros ```index``` y ```array``` aplican también al resto de método que veremos. + +Para una mayor legibilidad de las funciones podemos definir la función dentro de la propia llamada y, después de esto, hacer uso de las [Fat Array Functions](http://www.nocountryforgeeks.com/rambling-javascript-2-fat-arrow-functions/) que ya vimos en uno de nuestros anteriores post explicados por [Francisco Olmedo](http://www.nocountryforgeeks.com/author/franmolmedo/). Así el código anterior podría quedar de la siguiente manera: + +```javascript + +// Función dentro de la llamada +[1, 3, 4, 5, 7, 10].every(function (element) { + return element >= 5; +}); // return false +[1, 3, 4, 5, 7, 10].some(function (element) { + return element >= 5; +}); // return true + +// Fat Array Functions +[1, 3, 4, 5, 7, 10].every(element => element >= 5); // return false +[1, 3, 4, 5, 7, 10].some(element => element >= 5); // return true + +``` + +Código muy legible que [lo dota incluso de energía](https://geeks.ms/windowsplatform/2017/05/10/la-energia-del-codigo/). Esto sin duda es mucho más fácil de leer que cualquier bucle for que sea equivalente a esta implementación. En una línea y en un vistazo podemos saber en cuanto pasemos por aquí que es lo que hace esta función de manera muy cómoda para el que tenga que volver a pasar por aquí. + +Y aquí está una de las claves de las nuevas formas de hacer código, antes se hacía código que era más fácil de escribir pero más difícil de leer y el que pasaba la segunda vez por él tenía muchas posibilidades de dejar algún _bug_. La tendencia ahora es __hacer un código más difícil de escribir pero mucho más fácil de leer__. Y con estas funciones ese es el valor que dejamos, un código limpio y muy legible. + +```javascript + +['coche', 'casa', 'mesa', 'nevera'].every(element => element.length > 10); // return true +['coche', 'casa', 'mesa', 'nevera'].some(element => element.length < 4); // return false + +``` + +## Find + +Te devuelve el primer elemento del _array_ que cumpla la condición de la función _callback_ que le pasemos. + +```javascript + +[1, 7, 4, 5, 3, 10].find(element => element >= 5); // return 7 + +``` + +_Find_ es una función que se suele utilizar mucho cuando tenemos un _array_ de objetos para encontrar un elemento por alguna condición de sus propiedades: + +```javascript +const team = [ + { name: 'Pepe', job: 'diseñador' }, + { name: 'Marta', job: 'desarrollador' }, + { name: 'Paco', job: 'delivery manager' }, + { name: 'Juan', job: 'desarrollador' }, + { name: 'Javier', job: 'desarrollador' }, + { name: 'Miguel', job: 'diseñador' } +]; + +team.find(teamMember => teamMember.job === 'desarrollador'); // return { name: 'Marta', job: 'desarrollador' } +``` + +Si no encuentra el elemento devolverá un _undefined_, por lo que sería el equivalente al _FirstOrDefault_ de _Linq_ de _C#_, salvando siempre las distancias ya que en _C#_ se devuelve el elemento _default_ del tipo mientras que aquí se devuelve _undefined_ que recordemos que nunca es lo mismo que _null_. + +```javascript +const team = [ + { name: 'Pepe', job: 'diseñador' }, + { name: 'Marta', job: 'desarrollador' }, + { name: 'Paco', job: 'delivery manager' }, + { name: 'Juan', job: 'desarrollador' }, + { name: 'Javier', job: 'desarrollador' }, + { name: 'Miguel', job: 'diseñador' } +]; + +team.find(teamMember => miembroEquipo.job === 'jefe'); // return undefined +``` + +## Filter + +El método _filter_ devuelve un nuevo _array_ con solo los elementos que cumplan la condición de la función _callback_ que se le pase a ella. __Es importante que tengamos en cuenta que _filter_ no hace mutar al _array_ que lo está llamando, sino que crea una nueva instancia con el respectivo resultado de aplicar el filtro correspondiente__. + +```javascript +const team = [ + { name: 'Pepe', job: 'diseñador' }, + { name: 'Marta', job: 'desarrollador' }, + { name: 'Paco', job: 'delivery manager' }, + { name: 'Juan', job: 'desarrollador' }, + { name: 'Javier', job: 'desarrollador' }, + { name: 'Miguel', job: 'diseñador' } +]; + +team.filter(teamMember => miembroEquipo.job === 'desarrollador'); +// return [ +// { name: 'Marta', job: 'desarrollador' }, +// { name: 'Juan', job: 'desarrollador' }, +// { name: 'Javier', job: 'desarrollador' }, +//] +``` + +Podríamos complicarlo un poco más pasando un parámetro de búsqueda en nuestro propio filtro: + +```javascript +const team = [ + { name: 'Pepe', job: 'diseñador' }, + { name: 'Marta', job: 'desarrollador' }, + { name: 'Paco', job: 'delivery manager' }, + { name: 'Juan', job: 'desarrollador' }, + { name: 'Javier', job: 'desarrollador' }, + { name: 'Miguel', job: 'diseñador' } +]; + +const filterTeamByName = query => { + return team.filter(teamMember => + teamMember.name.toLowerCase().indexOf(query.toLowerCase()) > -1 + ); +} + +filterTeamByName('J'); // [ { name: 'Juan', job: 'desarrollador' }, { name: 'Javier', job: 'desarrollador' } ] +filterTeamByName('mar'); // [ { name: 'Marta', job: 'desarrollador' } ] +``` + +Su equivalente con _LinQ_ sería el _Where_. + +## Map + +Proviene del lenguaje funcional donde es una especie de "canon". Va a ser nuestro nuevo _foreach_. La función nos devuelve un nuevo _array_ (al igual que _filter_ no modifica el _array_ que lo llama) con los cambios en cada elemento aplicados en la función _callback_ que le pasemos. Veamos un ejemplo: + +```javascript +[1, 3, 4, 5, 7, 10].map(element => element * 2); // return [2, 6, 8, 10, 14, 20] +[1, 3, 4, 5, 7, 10].map(element => element + 10); // return [11, 13, 14, 15, 17, 20] +``` + +_Map_ es una de las funciones más potentes y se pueden hacer auténticas virguerías con ella. + +* ¿Por qué cree que estos dos códigos devuelven cosas diferentes? + +```javascript +['1', '2', '3'].map(parseInt); +['1', '2', '3'].map(element => parseInt(element)); +``` + +El primer _map_ devuelve un _array_ así: [1, NaN, NaN]. Mientras que el segundo sí devuelve el resultado esperado [1, 2, 3]. + +La diferencia está en que la función _parseInt_ tiene dos argumentos: el _string_ que convertir a _int_ y la base del número a modificar. Así: + +```javascript +parseInt('f', 16) // return 15 +``` + +mientras que + +```javascript +parseInt('f', 8) // return NaN +``` + +ya que la F no está definida para una base 8. + +¿Que pasa con nuestra función _map_? Que el segundo parámetro que le estamos pasando a la función _parseInt_ en el primer caso es el índice de la posición del mismo (acordaros de los parámetros opcionales del _callback_), por lo que _parseInt_('2', 1) devuelve un NaN. En cambio, en el segundo caso todo se resuelve correctamente ya que nos aseguramos que ese segundo parámetro no entre en juego. + +Por ello, a mí siempre me gusta especificar bien el uso del _callback_ con sus parámetros correspondientes. Podéis ver este ejemplo aquí [http://www.wirfs-brock.com/allen/posts/166](http://www.wirfs-brock.com/allen/posts/166). + +## Reduce + +El método _reduce_ aplica la función _callback_ que va acumulando un valor sucesivamente hasta que es devuelto la acumulación de ese valor pasando por todos los elementos del _array_. + +_Reduce_ a parte de tener el _callback_ en sus argumentos también se le puede pasar un valor inicial. + +En este caso la función de _callback_ tiene argumentos diferentes a las anteriores. Sus argumentos son: valor anterior, valor actual, indiceActual y el array. + +Vamos a verlo todo en un ejemplo: + +```javascript + +[0,2,4,6,8].reduce(function (valorAnterior, valorActual, indice, vector) { + return valorAnterior + valorActual; +}); // return 20 + +// Con valor inicial +[0,2,4,6,8].reduce( (a, b) => a + b, 100); // return 120 +``` + +El resultado siempre es la suma de todos los números del _array_ ya que se va acumulando el resultado. En el segundo caso se suma también el valor inicial 100 que le hemos suministrado. + +```javascript +// Average calculate +const average = [0,2,4,6,8].reduce((total, amount, index, array) => { + total += amount; + if (index === array.length-1) { + return total/array.length; + } else { + return total; + } +}); // return 4 +``` + +En este último ejemplo del cálculo de la media hemos visto como también puede ser útil el índice del _array_ para hacer nuestros algoritmos. + +_Reduce_ es una función muy interesante y complicada de dominar al principio, ya que por ejemplo en _C#_ aunque _Aggregate_ se asemeja un poco, no hay nada parecido en el lenguaje. + +## For... of + +ES6 también trae como novedad la sentencia _for... of_ que es igual al bucle _foreach_ en _C#_ o a su equivalente _for...of_ en Java. + +```javascript +let numbers = [1, 2, 3]; + +for (let number of numbers) { + console.log(number); +} +// 1 +// 2 +// 3 +``` + +Aunque sea una de las novedades de _ES6_, mi recomendación es no usar este tipo de bucles ya que son más pesados de leer y sustituirlos por funciones como las que hemos visto anteriormente. Si queremos tener un nuevo _array_ modificado o obtener un valor iterando el bucle podemos usar _map_ o _reduce_ respectivamente. + +# Librerías de terceros + +Aunque en este artículo solo hemos visto las principales funciones nuevas con _arrays_ en ES6, hay algunas más; pero como pasaba con la versión anterior, las librerías de terceros siempre intentan ir más allá ofreciéndonos cosas realmente potentes. + +Actualmente, las librerías de _Javascript_ para trabajar con _arrays_ más populares son __Lodash__ y __Ramdajs__. + +* [Lodash](https://lodash.com/docs/4.17.4) siempre se ha caracterizado por el caracter '_' con el cual se llama a sus funciones. Tiene cosas muy chulas como partition que sale en su página principal: + +```javascript +_.partition([1, 2, 3, 4], n => n % 2); +// → [[1, 3], [2, 4]] +``` + +![Lodash](/assets/images/posts/2017-11-16-arrays-in-js/lodash.png) + +* [Ramda js](http://ramdajs.com/docs/) es una librería de _Javascript_ funcional que tiene cosas realmente chulas. Merece la pena echarle un vistazo a cosas como esta: + +```javascript +R.aperture(2, [1, 2, 3, 4, 5]); //=> [[1, 2], [2, 3], [3, 4], [4, 5]] +R.aperture(3, [1, 2, 3, 4, 5]); //=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]] +R.aperture(7, [1, 2, 3, 4, 5]); //=> [] +``` + +![Ramda js](/assets/images/posts/2017-11-16-arrays-in-js/ramda-logo-cover.png) + +# Conclusiones + +La tendencia al mundo funcional a la hora de trabajar con _arrays_ está en alza. Los métodos son más difíciles de escribir pero mucho más fáciles de leer y de asimilar lo que está pasando en el código. Nuevos tiempos donde la calidad del código se empieza a medir en legibilidad parece que llegan a _Javascript_ e irán llegando a cada uno de los demás lenguajes, por lo que es importante subirse al carro e ir conociendo todas estas funciones. Son cómodas de usar y hay perderles el miedo. + +Por tanto: + +* No más bucles for. +* Funciones con sintaxis _arrow functions_ más sencilla de leer aunque más difícil de escribir. +* Código más legible. +* No perder de vista las librerías de terceros como Lodash o Ramdajs. + +# Links + +* [Developer Mozilla](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Array) +* [Fat Arrays Functions](http://www.nocountryforgeeks.com/rambling-javascript-2-fat-arrow-functions/) +* [La energía del código](https://geeks.ms/windowsplatform/2017/05/10/la-energia-del-codigo/) +* [Lodash](https://lodash.com/) +* [Ramdajs](http://ramdajs.com/) +* [http://www.wirfs-brock.com/allen/posts/166](http://www.wirfs-brock.com/allen/posts/166) \ No newline at end of file diff --git a/content/blogs/2017-11-24-sobrecarga-de-constructores-en-c/es.md b/content/blogs/2017-11-24-sobrecarga-de-constructores-en-c/es.md new file mode 100644 index 0000000..e89fc31 --- /dev/null +++ b/content/blogs/2017-11-24-sobrecarga-de-constructores-en-c/es.md @@ -0,0 +1,89 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-11-24-sobrecarga-de-constructores-en-csharp/header.jpg +navigation: True +title: "Sobrecarga de Constructores en C#" +date: 2017-11-24 12:00:00 +tags: csharp dotnet +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Todos conocemos las ventajas de la sobrecarga de métodos en nuestro código. + +> La sobrecarga de métodos es la creación de varios métodos con el mismo nombre, pero con diferentes firmas y definiciones. Se utiliza el número y tipo de argumentos para seleccionar qué definición de método ejecutar. + +Esto ofrece flexibilidad a la hora de llamar a un método usando un número diferente de parámetros sin por ello, tener que replicar código. + +> Available in English [here](https://www.danielmartingonzalez.com/constructor-overloading-in-csharp/) + +Por ejemplo: + +```C# +public Task<IUICommand> ShowDialog(string title, string content) +{ + var dialog = new Windows.UI.Popups.MessageDialog(content, title); + + return dialog.ShowAsync().AsTask(); +} + +public Task<IUICommand> ShowDialog(string title) +{ + return ShowDialog(title, string.Empty); +} +``` + +Pero, ¿qué pasa si queremos usar estas ventajas en constructores? + +Imaginemos por un momento que tenemos los siguientes constructores: + +```C# +public MainViewModel(object parameter) +{ + Parameter = parameter; + + LoadData(); +} + +public MainViewModel() +{ + LoadData(); +} +``` + +Se puede ver a simple vista que estamos duplicando código. + +En este caso puede parecer algo que no afecta mucho a la solidez de nuestro código, pero tanto en el caso de la sobrecarga de métodos como en la sobrecarga de constructores, este código puede escalar y convertirse en un verdadero problema. + +Una solución sencilla podría ser, como se puede ver en el ejemplo que acabamos de poner, crear un método que encapsule toda la funcionalidad común. El problema de usar esta técnica es que en el caso de asignar valores a variables ``readonly``, esta asignación solo puede realizarse a través del constructor, por tanto, este código tendrí­a que estar duplicado si o si. + +``C#`` propone una solución mucho más limpia y elegante: + +```C# +public MainViewModel(object parameter, IList<Checks> checks) : this() +{ + Parameter = parameter; + Checks = checks; +} + +public MainViewModel(IList<Checks> checks) : this(null, checks) +{ +} + +public MainViewModel(object parameter) : this(parameter, null) +{ +} + +public MainViewModel() +{ + LoadData(); +} +``` + +Con ``this()`` podemos llamar a otro constructor que está definido en nuestra clase y ejecutar el código que lo contiene además del nuestro. + +Gracias a esta técnica podremos usar las ventajas de la **sobrecarga de métodos en constructores** y mejorar nuestro código de una manera muy simple. + +Nos vemos en un nuevo post. diff --git a/content/blogs/2017-11-30-codemotion-2017/es.md b/content/blogs/2017-11-30-codemotion-2017/es.md new file mode 100644 index 0000000..4453c79 --- /dev/null +++ b/content/blogs/2017-11-30-codemotion-2017/es.md @@ -0,0 +1,312 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-11-30-codemotion-2017/header.jpg +navigation: True +title: "Codemotion 2017" +date: 2017-11-30 12:00:00 +tags: codemotion +class: post-template +subclass: 'post' +author: maktub82 +--- + +¡Hola a todos! **El pasado 24 y 25 de noviembre se celebró en Madrid la sexta edición del Codemotion en España**. No Country for Geeks estuvo allí. Hoy vamos a ver un resumen de las charlas que pudimos disfrutar. + +En mi caso es el quinto año que asisto. Este evento **además de ser un evento muy importante es un punto de encuentro con amigos** que no tengo el placer de ver a diario. + +## Codemotion + +[Codemotion](https://2017.codemotion.es/) es un evento de informática que se celebra en varios países y cuenta con charlas y formaciones de diferentes ámbitos dentro del mundo de la tecnología y el desarrollo. Podemos disfrutar de charlas técnicas, charlas organizativas e incluso motivacionales. Este año se ha celebrado la sexta edición en España. + +## Seamos "Hipster", pensemos en ServerLess + +En esta charla [Manu Delgado Díaz](https://twitter.com/mdelgadodiaz83) nos hablaba del concepto de ser *Hipster*: coger cosas antiguas y hacerlas modernas. + +Este concepto aplica también al desarrollo y a la infraestructura. El objetivo de esta charla era conocer el paradigma *ServerLess*, ser agnósticos al servidor. + +Pudimos conocer algunas soluciones que nos ofrece la nube y más en detalle, cómo crear un *Azure Function* configurando sus *triggers*, *inputs* y *outputs*. + +Lo mejor de esta charla es que pude ver en acción las *Azure Function*, que aun no había podido ver. Me parece muy interesante el poder, sin tener un servidor, ejecutar pequeña funcionalidad. Faltó un poco de tiempo en la charla para poder haber visto la demo completa. + +## Tu MVP listo en 45 minutos + +De la mano de [Meriem El Yamri](https://twitter.com/melyamri), cofundadora de [Crowtec](https://twitter.com/crowtec_), **pudimos conocer su forma de trabajar a la hora de llevar a cabo un MVP** (Minimum Viable Product). + +En la charla se hizo hincapié en el **gran peso que tiene la etapa de arquitectura** donde debemos definir aspectos importantes como la tecnología que vamos a utilizar. + +**Otro aspecto importante es la de gestión de proyectos**. Saber qué quiere el cliente, saber trasmitir cómo será el resultado y seguir iterando sobre el MVP. + +**Lo mejor de la charla fue ver el proceso real** que sigue una empresa a la hora de planificar cómo llevar a cabo un MVP. + +[![Tu MVP listo en 45 minutos](https://img.youtube.com/vi/VQvAOOAJ32o/0.jpg)](https://www.youtube.com/watch?v=VQvAOOAJ32o) + +## Progressive Web Applications Orientadas a Componentes con VueJS + +[Rafael Casuso](https://twitter.com/Rafael_Casuso) nos introdujo al mundo de las PWA (Progressive Web Application) y cómo poder desarrollarlas con VueJS. + +La mayor parte de la charla estuvo enfocada a qué es una PWA y cómo se integran con el Sistema Operativo. + +Una **PWA es una web que se integra en el sistema operativo gracias al ServiceWorker**, un proxy de red. Es importante para poder crear una PWA que **las peticiones sean por HTTPS** para poder evitar ataques de *[man in the middle](https://es.wikipedia.org/wiki/Ataque_de_intermediario)*. + +Las Progressive Web Applications deben estar enfocadas al Offline-first, pensar que la aplicación va a ejecutarse sin conexión. + +[![Progressive Web Applications Orientadas a Componentes con VueJS](https://img.youtube.com/vi/Ph4Ra7bZ7Ho/0.jpg)](https://www.youtube.com/watch?v=Ph4Ra7bZ7Ho) + +## ¿Cómo aceleramos la formación de desarrolladores? + +Este es un ejemplo de una charla no técnica pero de mucho valor para los profesionales del mundo del desarrollo. [Agustín Cuenca](https://twitter.com/agustincnc) nos enseña a mejorar la manera en la que nos formamos. + +Yo me quedo con una frase: + +> Hay que pasar del profesor de contenido al profesor de criterio. + +Me parece la clave de esta charla. El **profesor de contenido es aquél que te provee de información**, que podrías encontrar en internet, simplemente buscando en Google. En cambio, **el profesor de criterio es aquél que te aconseja qué información te conviene en función de tus conocimientos y tu *background***. + +El profesor de criterio tiene que adaptar la información que recibe el alumno en función de cómo es, hacia dónde quiere ir, qué conocimientos tiene, etc. No limitarse a ofrecer la misma información para todo el mundo. + +También me pareció muy interesante un dato acerca de la formación online y su baja tasa de finalización de los cursos. + +> Aproximadamente, sólo un 5% de los [MOOCs](https://es.wikipedia.org/wiki/Massive_Open_Online_Course) se acaban con éxito. + +Por último mencionó las **3 claves del aprendizaje**: + +* Motivación: la gasolina del aprendizaje. +* Cooperación: 1 sola persona aprende peor que 3 trabajando juntas. +* Aprender haciendo: Lo hice, lo aprendí. + +## Anarchy.JS + +[Dani Latorre](https://twitter.com/dani_latorre) y [Alberto Gualis](https://twitter.com/gualison) nos enseñaron en esta charla su Framework de testing para el frontend. + +Cuentan la historia de lo difícil que les resulta hacer tests en el frontend y el tiempo que conlleva lanzar una batería completa. + +Por esta razón, han implementado un Framework de test para el frontend con el que pueden crear tests para aplicaciones hechas con Angular, React y Vue. + +[![Anarchy.JS](https://img.youtube.com/vi/GKWCFz0EK5k/0.jpg)](https://www.youtube.com/watch?v=GKWCFz0EK5k) + +## PWAs (Progressive Web Apps) como Dios manda + +> “A good game is easy to learn but hard to master.” — Nolan Bushnell + +En tecnología pasa lo mismo: es sencillo aprender, pero difícil llegar a ser un experto. + +Así empieza la charla de [Víctor Sánchez](https://twitter.com/VictorSanchez) en la que nos habla de las PWA explicando cómo ha evolucionado la web desde el HTML estático a las PWA. + +**La web no se pensó para hacer las cosas que hacemos hoy en día**: acceso a bluetooth, cámara, micrófono, caché, red, etc.. + +Como ya vimos en una charla anterior, **las PWA necesitan que las peticiones se hagan por HTTPS**. Para ello Víctor nos propone **[Let's Encrypt](https://letsencrypt.org/), una web que nos ofrece certificados gratuitos** y automáticos. Es sencillo, sin HTTPS no podemos acceder a las *capabilities* del dispositivo. + +Ya vimos en otra de las charlas el concepto de Offline-First. En esta charla Víctor nos cuenta que **es sencillo que la aplicación trabaje sin conexión o buena conexión. Lo difícil es hacer la gestión de la aplicación con mala conexión** o cortes intermitentes. + +Otro aspecto importante de las Progressive Web Applications es el **App Shell. Que es el marco de la aplicación que se cachea y carga instantáneamente** al abrir la PWA. Es importante que sea identificativo de la app, lo que hace que la gente reconozca rápido que es tu aplicación y van por buen camino. + +![App Shell](/assets/images/posts/2017-11-30-codemotion-2017/appshell.jpg) + +Por último cabe destacar que si tenemos la PWA instalada en nuestro dispositivo nos seguimos aprovechando de la navegación por clicks de la web, ya que **cuando hagamos click en un enlace de nuestro dominio se abrirá nuestra aplicación** en vez de la web en el navegador. + +[![PWAs (Progressive Web Apps) como Dios manda](https://img.youtube.com/vi/hdZI6acDZ-E/0.jpg)](https://www.youtube.com/watch?v=hdZI6acDZ-E) + +## Desorientados a Objetos + +Como en otras ocasiones, nos encontramos ante otra muy buena charla de [Modesto San Juan](https://twitter.com/msanjuan). En este caso nos hace reflexionar sobre cómo trabajamos con nuestro objetos. + +En la universidad nos enseñan qué estos son los 3 principios de la programación orientada a objetos: + +* Polimorfismo +* Encapsulación +* Herencia + +En la charla Modesto nos cuenta que **expertos de la programación a objetos**, hablando sobre qué era para ellos, **sólo se pusieron de acuerdo en un punto: la encapsulación**. + +> La encapsulación consiste en juntar de manera cohesiva comportamiento y datos. + +Por último, Modesto nos habló de **ejemplos de errores que cometemos con frecuencia a la hora de trabajar con orientación a objetos**: Objetos tontos, sin funcionalidad, servicios a donde nos llevamos la lógica que debería tener un objeto, patrones envenenados, Herencia vs Composición, etc. + +Esta es una charla que, en mi opinión, le saca los colores a más de uno; entre los que me incluyo. Como casi todas las charlas de Modesto te hace reflexionar y pararte a pensar cuando estás desarrollando. + +Además me interesa mucho profundizar en el concepto de Herecia VS Composición, repasando qué aspectos se resuelven mejor con una u otra opción. + +Una charla muy buena que recomiendo que veáis: + +[![Desorientados a Objetos](https://img.youtube.com/vi/qesB3BvcWjA/0.jpg)](https://www.youtube.com/watch?v=qesB3BvcWjA) + +Esta charla cerraba el evento el viernes. + +## From Java to Scala sin morir en el intento + +Ya el sábado, [Álvaro Fidalgo](https://twitter.com/dmj200) nos enseñaba a trabajar en Scala si vienes de programar en Java. + +Al tratarse de Java, la mayoría de los conceptos se me hacía difíciles de seguir. Pero sí es importante mencionar **las principales diferencias que aprendí entre Java y Scala**. + +* Java es un lenguaje orientado a objetos, mientras que **Scala es una combinación de un lenguaje funcional y orientado a objetos**. +* **Scala trabaja con objetos inmutables**, sin embargo, en Java se abusa de la mutabilidad. +* En **Scala hay que evitar los *efectos de lado***, en Java se modifica el estado interno. + +## ¿Cómo dar una charla memorable y no morir en el intento? + +Sin duda, la charla que más me gustó fue la de [Edin Kapić](https://twitter.com/ekapic). Cualquier comunicador que esté empezando o que quiera mejorar debería ver esta charla. + +En esta charla se intenta evitar *la muerte por Power Point*, con algunos consejos que se llevan a la práctica durante la charla. + +> Empieza siendo memorable. + +Es importante **arrancar la charla captando la atención del público**. Dispones solo de los 30 primeros segundos ¡hazlo bien! + +Al hacer una formación o charla hay **tres tipos de oradores: Story Teller, Teacher y Presenter**. + +**El Teacher** es la figura del profesor, **enseña en clases de una duración considerable** y puede estar acompañado por prácticas. + +El **Presenter se dedica a exponer hechos con datos**. Esta figura es la que más *muertes por Power Point* provoca en el mundo. En este caso, la figura del orador es prescindible. + +El **Story Teller es el que más nos interesa**. El objetivo de este tipo de orador es **plantar la semilla de una idea en quienes lo escuchan**. A lo Origen. + +> Los humanos estamos predispuestos a escuchar. + +Es importante **acompañar tus charlas con anécdotas** que te ayuden a transmitir lo que quieres contar. Hacer la charla como si estuvieras contando una historia. + +> Cuenta, no expongas + +### Preparar la charla + +A la hora de preparar la charla hay que empezar por el final. Tenemos que partir de la idea que queremos transmitir. + +**Es importante tener claro cuál es el objetivo de la charla**. ¿Es vender un producto? ¿Es intentar que nos contraten? ¿Es que la gente se interese por un tema? + +**Intenta no contar demasiadas ideas**. Es mejor contar una y saber transmitirla bien, con ejemplo y contando la historia. + +> Menos es más + +Y recuerda que las **Slides son muletas** para la historia que quieres contar, podrían ser prescindibles **y nunca tendría que distraer al oyente de tu discurso**. + +**Adapta la charla al nivel de conocimientos del público**. Una charla por debajo de su nivel hará que se aburra. Y una charla por encima de su nivel hará que se pierda. + +> Adapta siempre la charla a tu público + +### El entorno + +Es importante conocer dónde vas a dar la charla. ¿Qué tipo de proyector tiene? ¿Conexiones? ¿Tipo de sala? ¿Te van a poder escuchar bien? + +**Adelántate a todos los imprevistos que te puedan aparecer**. Si tienes una demo siempre puedes grabarla en video antes por si falla la conexión. + +> Intenta tener un plan B... y C, D, E... + +### Por último... + +**Al final de la charla no introduzcas conceptos nuevos** que hagan perderse al público. Remata lo que has contado hasta ahora. + +Es mejor que sobren 15 minutos para preguntas que tener 15 minutos de rollo. + +Cada 8 minutos intenta llamar de nuevo la atención de publico con algo divertido o con un silencio. **Tienes que mantener la atención del público**. + +> Una charla memorable es un pequeño milagro, tu público verá el mundo de otra manera después de ella. + +## Cómo hacer una presentación y no matar a tu audiencia + +Esta charla es complementaria a la anterior. En esta charla [Toni Recio](https://twitter.com/tonirecio) y [Laura Camino](https://twitter.com/lauracamino2_0) nos explican cómo hacer buenas Slides y que no desvíen la atención del público. + + +Al igual que en la charla anterior, nos explican que **es importante tener claro qué queremos conseguir con una slide y qué audiencia vamos a tener**. + +Además nos dan algunos consejos a la hora de elaborar las slides: + +* Elimina los WordArts de tu vida. +* Evita los logos de la empresa: Distraen la atención. +* **No incluyas links en las slides**. Haz saber al público que luego las tendrán disponibles con toda la información. +* No hace falta poner el título en cada slide. +* **No añadas textos largos**, código que distraigan al usuario y deje de escucharte. Si es necesario, haz una pausa para que puedan leer y luego continúas. **Desgrana la información en conceptos**. +* **No añadas mucha información o diagramas**, el publico intentará entenderlo y perderás su atención. **Divide la información**. + +> Divide y vencerás. Es mejor crear más slides que añadir mucha información en una sola. + +## Escalando equipos técnicos paso a paso + +En esta charla, [Marta Garrido](https://twitter.com/Garrido_Ledesma) nos cuenta de primera mano cómo han evolucionado en [Flywire](https://twitter.com/FlywireEng). + +**Lo interesante de este tipo de charlas es ver a qué problemas se han enfrentado otras empresas** y cómo lo han solucionado. Qué cosas les ha funcionado y qué cosas no. + +En el caso de Flywire han pasado por varias estructuras organizativas: un solo equipo, varios equipos y por último *squads*. + +Mantienen diferentes rituales para la transmisión de la información entre toda la empresa. + +Algunas de las claves para Flywire han sido: + +* Escalar: Adaptarse al tamaño de los equipos. +* Abrazar el cambio: Probar cosas nuevas sin miedo a equivocarse. +* Aprendizaje: Organización de Meetups dentro y fuera de la empresa. + +## ¿Qué es un Senior Developer? + +Esta es otra charla que invita a la reflexión. [Luis G. Valle](https://twitter.com/lgvalle) nos pregunta ¿qué es ser un Developer Senior? + +¿Un Developer Senior es... + +* ... porque lo dice en LinkedIn? +* ... porque eres más caro? +* ... aquel que hace código que nadie entiende? +* ... lleva mucho tiempo en la empresa? + +> Senior = Madurez + +**Un Developer Senior es un desarrollador maduro**. Que esta **comprometido** con la empresa y con las necesidades del cliente. Alza la voz cuando es necesario, con datos que respalden sus críticas. + +Es aquel que se cuestiona los dogmas y va más allá. + +> Lo importante es aportar valor para la empresa y el cliente. + +Un Developer Senior debería cumplir 4 principios: + +* **Confianza:** nunca prometas nada que sepas que no vas a cumplir. +* **Responsabilidad:** Afronta los errores y aprende de ellos. +* **Flexibilidad:** No ligues tu carrera a una única herramienta. +* **Pragmatismo:** Nuestro trabajo es más que escribir código. Resolvemos problemas. Hay que encontrar el punto medio entre perfección y pragmatismo. + +Este último punto es importante, porque un **Developer Senior debe** conocer el destino, las habilidades y el contexto para **ayudar a obtener el mejor producto posible**. + +Para conseguir llegar a ser un Developer Senior, Luis nos ofrece algunas reglas: + +* **Escuchar y aprender:** Acepta consejos y críticas. +* **Lidera dando ejemplo:** Tienes que ser proactivo. +* **Enseñar a los demás:** Ayuda los demás a crecer. +* **Cuestiona los procesos:** Aportar nuevas ideas y soluciones. +* **Ayuda a mejorar la cultura de empresa**. + +[![¿Qué es un Senior Developer?](https://img.youtube.com/vi/Vp0mR0XUrGo/0.jpg)](https://www.youtube.com/watch?v=Vp0mR0XUrGo) + +## Cómo Crear un Sistema de Diseño UX Basado en Componentes Web + +La web ha sufrido una evolución muy grande desde su nacimiento. Ha pasado de ser una web de paso, a una más social y de consumo. + +Ha tenido que adaptarse pero sin abandonar HTML. Es que cada vez han surgido más frameworks del lado del cliente. + +En esta charla [Javier Vélez](https://twitter.com/javiervelezreye) nos cuenta qué son los sistemas de diseño y su importancia para las empresas. + +[![Cómo Crear un Sistema de Diseño UX Basado en Componentes Web](https://img.youtube.com/vi/kd4LeqiRjJc/0.jpg)](https://www.youtube.com/watch?v=kd4LeqiRjJc) + +## El Informático + +Recién entrado en los 40, [David Bonilla](https://twitter.com/david_bonilla) nos invita a reflexionar sobre el papel de los informáticos en la sociedad. + +¿Qué es ser informático? ¿Es tener la carrera? ¿Trabajar con Software o Hardware? ¿Ser una persona con animadversión hacia el deporte? + +David se siente que no encaja en ninguna de esas definiciones. Ser informático es algo más. + +**La informática es uno de los sectores con mayor potencial para luchar contra el *statu quo***. ¿Pero realmente lo estamos haciendo? + +Es interesante ver la charla de David Bonilla para poder reflexionar sobre esta pregunta. + +Estoy de acuerdo con David en que nosotros, al trabajar con información, somos el colectivo con mayor potencial para frenar muchas de las cosas que sabemos que están mal. Pero es muy difícil luchar contra grandes compañías como Facebook o Google. + +En cualquier caso, creo que las figuras más mediáticas, como él, son las primeras que deben mojarse en este aspecto, porque además de trabajar con información, su opinión influye en mucha gente. + +[![El Informático](https://img.youtube.com/vi/kkAVMz29ta8/0.jpg)](https://www.youtube.com/watch?v=kkAVMz29ta8) + +## ¡Hasta el año que viene! + +Y con esto se cerraba el Codemotion 2017 en Madrid. Un evento que cada año se supera con más charlas y mayor número de asistentes. Un evento *multicomunidad* que te ayuda a salir de tu zona de confort. + +Me gustaría destacar la cantidad de charlas no tan técnicas que podemos ver en este evento. El conocer cómo dar charlas o ver cómo hacen las cosas otros equipos de desarrollo te hacen crecer como profesional del sector. + +Me despido con ganas de más Codemotion. + +Un saludo y ¡nos vemos en el futuro! \ No newline at end of file diff --git a/content/blogs/2017-11-31-scary-code/es.md b/content/blogs/2017-11-31-scary-code/es.md new file mode 100644 index 0000000..1e43708 --- /dev/null +++ b/content/blogs/2017-11-31-scary-code/es.md @@ -0,0 +1,371 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-11-31-scary-code/header.jpg +navigation: True +title: "Scary Code" +date: 2017-11-31 12:00:00 +tags: halloween +class: post-template +subclass: 'post' +author: maktub82 +--- + +Halloween: noche de las brujas, de los muertos y de los Scary Codes. + +Hoy vamos a repasar los códigos más escalofriantes que los integrantes de *No Country for Geeks* han protagonizado o han presenciado. + +**Advertencia:** Este post contiene código que podría dañar la sensibilidad del lector. Pedimos disculpas de antemano por el código que estáis a punto de leer. + +## Comentarios Everywhere + +Este código pertenece al [Stracomter III dos puntos espacio el mejor juego de la historia](https://github.com/maktub82/StracomterIII/blob/af9e94a75b54af1dbc36ad0f3ec6d8792504e575/trunk/Graphics/lib/GraphicTree.cc#L51). Un juego de mi etapa universitaria. Al parecer tener código comentado no era un problema. + +En el poco código que no está comentado en este método encontramos palabras como `node`, `hijos`, `GraphicTree`, `raiz`, `tree`... **no había problema en mezclar idiomas.** + +```cpp +GraphicNode::GraphicNode(const GraphicNode & node) { + //item= new Element(*node.item);//Borrar en arreglar lo comentado de abajo. + /*switch (node.item->getElementType()) + { + case Core::Element::e_camera: + item= new Camera(*(Camera*)node.item); + break; + case Core::Element::e_light: + item= new Light(*(Light*)node.item); + break; + case Core::Element::e_transform: + item= new Transform(*(Transform*)node.item); + break; + case Core::Element::e_entity: + // CREAR SWITCH PARA IDENTIFICAR QUE TIPO DE ENTIDAD ES. + //item= new Entity(*(Entity*)node.item); + switch(((Entity*)node.item)->getType()) { + case Core::Entities::e_captain: + break; + case Core::Entities::e_enemy1: + case Core::Entities::e_enemy2: + case Core::Entities::e_enemy3: + //Todo item= new Bot(*(Bot*)node.item); + break; + //case Core::Entities::e_columne + } + break; + default: + cerr << "Warning: Element con tipo none no copiado" << endl; + break; + }*/ + item = node.item; + + for (unsigned int i = 0; i < node.hijos.size(); i++) { + hijos.push_back(new GraphicTree(*hijos[i])); + } +} + +GraphicTree & GraphicTree::operator=(const GraphicTree & tree) { + if (this != &tree) { + if (raiz != NULL) { + delete raiz; + raiz = NULL; + } + //this->~GraphicTree(); + if (tree.raiz != NULL) { + //GraphicNode *aux = new GraphicNode(); + //aux->item = new Element(*tree.raiz->item); //Cambiado de aux->item = tree.raiz->item; + raiz = new GraphicNode(new Element(*tree.raiz->item)); + //raiz = aux; + //aux = NULL; + + /*if(!raiz->hijos.empty()) + { + for(unsigned int i=0; i<raiz->hijos.size(); i++) + { + if(raiz->hijos[i]!=NULL) + { + delete raiz->hijos[i]; + raiz->hijos[i]=NULL; + } + } + raiz->hijos.clear(); + }*/ + for (unsigned int i = 0; i < tree.raiz->hijos.size(); i++) { + raiz->hijos.push_back(new GraphicTree(*tree.raiz->hijos[i])); + } + } + } + return (*this); +} +``` + +## Programación orientada a Booleanos + +¿Excepciones? ¿Qué es eso? ¿Se come? Yo hago mis cosas en el método y si falla devuelvo `false`, y si todo va bien devuelvo `true`. + +```csharp +public Task<bool> AddUser(string userId) +{ + var success = true; + + try + { + await _provider.GetUserById(userId); + } + catch + { + success = false; + } + + return success; +} +``` + +## Programación orientada a Enteros + +Nada de excepciones, ni objetos de error ni nada. Nosotros usamos enteros y ya está. Los enteros los entiende todo el mundo. + +```csharp +public async Task<int> RegisterValidate(UserReciveViewModel user) +{ + if (!UserNameValidate(user.username)) + { + return USERNAME_ERROR; + } + + if (!EmailValidate(user.email)) + { + return EMAIL_ERROR; + } + + if (!PasswordValidate(user.password)) + { + return PASSWORD_ERROR; + } + + if (!ConfirmPasswordValidate(user.password, user.confirmPassword)) + { + return PASSWORD_COINCIDENCE_ERROR; + } + + if (!await UserNameRepositoryExisting(user.username)) + { + return USERNAME_REPEATED; + } + + if (! await EmailRepositoryExisting(user.email)) + { + return EMAIL_REPEATED; + } + + return CORRECT; +} +``` + + +## Los terroríficos Enums + +En noches de luna llena los objetos se transforman en Enums y pueden llegar a tener propiedades. Se cuentan historias de que hasta los han oído aullar. + +```csharp +[AttributeUsage(AttributeTargets.Field)] +public class FilterAttribute : Attribute +{ + public string Query { get; set; } + public string FieldToOrderBy { get; set; } + public int MaxResult { get; set; } +} + +public enum FilterType +{ + [Filter(Query = "SELECT * FROM User WHERE Username == {0}", FieldToOrderBy = "Username", MaxResult = 50)] + Username, + [Filter(Query = "SELECT * FROM User WHERE Category == {0}", FieldToOrderBy = "Category", MaxResult = 12)] + Category, + [Filter(Query = "SELECT * FROM User WHERE Country == {0}", FieldToOrderBy = "Username", MaxResult = 50)] + Country, +} +``` + +## Un dado... + +Lo creáis o no, este código es para hacer un dado. Yo no logro entenderlo... pero funciona. + +```js +loop = setInterval(function () { + var random = Math.floor(Math.random() * 5) +1; + lanzar.textContent = random; + contador++; + + if (contador == 6) { + clearInterval(loop); + + loop = setInterval(function () { + var random = Math.floor(Math.random() * 5) +1; + lanzar.textContent = random; + contador++; + + if (contador == 12) { + clearInterval(loop); + contador = 0; + valueDados = random; + + } + + }, 100); + } + +}, 50); +``` + +## Es tan absurdo... + +...cuando haces una comprobación que devuelve `true` o `false` para acabar devolviendo `true` o `false`... 🙃 + +```csharp +public bool IsAdult(int age) +{ + if(age >= 18) + { + return true; + } + else + { + return false; + } +} +``` + +¿Quién no ha intentado hacer un *parser* de JSON porque el "False" con mayúsculas no sabía deserializarlo? + +```js +function parseBool(bool) { + if(bool === "False") { + return false; + } + return true; +} +``` +**Importante:** Podríamos calificar este código de *panojita fina*. + +## ¡Usemos nuestros propios formatos! + +No nos gusta usar ni JSON ni XML, lo que nos gusta es usar S#: Serialización con almohadillas. + +```csharp +public void SendMessage(Message message) +{ + var userSend = SocketCollection.Where(p => p.Id = message.Id).SingleOrDefault(); + + if(userSend != null) + { + string send = message.Type + "#" + message.GameId + "#" + message.RivalName + "#" + message.Score + "#" + message.Position; + userSend.Send(send); + } +} +``` + +Es importante que el nombre del rival no contenga almohadillas... 😂 + +```js +function deserialize(json) { + var fields = json.split("#"); + + return { + Type: fields[0], + GameId: fields[1], + RivalName: fields[2], + Score: fields[3], + Position: fields[4] + }; +} +``` + +¿Y quién no ha almacenado los datos de las aplicaciones en un fichero de texto local serializado con S#? + +**Nota:** De nuevo, pedimos disculpas. + +## No siempre fue fácil cerrar la aplicación + +En versiones antiguas de Windows Phone no existía una API que te permitiera cerrar la aplicación... Así que teníamos que ser más... *imaginativos*. + +```csharp +public static void Exit() +{ + var zero = 0; + var throwException = 1 / zero; +} +``` + +## ¿El algoritmo mas ineficiente del mundo? + +**Nota:** Esto es una recreación de un código que hice en la universidad. + +La idea era hacer un algoritmo que dado un número par me diera dos números primos que sumados dan como resultado ese número par. + +Y este fue el resultado. Obviamente tardaba mucho tiempo en ejecutarse el algoritmo. + +```csharp +public static void GetSumFactor(int num) +{ + for (int i = 2; i < num; i++) + { + if(IsPrimo(i)) + { + for (int j = 2; j < num; j++) + { + if(IsPrimo(j)) + { + if(j + i == num) + { + Console.WriteLine($"{num}: {i} + {j}"); + return; + } + } + } + } + } +} + +public static bool IsPrimo(int num) +{ + var isPrimo = true; + + for (int i = 2; i < num-1; i++) + { + if(num % i == 0) + { + isPrimo = false; + break; + } + } + return isPrimo; +} +``` +Hay muchos y grandes fallos. Más allá de las mejoras que se puedan hacer con conocimientos matemáticos avanzados sobre los número primos hay fallos de concepto muy importantes. + +1. **El cálculo de si es primo o no es muy mejorable.** Sin conocer las reglas de la búsqueda de primos, lo mínimo sería que recorriéramos en el for hasta num / 2. +1. Si os fijáis en el algoritmo, **cuando encontramos un número primo, vuelvo a iterar buscando primos y cuando encuentro uno compruebo si la suma es igual al numero par introducido**. Simplemente bastaría con comprobar si es primo el número menos el primo que ya he encontrado... **¡un despropósito!** +1. Al igual que en la búsqueda de primos, no es necesario iterar hasta num en el for. +1. Y ya simplemente por afinar... **en ningún momento compruebo que el número introducido sea par...** + +**Nota:** Sentimos cualquier daño que este código haya podido causar. + +## Bonus Track: La gente de JavaScript se lo monta a lo grande + +Un ejemplo para ordenar números de una forma muy creativa. Visto en [Twitter](https://twitter.com/JavaScriptDaily/status/856267407106682880). + +```js +numbers = [8, 42, 38, 111, 2 39, 1]; + +numbers.forEach(num => +{ + setTimeout(() => { console.log(num) }, num); +}); +``` + +## Buenas noches + +Desde *No Country for Geeks* esperamos que ninguno de los ejemplos anteriores os provoquen pesadillas... + +Estos son solo algunos ejemplos de los Scary Codes ¿Qué otros ejemplos de códigos terroríficos se os ocurren? ¡Dejadlo en comentarios! + +Un saludo y... Scary coding! diff --git a/content/blogs/2017-12-07-visual-studio-team-services-desde-terminal/es.md b/content/blogs/2017-12-07-visual-studio-team-services-desde-terminal/es.md new file mode 100644 index 0000000..8266a86 --- /dev/null +++ b/content/blogs/2017-12-07-visual-studio-team-services-desde-terminal/es.md @@ -0,0 +1,186 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/header.jpg +navigation: True +title: "Visual Studio Team Services desde Terminal" +date: 2017-12-07 12:00:00 +tags: visualstudio git tools +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Los informáticos somos vagos por naturaleza, de hecho, la informática nació con el propósito de ordenar la información y automatizar las tareas de ordenación y tratamiento de los datos y la información que se obtiene de ellos. Ante este hecho hay un principio claro: + +> Si tienes que hacer la misma tarea más de una vez, automatízala. + +Si a esto le unimos el hecho de que odio tener muchas herramientas abiertas, que a su vez hacen muchas cosas diferentes, y que quizás no tendrían que hacer por sí mismas, entramos en un estado de locura que causa un caos en el escritorio del desarrollador. + +![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/caos.jpg) + +Vamos a intentar recortar la cantidad de herramientas abiertas para que sea más sencillo y productivo avanzar en nuestro proyecto con una serie de post. + +> Available in English [here](https://www.danielmartingonzalez.com/visual-studio-team-services-from-console/). + +## Microsoft Visual Studio Team Services CLI + +En el equipo de **No Country For Geeks** usamos *Git* y publicamos código a través de *Pull Request* en *Visual Studio Team Services*. No vamos a entrar en porque las ***Pull Request* son una de las mejores formas de hacer avanzar al equipo en calidad de código, compromiso con los proyectos y conocimiento compartido:** está de sobra demostrado. + +Para ver el listado de las *Pull Request* en las que trabaja el equipo en los diferentes proyectos basta con acceder a la sección ``https://{account}.visualStudio.com/_pulls``. El problema con esta página es que no posee notificaciones, por tanto, pese a ser una buena práctica acceder a la página nada más empezar la jornada laboral y revisar las *Pull Request* pendientes de tu equipo, es cierto que en una jornada de 8 horas pueden surgir una gran cantidad de *Pull Request* y al final acabas teniendo siempre la página abierta. + +Lo que hacemos hasta el momento es tener notificaciones con algunas alertas a través de correo electrónico, que es actualmente lo que permite *Microsoft* en su herramienta. Sin embargo, que os podemos contar de los correos electrónicos, en la mayoría de casos tenemos una regla de que todos los avisos que suceden en *VSTS (Visual Studio Team Services)* se pasen a una carpeta silenciada que se revisa cada varios días porque no son avisos tan importantes como para tenerlos en cuenta. + +Como he dicho usamos *Git*, y además como somos puristas solemos usar la consola para realizar comandos sobre el repositorio. ¿Y si no necesitásemos tener abierto el correo electrónico, ni el navegador web para realizar la consulta de las *Pull Request* pendientes del equipo? Entra en juego [Microsoft Visual Studio Team Services CLI](https://github.com/Microsoft/vsts-cli). + +> **[VSTS CLI](https://docs.microsoft.com/en-us/cli/vsts/overview)** is a new command line interface for *Visual Studio Team Services (VSTS)* and *Team Foundation Server (TFS) 2017 Update 2* and later. + +En qué se traduce esto. Pues a grandes rasgos es una interfaz para nuestra línea de comandos que agrega funcionalidades extra para trabajar con *Visual Studio Team Services* y *Team Foundation Server*. + +### Instalación + +**VSTS CLI** tiene soporte **Cross-Platform** para *Windows, Linux, MacOS* e imagen *Docker*, y para instalarlo solo hemos de seguir este [**link**](https://docs.microsoft.com/es-es/cli/vsts/install?view=vsts-cli-latest). En *Windows* la instalación es tan sencilla como descargar y ejecutar un instalador. + +Una vez instalado, podemos abrir nuestra consola favorita y ejecutar los siguientes comandos. Si recibimos un listado de comandos disponibles la instalación ha sido exitosa: + +```Shell +vsts -h +vsts code -h +``` + +![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/help.jpg) + +### Iniciando sesión + +Para poder acceder a la información contenida en la instancia de *Visual Studio Team Services* en la que solemos trabajar necesitamos iniciar sesión, para ello ejecutamos el siguiente comando: + +```Shell +vsts login --instance https://MYACCOUNT.visualstudio.com --token MYTOKEN +``` + +Para obtener el *token* necesario, basta con acceder a la sección seguridad de nuestra cuenta en la instancia de **VSTS** y solicitar un **Personal Access Token**. Más información [aquí](https://docs.microsoft.com/es-es/vsts/accounts/use-personal-access-tokens-to-authenticate). + +Si queremos que esa instancia sea la usada por defecto, solo tenemos que usar el siguiente comando: + +```Shell +vsts configure --defaults instance=http:/MYACCOUNT.visualstudio.com +``` + +### Parámetros globales + +Antes de entrar en materia y describir algunos de los comandos más interesantes, conviene detallar algunos parámetros comunes a la mayoría de comandos que vendrán genial para obtener el mejor rendimiento a la herramienta. + +``--debug`` +Aumenta el nivel de detalle de la respuesta con datos de depuración. + +``--help -h`` +Como ya es común en los comandos de consola, muestra ayuda acerca del comando que se está ejecutando + +``--output -o`` +Detalla el formato de salida del comando. Se contempla: + +- ``json``: Texto en *json*. Por defecto. + +- ``jsonc``: Texto en *json* con colores (puede no funcionar en algunos terminales). + +- ``table``: Formato tabla con columnas para una lectura más sencilla. + +- ``tsv``: Formato separado por tabulador. + +Puedes cambiar la salida por defecto ejecutando ``vsts configure --default-output [json, jsonc, table, tsv]`` + +``--query`` +Consulta *JMESPath*. En [http://jmespath.org/](http://jmespath.org/) tienes más información y ejemplos. + +``--verbose`` +Incrementa el nivel de detalle. Pero sin datos de depuración, usa ``--debug`` para si es lo que necesitas. + +### Todo listo + +Ya tenemos todo listo para realizar consultas y obtener la información que nos interesa, para ello disponemos de la sección [**Reference** de la documentación](https://docs.microsoft.com/en-us/cli/vsts/get-started?view=vsts-cli-latest). + +![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/references.jpg) + +Algunos ejemplos: + +- Ver todas las *Pull Request* de un proyecto (de manera amigable): + + ```Shell + vsts code pr list -p PROYECTO -o table + ``` + + ![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/example1.jpg) + +- Abrir una *Pull Request* en el navegador: + + ```Shell + vsts code pr show --id 5661 --open + ``` + +- Aprobar una *Pull Request*: + + ```Shell + vsts code pr set-vote --id 5661 --vote approve -o table + ``` + + ![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/example3.jpg) + +- Ver un listado de todos los repositorios de un proyecto: + + ```Shell + vsts code repo list -p PROYECTO -o table + ``` + + ![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/example4.jpg) + +- Ver un listado de todos los proyectos del equipo: + + ```Shell + vsts project list -o table + ``` + + ![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/example5.jpg) + +- Crear un *Work Item* en un proyecto: + + ```Shell + vsts work item create --title "Login has a bug." --type "Bug" + ``` + +Y esto son solo unos pocos ejemplos. + +Como vemos podemos de manera sencilla movernos por *Visual Studio Team Services* sin necesidad de acceder al navegador, ni de tener otras ventanas abiertas. Como con todo, hace falta práctica para ir cogiendo todos los comandos disponibles y agilidad en su uso, pero una vez conseguido es bastante sencillo y se pueden hacer la mayoría de cosas que se hacen en el día a día sin tener que abrir otras herramientas. + +## One more thing + +Os dejo un par de anotaciones que pueden todavía mejorar más nuestros desarrollos. + +### Usar VSTS CLI con GIT + +Dado que en muchos casos lo que ocurre es que simplemente queremos unificar *Git* con *VSTS* realizando una unión a través de las *Pull Request*, podemos agregar alias a *Git* para realizar esta gestión de manera sencilla y rápida. Para ello, solo debemos ejecutar la siguiente línea: + +```Shell +vsts configure --use-git-aliases yes +``` + +Con ello, hacemos que podamos ejecutar el comando ``git pr`` y que se traduzca en ``vsts code pr`` con todas sus variantes como, por ejemplo: + +```Shell +vsts code pr create --target-branch {branch_name} +``` + +puede escribirse como: + +```Shell +git pr create --target-branch {branch_name} +``` + +### Usando la consola desde Visual Studio + +Si además de todo lo hecho, queremos quitarnos una ventana más. Podemos incluir una consola con todos estos comandos en nuestro *IDE* de desarrollo favorito *Visual Studio*. Para ello, basta con instalar desde el instalador [PowerShell Tools for Visual Studio](https://marketplace.visualstudio.com/items?itemName=AdamRDriscoll.PowerShellToolsforVisualStudio2017-18561). + +A continuación, abrimos Visual Studio y podremos desde el menú *View -> Other Windows -> PowerShell Interactive Window*. + +Desde esta ventana podremos ejecutar cualquier comando de `git` o `vsts`. + +![](/assets/images/posts/2017-12-07-visual-studio-team-services-desde-terminal/powershellinteractivewindow.jpg) diff --git a/content/blogs/2017-12-14-estudiemos-japones-juntos/es.md b/content/blogs/2017-12-14-estudiemos-japones-juntos/es.md new file mode 100644 index 0000000..4c2bd15 --- /dev/null +++ b/content/blogs/2017-12-14-estudiemos-japones-juntos/es.md @@ -0,0 +1,113 @@ +--- +layout: post +current: post +cover: assets/images/posts/2017-12-14-estudiemos-japones-juntos/header.jpg +navigation: True +title: "Estudiemos japonés juntos" +date: 2017-12-14 12:00:00 +tags: nihongo +class: post-template +subclass: "post" +author: franmolmedo +--- + +Al habla un estudiante frustrado de japonés. Y no porque no haya tenido constancia o porque nunca haya pasado del [Hiragana](https://es.wikipedia.org/wiki/Hiragana]) y [Katakana](https://es.wikipedia.org/wiki/Katakana); sino más bien por el hecho de haber alcanzado cierto nivel y haberlo perdido con los años. Sobre todo, el tema que más me duele son los [_Kanjis_](https://es.wikipedia.org/wiki/Kanji). De estudiarlos con ilusión, tratar de comprender el origen, repasarlos una y mil veces trazo a trazo a olvidarlos: básicamente debido a no usarlos en mi día a día. + +Me encuentro en ese punto en el que o retomo el estudio dirigiéndome hacia un objetivo claro (en este caso superar el nivel **JLPT-2**) o dejo definitivamente el idioma de lado. + +Y, de repente, me surge la inspiración. ¿Por qué no retomar los Kanjis desde el principio, compaginando el estudio con el desarrollo de una app que nos permita repasarlos, tenerlos estructurados por niveles y revisar vocabulario? + +## Idea + +En mi opinión, la única forma de estudiar _kanjis_ y que el esfuerzo merezca la pena, consiste en la repetición. Por experiencia puedo decir que no sirve de nada el tratar de memorizar el ideograma, luego las lecturas y, posteriormente, varias palabras de voculabulario que lo usen si todo queda ahí. En estos casos, es necesario volver a revisar el _kanji_ cada cierto tiempo, ver que lo has afianzado, refrescar vocabulario y, muy importante y que a veces es dejado de lado, ver ejemplos de frases donde se haga uso del _kanji_ y poder identificar su significado por el contexto. + +Todo lo anterior se refiere sólamente a la capacidad de reconecer un _kanji_, o una palabra que lo use, dentro de un texto. El hecho de poder escribirlo correctamente requiere un esfuerzo netamente superior y ahí no hay más que hacerlo una y otra vez repetidamente en el tiempo. Sin embargo, ésto no se requiere en los exámenes de certificación de idioma; por lo que, nosotros nos centraremos únicamente en tratar de reconocerlos. + +¿Y qué vamos a hacer? + +- Generar una base de conocimiento de kanji basándonos en varias fuentes (libros kanken, kanji in context, kanjidic, basic kanji book, intermediate kanji book, kanjivg, nihongo-pro...) +- Llevar a cabo un API que exponga todo este conocimiento a aplicaciones externas. +- Crear una aplicación que nos permita estudiar de la forma que hemos comentado: + - Que nos vaya proponiendo _kanjis_ para que podamos identificarlos, haciéndolo según nuestro nivel y según el grado de experiencia con dicho _kanji_ que el usuario tenga. Por ejemplo, un día nos propone un kanji básico, el cual ya tenemos muy afianzado. Es importante que podamos comunicarle a la aplicación este hecho para que no nos vuelva a proponer este kanji hasta pasado un tiempo. + - Que nos permita filtrar los kanjis, vocabulario y ejemplos de frases por nuestro nivel de idioma para poder estudiarlos. + +¿Por qué hacerlo todo desde cero y no utilizar aplicaciones / base de datos existentes? + +- La razón fundamental es que el realizar todo el proceso favorece el aprendizaje. +- No he encontrado ninguna aplicación que ofrezca toda la funcionalidad que deseo. +- Montar toda la estructura de una aplicación, desde la Base de datos, el servicio web que nos va a permitir conectarnos como usuario autenticado y nos va a proporcionar la información de nuestros progresos y los _kanjis_ a estudiar; hasta un frontal web y una aplicación multiplataforma que nos permitan presentarle al usuario toda esta información, nos va a permitir ir desgranando el cómo desarrollar una aplicación end-to-end, con tecnología moderna y aplicando, dentro de nuestro conocimiento, las mejores prácticas posibles. + +## Objetivo + +Como se ha comentado previamente el objetivo es superar el JLPT nivel 2. **_¿Qué es el JLPT?_** El **_Japanese language proficiency test_** o (日本語能力試験) es un examen de certificación de nivel de idioma japonés. Actualmente existen 5 niveles diferentes a los que se puede optar, yendo en nivel ascendente de dificultad; es decir, el nivel 5 es el nivel más básico, mientras que el nivel 1 es el más avanzado. De entre todos los niveles, he decidido prepararme para el segundo porque es el nivel mínimo exigido para poder trabajar en Japón (hablamos de trabajos medianamente cualificados) y porque, sinceramente, creo que el el salto existente entre los niveles 2 y 1 es demasiado elevado y se me presenta como algo inalcanzable en la actualidad. + +En la página oficial del [JLPT](http://www.jlpt.jp/e/) podemos encontrar el siguiente cuadro donde se nos muestra la estructura del examen en cuestión: + +![jlpt-levels](/assets/images/posts/2017-12-14-estudiemos-japones-juntos/jlpt-levels.png) + +Así mismo, se nos presenta un otro cuadro informativo con cada una de estas secciones más detalladas + +![jlpt-exam](/assets/images/posts/2017-12-14-estudiemos-japones-juntos/jlpt-exam.png) + +Todo objetivo tiene que cumplir una serie de condicionantes para que realmente sea un objetivo y no una quimera. Uno de esos condicionantes es fijar una fecha límite para su cumplimiento. Actualmente se realizan dos convocatorias anuales del _JLPT_: una durante la primera semana de julio y otra durante la primera semana de diciembre. Nuestro objetivo es presentarnos al examen en **DICIEMBRE DE 2018**. + +#### Alcance + +En esta actividad nos vamos a centrar en preparar la parte dedicada al conocimiento del vocabulario y de los kanjis. Para el nivel 2 es necesario reconocer un total de **1140** **_kanji_**, que combinaremos para conseguir abarcar también el máximo número de palabras de vocabulario posible. Igualmente, con los ejemplos de frases y expresiones que iremos introduciendo avanzaremos un poco en el estudio de gramática; pero, lógicamente, será necesario un estudio más profundo posterior. + +#### Pasar el test + +Es interesante recopilar algunos consejos de personas que se han enfrentado a este reto y han conseguido superarlo. Quiero resaltar aquí algunos que nos daba el autor de la página [Nihongo pera pera](https://nihongoperapera.com): + +- Sin conocer en profundidad los Kanjis exigidos no hay ninguna posibilidad de aprobar. +- Sólo se exige un 60% de respuestas acertadas para superar el examen y no existe penalización por errores; así que, en caso de dudas marcar alguna de las opciones existentes (recordemos que es un examen tipo test). +- Tras el estudio de Kajis, trata de enfrentarte a lectura de cada vez más nivel. En ella podrás poner en práctica tu conocimiento y, a su vez, afianzar todo lo aprendido. + +## Material + +Contamos con una gran variedad de libros, diccionarios, videos online o páginas web donde podemos ir recopilando el material necesario para nuestro estudio. Sin embargo, yo he seleccionado, como recurso principal, una serie de libros que constituyen el _"manual oficial"_ para presentarse a un examen conocido como **Kanken** (漢検). Este examen constituiría, por denominarlo de algún modo, como el examen oficial de aptitud en _Kanji_. Si bien difiere respecto al **JLPT** ya que tiene un sentido menos académico y se utiliza (incluso por propios japoneses) como un sistema para probarte a tí mismo tu conomiento de _kanji_. + +Estos libros recorren el aprendizaje de _Kanji_ que realiza un niño japonés desde el primer curso de primaria en adelante. El primer libro, comienza presentando los silabarios _Hiragana_ y _Katakana_ para pasar, posteriormente a introducir los primeros 80 _Kanji_ (Estos son los que un niño de 6 años estudiaría durante su primer curso en la escuela). + +En total la colección son de 10 libros y un recopilatorio; pero, nosotros, utilizaremos los nueve primeros, ya que con ellos abarcaremos la totalidad de _Kanji_ que se exigen para el **JLPT 2**. + +![Kanken](/assets/images/posts/2017-12-14-estudiemos-japones-juntos/kanken.jpg) + +![Kanken-inside](/assets/images/posts/2017-12-14-estudiemos-japones-juntos/kanken-inside.jpg) + +Así mismo, la colección tiene un cuadernillo de trabajo para cada uno de los tomos, que nos proporcionará ejemplos de palabras y expresiones para asociar con los _Kanji_ que vayamos aprendiendo: + +![Workbook](/assets/images/posts/2017-12-14-estudiemos-japones-juntos/workbook.jpg) + +![workbook-inside](/assets/images/posts/2017-12-14-estudiemos-japones-juntos/workbook-inside.jpg) + +Igualmente, nos servirán de apoyo otros libros y herramientas como: + +- [Kanji in context](http://bookclub.japantimes.co.jp/en/title/KANJI%20IN%20CONTEXT%20%5BRevised%20Edition%5D) +- [Basic kanji book](https://www.amazon.es/Basic-Kanji-Book-v-1/dp/4893580914) +- [Intermediate kanji book](https://www.amazon.es/Intermediate-Kanji-Book-VOL-1-Rev/dp/4893588109/ref=sr_1_1?ie=UTF8&qid=1513208868&sr=8-1&keywords=intermediate+kanji+book) +- [KanjiDIC](http://www.edrdg.org/kanjidic/kanjidic.html) +- [Nihongo pro](https://www.nihongo-pro.com/kanji-pal/list/jlpt): Esta página es muy útil e interesante ya que nos proporciona información de los _Kanji_ de cada nivel de **JLPT** y una relación de palabras y frases asociados a cada uno. Al entrar en el detalle de cada _Kanji_, podemos ver el orden de trazos, la composición de radicales y algunos detalles más que tendremos ue tener en cuenta en nuestra aplicación. + +## La aplicación + +Y por último, y no por ello menos importante, tenemos que hablar un poco de qué tecnologías vamos a usar para desarrollar la aplicación que nos ayude en nuestro estudio. +Tendremos lo siguiente: + +- Parte de servidor: + - **Mongo DB** como base de datos. + - **Node.js** con **express** y **GraphQL** como servidor. +- En cliente: + - Una aplicación web _SPA_(Single page appliation) utilizando **React** como librería de Interfaces de Usuario y **Redux** para manejo del estado de la aplicación. Gracias a utilizar **GraphQL** en servidor, emplearemos en nuestro cliente **ApolloClient** para hacer las peticiones de datos a servidor, ver cómo añadir nuevos datos desde cliente, y como manejar elementos tan interesantes como el cacheo de peticiones. + - Aplicaciones nativas de _iOS_ y _Android_ utilizando **React Native**, lo que nos va a permitir, reutilizar buena parte del código que hayamos realizado para la aplicación web, manteniendo intacto el mismo stack (react, redux, apollo client). + +### Trabajo futuro + +Y hasta aquí esta invitación para acompañarme en mi pequeña aventura. La idea es ir elaborando poco a poco toda la estructura de la aplicación, a la vez que vayamos introduciendo el contenido de esos **1140 Kanjis** +que tenemos por delante. + +En la siguiente entrada, empezaremos a plantear nuestra capa de datos, como vamos a realizar los esquemas de nuestros documentos y qué información vamos a ir almacenando de cada uno de los Kanji. + +A todos aquellos interesados en aprender un nuevo idioma, o que se estén preparando para el examen o si lo tuyo es la programación y te gustaría colaborar, no dudéis en poneros en contacto con nosotros o decirlo en los comentarios. Incluso, si alguien se anima, podemos formar un grupo de estudio. + +Espero que os resulte atractiva la idea. diff --git a/content/blogs/2018-01-09-atributos-en-c/es.md b/content/blogs/2018-01-09-atributos-en-c/es.md new file mode 100644 index 0000000..1bb8307 --- /dev/null +++ b/content/blogs/2018-01-09-atributos-en-c/es.md @@ -0,0 +1,292 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-01-09-atributos-en-c/header.jpg +navigation: True +title: "Atributos en C#" +date: 2018-01-09 12:00:00 +tags: csharp +class: post-template +subclass: 'post' +author: maktub82 +--- + +Muchas veces hemos utilizado atributos en C# para miembros o clases. En este post vamos a ver cómo crear nuestros propios atributos que nos van a permitir añadir información extra a algunos elementos. + +Vamos a trabajar con atributos para enriquecer un poco a los enumerados, que muchas veces se nos quedan un poco cortos. + +## Algunos ejemplos + +Hay una gran cantidad de ejemplos de atributos que hemos podido utilizar alguna vez: Newtonsoft Json, Entity Framework, xUnit, etc. + +```csharp +// Newtonsoft Json +[JsonIgnore] +public string InternalId {get;set;} + +[JsonProperty(“display_name”)] +public string DisplayName {get; set;} + +// Entity Framework +[Key] +public int Id {get;set;} + +[MaxLength(10),MinLength(5)] +public string BloggerName { get; set; } + +// xUnit +[Fact] +public void WhenTest_ItShouldBeReturnTrue(){} + +[Fact(Skip=”Skip because yes”)] +public void WhenTest_ItShouldBeReturnTrue(){} +``` + +## Antes de empezar… + +Para crear un atributo debemos crear una clase que herede de `Attribute`. Además podemos especificar algunas propiedades utilizando el atributo `AttributeUsage`. + +Con este atributo podemos indicar: + +* **ValidOn:** targets válidos para el atributo que vamos a crear. [Aquí](https://msdn.microsoft.com/en-us/library/system.attributetargets.aspx) podemos consultar los posibles valores. +* **AllowMultiple:** nos indica si el atributo se puede especificar más de una vez para un mismo elemento. Por defecto `false`. +* **Inherited:** nos indica si el atributo lo pueden heredar las clases derivadas. Por defecto `false`. + +Antes de seguir debemos hacer una diferenciación entre parámetros *posicionales* y parámetros con nombre. + +**Los parámetros *posicionales* son obligatorios y los especificaremos en el constructor** del atributo mientras que **los parámetros con nombre, que son opcionales, los especificaremos como propiedades** y deberemos nombrarlos al usar el atributo. + +**Nota:** Los parámetros de los atributos deben ser un valor constante de un tipo simple (`string`, `enum`, `Type`...). + +[Aquí](https://msdn.microsoft.com/es-es/library/z0w1kczw%28VS.80%29.aspx) puedes leer la documentación de los atributos. + +## Creando nuestro primer atributo: Display Name + +Uno de los problemas habituales con los enumerados es cuando los quieres mostrar por pantalla para, por ejemplo, que el usuario pueda filtrar los datos por ese valor del enumerado. + +Probablemente todos hayamos hecho algo como un switch para mostrar un texto u otro. Pues bien, esto lo podemos resolver fácilmente con un atributo. + +En este ejemplo vamos a tener un solo parámetro *posicional* llamado `DisplayName` que será el texto a mostrar. + +Para crear nuestro atributo `DisplayName` **debemos crear una clase que herede de `Attribute`**. Como vamos a añadirle este atributo a cada valor de enumerado le indicaremos que el **target es Field**. + +```csharp +[AttributeUsage(AttributeTargets.Field)] +public class DisplayNameAttribute : Attribute +{ + public readonly string DisplayName; + + public DisplayNameAttribute(string displayName) + { + DisplayName = displayName; + } +} +``` + +```csharp +public enum Fruit +{ + [DisplayName("Lemon")] + Lemon, + [DisplayName("Watermelon")] + Watermelon, + [DisplayName("Orange")] + Orange, + [DisplayName("Blood Orange")] + BloodOrange, + [DisplayName("Kiwi")] + Kiwi, + [DisplayName("Banana")] + Banana +} +``` + +Código de ejemplo [GitHub](https://github.com/maktub82/Samples/blob/master/Attributes/Maktub82.Samples.Attributes/Attributes/DisplayNameAttribute.cs). + +## Consultando atributos gracias a la reflexión + +Ahora que hemos enriquecido nuestro enumerado necesitamos tener una forma de poder consultar la nueva información. Para ello vamos a utilizar reflexión. + +Con el siguiente método **obtenemos todos los atributos de un determinado tipo del enumerado** pasado por parámetro. + +```csharp +private static IEnumerable<T> GetAttributes<T>(Enum enumValue) where T : Attribute +{ + // Obtenemos el tipo + var type = enumValue.GetType(); + // La información del valor concreto del enumerado + var memberInfo = type.GetMember(enumValue.ToString()); + // Obtenemos todos los atributos del miembro + var attributes = memberInfo[0].GetCustomAttributes(typeof(T), false); + + return attributes.Cast<T>(); +} + +private static T GetFirstOrDefaultAttribute<T>(Enum enumValue) where T : Attribute +{ + var attributes = GetAttributes<T>(enumValue); + return attributes.FirstOrDefault() as T; +} +``` + +Para que sea más sencillo e intuitivo de utilizar vamos a crear un método de extensión que nos devuelva el display name. Simplemente una vez tenemos el atributo obtenemos el miembro público `DisplayName`. + +```csharp +public static string GetDisplayName(this Enum enumValue) +{ + var attribute = GetFirstOrDefaultAttribute<DisplayNameAttribute>(enumValue); + return attribute != null ? attribute.DisplayName : string.Empty; +} +``` + +Ahora podemos utilizar el método `GetDisplayName` para obtener el `DisplayName` de un enumerado. + +```csharp +Fruit.BloodOrange.GetDisplayName(); +Fruit.Watermelon.GetDisplayName(); +Fruit.Lemon.GetDisplayName(); +``` + +Código de ejemplo [GitHub](https://github.com/maktub82/Samples/blob/master/Attributes/Maktub82.Samples.Attributes/Extensions/EnumExtensions.cs). + +## Parámetros nombrados + +En este ejemplo vemos cómo definir atributos con nombre y cómo especificarlos a la hora de usarlo. + +```csharp +[AttributeUsage(AttributeTargets.Field)] +public class CenturyDataAttribute : Attribute +{ + private int startYear; + + public int StartYear + { + get { return startYear; } + set { startYear = value; } + } + + private int endYear; + + public int EndYear + { + get { return endYear; } + set { endYear = value; } + } + + public readonly string DisplayName; + + public CenturyDataAttribute(string displayName) + { + DisplayName = displayName; + } +} +``` + +Como vemos, basta con añadir una propiedad en el atributo para definir un parámetro con nombre. + +A la hora de usarlo simplemente nombramos los parámetros y le asignamos un valor después de los parámetros *posicionales*. + +```csharp +public enum Century +{ + [CenturyData("15th", StartYear = 1401, EndYear = 1500)] + XV, + [CenturyData("16th", StartYear = 1501, EndYear = 1600)] + XVI, + [CenturyData("17th", StartYear = 1601, EndYear = 1700)] + XVII, + [CenturyData("18th", StartYear = 1701, EndYear = 1800)] + XVIII, + [CenturyData("19th", StartYear = 1801, EndYear = 1900)] + XIX, + [CenturyData("20th", StartYear = 1901, EndYear = 2000)] + XX +} +``` +**Importante:** Este **código es un ejemplo** para mostrar los parámetros con nombre en un atributo. **Hay que tener cuidado a la hora de desarrollar y saber cuándo es necesario o no el uso de los atributos.** + +Código de ejemplo [GitHub](https://github.com/maktub82/Samples/blob/master/Attributes/Maktub82.Samples.Attributes/Attributes/CenturyDataAttribute.cs). + +## Múltiples atributos + +En este caso queremos mostrar por pantalla unas categorías para el filtrado de datos. Una vez el usuario seleccione una categoría se debe hacer una llamada a una API. + +Surgen dos problemas: por un lado por pantalla se debe ver un texto amigable para cada categoría y por otro lado necesitamos saber el valor de cada categoría en el API (que además es un valor múltiple). + +Para poder mostrar un texto amigable por pantalla para cada categoría basta con añadir el atributo `DisplayName` que hemos creado antes. Y para obtener el valor que tiene en el API crearemos otro atributo llamado `ApiValue`. En este caso **el atributo `ApiValue` se podrá asignar varias veces a un elemento**. + +```csharp +[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] +public class ApiValueAttribute : Attribute +{ + public readonly string ApiValue; + + public ApiValueAttribute(string apiValue) + { + ApiValue = apiValue; + } +} +``` + +Ahora simplemente en nuestro enumerado ya podemos especificar el nombre a mostrar y los diferentes valores que tienen las categorías en el API. + +```csharp +public enum Category +{ + [DisplayName("Series")] + [ApiValue("series")] + [ApiValue("tv-series")] + [ApiValue("tv-vod")] + Series, + [DisplayName("Films and Movies")] + [ApiValue("movies")] + [ApiValue("films")] + Films, + [DisplayName("Documentary")] + [ApiValue("tv-documentary")] + Documentary +} +``` + +Al igual que antes obtenemos la información del enumerado gracias a su atributo. + +```csharp +public static string GenerateQuery(this Enum enumValue) +{ + var attributes = GetAttributes<ApiValueAttribute>(enumValue); + IEnumerable<string> values = attributes.Select(attribute => attribute.ApiValue); + + return $"{string.Join(",", values)}"; +} +``` + +Aquí podemos ver un ejemplo de ejecución: + +```csharp +$"To Search by {Category.Films.GetDisplayName()} category: +http://example.api.fake?query=the&category={Category.Films.GenerateQuery()" + +// Result +To Search by Films and Movies category: http://example.api.fake?query=the&category=movies,films + +``` + +Código de ejemplo [GitHub](https://github.com/maktub82/Samples/blob/master/Attributes/Maktub82.Samples.Attributes/Attributes/ApiValueAttribute.cs) + +## Conclusiones + +Como hemos visto, los atributos son muy potentes para añadir información a elementos. En nuestro caso lo hemos visto para trabajar con enumerados pero como hemos visto en los ejemplos del principio en C# se pueden utilizar para muchos usos. + +Por otro lado **no debemos abusar de su uso y siempre debemos plantearnos si tienen sentido o no**. Como con todo, hay que tener cuidado al usarlos ya que **tenemos que tener en cuenta el abuso de la reflexión, además del abuso decoradores que dificultan la legibilidad del código**. + +La otra cosa a tener en cuenta es que **los parámetros de los atributos solo pueden ser valores constantes y en muchos casos nos puede limitar**. + +## Referencia + +* [Código de ejemplo](https://github.com/maktub82/Samples/tree/master/Attributes) +* [AttributeTargets Enumeration]( +https://msdn.microsoft.com/en-us/library/system.attributetargets.aspx) + +Free Vector Graphics by [vecteezy.com](https://www.vecteezy.com/vector-art/139604-pen-holder-desk-vector) + +Un saludo y... ¡Nos vemos en el futuro! \ No newline at end of file diff --git a/content/blogs/2018-01-22-tips-tricks-diseno-para-desarrolladores/es.md b/content/blogs/2018-01-22-tips-tricks-diseno-para-desarrolladores/es.md new file mode 100644 index 0000000..88dbb9b --- /dev/null +++ b/content/blogs/2018-01-22-tips-tricks-diseno-para-desarrolladores/es.md @@ -0,0 +1,118 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/header.jpg +navigation: True +title: "Tips & Tricks - Diseño para desarrolladores - El Color" +date: 2018-01-22 12:00:00 +tags: design +class: post-template +subclass: 'post' +author: elenaps +--- + + +Mis compañeros de **No Country for Geeks** llevan tiempo pidiéndome que escriba tips and tricks para desarrolladores que desean diseñar sus propias cabeceras de sus blogs personales. + +El requisito básico es tener la predisposición de aprender algo que no parece muy extraño (¿quién no ha combinado una camiseta con un pantalón o dibujado y coloreado algo alguna vez?) pero que tiene su intríngulis si se quiere hacer bien. + +Espero que con los siguientes tips puedas dar algún paso más en este mundillo, y como no podría ser de otra manera, lo primero que hay que hacer es investigar y curiosear. + +## INVESTIGA Y CURIOSEA +Vamos a ver cuál es la mejor forma de transmitir tu mensaje a través de un elemento ilustrativo. + +Escribe en una hoja palabras que creas que pueden representar tu post, tienes que conseguir abstraer el concepto principal preguntándote: + +¿Cómo puedes invitar a alguien a que lea tu post únicamente con una imagen?, ¿qué quieres destacar?, ¿qué elemento podría representarlo?, ¿quién va a leerlo? o ¿cómo lo hacen en otros blogs o páginas tecnológicas que sueles usar de referencia? + +Una vez superada la fase *Sherlock Holmes* y el concepto esté claro, es el momento de **curiosear**. + +Una forma de nutrirse visualmente es curioseando, tienes que ver qué hay hecho, cuál es la tendencia más destacable o qué colores suelen prevalecer. + +Tenía un profesor que nos repetía todo el rato *“Para aprender hay que ver”*, es decir, hay que ver qué hay en el mercado, qué hubo e intentar adivinar lo que habrá. Conocer qué han hecho otros, tanto grandes, como no tan grandes; tanto genialidades como despropósitos; en definitiva, tener una visión global de lo que se hace, y a partir de ahí se podrá dar rienda suelta a la creatividad. + +Como podéis ver, es muy similar al sector del desarrollo de software: un buen desarrollador necesita actualizarse para no perder valor profesional. El diseñador necesita educar sus ojos, y como he dicho antes, mirando se aprende. + +Actualmente existen diversas fuentes de inspiración por internet. A continuación te propongo algunas que pueden gustarte: +- [Pinterest](https://www.pinterest.es) +- [Designspiration](https://www.designspiration.net) +- [Behance](https://www.behance.net) +- [Instagram](https://www.instagram.com) +- [Mr-cup](http://www.mr-cup.com/blog.html) + +Este tipo de páginas te ayudarán a decidirte por, por ejemplo, una **tipografía** que te ha gustado, o de repente se te puede ocurrir una **composición** solo con ver cómo están distribuidos los elementos de una imagen. + +Ahora bien, mucho cuidado con el copieteo, de lo que se trata es de inspirarte, no de *fusilar* un diseño. Lo divertido precisamente es la fase de creación y lo satisfactorio que es comprobar cómo una composición queda perfectamente compensada sólo con mover un elemento un par de píxeles hacia un lateral. + +Así que este será el primer tip que voy a darte, cómo sacar provecho de referencias visuales que te gusten, y vamos a hacerlo con **El Color** como tema principal. + +## Primer Tip: El Color +Podría explicarte qué son los **[colores primarios](https://es.wikipedia.org/wiki/Color_primario )**, qué es una **[armonía cromática](https://es.wikipedia.org/wiki/Armon%C3%ADa_crom%C3%A1tica)**, o por qué se dice que el negro es la **[ausencia de luz](https://es.wikipedia.org/wiki/Negro_(color))**, pero creo que sobre el **[color](https://es.wikipedia.org/wiki/Color)** hay mucha documentación que podréis encontrar en internet, y el objetivo principal de este post es que el desarrollador pueda diseñar su cabecera de una manera fácil y rápida. Luego ya depende de cada uno si quiere profundizar más o menos. + +Así que vamos a crearnos una cabecera utilizando el siguiente tip: + +### La elección del color +Un buen tip para decidirte por una **gama de colores** de manera rápida sería sacando los colores que componen una imagen que te guste. + +#### ¿Qué vamos a necesitar? +#### RECURSOS + +- Una imagen. + +Puedes seguir las instrucciones con una imagen tuya o también puedes descargarte alguna imagen de un banco de imágenes. + +Por ejemplo, **[Pixabay](https://pixabay.com/)**, que es un banco de imágenes libres de derechos de autor, donde encontrarás gran variedad de imágenes por temas. + + +#### SOFTWARE + +- Un editor de gráficos vectoriales. Por ejemplo, ***Illustrator***, *Inkscape* o *CorelDraw*. + +Hay diversos programas que podrás utilizar para tus diseños. A mí personalmente los que más me gustan y más utilizo son ***Photoshop*** e ***Illustrator***. + +Puedes hacerlo con lo que más te apañes, pero si te animas iremos viendo paso a paso cómo se hace con estos programas. + +En este caso voy a utilizar ***Illustrator*** porque es más sencillo crear rectángulos e ir asignándoles un color con el cuenta gotas. Por norma general, únicamente utilizo ***Photoshop*** cuando quiero hacer un montaje con varias imágenes o cambiar las características de una imagen, como tamaño, color y resolución; o incluso cuando quiero darle un tratamiento especial a un fondo. + +### Empezamos + +Para abrir una imagen en illustrator se hace de la siguiente manera: + +1- Crea un nuevo documento + +2- Archivo/Colocar../Eliges la imagen que te guste + +3- Ajusta el documento al tamaño de la imagen dejando un margen derecho para añadir cuatro rectángulos. + +![](/assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/ajustarimagen.jpg) + +4- **[Trick]** Si creas un rectángulo y arrastras el rectángulo mientras mantienes presionados **Alt + Shift**, este se duplicará alineándose al rectángulo ya creado. + +![](/assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/duplicaruno.gif) + +Si ahora pulsas **Ctrl + D** (PC) o **Cmd + D** (Mac), se duplicará de nuevo con las mismas características que el anterior. Es decir, mismo tamaño y mismo desplazamiento. + +![](/assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/duplicarvarios.gif) + +Quedaría algo parecido a esto: + +![](/assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/crearcajas.gif) + +5- Por último, utilizaremos el **cuenta gotas** para ir guardando los colores destacados. + +Si haces esto cada vez que te enfrentes a un diseño nuevo, podrás crear tu propia biblioteca de gamas de colores. + +![](/assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/cuentagotas.jpg) + +Y este es el resultado al aplicar la gama de colores en nuestra cabecera, a ver que te parece ;) + +![](/assets/images/posts/2018-01-22-tips-tricks-diseno-para-desarrolladores-color/header.jpg) + + + +### En resumen: +Hay que investigar e identificar de qué manera trasmitiremos de manera óptima nuestro mensaje. En este post hemos aplicado el proceso a la elección de colores, aunque un razonamiento similar podría aplicarse a la elección de la tipografía, estilo de ilustración, composición, etc. + +Espero que os haya sido de gran utilidad e iré publicado más **Tips&Tricks** de diseño para desarrolladores :) + + diff --git a/content/blogs/2018-02-06-templates-string-in-javascript/es.md b/content/blogs/2018-02-06-templates-string-in-javascript/es.md new file mode 100644 index 0000000..4859c20 --- /dev/null +++ b/content/blogs/2018-02-06-templates-string-in-javascript/es.md @@ -0,0 +1,222 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-02-06-templates-string/header.jpg +navigation: True +title: "Templates String en Javascript" +date: 2018-02-06 12:00:00 +tags: javascript rambling-javascript +class: post-template +subclass: 'post' +author: aclopez +--- + +"Primero resuelve el problema. Entonces, escribe el código" John Johnson + +En el lejano oeste de _Javascript_, uno de los ámbitos abiertos es como manejar las cadenas de texto. Aun hoy en día los navegadores son capaces de lidiar con las dobles comillas en los _strings_ mientras ya existen muchos _developers_ que no pueden con un fenómeno tal, que acaban cambiando de proyecto. Bromas aparte, ___ES6_ ha mejorado mucho el tratamiento en las _Templates con Strings___. Ahora ya podemos tener un código mucho más legible gracias a las nuevas características que nos ofrecen poniendo un poquito de empeño de nuestra parte. + +Para ello en el post trataremos los siguientes puntos: + +* String interpolation +* Expresiones embebidas +* Cadenas con Multilíneas +* Postprocesado de Templates + +## String interpolation + +Para las cadenas fijas, cuyo contenido no sea dinámico se seguirá utilizando las comillas simples para las cadenas; pero la novedad de ES6 es que para los _strings_ cuyo contenido queramos interpolar cualquier variable las podemos encerrar en comillas invertidas `. Para poner variables dentro del string tendremos que usar la sintaxis ```${variable}``` y con ello podemos conseguir estos resultados: + +```javascript + +const name = 'Pepe' +const greetings = `Buenos días ${name}` +console.log(greetings) + +// Buenos días Pepe. + +``` + +Podemos poner tantas variables como queramos dentro de nuestra cadena. + +```javascript + +const name = 'Pepe' +const date = new Date().toLocaleString() +const greetings = `Buenos días ${name}. Hoy ${date} queremos desearle que pase un buen día.` +console.log(greetings) + +// Buenos días Pepe. Hoy 2/2/2018 12:48:04 queremos desearle que pase un buen día. + +``` + +## Expresiones embebidas + +Si vamos un paso más allá del apartado anterior, __ES6 no solo nos permite interpolar variables, sino que dentro de los corchetes del dolar podemos poner expresiones.__ Por ejemplo la suma de dos números, la concatenación de varios _strings_ o trabajar con _arrays_: + +```javascript +const name = 'Pepe' +const yearBirth = 1992 +const date = new Date().toLocaleString() +const greetings = `Buenos días ${name}. Tu edad es ${new Date().getFullYear() - yearBirth} años. Hoy ${date} queremos desearle que pase un buen día.` +console.log(greetings) + +// Buenos días Pepe. Tu edad es 26 años. Hoy 2/2/2018 12:48:04 queremos desearle que pase un buen día. + +``` + +Y lo que viene como anillo al dedo es su uso con la función ```join``` de un _array_: + +```javascript +const name = 'Pepe' +const yearBirth = 1992 +const date = new Date().toLocaleString() +const hobbies = ['chess', 'running', 'basket'] +const greetings = `Buenos días ${name}. Tu edad es ${new Date().getFullYear() - yearBirth} años. Hoy ${date} queremos desearle que pase un buen día. Tus hobbies son: ${hobbies.join(', ')}` +console.log(greetings) + +// Buenos días Pepe. Tu edad es 26 años. Hoy 2/2/2018 12:59:56 queremos desearle que pase un buen día. Tus hobbies son: chess, running, basket + +``` + +Por supuesto que la potencia de estas templates permiten usar operadores como el ternario (``` a ? a : b```), ejecutar funciones en su interior o operadores lógicos que vienen muy bien para los valores _undefined_: + +```javascript +const name = 'Pepe' +const greetings = `Buenos días ${name.toUpperCase()}.` +console.log(greetings) + +// Buenos días PEPE. +``` + +```javascript + +const yearBirth = undefined +const date = new Date().toLocaleString() +const ageStr = `Tu edad es ${yearBirth ? new Date().getFullYear() - yearBirth : 'desconocida'}.` +console.log(ageStr) + +// Tu edad es desconocida + +``` + +```javascript +const age = undefined +const ageStr = `Tu edad es ${age || 'desconocida'}.` +console.log(ageStr) + +// Tu edad es desconocida +``` + +## Multilínea + +Otra ventaja de usar las comillas investidas es que para usar multilínea solo hay que ponerla tal cual. Fácil y práctico. + +```javascript +const name = 'Pepe' +const yearBirth = 1992 +const date = new Date().toLocaleString() +const hobbies = ['chess', 'running', 'basket'] +const greetings = `Buenos días ${name}. +Tu edad es ${new Date().getFullYear() - yearBirth} años. +Hoy ${date} queremos desearle que pase un buen día. +Tus hobbies son: ${hobbies.join(', ')}` +console.log(greetings) + +// Result: +Buenos días Pepe. +Tu edad es 26 años. +Hoy 2/2/2018 12:59:56 queremos desearle que pase un buen día. +Tus hobbies son: chess, running, basket + +``` + +## Postprocesado de templates + +Esta es quizá la característica más desconocida de las nuevas. Podemos postprocesar un template en funciones. ¿Que quiere decir esto? Lo mejor es un ejemplo: + +```javascript + +const greetings = name => `Buenos días ${name}` +console.log(greetings`Pepe`) + +// Buenos días Pepe. + +``` + +Si os fijáis para poder procesar la función ```greetings``` no ha sido necesario usar paréntesis, sino que poniendo directamente el _template_ ha sido suficiente. Esto es interesante para poder generar html: + +```javascript + +function generateTemplate (strings, ...keys ) { + return function(data) { + let result = strings.slice(); + + keys.forEach((key, i) => { + result[i] = `${result[i]}${data[key]}` + }) + + return result.join( '' ) + } +}; + +var person = { + name: 'Pepe', + age: 26 +}; + +var personTemplate = generateHtml`<div> + <h1>${'name'}</h1> + <p>Tu edad es ${'age'}</p> +</div>` + +console.log(personTemplate(person)); + +// <div> +// <h1>Pepe</h1> +// <p>Tu edad es 26</p> +// </div> + +``` + +El código tanto de ```person``` como de ```personTemplate``` es muy claro y solo con verlo intuyes lo que hace. La magia está en el generateTemplate. Esta función recibe el array ```strings``` dividido justo por las keys, y luego las propias keys, que operando con ellas conseguimos unirlo todo. Fijaos en como traduce todo el código ES6: + +```javascript + +'use strict'; + +var _templateObject = _taggedTemplateLiteral(['<div>\n <h1>', '</h1>\n <p>Tu edad es ', '</p>\n</div>'], ['<div>\n <h1>', '</h1>\n <p>Tu edad es ', '</p>\n</div>']); + +function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } + +var generateHtml = function generateHtml(strings) { + for (var _len = arguments.length, keys = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + keys[_key - 1] = arguments[_key]; + } + + return function (data) { + var result = strings.slice(); + + keys.map(function (key, i) { + result[i] = '' + result[i] + data[key]; + }); + + return result.join(''); + }; +}; + +var person = { + name: 'Pepe', + age: 26 +}; + +var personTemplate = generateHtml(_templateObject, 'name', 'age'); + +console.log(personTemplate(person)); + +``` + +En la traducción se crea el array ```_templateObject``` con los valores cortados justo por las keys. Luego tenemos accesibles las keys y lo juntamos todo para encajarlo y obtener nuestro div. + +## Conclusiones + +El código con las nuevas características de __ES6 siempre queda más legible__ que anteriormente; pero esto siempre tiene un coste. En este caso, que __el código es más difícil de escribir al principio__ ya que nos tenemos que adaptar y aprender estas características, pero una vez las interioricemos y las usemos en el día a día seguro que conseguimos este código legible cada vez de manera más natural. \ No newline at end of file diff --git a/content/blogs/2018-02-13-hoc-vs-render-props/es.md b/content/blogs/2018-02-13-hoc-vs-render-props/es.md new file mode 100644 index 0000000..57e31bd --- /dev/null +++ b/content/blogs/2018-02-13-hoc-vs-render-props/es.md @@ -0,0 +1,358 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-02-13-hoc-vs-render-props/mainPicture.jpg +navigation: True +title: "HOC vs Render Props" +date: 2018-02-13 12:00:00 +tags: react javascript +class: post-template +subclass: "post" +author: ivanrodri +--- + +En **React** para reutilizar o compartir lógica entre componentes podemos usar patrones como puede ser **HOC (High Order Component)** o **Render props**. + +# ¿Que es un HOC? + +Un **HOC (High Order Component)** o traducido **Componente de Order Superior** es un patrón que no es parte del **API** de **React**, si no que llega por parte de la **programación funcional**. Este patrón consiste en una **función** que recibe una o mas **funciones** como argumento y devuelven una nueva **funcion** o **componente**, también existen los **HOC** de **reducers**, en definitiva un **HOC** es una **función** que retorna otra **función**. + +Este patrón lo usan librerías como **Redux** con el **connect()** o **Apollo** con **graphql()** + +# ¿Que es Render Props? + +Es un patrón que nos permite compartir código teniendo un **componente** el cual recibe una **función render** vía **props** la cual será el **render** que ejecutará el **componente**. + +Este patrón lo usan librerías como **react-router** o **react-motion**. + +# Desventajas de usar HOC + +- No nos protegen de colisiones entre nombres de **props**, de esta manera no sabemos que **HOC** nos da ese valor +- Los HOC nos restringen la composición ya que va a ser una composición estática +- Los HOC se evalúan en tiempo de compilación. + +⚠️ **El siguiente ejemplo podría resolverse de una manera distinta y mas común. !Sólo de trata de un ejemplo!** + +# HOC example + +En este caso vamos a realizar un ejemplo de un input en el cual, al escribir nos mostrará el mensaje en pantalla. + +![Input example](/assets/images/posts/2018-02-13-hoc-vs-render-props/classComponent.gif) + +```javascript +import React from "react"; +import ReactDOM from "react-dom"; + +class WriteMessage extends React.Component { + constructor() { + super(); + this.state = { + message: "" + }; + } + + updateValue({ target: { value } }) { + this.setState({ message: event.target.value }); + } + + render() { + const { message } = this.state; + return ( + <div> + <input + type="text" + value={message} + onChange={event => this.updateValue(event)} + /> + <div> + <span>{message}</span> + </div> + </div> + ); + } +} + +ReactDOM.render(<WriteMessage />, document.querySelector("#app")); +``` + +En este caso hemos creado un **componente** que nos muestra el texto escrito en el input en un **span**, es posible que esta funcionalidad queramos usarla en mas de un **componente** y queramos reutilizar el código para no repetir en todos lo mismo. Para compartir código, podemos usar un **HOC**. + +```javascript +import React from "react"; +import ReactDOM from "react-dom"; + +const WriteMessage = Message => + class extends React.Component { + constructor() { + super(); + this.state = { + message: "" + }; + } + + updateValue({ target: { value } }) { + this.setState({ message: event.target.value }); + } + + render() { + const { message } = this.state; + return ( + <div> + <input + type="text" + value={message} + onChange={event => this.updateValue(event)} + /> + <div> + <Message message={message} /> + </div> + </div> + ); + } + }; + +class Message extends React.Component { + render() { + return <span>{this.props.message}</span>; + } +} + +const AppWithMessage = WriteMessage(Message); + +ReactDOM.render(<AppWithMessage />, document.querySelector("#app")); +``` + +En este caso hemos creado un **HOC** el cual va a tener la funcionalidad de mantener el estado del input y se lo va a pasar al **componente** que le estamos pasando como parámetro mediante una **prop**. ¿Qué pasaría si quisieramos que uno de nuestros componentes que usa el **HOC** tuviese un mensaje distinto? + +```javascript +import React from 'react'; +import ReactDOM from 'react-dom'; + +const WriteMessage = Message => + class extends React.Component { + constructor() { + super(); + this.state = { + message: '' + }; + } + + updateValue({ target: {value} }) { + this.setState({ message: event.target.value }) + } + + render() { + const { message } = this.state; + return( + <div> + <input type='text' value={ message } onChange={ event => this.updateValue(event) } /> + <div> + <Message message={ message } /> + </div> + </div> + + ) + } + } + +const WithCustomMessage = Component => + class extends React.Component { + constructor() { + super(); + this.state = { + message: 'My custom message:' + } + } + render() { + return <Component message={ this.state.message } /> + } + } + +class Message extends React.Component { + render() { + return <span>{ this.props.message }</span> + } +} + + +const AppWithCustomMessage = WriteMessage(WithCustomMessage(Message); + +ReactDOM.render(<AppWithCustomMessage/>, document.querySelector('#app')); +``` + +En este caso, hemos creado otro **HOC** que estará entre el **HOC** con la funcionalidad y el **componente** que muestra el mensaje. ¿Funciona correctamente? + +![Props colision](/assets/images/posts/2018-02-13-hoc-vs-render-props/customMessage.gif) + +En este caso nos encontramos con el problema de colisión de **props** el **HOC** **WithCustomMessage** esta machacando las **props** que esta pasando **WriteMessage**, para que esto funcione, tenemos que cambiar el nombre de una de las **props**. + +```javascript +import React from "react"; +import ReactDOM from "react-dom"; + +const WriteMessage = Message => + class extends React.Component { + constructor() { + super(); + this.state = { + message: "" + }; + } + + updateValue({ target: { value } }) { + this.setState({ message: event.target.value }); + } + + render() { + const { message } = this.state; + return ( + <div> + <input + type="text" + value={message} + onChange={event => this.updateValue(event)} + /> + <div> + <Message message={message} /> + </div> + </div> + ); + } + }; + +const WithCustomMessage = Component => + class extends React.Component { + constructor() { + super(); + this.state = { + message: "My custom message:" + }; + } + render() { + return <Component {...this.props} custom={this.state.message} />; + } + }; + +class Message extends React.Component { + render() { + const { message, custom } = this.props; + return ( + <span> + {custom} {message} + </span> + ); + } +} + +const AppWithCustomMessage = WriteMessage(WithCustomMessage(Message)); + +ReactDOM.render(<AppWithCustomMessage />, document.querySelector("#app")); +``` + +Tendríamos que hacerlo de esta manera para que la **composición** de **HOC** no tenga colisiones en las **props**. + +Antes hemos mencionado que la **composición** con **HOC** es una **composición** estática, esa **composición** estática surge en esta linea: + +```javascript +const AppWithCustomMessage = WriteMessage(WithCustomMessage(Message)); +``` + +# Render Props example + +En este caso vamos a realizar el mismo ejemplo pero usando el **patrón Render props** que consiste en tener un **componente** con la funcionalidad compartida pasarle una función como render. + +```javascript +import React from "react"; +import ReactDOM from "react-dom"; + +class WriteMessage extends React.Component { + constructor() { + super(); + this.state = { + message: "" + }; + } + + updateValue({ target: { value } }) { + this.setState({ message: event.target.value }); + } + + render() { + return ( + <div> + <input + type="text" + value={this.state.message} + onChange={event => this.updateValue(event)} + /> + <div>{this.props.render(this.state)}</div> + </div> + ); + } +} + +const Message = ({ message }) => <span>{message}</span>; + +ReactDOM.render( + <WriteMessage render={Message} />, + document.querySelector("#app") +); +``` + +Lo que estamos haciendo es renderizar un **WriteMessage** al cual le pasamos una **prop** **render** con la función que queremos que renderice, a la cual le le va a psar un parametro del cual hacemos el **destructuring** y cogemos el **message** para luego mostrarlo en el span. + +De esta manera tendríamos el input que muestra el texto escrito, ahora vamos a hacer que tenga el mensaje custom. + +```javascript +import React from "react"; +import ReactDOM from "react-dom"; + +class WriteMessage extends React.Component { + constructor() { + super(); + this.state = { + message: "" + }; + } + + updateValue({ target: { value } }) { + this.setState({ message: event.target.value }); + } + + render() { + return ( + <div> + <input + type="text" + value={this.state.message} + onChange={event => this.updateValue(event)} + /> + <div>{this.props.render(this.state)}</div> + </div> + ); + } +} + +const WithCustomMessage = ({ message }) => ( + <span> + My custom message: <Message message={message} /> + </span> +); + +const Message = ({ message }) => <span>{message}</span>; + +ReactDOM.render( + <WriteMessage render={WithCustomMessage} />, + document.querySelector("#app") +); +``` + +De esta manera tendíramos el mismo funcionamiento. + +Usando **Render Props** evitamos la coisión de **props** que teniamos usando los **HOC** y nuestras **composiciones** ahora son **dinamicas**, todo sucede en el **render** por lo cual podremos aprovechar el ciclo de vida de **React** y el flujo natural de **prop** y **state**. + +# Conclusión + +Con el uso de este **patrón** se puede reemplazar cualquier **HOC** por un componente con **Render props** pero no todos los componentes con **Render props** se pueden hacer con un **HOC**. + +![Conclusion tweet](/assets/images/posts/2018-02-13-hoc-vs-render-props/tweet.jpg) diff --git a/content/blogs/2018-02-22-tips-para-mejorar-bases-de-datos-sql-server/es.md b/content/blogs/2018-02-22-tips-para-mejorar-bases-de-datos-sql-server/es.md new file mode 100644 index 0000000..ff161a3 --- /dev/null +++ b/content/blogs/2018-02-22-tips-para-mejorar-bases-de-datos-sql-server/es.md @@ -0,0 +1,65 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-02-22-tips-para-mejorar-bases-de-datos-sql-server/header.jpg +navigation: True +title: "Tips para mejorar Bases de Datos SQL Server" +date: 2018-02-22 12:00:00 +tags: sql databases azure +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Hace unos días aprendí una valiosa lección acerca de lo que hay que hacer y no en bases de datos. Unos pequeños *tips* que nos ayudarán a optimizar nuestras bases de datos **SQL Server** y con ello a realizar consultas de manera más rápida. + +> Available in English [here](https://www.danielmartingonzalez.com/tips-to-improve-sql-server-databases/) + +## Nunca uses palabras reservadas + +Hay ciertas palabras que suelen estar reservadas en **SQL Server**. Palabras como `Id` o `Name` pueden usarse como nombre de columnas, pero no está recomendado, la razón, aparte de que por convención no se deben usar, una más práctica es la velocidad a la que hacemos consultas manuales con *joins* de por medio. + +Pongamos por ejemplo que queremos hacer un *Join* completo de dos tablas, vamos a hacerlo sencillo y no vamos a recortar columnas a mostrar. Supongamos que existe una relación `1 a N` entre *Table1* y *Table2* + +```SQL +SELECT * FROM Table1 JOIN Table2 ON Table1.Table1Id = Table2.Table1Id +``` + +En el caso de no usar palabras reservadas, el *Join* de tablas quedaría así de simple, puesto que *Table1* tendría su clave primaria *Table1Id*, y *Table2* la suya *Table2Id*. + +Si usásemos la palabra reservada `Id` y `Name`, la consulta daría error ya que no sabría si nos referimos al *Id* y *Name* de *Table1* o de *Table2*. Para arreglarlo tendríamos que hacer un **Select** por cada columna que queramos mostrar. Contando con solo tener las columnas `Id` y `Name` en ambas tablas quedaría de la siguiente forma: + +```SQL +SELECT Table1.Id, Table1.Name, Table2.Id, Table2.Name +FROM Table1 JOIN Table2 ON Table1.Id = Table2.Table1Id +``` + +Todo esto incrementa lo que tardamos en redactar las consultas, sobre todo aquellas que queremos hacer de manera rápida para realizar pequeñas comprobaciones. Imaginaos si ambas tablas tienen 15 columnas y queremos mostrar todas ellas, tendríamos que hacer un **SELECT** indicando cada una de ellas por separado. + +## Usa varchar en lugar de char + +Seguramente existirán bases de datos antiguas donde varchar no podrá usarse, pero lo más seguro es que tengas acceso a este tipo de datos para tus columnas. + +Siempre que puedas utiliza **varchar** en lugar de **char**. La razón es el tamaño que ocupan ambas. Mientras que **char** ocupa todo el tamaño que indiquemos de manera fija, **varchar** ocupará solo el tamaño que necesitemos en cada momento. + +## Cuidado con la n + +Mucha gente suele incluir la **n** en todos los **char**/**varchar** por defecto, muchas veces sin saber su significado o *por que todo el mundo lo hace*. + +La diferencia entre **varchar** y **nvarchar**, radica en que **nvarchar** utiliza caracteres *unicode*, esto quiere decir que ocupará **EL DOBLE** que **varchar** pero nos dará compatibilidad con diferentes idiomas y caracteres especiales. Si la base de datos solo va a usarse en una región local y no tenemos intención de expandirlo al resto del mundo, conviene utilizar **varchar** para reducir hasta la mitad el espacio utilizado en todas estas columnas. + +## Indica el tamaño de las columnas cuando sea posible + +Otro punto donde podemos optimizar es indicando el tamaño que van a tener nuestras columnas. En el caso de **char**/**nchar**, será el tamaño que ocupen siempre, en el caso de **varchar**/**nvarchar**, el tamaño máximo que van a ocupar. Vamos con unos ejemplos: + +- Si vamos a guardar **Urls** en nuestra columna, lo propio, dada la especificación de una *Url*, es que el tipo de dato de la columna sea `nvarchar(255)`. +- Si vamos a guardar una **descripción completa**, podemos utilizar `nvarchar(MAX)` para que el usuario escriba todo lo que crea necesario. +- Para los **nombres** lo usual es usar `nvarchar(100)` o `nvarchar(255)`. + +## Guarda la relación de enumerados también en Base de Datos + +Si vas a tener una columna de tipos, por ejemplo, la moneda utilizada en una tabla de transacciones, no guardes el tipo como `Euros` o `Dolares`. Guarda un **byte** con el valor numérico referenciado a otra tabla donde tengas todas las monedas guardadas. De esta manera consultar la lista de tipos de monedas es muy rápido y tenemos una referencia directa ocupando un mínimo espacio. + +## Conclusión + +Con estos breves tips mejoraremos el rendimiento de nuestras bases de datos y optimizaremos el espacio para que las consultas sean menos costosas y más rápidas. diff --git a/content/blogs/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/es.md b/content/blogs/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/es.md new file mode 100644 index 0000000..f90edb2 --- /dev/null +++ b/content/blogs/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/es.md @@ -0,0 +1,301 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/header.jpg +navigation: True +title: "Crear un botón en Wave Editor (Wave Engine)" +date: 2018-03-08 12:00:00 +tags: csharp wave-engine +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Para los que no lo conozcan, [**Wave Engine**](https://waveengine.net/) es un motor para el desarrollo de videojuegos cross-platform orientado a componentes. Ideado para la creación de juegos en **2D y 3D** y totalmente gratuito. Os animamos a que lo probéis. Para este tutorial solo hará falta entender cómo funciona el motor y crear [nuestro primer juego](https://github.com/WaveEngine/Documentation/wiki/My-First-Application). + +El código completo está disponible en [***GitHub***](https://github.com/danimart1991/WaveEngine.ButtonUI). + +Aunque este artículo pueda parecer básico, por el momento no existe el control **Button** en **Wave Editor**, el editor de desarrollo usado por *Wave Engine*. Por ello, y de manera sencilla, vamos a crear un botón para la [*UI*](https://es.wikipedia.org/wiki/Interfaz_de_usuario) capaz de usarse tanto en juegos *2D*, como *3D*, necesitando en este último caso una cámara *2D* que actúe de [*HUD*](https://es.wikipedia.org/wiki/HUD_(inform%C3%A1tica)). + +## Componentes + +Partimos del [proyecto base introductorio](https://github.com/WaveEngine/Documentation/wiki/My-First-Application) que *Wave Engine* ofrece a nuevos desarrolladores. + +Para ello, creamos un nuevo proyecto e incluimos un objeto *Cube* de tipo *Primitive 3D*. Agregamos el componente *Spinner* al cubo para que rote. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot1.png) + +Wave Engine está basado en [la programación orientada a entidades](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/). Un componente no es más que cada pieza que compone una entidad. Todos los componentes heredan de la clase [**Component**](http://doc.waveengine.net/index.html#frlrfWaveEngineFrameworkComponentClassTopic.html). + +Por el momento, la creación de componentes está supeditada al uso de **Visual Studio**. Abrimos nuestro proyecto en *Visual Studio* y empezamos a trabajar. + +## Creación del componente ButtonUI + +Es importante tener la solución ordenada. Nuestro proyecto puede crecer y tener la solución ordenada es una forma de acceder a cada elemento del juego de manera rápida. + +Una vez abierta la solución en *Visual Studio*. Creamos la carpeta *Components* dentro del proyecto *Shared*. Es decir, dentro del proyecto común a todas las plataformas. Dentro de ella creamos la clase *ButtonUI* que será nuestro componente. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot2.png) + +Abrimos la clase recién creada y empezamos a programar. + +## Programación base del componente ButtonUI + +Indicamos que la clase es pública y hereda de la clase *Component*. Que se encuentra en el namespace *WaveEngine.Framework*. Borramos todos los using, ya que al existir clases con el mismo nombre entre los namespaces de *WaveEngine* y el sistema, pueden entrar en conflicto. + +Añadimos a la clase el atributo *DataContract*, cuyo namespace es *System.Runtime.Serialization*. Añadimos como parámetro namespace, *WaveEngine.Components.Gestures*. + +```C# +[DataContract(Namespace = "WaveEngine.Components.Gestures")] +public class ButtonUI : Component +{ +} +``` + +Con este atributo. Le indicamos al motor que hemos creado un componente nuevo, que hará uso de los gestos para su funcionalidad. En principio el tipo de componente creado, no es más que una clasificación como veremos más adelante para que el desarrollador conozca qué tipo de funcionalidad esperar a la hora de usar el componente. Se puede indicar cualquier otro tipo existente en el namespace *WaveEngine.Components*. + +Agregamos como propiedades los componentes ``TouchGestures`` (*WaveEngine.Components.Gestures*) y ``Sprite`` (*WaveEngine.Components.Graphics2D*) con el atributo *RequiredComponent*. Este atributo indica al motor que estos dos componentes son necesarios, y que para usar el componente que estamos creando es necesario que el usuario agregue a la entidad estos componentes. Ambos componentes se usarán más adelante. El primero para la gestión de los gestos que el usuario realice sobre la entidad, como presionarla; el segundo para mostrar el botón. + +```C# +[RequiredComponent] +public TouchGestures TouchGestures = null; + +[RequiredComponent] +public Sprite Sprite = null; +``` + +Agregamos la propiedad *PressedTexturePath*, esta propiedad de tipo *string*, será la ruta de la textura usada para mostrar cuando el botón esté siendo presionado por el usuario. Para evitar problemas de acceso, asignamos el *get* y *set* a un *string* privado. + +Añadimos los atributos *DataMember*, que indica al motor que es una propiedad que debe mostrar y que el usuario puede cambiar, y el atributo ``RenderPropertyAsAsset`` con el parámetro ``AssetType.Texture`` (*WaveEngine.Common.Attributes*) que indica al motor que debe mostrar esta propiedad como una selección de texturas. + +```C# +private string _pressedTexturePath = null; + +[RequiredComponent] +public TouchGestures TouchGestures = null; + +[RequiredComponent] +public Sprite Sprite = null; + +[RenderPropertyAsAsset(AssetType.Texture)] +[DataMember] +public string PressedTexturePath +{ + get { return _pressedTexturePath; } + set { _pressedTexturePath = value; } +} +``` + +Añadimos tres variables privadas más, la primera ``static int instances``, hará de contador para asignar un nombre único a cada botón que añadamos al proyecto; la segunda ``bool backToTexturePath`` hará de control para saber si el botón tiene que cambiar de textura entre la usada en un estado normal y la usada en un estado presionado; la tercera ``string texturePath`` será la variable donde guardaremos la textura inicial del botón para no perderla al cambiar a la textura cuando el botón está presionado. + +```C# +private static int _instances; + +private string _texturePath = null; +private bool _backToTexturePath = false; +``` + +Para terminar la programación básica del componente, incluimos el constructor haciendo referencia al constructor base con el nombre del botón. + +```C# +public ButtonUI() +:base("Buttons" + _instances++) +{ +} +``` + +## Añadiendo funcionalidad al botón + +Para que el botón haga lo que debe, debemos añadir una serie de métodos: + +Sobrescribimos el método ``Initialize``, en este método le indicamos al componente lo que debe hacer cuando sea inicializado. Por el momento, nos registramos a los eventos de presión del botón y guardamos la textura inicial del botón para usarla más tarde. + +```C# +protected override void Initialize() +{ + base.Initialize(); + + TouchGestures.TouchPressed -= OnTouchGesturesTouchPressed; + TouchGestures.TouchPressed += OnTouchgesturesTouchPressed; + TouchGestures.TouchReleased -= OnTouchGesturesTouchReleased; + TouchGestures.TouchReleased += OnTouchGesturesTouchReleased; + + if (Sprite != null && !string.IsNullOrEmpty(Sprite.TexturePath)) + { + _texturePath = Sprite.TexturePath; + } +} +``` + +En los eventos registrados vamos a poner la funcionalidad de cambio de textura. Con esto, el jugador verá como el botón cambia cuando es presionado y vuelve a un estado normal cuando deja de presionarlo. + +```C# +private void OnTouchGesturesTouchReleased(object sender, GestureEventArgs e) +{ + if (!string.IsNullOrWhiteSpace(_texturePath && _backToTexturePath) + { + _backToTexturePath = false; + ChangeSpriteTexturePath(_texturePath); + } +} + +private void OnTouchGesturesTouchPressed(object sender, GestureEventArgs e) +{ + // Asking for !_backToTexturePath avoids to execute the if when has been done once before + if (!string.IsNullOrWhiteSpace(_pressedTexturePath) && !_backToTexturePath) + { + ChangeSpriteTexturePath(_pressedTexturePath); + _backToTexturePath = true; + } +} + +private void ChangeSpriteTexturePath(string imagePath) +{ + if (Sprite != null) + { + Sprite.TexturePath = imagePath; + } +} +``` + +La funcionalidad principal del botón está terminada. + +## Probando el botón en Wave Editor + +Para ver si funciona el botón, y que resultados hemos obtenido de este simple componente, volvemos a *Wave Editor*, y añadimos si no tenemos ya una cámara *2D*. Para ello, tenemos que asegurarnos de tener seleccionado el modo *2D* del editor, y añadimos una entidad de tipo *Fixed Camera 2D* a la lista de entidades. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot3.png) + +A continuación, añadimos un *Sprite2D*, al que añadimos los componentes necesarios *RectangleCollider2D* y *TouchGestures*, aunque el primero no lo hemos indicado en el componente ButtonUI, el componente *TouchGestures* lo necesita para saber en qué punto el usuario ha realizado el gesto en la entidad. Por último, añadimos nuestro componente ButtonUI. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot4.png) + +Una vez añadidos los componentes, tenemos que configurarlos. Para ello, añadimos los assets necesarios para el botón en estado normal y presionado. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/ButtonPlay.png) + +Podemos meterlos dentro de la carpeta *Assets* de nuestro proyecto. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot5.png) + +Por último, añadimos los *assets* a las propiedades *TexturePath* del componente *Sprite* y *PressedTexturePath* de nuestro componente. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot6.png) + +Ya podemos probar el botón en nuestro juego y ver como la textura cambia cuando es presionado. + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot7.png) + +## Incluyendo eventos y acciones + +Ahora mismo tenemos un botón que visualmente cumple su función, pero que no realiza ninguna acción al ser presionado. Para que realice esta y otras acciones, solo tenemos que añadir el manejador de eventos común y lanzarlo cuando nos interese, en este caso, cuando el usuario termine de presionar el botón. + +```C# +public event EventHandler Click; + +protected override void Initialize() +{ + base.Initialize(); + TouchGestures.TouchPressed -= OnTouchGesturesTouchPressed; + TouchGestures.TouchPressed += OnTouchgesturesTouchPressed; + TouchGestures.TouchReleased -= OnTouchGesturesTouchReleased; + TouchGestures.TouchReleased += OnTouchGesturesTouchReleased; + + if (Sprite != null && !string.IsNullOrEmpty(Sprite.TexturePath)) + { + _texturePath = Sprite.TexturePath; + } +} + +private void OnTouchGesturesTouchReleased(object sender, GestureEventArgs e) +{ + if (!string.IsNullOrWhiteSpace(_texturePath) && _backToTexturePath) + { + _backToTexturePath = false; + ChangeSpriteTexturePath(_texturePath); + } + + if (Click != null) + { + Click(sender, e); + } +} +``` + +Ahora que el botón avisa a cualquiera que esté suscrito a él de que ha sido presionado, podemos irnos a la escena donde esté colocado el botón y suscribirnos para realizar una acción. Para probar, vamos a hacer que el cubo active y desactive el giro cada vez que presionemos el botón. + +Abrimos la clase *MyScene*, sobrescribiremos el método *Start* y *End* para buscar el botón en la escena, suscribirnos y desuscribirnos del evento. No se nos puede olvidar incluir una variable donde guardar el botón. + +```C# +private ButtonUI _playButtonUI; + +protected override void CreateScene() +{ + Load(WaveContent.Scenes.MyScene); +} + +protected override void Start() +{ + base.Start(); + + _playButtonUI = EntityManager.Find("playButton").FindComponent<ButtonUI>(); + _playButtonUI.Click += OnPlayButtonClicked; +} + +protected override void End() +{ + base.End(); + + _playButtonUI.Click -= OnPlayButtonClicked; +} + +private void OnPlayButtonClicked(object sender, EventArgs e) +{ + +} +``` + +El nombre indicado para buscar el botón tiene que ser exactamente el nombre del botón indicado en el editor: + +![](/assets/images/posts/2018-03-08-crear-un-boton-en-wave-editor-wave-engine/screenshot8.png) + +A continuación, buscamos del mismo modo el cubo y su componente *Spinner* para poder activarlo y desactivarlo. + +```C# +private ButtonUI _playButtonUI; +private Spinner _cubeSpinner; + +protected override void CreateScene() +{ + Load(WaveContent.Scenes.MyScene); +} + +protected override void Start() +{ + base.Start(); + + _playButtonUI = EntityManager.Find("playButton").FindComponent<ButtonUI>(); + _playButtonUI.Click += OnPlayButtonClicked; + + _cubeSpinner = EntityManager.Find("cube").FindComponent<Spinner>(); +} + +protected override void End() +{ + base.End(); + _playButtonUI.Click -= OnPlayButtonClicked; +} + +private void OnPlayButtonClicked(object sender, EventArgs e) +{ + _cubeSpinner.IsActive = !_cubeSpinner.IsActive; +} +``` + +Guardamos, volvemos al editor y ejecutamos. Veremos cómo al presionar activaremos y desactivaremos el giro del cubo. + +## Conclusión + +Hemos visto como de manera sencilla podemos crear un componente que actúe de botón y poder usarlo en *Wave Editor* para, de una manera más gráfica, diseñar nuestras interfaces en *Wave Engine*. + +El código completo de este artículo está disponible en [**GitHub**](https://github.com/danimart1991/WaveEngine.ButtonUI). \ No newline at end of file diff --git a/content/blogs/2018-04-02-react-context-api-y-portals/es.md b/content/blogs/2018-04-02-react-context-api-y-portals/es.md new file mode 100644 index 0000000..ad2e3d6 --- /dev/null +++ b/content/blogs/2018-04-02-react-context-api-y-portals/es.md @@ -0,0 +1,302 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-04-02-react-context-api-y-portals/header.png +navigation: True +title: "React: Context API y Portals" +date: 2018-04-02 12:00:00 +tags: react +class: post-template +subclass: "post" +author: franmolmedo +--- + +Cuando abrazas una tecnología o forma de hacer las cosas es agradable ver como cada cierto tiempo van introduciendo sucesivas mejoras que hacen que ciertas limitaciones que has ido encontrando dejen de serlo. +La comunidad en torno a React es impresionante. No sólo los creadores originales de la librería (Facebook), sino infinidad de personas que han aportado su granito de arena para hacer que la experencia de desarrollo y, sobre todo, los resultados sean muy buenos. Librerías como [Flux](https://github.com/facebook/flux), [Redux](https://github.com/reactjs/redux), [Apollo](https://github.com/apollographql/react-apollo), [Formik](https://github.com/jaredpalmer/formik), [redux-saga](https://github.com/redux-saga/redux-saga)... nos hacen la vida más sencilla a aquellos que trabajamos dia a día con todas ellas. +Para este mismo año se han anunciado nuevas mejoras (como el [async rendering](https://www.youtube.com/watch?v=v6iR3Zk4oDY&t=230s)) muy interesantes para aplicar en nuestras aplicaciones. +En esta pequeña entrada me gustaría hablar sobre dos de los últimos añadidos a React, que son el context API y los React Portals. Empecemos. + +## Preparando el entorno + +Para hacer las pruebas vamos a utilizar la última versión disponible de React, en este momento (16.03.alpha1). Para ello, partimos de [create-react-app](https://github.com/facebook/create-react-app), el starter amparado por Facebook para aplicaciones React que nos monta la estructura básica de la aplicación. Nos vamos a nuestra consola y creamos la aplicación de ejemplo: + +```javascript +npx create-react-app react-context-portals-example +``` + +![create-react-app](/assets/images/posts/2018-04-02-react-context-api-y-portals/create-react-app.png) + +Nos situamos en la carpeta que nos ha creado y abrimos la aplicación en nuestro editor favorito. **_Create React App_** nos crea una aplicación React bastante estándar. +![app-structure](/assets/images/posts/2018-04-02-react-context-api-y-portals/app-structure.png). + +Vamos a abrir el fichero package.json y vamos a actualizar la versión de los paquetes correspondientes a react y a react-dom. En concreto, vamos a utilizar, como ya hemos comentado, la última disponible en estos momentos. El fichero tiene que quedar de la siguiente forma: + +```javascript +{ + "name": "react-context-portals-example", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^v16.3.0-alpha.3", + "react-dom": "^v16.3.0-alpha.3", + "react-scripts": "1.1.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +Una vez hecho esto, nos vamos a la consola y ejecutamos `yarn start` para que se nos lance la aplicación en nuestro navegador: + +![base-app](/assets/images/posts/2018-04-02-react-context-api-y-portals/base-app.png). + +### Context API + +En React, cuando queremos pasar información desde un componente padre a un componente hijo utilizamos las props. El ejemplo más sencillo que se me ocurre es un contador. Tenemos el componente padre que gestiona la cuenta y en su estado mantiene el valor actual de la misma. Por otro lado, tenemos un componente hijo que se va a encargar de mostrar dicho valor por pantalla. +Nuestro componente aplicación sería algo parecido a esto: + +```javascript +import React, { Component, Fragment } from "react"; +import Counter from "./Counter"; + +class App extends Component { + state = { + current: 0 + }; + + increment = () => this.setState({ current: this.state.current + 1 }); + decrement = () => this.setState({ current: this.state.current - 1 }); + + render() { + return ( + <Fragment> + <Counter currentValue={this.state.current} /> + <button onClick={() => this.increment()}>INCREMENT</button> + <button onClick={() => this.decrement()}>DECREMENT</button> + </Fragment> + ); + } +} + +export default App; +``` + +Tenemos el estado que definimos con el valor inicial de 0 dentro de la propiedad current y, posteriormente, declaramos dos funciones, una para incrementar este valor y otra para decrementarlo, estableciendo el nuevo valor en el state. Posteriormente, tenemos un par de botones que invocan a estas funciones en el evento click y el componente hijo **Counter** que se encarga de mostrar por pantalla el valor actual del contador. Este componente consistiría realmente en un componente funcional muy sencillo que recibe como prop **_currentValue_** y la muestra por pantalla. + +```javascript +import React from "react"; + +export default ({ currentValue }) => ( + <h1>The current value is: {currentValue}</h1> +); +``` + +Así, la aplicación quedaría de la siguiente forma: + +![counter-app](/assets/images/posts/2018-04-02-react-context-api-y-portals/counter-app.gif). + +El proceso se hace un poco más farragoso si hubieran más niveles de componentes intermedios. Imaginemos ahora que toda la funcionalidad de nuestro contador se encuentra en un componente Counter, y dentro de éste tendríamos los botones y la referencia al componente presentacional, al cual le tenemos que pasar el valor actual del contador desde el componente aplicación. La estructura sería la siguiente: + +- Componente App: + +```javascript +import React, { Component } from "react"; +import Counter from "./Counter"; + +class App extends Component { + state = { + current: 0 + }; + + render() { + const actions = { + increment: () => this.setState({ current: this.state.current + 1 }), + decrement: () => this.setState({ current: this.state.current - 1 }) + }; + + return <Counter currentValue={this.state.current} actions={actions} />; + } +} + +export default App; +``` + +- Componente Counter: + +```javascript +import React, { Fragment } from "react"; + +import CounterValue from "./CounterValue"; + +export default ({ currentValue, actions }) => ( + <Fragment> + <CounterValue currentValue={currentValue} /> + <button onClick={() => actions.increment()}>INCREMENT</button> + <button onClick={() => actions.decrement()}>DECREMENT</button> + </Fragment> +); +``` + +- Componente CounterValue: + +```javascript +import React from "react"; + +export default ({ currentValue }) => ( + <h1>The current value is: {currentValue}</h1> +); +``` + +En este caso vemos que el valor actual del contador tiene que llegar desde el componente **_App_** hasta el componente **_CounterValue_** pasando por **_Counter_**. Todavía en este caso sería manejable este paso de elementos por **_props_**, pero podemos extrapolarlo a un caso en el que hayan 6, 7 o 10 niveles intermedios y ya no sería tan manejable. Igualmente, hemos remodelado el state para definir las funciones dentro del mismo y pasarlas a los componentes hijos de forma más sencilla. + +#### Context API al rescate + +En un caso como el comentado, quizás se nos ocurra utilizar una librería externa para el manejo del estado de la aplicación (como [redux](https://github.com/reactjs/redux)) conectaría mis componentes a la store utilizando el **HOC** **_connect_** de [react-redux](https://github.com/reactjs/react-redux) para mapear las propiedades de nuestro estado como prop del componente deseado. +Con **_context_** el procedimiento sería parecido. En primer lugar, generaremos nuestro contexto para mapear nuestra store: + +![creating-context](/assets/images/posts/2018-04-02-react-context-api-y-portals/creating-context.gif). + +El código final de nuestro conexto quedará de la siguiente forma: + +```javascript +import React, { Component } from "react"; + +export const AppContext = new React.createContext(); + +export class AppProvider extends Component { + state = { + current: 0 + }; + + increment = () => this.setState({ current: this.state.current + 1 }); + decrement = () => this.setState({ current: this.state.current - 1 }); + + render() { + const value = { + state: this.state, + actions: { + increment: this.increment.bind(this), + decrement: this.decrement.bind(this) + } + }; + + return ( + <AppContext.Provider value={value}> + {this.props.children} + </AppContext.Provider> + ); + } +} +``` + +Resumimos lo que hemos hecho: + +- Se ha creado un contexto en AppContext, que se tiene que exportar para ser utilizado por los componentes que quieran utilizar el contexto. +- En el componente AppProvider almacenamos el estado que queramos compartir y las diferentes acciones que modifican dicho estado. +- En el método render utilizamos el **Provider** de nuestro **AppContext** y le pasamos el objeto que hemos generado con el estado y las acciones como prop **_value_** +- Renderizamos los hijos + +Ahora vamos a ir modificando los componentes que habíamos codificado anteriormente para que utilicen nuestro nuevo contexto: + +- Componente App: + +```javascript +import React from "react"; + +import { AppProvider } from "./AppContext"; +import Counter from "./Counter"; + +const App = () => ( + <AppProvider> + <Counter /> + </AppProvider> +); + +export default App; +``` + +En nuestro componente inicial de la aplicación, simplemente wrappeamos el método render con nuestro provider para tener el contexto disponible en cualquier componente hijo. Como vemos, al componente **Counter** ya no necesitamos pasarle ningún valor por props, ya que los recogerá en el contexto. + +- Componente Counter: + +```javascript +import React, { Fragment } from "react"; + +import { AppContext } from "./AppContext"; +import CounterValue from "./CounterValue"; + +const Counter = ({ currentValue, actions }) => ( + <AppContext.Consumer> + {({ state: { current }, actions: { increment, decrement } }) => ( + <Fragment> + <CounterValue currentValue={current} /> + <button onClick={() => increment()}>INCREMENT</button> + <button onClick={() => decrement()}>DECREMENT</button> + </Fragment> + )} + </AppContext.Consumer> +); + +export default Counter; +``` + +En este caso, hemos importado nuestro contexto e invocado al **Consumer** para poder recuperar los datos del contexto de la aplicación. Como vemos, utilizamos una **renderProp** en donde recibimos todo lo que habíamos añadido en el contexto: el **state** y las **actions**. Utilizando destructuring accedemos a esos elementos que lo usamos en el componente. + +Por último, el componente **CounterValue** queda igual que en el caso anteior. Le podemos seguir pasando el valor por props al tratarse de una relación directa padre-hijo. Sin embargo, si quisieramos podríamos, de la misma forma, utilizar el valor recuperándolo directamente del contexto y nos ahorraríamos establecer esa relación. + +### React Portals + +La otra idea que quería comentar en esta entrada son los Portals. Cuando hablamos de una aplicación en React, generalmente se renderizará en un contenedor definido en el **_HTML_** principal. Revisando la aplicación que hemos desarrollado anteriormente vemos lo siguiente: + +```javascript +ReactDOM.render(<App />, document.getElementById("root")); +``` + +![portal-init](/assets/images/posts/2018-04-02-react-context-api-y-portals/portal-init.PNG). + +En **_index.js_** estamos estableciendo que nuestra aplicación se renderice dentro del elemento con id **_root_**, que, como vemos, es un **div**. +Sin embargo, algunas veces podemos querer cambiar este comportamiento y renderizar componentes fuera de este contenedor. Para ello, utilizaremos los portals. +Imaginemos que queremos renderizar un **div** de nuestro componente **_Counter_** fuera del contenedor principal: + +```javascript +import React, { Fragment } from "react"; +import ReactDOM from "react-dom"; + +import { AppContext } from "./AppContext"; +import CounterValue from "./CounterValue"; + +const Counter = ({ currentValue, actions }) => ( + <AppContext.Consumer> + {({ state: { current }, actions: { increment, decrement } }) => ( + <Fragment> + <CounterValue currentValue={current} /> + <button onClick={() => increment()}>INCREMENT</button> + <button onClick={() => decrement()}>DECREMENT</button> + {ReactDOM.createPortal( + <div>PORTAL EXAMPLE</div>, + document.getElementById("portal") + )} + </Fragment> + )} + </AppContext.Consumer> +); + +export default Counter; +``` + +Importamos **ReactDOM**, para poder crear el portal. El método **_createPortal_** tiene dos parámetros: + +- El primero es lo que queremos renderizar en el portal (componentes, elementos jsx...) y el segundo parámetro es el elemento del DOM donde queremos que se renderice. En este caso, hemos añadido un nuevo div con id **_portal_** que es el que utilizamos aquí. + El resultado es el siguiente: + +![portal-end](/assets/images/posts/2018-04-02-react-context-api-y-portals/portal-end.PNG). + +## Conclusiones + +Sólo hemos mostrado un pequeño ejemplo de lo que se puede conseguir con estas nuevas APIs. Día a día se van añadiendo más y más posibilidades por lo que continuamente hay que estar revisando los nuevos avances para incorporarlos a nuestros proyectos. + +Happy ReactCoding!!!! diff --git a/content/blogs/2018-04-12-pull-requests/es.md b/content/blogs/2018-04-12-pull-requests/es.md new file mode 100644 index 0000000..9321499 --- /dev/null +++ b/content/blogs/2018-04-12-pull-requests/es.md @@ -0,0 +1,201 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-04-12-pull-requests/header.png +navigation: True +title: "Pull Requests: En mi Equipo Funcionan" +date: 2018-04-12 12:00:00 +tags: filosofia git +class: post-template +subclass: 'post' +author: aclopez +--- + +"Somos lo que hacemos de forma repetida. La excelencia, entonces, no es un acto, sino un hábito." Aristoteles + +# Pull Requests + +La traducción directa sería algo así como "Petición de Validación". Una Pull Request es la acción de validar un código que se va a mergear de una rama a otra. En este proceso de validación pueden entrar los factores que queramos: Builds (validaciones automáticas), asignación de código a tareas, validaciones manuales por parte del equipo, despliegues, etc. + +## Planteamiento + +¿Por qué las PullRequests? No sé si has oido hablar de ellas, pero la comunidad está muy dividida de si aportan o no aportan valor. Este documento tiene como objetivo servir de base para los nuevos y dar los puntos de vista de por qué se han elegido las Pull Requests (PRs a partir de ahora) como una rutina más en mi equipo. + +### Estética del Repositorio de Git + +_Git_ se ha asentado como nuestro repositorio de código fuente. Todos lo usamos, pero a mi me gustaría ir más allá y lanzar una pregunta: como desarrolladores, ¿cual es nuestra responsabilidad con el repositorio de código? + +Creo que todo DoD ([Definition of Done](https://apiumhub.com/tech-blog-barcelona/definition-of-done-examples-software-projects/)) tiene como primer paso subir el código al repositorio. Parece algo básico pero siempre viene bien recordarlo. Esto es crucial para que no existan las clásicas complicaciones de que "el código solo está en mi máquina". Estas cosas siempre suelen suceder cuando el programador que tiene el código en su equipo local está de vacaciones. + +Pero si vamos un poco más allá, en nuestro equipo nos planteamos el dilema de __¿cómo de responsables somos de como queda el código en el repo de Git?__. Muchas veces uno empieza a hacer un commit detrás de otro, con nombres como _"Fix task 12553"_, _"Change name"_, etc. que dejan el repo muy feo de ver. Es cierto que el código compila y funciona pero ¿es esto suficiente? ¿somos responsables de hacer buenos commits? ¿o eso da un poco igual? + +La respuesta para nosotros es clara: __el desarrollador es responsable de que los commits que haga queden los más limpios posibles__. Poner el nombre a un _Commit_ es todo un arte, ya que tiene que ser claro y simple, pero además hay que intentar ser profesional o lo que es lo mismo, hay que darle cariño al repositorio. Esto no quiere decir que si nos equivocamos no podamos subir un commit de Fix, porque el error es inevitable, pero si que estos _Commits_ deberían ser excepcionales. + +A todo esto también nos van a ayudar las Pull Requests. Cuando hagamos un PR, el resto del equipo podrá ver el nombre que le queremos poner al Commit y discutir si es un buen nombre o se podría mejorar con algún matiz. Y he dicho al Commit, porque finalmente __la rama que queremos mergear con nuestra rama principal acabará convertida en un solo Commit__. El proceso y los Commits que han hecho falta para llegar al código final da igual. Han podido haber muchos commits de "Fix Styles", "Change Service Name", etc. todos esos Commits se juntarán en uno con un nombre claro de la tarea que se ha realizado. "Create login page", por ejemplo, reescribiendo toda la historia del procedimiento, ya que en el resultado final lo único que nos interesará por tarea es este último Commit final. + +Por ello, lo que hemos planteado aquí en el equipo son PRs con [un rebase interativo](https://git-scm.com/book/es/v1/Las-herramientas-de-Git-Reescribiendo-la-historia). Vamos a usar toda la potencia de Git para juntar estos commits en uno para que cuando podamos ver el resultado final, todo quede bien enmarcado en un _commit_. Esto lo conseguimos con: + +```bash +git rebase -i develop +``` + +No os preocupéis de momento por el procedimiento, quedaos con el concepto. Si os imagináis una rama _develop_ a la cual se le hacen PRs constantemente desde otras ramas tipo _feature/12545_, al final develop tendrá dos _commits_ por PR, el _commit_ creado y el commit del merge a develop o incluso podemos juntar estos dos commits en uno y dejar un solo commit por PR con un último squash en el interfaz de la herramienta de PR que usemos. Así cuidamos nuestro repositorio, y una vez que lo veamos desde _Git_ será estético de ver y podremos movernos por los commits de manera cómoda. + +![Repo en Git](/assets/images/posts/2018-04-12-pull-requests/GitRepo.png) + +__Hacer las cosas bien en el repositorio es importante y siempre cuesta más trabajo que dejarlo todo descuidado__; pero es verdad que para hacer todo esto no son necesarias las PRs, aunque sí son un buen instrumento de que todo este procedimiento no se olvide y salga de manera natural casi sin darnos cuenta de que está ahí. + +### Herramienta de hacer equipo + +Todo lo ágil está de moda; pero la pregunta que surge aquí es: __¿de qué somos responsables en el proyecto?__ Es decir, si yo soy desarrollador de backend, me da igual como esté el código en frontend. ¿Es cada desarrollador responsable del código que sube? ¿yo no tengo responsabilidad del código que están picando mis compañeros? Estos pensamientos no pueden existir en nuestro equipo. Los proyectos son un todo y todos somos responsables de todo lo del proyecto y los proyectos que tenga el equipo. El cliente al final no va a decir: "¡Que bien está la arquitectura del proyecto!". El cliente va a querer un buen desarrollo en su conjunto, por lo que, nosotros, como equipo, debemos ir [pulgada a pulgada](https://www.youtube.com/watch?v=FNy4r_8_xHo) juntos apoyándonos en todas las partes de los diferentes proyectos. + +Por tanto, si en una demo se ha olvidado poner cualquier cosa, no es culpa del desarrollador que lo hizo, es siempre culpa del equipo. Los proyecto**s** son parte del equipo, es decir, no solo su proyecto en la parte que esté desarrollando; sino todos los proyectos serán objetos de PRs y usted, como miembro del equipo deberá ser proactivo y participar en la mayoría de PRs que pueda: + +* Las PRs serán una revisión de código. No pueden usarse las PRs como enfrentamientos personales. Es una revisión de código donde si el desarrollador lo ve oportuno puede dejar su comentario y parar el PR para que le den respuesta. Para ello pondrá el estado de su revisión a _Pending for the Author_ en el _VSTS_. +* El desarrollador también puede dejar preguntas de como se ha realizado cierta tarea y como se ha llegado a esa solución, para que la PR también sirva de aprendizaje. En estos casos es recomendable no parar las PRs, sino aprobarlas con comentarios. El autor siempre nos responderá y podrá plantear o bien una charla al equipo o una respuesta con algún artículo sobre el tema o algún debate, pero si se ha aprobado la PR, el propio autor resolverá el comentario con "Won't Fix" y mergeará. +* Las PRs no son revisiones solo negativas. Le animamos a que pueda poner comentarios positivos a algo que vea muy bien. Incluso los gifs suelen darle mucha vida a las revisiones de código. VSTS soporta _markdown_ por lo que cualquier comentario creativo es bienvenido. __Los comentarios positivos son__, me atrevería a decir, __más importantes que los comentarios negativos__. Premie el buen trabajo de su equipo. + +__Un PR sin comentarios seguramente sea un PR mal revisado__. No es que siempre se cometan errores, pero seguro que siempre hay ideas que debatir. La riqueza en el intercambio de opiniones debería fluir. __Un código revisado por un compañero siempre será mejor código que un código que solo lo haya desarrollado usted__. Incluso una revisión propia siempre es mejor que dejarlo sin ninguna revisión, ya que durante el desarrollo solemos estar viciados y puede que se nos haya escapado algún detalle tonto. Esto también ayudará a los miembros del equipo que estén solos en el proyecto a poder compartir sus ideas y su código y que no se sientan la soledad en su solución. Recordamos que todos los proyectos son de todos, por lo que ellos recibirán _feedback_ de su código y podrán salir a ver otro tipo de proyectos. + +Por último, si lleva un día sin hacer un solo PR, esto será un mal síntoma. Los PRs deben ser cuanto más cortos mejor, ya que así la revisión será más efectiva. Si hacemos PRs de dos archivos la gente que los revises encontrará más cosas que si pone un PR con 30 archivos, donde sus revisores se cansaran y estarán muy tentados a darle al botón de aprobar sin apenas echarle un vistazo a su código. ¡Los PRs y las ramas en _Git_ no valen dinero! Por lo que le animamos a hacer cuantas más PRs mejor, así se tiene más posibilidades de ser el que haga la PR número 100, 1.000 o 10.000, en nuestro equipo seguro que le cae alguna cerveza gratis. + +## Procedimiento + +El procedimiento de generar las PRs dependerá de la estrategia de ramas que tengamos. En nuestro equipo tenemos dos estrategias de ramas, con _feature/branches_ y con _tasks/branches_. + +### Estrategia de ramas + +Hay mucha teoría sobre la estrategia que se pueden llevar con las ramas y os recomiendo echar un vistazo a [Git Flow](http://aprendegit.com/que-es-git-flow/) + +![Git Flows](/assets/images/posts/2018-04-12-pull-requests/gitflow.png) + +Aquí vamos a ver las estrategias de ramas que se llevan a cabo en mi equipo. En ambas estrategias tendremos dos ramas principales: _master_ y _develop_: + +* _master_: será la rama que contendrá el código que está puesto en producción. +* _develop_: será la rama que contendrá el código en desarrollo. + +En la estrategia de _feature/branches_ se sacará una rama por cada _feature_ de _develop_. Esta rama no se mergeará con _develop_ hasta que no se haya finalizado la _feature_ completa. Se irán sacando ramas de la rama de la _feature_, para implementar las diferentes tareas y están serán las ramas que al querer ir a su rama de feature se crearán las PRs. Cuando se mergee la rama de la feature contra develop habrá otro PR de revisión de que no falta nada de lo realizado en la feature, ya que el código habrá sido revisado en cada una de las PRs de las distintas tareas. + +En la estrategia de _task/branches_ directamente se sacan las ramas de _develop_. Esto es para proyectos más pequeños, en las que se pueden juntar los commits de las distintas _features_. + +### Procedimiento en Git + +Para poner el ejemplo del procedimiento en una consola de Git seguiremos la estrategia de ramas de _tasks/branches_. + +Si nos encontramos en _develop_ y queremos llevar a cabo la tarea 23154, crearemos la rama _feature/{idtarea}_. (En el caso de _features/branches_, la nomenclatura para crear ramas será _feature/{idfeature}/{idtask}_). Para crear la rama ejecutamos: + +```bash +git checkout -b feature/23154 +``` + +Esto nos generará una nueva rama, y nos hará cambiar a ella. Para crear ramas en Git también se puede usar el comando (```git branch -a feature/23154```) y luego con _checkout_ ir a ella (```git checkout feature/23154```). + +Una vez aquí desarrollaremos nuestro código y pondremos varios commit en la rama: + +```bash +git commit -m "Create main layout" +git commit -m "Add translations" +git commit -m "Fix styles" +``` + +Cuando tenemos tres commits, deberemos juntarlos todos en uno con un rebase iterativo. + +```bash +git rebase -i develop +``` + +Esto nos abrirá el editor que tengamos por defecto para trabajar con Git. Si nunca lo habéis configurado os abrirá Vi, que a mí no me convence mucho. Os recomiendo que os bajéis [Notepad++](https://notepad-plus-plus.org/download/v7.5.6.html), y lo pongáis como vuestro editor con este comando. Echarle un vistazo a este [enlace](https://stackoverflow.com/questions/10564/how-can-i-set-up-an-editor-to-work-with-git-on-windows): + +```bash +git config --global core.editor "\"c:\Program Files\Notepad++\notepad++.exe\"" +``` + +Una vez hecho esto, se abrirá el editor con los commits que tenemos en la rama. + +Para dejar todos los commits como uno, tendremos que dejar el primer commit a _pick_, y poner los restantes a _squash_ (con una s solo también vale). Cuando lo tengamos listo, guardamos el archivo, cerramos el editor y nos saldrá una nueva pantalla con los commits que vamos a "squashear". En esta pantalla podemos cambiar el mensaje del commit general poniendo algo entre la línea 1 y 2, dejando así ese como mensaje global. Suele ser útil si te has despitado en los mensajes de tus commits. + +![Git rebase](/assets/images/posts/2018-04-12-pull-requests/gitrebase.gif) + +Si todo ha salido correctamente podemos hacer un ```git push origin feature/23154``` y esto subirá la rama al repositorio y podremos crear la PR en el VSTS (ver procedimiento VSTS). Si hemos creado alguna PR en esta rama en VSTS tendremos que usar la opción -f de push: ```git push origin feature/23154 -f```. + +Cada vez que actualicemos la PR con cualquier feedback que nos hayan dado, al tener que hacer un nuevo commit sobre la rama, tenemos que repetir el proceso del rebase iterativo. Recuerda que __los PRs no son bloqueantes, y que mientras puedes cambiar de rama y continuar con cualquier otra tarea en una rama diferente.__ + +Uno de los problemas más comunes al hacer PRs, es que no tengamos _develop_ actualizado. Por lo que tendremos que realizar un ```git fetch``` para actualizar el repo y, una vez actualizado, tendremos que traernos los cambios de develop con: + +```bash +git rebase develop +``` + +No confundir con el comando de _rebase_ anterior, ya que este no tiene el parámetro _i_. Si no hay conflictos ya podremos subir nuestro código si lo tenemos "squasheado", pero si existe algún conflicto tendremos que ir a resolverlo. Para resolver los conflictos podéis usar vuestro editor favorito. Tanto [Visual Studio](https://docs.microsoft.com/en-us/vsts/git/tutorial/merging?view=vsts&tabs=visual-studio) como [Visual Studio Code](https://code.visualstudio.com/docs/editor/versioncontrol) tienen buenas herramientas para resolver conflictos en Git. + +Una vez resueltos los conflictos, hay que añadir los cambios con un ```git add . ``` y luego confirmar el rebase con ```git rebase --continue```. __En los procesos de resolver conflictos no hay que hacer ningún commit.__ + +![Git rebase](/assets/images/posts/2018-04-12-pull-requests/gitrebase.png) + +Todo resuelto, podemos "squashear" nuestros commits y "pushear" la rama para crear el PR. + +### Procedimiento en VSTS + +Existen muchas herramientas para poder trabajar con Git. En nuestro caso usamos [VSTS](https://www.visualstudio.com/es/team-services/) pero seguro que herramientas como [Bitbucket](https://bitbucket.org/) o [Gitlab](https://about.gitlab.com/) ofrecen soluciones similares. + +Una vez "pusheada" la rama debemos ir al VSTS para poder crear la PR. Hay muchas maneras de crear la PR aunque normalmente si acabas de subir una rama, VSTS te dará la opción por defecto de que se acaba de pushear una rama y si quieres crear un PR. Si no, puedes ir al menú de Branches y en tu branch darle a _Create New Pull Request_ o en _Pull Request_ -> _Create New Pull Request_ + +![Create Pull Request](https://docs.microsoft.com/en-us/vsts/git/_img/gitquickstart-vs2017/create-pull-request-web-portal.png?view=vsts) + +Dentro de la creación de la Pull Request se habrá cogido la rama por defecto que querías usar si has ido por Branches o tendrás que elegir ambas. Selecciona que dos ramas quieres mergear y pon abajo en la tarea, el número de la tarea con la que está relacionada este PR. Es una buena práctica poner siempre una tarea asociada para que todo quede armónico entre nuestro repo y las tareas del VSTS. Cuando lo hayas rellenado todo podrás crear la PR. + +![PR](https://docs.microsoft.com/en-us/vsts/git/_img/gitquickstart-vs2017/create-pull-request.png?view=vsts) + +En tu nueva PR, puede que haya una Build que pasar. Esto lo podrás ver en el menú de la izquierda donde están todas las condiciones obligatorias y opcionales de la PR. Si es así una vez que haya pasado la Build, se quedará el check en verde. Si no se ha pasado, se quedará en rojo y podemos volver a lanzarla en los puntitos que siguen a la Build, una vez hayamos encontrado el problema. Las Builds se lanzan cada vez que hacemos un update sobre el código de la PR. + +![Build en Pull Request](http://sklyarenko.net/images/november2017/vsts_pr_status_building.png) + +Los revisores obligatorios se ven también en este menú. Cuando nos hayan aprobado la PR saldrán ahí. También podemos ver si tenemos comentarios sin resolver. Los comentarios podemos ponerlos en Resolved, si lo hemos resuelto una vez que hemos actualizado la PR o en _Won't Fix_, si consideramos que no lo vamos a resolver, poniendo un comentario de explicación de porque no lo resolvemos. + +Podemos configurar todas estas opciones en la pantalla del VSTS de Políticas de Ramas de la rama que queramos obligar a ello. Así, si le ponemos Revisores Obligatorios, esto hará que no podamos hacer un commit directamente a develop desde Git y siempre tengamos que pasar por este procedimiento, por lo que lo que comentamos antes de que el código es responsabilidad del equipo, estaríamos haciendo que se cumpliera. Igual pasa con las Builds, asignación de tareas y otras configuraciones. + +![Configuración de Políticas de Rama en Git](https://docs.microsoft.com/en-us/vsts/git/_img/branches/branches_context_menu_policy.png?view=vsts) + +![Configuración de Políticas en las Ramas](http://sklyarenko.net/images/november2017/vsts_branch_policy.png) + +#### Flujo de validaciones de los revisores + +Como revisores podemos: + +* Aprobar un PR. +* Aprobar un PR con comentarios: esto significará que hemos dejado algún comentario, pero que consideramos que la PR puede mergearse sin problemas, ya que ha sido un comentario que al valorarlo el desarrollador decidirá si lo resuelve o no. +* Esperar respuesta del autor: consideramos que nuestra pregunta/comentario es importante para el PR, por lo que esperamos que el autor nos responda antes de poder dar por aprobado este PR. (Insisto una vez más que una PR no es bloqueante para seguir trabajando, ya que se puede crear una rama a parte y seguir con otra tarea mientras esto se resuelve.) + +## Chuletas de Git + +```bash +# Flujo de una rama de una tarea +git checkout -b feature/23154 + +git commit -m "Create main layout" +git commit -m "Add translations" +git commit -m "Fix styles" + +git rebase -i develop + +git push origin feature/23154 +``` + +```bash +# Flujo de actualizacion de develop con conflictos +git checkout develop +git pull origin develop +git checkout feature/23154 + +git rebase develop +(Conflictos: resolverlos) + +git add . +git rebase --continue +``` + +## Conclusiones + +Este es el procedimiento general del equipo, pero seguro que cada proyecto tiene sus particularidades. Esto solo es un punto de vista para que te hagas una idea de nuestro planteamiento a la hora de trabajar con los PRs. Si te atreves a ponerte en marcha con ellas seguro que al principio tiendes a sentirte algo perdido, por lo que no dudes en preguntar a tu equipo y en hacer los primeros PRs con pair programming con algún compañero. + +Los primeros PRs serán costosos, pero ya verás como después de un par de PRs acaba siendo una rutina agradable que ni te das cuenta que pasa. \ No newline at end of file diff --git a/content/blogs/2018-04-19-el-buen-informatico/es.md b/content/blogs/2018-04-19-el-buen-informatico/es.md new file mode 100644 index 0000000..4bcb595 --- /dev/null +++ b/content/blogs/2018-04-19-el-buen-informatico/es.md @@ -0,0 +1,50 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-04-19-el-buen-informatico/header.jpg +navigation: True +title: "El buen informático" +date: 2018-04-19 12:00:00 +tags: team thought +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Llegas a la oficina. Como cada día, enciendes tu ordenador, revisas los correos, los [*Pull Request*](http://www.nocountryforgeeks.com/pull-requests/) de tu equipo; y, finalmente, qué tarea tienes que desempeñar hoy. El proyecto, un sistema de mensajería entre empleados de una gran empresa. + +> Incluir rol de administrador para ver y gestionar los mensajes privados entre los usuarios. (1 Jornada) + +Te preguntas, ¿Cómo es posible que al *Product Owner* se le ha olvidado incluir una descripción detallada?, ¿1 Jornada? Me parece poco. + +> Available in English [here](https://www.danielmartingonzalez.com/the-good-it-guy/). + +**Ryan, cazaba con su padre cuando era pequeño**. Era algo que le gustaba. La mezcla de naturaleza en estado puro, con la unión que conseguía en esas pocas horas que disfrutaba junto a su padre, le transmitía bastante serenidad y confianza en sí mismo. Aunque ahora vivía en la ciudad. Se sacó hace unos meses su permiso de armas para, siendo ahora el padre, disfrutar junto a su hijo de esos momentos que obtuvo cuando era niño. De paso, podría aprovechar el arma para, gracias a la segunda enmienda, proteger su casa ante cualquier asaltante que quisiese arrebatar la paz a su familia. + +**Rudolf, es un pandillero de 16 años**. Vive en un barrio nada recomendado de Chicago, y junto a su mejor amigo de la infancia, le han ordenado la tarea de robar todos los paquetes de droga posibles en un almacén de una banda rival. En su tarea, pese a que no debería haber nadie en la zona, se encuentran con pandilleros de la banda a la que iban a robar y se enzarzan en un tiroteo en el que matan a su mejor amigo. + +Estados Unidos lleva casi toda su historia moderna con un **fuerte debate sobre las armas**. En un país donde tener arma es un derecho, pero cada hora, se producen tres muertes por arma de fuego, llegamos a un choque en ámbitos de sociedad y política difícilmente asumible. + +Empiezas a hacer la tarea asignada. Tras preguntar al *Product Owner*, a tus compañeros de equipo… Por fin tienes claro lo que tienes que hacer, y con una jornada de retraso por fin tienes lista la funcionalidad. + +Te dispones a probar la nueva funcionalidad antes de hacer un [*Pull Request*](http://www.nocountryforgeeks.com/pull-requests/) a la rama *develop*. + +Como **administrador**, ves varios mensajes privados entre usuarios quejándose de los sueldos, otros tantos de las horas extra, y unos pocos con insultos a los responsables que tienen por encima, incluido el jefe de la empresa. + +**Un “click” resuena en tu cabeza.** Un resquicio de lo pasado con Cambridge Analytica y Facebook. Personas que, por tener un trato especial, tienen acceso a datos que quizás no deberían. + +¿Qué pasará con esos empleados el día que a algún responsable le dé por mirar estos **mensajes “privados”**? + +Hablas con el equipo. Variedad de opiniones. + +- **Éticamente no se debería hacer**. Estas poniendo el riesgo el trabajo y la reputación de los empleados de la empresa. + +- **Deberíamos hacerlo**. Los empleados no deberían escribir en la herramienta este tipo de contenido. Además, el cliente paga por nuestros servicios y no deberíamos preocuparnos de si es ético o no. + +Quizás más importante. ¿Qué pasa si se usa inadecuadamente? ¿De quién es la responsabilidad? ¿Tuya por desarrollarlo? ¿Del cliente? ¿De todo el equipo? + +Si lo piensas, no se aleja mucho de las historias que he contado unos párrafos más arriba. La aplicación ahora incluye una funcionalidad o herramienta que puede usarse con fines ilícitos. Pero nos encontramos con la misma discusión que el hecho de poseer un arma. + +**Un arma no es más que una herramienta**. Puede usarse para distintos fines, muchos de ellos no éticos. Tu has creado una funcionalidad o herramienta que puede usarse con los mismos fines. Quizás pueda parecer nimio. Pero estás jugando con el empleo de personas y sus familias. Es posible que el ejemplo que he puesto no sea muy grave, pero ¿qué pasa con los informáticos encargados de programar armas para el ejercito? Entraríamos en una línea muy delgada difícil de trazar. + +**La conclusión**, al igual que pasa con el debate de las armas en Estados Unidos, y con los soldados en el campo de batalla, creo que está en la conciencia de cada uno. Lo que espero es que al menos como desarrollador, te preocupen estos temas, te plantees como afecta la ética de tu trabajo al resto de personas, y que hagas lo posible por seguir tus convicciones. Solo con eso, ya serás mejor persona y mejor informático. diff --git a/content/blogs/2018-05-17-diferencis-entre-graphql-rest/es.md b/content/blogs/2018-05-17-diferencis-entre-graphql-rest/es.md new file mode 100644 index 0000000..d00d185 --- /dev/null +++ b/content/blogs/2018-05-17-diferencis-entre-graphql-rest/es.md @@ -0,0 +1,160 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-05-17-diferences-between-graphql-&-rest/graphql-vs-rest.png +navigation: True +title: "Diferencias entre GraphQl & REST" +date: 2018-05-17 12:00:00 +tags: graphql rest +class: post-template +subclass: "post" +author: ivanrodri +--- + +En el mundo de las **APIs** la necesidad de avanzar más rápido en productos más complejos hace que estemos en una constante innovación y evolución, esto hace que las tecnologías sean remplazadas por otras más innovadoras. **REST** y **GraphQL** son dos maneras distintas de enviar datos a través de **HTTP**. + +La arquitectura **REST** es la forma más tradicional y usada para el desarrollo de **APIs**, ha ganado muchos adeptos debido a su robustez. En la actualidad este tipo de arquitectura denota cada vez más sus limitaciones y su inflexibilidad. + +**GraphQL** se presenta como una nueva y revolucionaría forma de pensar acerca de las **APIs**. De hecho, **GraphQL** quiere mejorar las debilidades que tiene **REST** + +# REST + +**REST** utiliza los verbos **HTTP CRUD (GET, POST, PUT, DELETE)** para realizar las operaciones, una **API** necesita (Crear, Eliminar, Modificary Consultar). **REST** sigue un modelo orientado a recursos que están representados por una **URL** única. Hacer una **API** totalmente **REST** es un trabajo complicado debido a todas las normas que esta arquitectura requiere, debido a eso, hay muchas **API REST** que tienen **custom endpoints**. + +# GraphQL + +**GraphQL** en un lenguaje de consultas que permite definir de una forma sencilla e independiente de la tecnología los datos que queremos que nos devuelva el servidor. Solo tiene un único **endpoint** y se basa en los **Schemas**, para conseguir realizar las mismas acciones que permite **REST**, **GraphQL** usa **Query**, **Mutation** y **Suscription** los cuales nos van a permitir realizar las acciones de Crear, Eliminar, Modificar, Consultar y hacer conexiones en tiempo real. Fue creada por **Facebook** en 2012 pero no fue de código abierto hasta 2015. Este lenguaje de consultas surgió por las necesidades de producto que tenía **Facebook** con su aplicación móvil cuando la quiso migrar a plataforma nativa, ya que anteriormente era un **Web View**. **GraphQL** puede ser usado en cualquier lenguaje como **Node, Python, Ruby, C#, Java, etc**. + +# Diferencias + +## Obtención de datos + +En la actualidad las aplicaciones móviles y web requieren grandes conjuntos de datos que necesitan de recursos relacionados. Obtener esos datos a través de **API REST** requiere de múltiples llamadas (paralelas o secuenciales) para obtener toda la información que se necesita. +Uno de los puntos más importantes que nos ofrede **GraphQL** es la obtención de datos. En **GraphQL** solo tenemos un único **endpoint** (`/graphql`) con el que accedemos a los datos del servidor. Con una única llamada podemos obtener un objeto con sus objetos relacionados. + +Si nos imaginamos el caso de querer mostrar la información de una **serie** con su información básica, sus **episodios** e información del **autor**. Para **REST** primero tendremos que traernos la serie `myApi.com/series/:id` y luego tendremos que consultar sus **episodios** uno a uno. que son recursos relacionados `myApi.com/episodes/:id` y también cargar su **autor** llamando a `myApi.com/author/:id`. Esto significa que necesitamos más de una llamada para componer todos nuestros datos. + +En el caso de **GraphQL** con una única **query** podemos obtener todos los datos. + +``` +{ + serie (id: {id}) { + id, + title, + language, + image, + published, + author { + name, + lastName, + } + episodes { + id, + title, + description, + image, + time, + } + } +} +``` + +El resultado de esta query, será un **JSON** con el mismo formato que el que describe la query. + +```javascript +{ + "id": 5, + "title": "Peaky Blinders", + "language": "English", + "image": "myApp.com/peakyBlinders.jpg", + "published": "2017-09-01", + "author": { + "name": "Steven", + "lastName": "Knight" + }, + "episodes": [ + { + "id": 1, + "title": "Episode 1", + "description": "", + "image": "myApp.com/peakyBlinders1.jpg", + "time": 60000 + }, + { + "id": 2, + "title": "Episode 2", + "description": "", + "image": "myApp.com/peakyBlinders2.jpg", + "time": 60000 + } + ] +} +``` + +## Over Fetching / Under Fetching + +**Over Fetching**: Con **REST** nos encontramos el problema que podemos obtener datos innecesarios. Cada **endpoint** tiene una estructura fija de datos que nos va a retornar cada vez que hagamos una petición. En muchas ocasiones no necesitamos toda la información y acabamos ignorando muchos de los datos, lo que indica que no estamos siendo eficientes. Este problema hace que consumamos más ancho de banda y tengamos una carga más lenta, en dispositivos móviles le estaremos consumiendo más datos de los necesarios al usuario. + +Si volvemos al caso anterior y solo necesitamos pedir los campos **image y title** de los episodios de una **serie**, con **REST** no podríamos hacerlo, siempre recibiremos toda la información de cada uno de los episodios. + +**GraphQL** nos permite pedir únicamente los campos que necesitamos. + +```javascript + serie (id: {id}) { + id, + title, + language, + image, + published, + author { + name, + lastName, + } + episodes { + title, + image, + } + } +``` + +**Under Fetching**: Es el problema contrario comentado anteriormente, quizás necesitamos información que un único **endpoint** no nos va a dar por completo, en este caso necesitamos hacer una llamada adicional para traerse la información. En este caso nos encontramos con el problema de **N + 1** peticiones y es un problema de rendimiento de **API REST** y consultas a base de datos. + +## Versionado + +Otro gran problema que tiene **REST** es el versionado, este no es trivial. Si necesitamos soportar múltiples versiones normalmente significa crear nuevos **endpoints**. Esto nos generará más problemas cuando es usado y su mantenimiento será más costoso, esto puede causar **código spagetti** o que tengamos que duplicar código en el servidor. + +Con **GraphQL** no tenemos la necesidad de versionado, nosotros podemos añadir campos o tipos facilmente sin modificar o romper las queries existentes. También podemos marcar campos como **deprecated** para excluirlos de la respuesta. + +## Documentación + +Tener una buena documentación de la **API** es esencial para que el consumidor pueda hacer un buen consumo de ella y conozca los **endpoints** y sepa que parámetros tiene que enviar y que le van a devolver. Los desarrolladores por norma general odiamos hacer documentación, muchas veces nos encontramos **APIs** que no tienen una buena documentación o no esta actualizada y el tiempo de desarrollo de nuestras aplicaciones aumenta cuando queremos consumirlas. Existen herramientas como **Swagger** que nos ayuda a tener una documentación pero si nos salimos del standar **REST** no lo va a soportar. + +## Cacheo + +**REST** está implementado mediante **HTTP** y este tiene implementado el almacenamiento en **caché**, el cliente puede usar el almacenamiento en **caché** para evitar volver a buscar recursos. **GraphQL** no tiene un mecanismo de almacenamiento en **caché** lo que deja a los clientes con la responsabilidad de encargarse del almacenamiento en **caché**. + +## Manejo de errores + +El manejo de errores en **REST** es sencillo, solo nos basta con mirar los headers de la llamada **HTTP** para conocer el status de la respuesta. Existen varios códigos de status que cada uno respresenta un tipo de error, redireccion o correcto (2xx, 3xx, 4xx, 5xx) debido a estos códigos podemos conocer facilmente el error y solucionarlo. En **GraphQL** siempre obtendremos una respuesta con un código 200, cuando ocurre un error procesando **Queries** el mensaje de error es enviado al cliente en la respuesta. Esto es debido a que en **GraphQL** nosotros podemos lanzar mas de una query a la vez, si una de las consultas falla, no deberíamos cambiar el código de estado para toda la solicitud. Algunas de las consultas en la petición se pueden resolver adecuadamente, otras pueden fallar, pero aún tenemos que devolver el status 200. + +## Analisis de datos en el servidor + +**GraphQL** nos permite obtener información precisa sobre los datos que se solicitan al servidor. A medida que cada cliente especifica la información que le interesa, es posible entender como se están usando los datos disponibles. Esto nos puede ayudar a eliminar campos especificos que ya ningún cliente usa. También podemos analizar el rendimiento de las peticiones solicitadas por el servidor. **GraphQL** utiliza **resolvers** para recopilar los datos que solicita un cliente, podemos medir el rendimiento de estos **resolvers** para obtener información de cuellos de botella en el sistema. + +# Ecosistema GraphQl + +El ecosistema **GraphQL** nos da una gran cantidad de librerías, herramientas y servicios. Aqui os dejo un par de ellos. + +## Cliente + +- [Apollo Client](https://www.apollographql.com/docs/): Es una librería potente y flexible. Está soportado para varias plataformas y frameworks (React, Vuea, Angular, iOS, Android). + +## Tools + +- [GraphiQL](https://github.com/graphql/graphiql): Es una interfaz interactiva en el navegador que nos permite explorar y probar los **Schemas** que existen en el **Server** + +- [Apollo Optics](https://www.apollographql.com/engine/): Nos permite visualizar, monitorizar y scalar nuestros servicios **GraphQL** + +# Conclusión + +**GraphQL** implementa funcionalidades que solventan los problemas que tiene **REST**. Se centra en las necesidades de desarrollo que tenemos actualmente para crear **APIs** que se amolden a cualquier tipo de uso, su mantenimiento es menos costoso y nos permite agilizar los tiempos de desarrollo y cambios en la parte cliente. Tiene una comunidad muy activa que trata de mejorar el producto y sobre todo la experiencía de desarrollo. diff --git a/content/blogs/2018-05-31-sinergia-del-equipo/es.md b/content/blogs/2018-05-31-sinergia-del-equipo/es.md new file mode 100644 index 0000000..8eee36c --- /dev/null +++ b/content/blogs/2018-05-31-sinergia-del-equipo/es.md @@ -0,0 +1,46 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-05-31-sinergia-equipo/header.png +navigation: True +title: "La Sinergia del Equipo" +date: 2018-05-31 12:00:00 +tags: filosofia +class: post-template +subclass: 'post' +author: aclopez +--- + +"La belleza de un movimiento de ajedrez no se refleja en su apariencia, sino en el pensamiento que hay detrás de él." [Tarrasch](https://es.wikipedia.org/wiki/Siegbert_Tarrasch) + +"A los usuarios finales les da igual que usemos o no DDD, ellos eso no lo ven". Creo que esta frase puede ser escuchada en cualquier equipo de desarrollo de software. Es una creencia muy común, y bajo mi punto de vista errónea, que los usuarios finales no sean conscientes que usemos o no ciertos patrones/diseños que den calidad a nuestro código. Es verdad que no van a conocer el término DDD y les va a importar poco lo que quieran decir sus siglas; pero ellos sí van a percibir si el producto software que están usando ha sido desarrollado con calidad o se ha sacado una versión rápida por presiones de tiempo o cualquier otro impedimento. + +...Una vez más ahí estaba Resharper, resaltándome una variable que no está siendo usada en mis narices. Programando soy un poco despistado. Menos mal que el programita de JetBrains te avisa de estas cosas y solo con acercar el ratón a la variable te sale la bombilla, le das a arreglar y listo. Creo que es una de las mejores herramientas de programación, ¡no sé qué haría yo sin ella!... + +Un producto software siempre tiene un equipo detrás. El product owner, el scrum master, los desarrolladores, los analistas, los diseñadores, los posibles comerciales de ese producto, etc. ¿Creéis que el usuario final no ve si en un equipo ha habido compenetración desarrollándolo? En mi opinión eso es algo que se acaba viendo. Quizás no se oyen los gritos que puedas percibir en un restaurante cuando hay mal ambiente entre camareros y cocinas, como pasa en los programas de Chicote; pero puede que esos "gritos" o malentendidos dentro del equipo de desarrollo o entre los desarrolladores y las demás piezas de un producto software, no se escuchen literalmente, pero seguro que quedan reflejados en algún bug, alguna implementación no optimizada para el uso de un usuario o incluso en alguna grave vulnerabilidad. + +...Se ha acercado Juan a mi sitio. Se cree muy listo. Le quería preguntar una cosa sobre si era posible quitar un switch del código. Él siempre dice que se pueden eliminar, pero como se cree tan "listillo" casi nadie le hace caso ya cuando habla. Tienes que ir con pies de plomo con él. Pues después de resolverme la pregunta me ha dicho: "¿pero esa variable la estas usando? Si no la usas bórrala". No sé para qué me avisa de esas cosas ¿Para hacerse el importante? ¿Acaso le he preguntado sobre eso? El código funciona con o sin esa variable. Como le gusta encontrar los fallos de los demás... + +¿Habéis sido alguna vez Juan? ¿O quizás habéis sido alguna vez el programador/programadora que se metía con Juan? ¿Y qué me decís del que usaba Resharper? Está muy de moda el término de ["Comunicación No Violenta"](http://www.comunicacionnoviolenta.com/), y su práctica en el mundo del software entre desarrolladores. ¿Creéis que Juan ha sido mucho más violento que Resharper? Ambos han dado la misma información; pero su receptor ha obtenido el mensaje de manera diferente. ¿Y si os dijera que el desarrollador/desarrolladora que se metía con Juan era el mismo que dijo la frase de Resharper? + +Yo pienso que nos estamos volviendo locos. Si ya es difícil dominar el arte de la programación, añadir a nuestras aptitudes el arte de la comunicación no violenta me parece demasiado. Creo que más que el emisor del mensaje, hay que trabajar en el receptor, para que no vea maldad en su equipo. Es fácil que el emisor del mensaje tienda a exagerar el estado del código, sobre todo si su visión es negativa, ya que en nuestro lenguaje tendemos a exagerar más las cosas negativas que a elogiar las positivas. Pero eso no debe bloquear al equipo y quedarse mirando el dedo que señala la luna; sino que, para mí, en un buen equipo el receptor debería ser consciente que detrás de ese dedo le están señalando la luna que se le ha pasado sin querer, y si éste no es capaz de reaccionar así, debe haber algún miembro del equipo que medie para que los dos desarrolladores se entiendan. + +Quizá si una máquina te dice que tienes una vulnerabilidad porque estás paginando los resultados de tu consulta de EF no te moleste tanto si te lo dice tu compañero de enfrente. Claro, tu compañero de enfrente, ese con el que "compites" por tu puesto. A Resharper nadie le va a quitar el puesto... + +Yo he estado en los dos lados. En el de Juan y en el de su compañero. Me da pena que vivamos en un mundo capitalista donde se "compite" por un puesto de trabajo y muchas veces los sueldos de los programadores no sean acordes a su función en el equipo, pero son las reglas del tiempo que nos ha tocado vivir y eso debería quedar aparte. Nunca se compite con tus compañeros de equipo. Esa lucha del sueldo será siempre entre tu jefe y tú, pero tu equipo debe quedar fuera de esa batalla. Por tanto, nadie compite contra nadie, mucho menos dentro de un mismo equipo creando un producto software, por mucho que esté en juego el puesto de jefe del producto estrella de tu empresa donde te da acceso directo a cosas de jefes. Eso será más política que desarrollo de software, y si no eres capaz de tener un equipo sin ese tipo de problemas, tu usuarios finales "oirán" esas discusiones en tu producto final. + +Evidentemente influye la manera que tenemos de expresarnos. Es algo que no hay que esconder. Pero vuelvo a la pregunta de: ¿es Juan más violento que Resharper? Es muy complicado ser siempre complaciente con tu receptor, en especial si ya te han etiquetado de "hater". Confiamos ciegamente en lo que dice Resharper, porque, sin lugar a dudas, es mucho más fiable que cualquier persona; pero no somos capaces de preguntar después de una crítica de un compañero el porqué de su feedback negativo. Es decir, quizás sea cierto que sin eso no funcione, pero cuando alguien dice "eso es una mierda" hay un motivo detrás, y si lo único que pensamos es en nuestro ego y en que ha dicho que lo nuestro es una mierda por atacarnos, nos vamos a perder ese porqué, esa oportunidad de aprender algo nuevo. Ya que, seamos realistas, es una oportunidad. Puede que eso que esconde ese "esto es una mierda", "no sé como programáis aquí" o un largo etcétera de expresiones "violentas" no sea más que un desconocimiento por parte del emisor, pero ¿no merece la pena escucharlo si tenemos alguna duda al respecto? + +He estado en el lado de Juan y sé que es difícil que se pasen el día cuestionando tus comentarios. Sobre todo porque no los haces por molestar, los haces por mejorar al equipo. Pero se produce un efecto rebote y el equipo, en lugar de mejorar, empeora, ya que acaba tomando una posición a la defensiva frente a ti. + +También he estado en el lado de sentir el ego muy alto por algo que habían dicho sobre mi código. Tengo que reconocer que al principio me lo tomaba todo fatal. De hecho, no era capaz de reaccionar. Pero ahora intento siempre responder con un "¿por qué dices eso?" ante alguien dice que eso está "mal". Hay muchos tonos de mal y a mí me gusta saber la mejor solución a un problema software; aunque luego no dé tiempo a implementarla si estamos muy justos de tiempo o haya cualquier otro impedimento que no nos permita refactorizarlo todo. + +Por eso soy un fiel defensor de las [PRs](http://www.nocountryforgeeks.com/pull-requests/), donde todo el mundo puede recibir feedback de cualquiera del equipo. Pero para ello hay que ser serios. No dejemos que las herramientas automáticas nos dominen y hablemos como personas. Un feedback sobre nuestro código siempre está hecho para que lo mejoremos, olvidemos rencillas personales y preguntemos qué hay detrás de un comentario, ya que no sabemos si lo que nos han dicho en un tono "violento" es porque esa persona ha tenido una mala semana en el ámbito personal, si está desarrollando mucho código que no le gusta como está y tiene ya la expresión de "mierda de código" en la cabeza o simplemente está cansado de que lo cuestionen. + +Creemos un equipo de desarrollo con una sinergia positiva. Actuemos como personas entre todos y para paliar las rencillas que existan si hay alguna se pueden tomar medidas como: desayunar juntos los viernes, comer juntos un día a la semana en equipo sin hablar de código, ir a tomar cervezas con todo vuestro equipo para reír por cualquier tontería. El contacto humano fuera de los bits mejora mucho la unidad del equipo de desarrollo. + +Hay que trabajar en equipo esa sinergia para que no nos acaben dominando las máquinas como Resharper y no tengamos que acabar hablando como ellas, sin expresividad, ya que lo bueno que todavía tenemos los programadores es que somos personas humanas. Disfrutemos del tiempo que nos queda programando hasta que esas máquinas sin expresividad nos sustituyan; pero mientras ¡hablemos! ¡mejoremos como equipo! Y compartamos todas las ideas que tenemos en la cabeza con los demás sin prejuicios absurdos. + +¡Disfrutad de vuestros equipos! No solo programando, sino disfrutar también de las personas que tenéis detrás de esos programadores. Esa sinergia o complicidad o lo que surja entre vosotros seguro que acaba llegando al usuario final. + +Porque, parafraseando la frase del principio, la belleza de un producto software no radica en su apariencia, sino en los pensamientos o ideas que han surgido en el equipo que hay detrás de él. \ No newline at end of file diff --git a/content/blogs/2018-07-18-nuevo-react-boilerplate/es.md b/content/blogs/2018-07-18-nuevo-react-boilerplate/es.md new file mode 100644 index 0000000..7710e9b --- /dev/null +++ b/content/blogs/2018-07-18-nuevo-react-boilerplate/es.md @@ -0,0 +1,88 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-07-18-react-boilerplate/main.png +navigation: True +title: "Nuevo react boilerplate" +date: 2018-07-18 12:00:00 +tags: react +class: post-template +subclass: "post" +author: ivanrodri +--- + +# Nuevo react boilerplate + +Los miembros de **NoCountryForGeeks** hemos estado trabajando en un [proyecto base de **React**](https://github.com/NoCountryForGeeks/react-boilerplate) para que otros desarrolladores puedan aprovecharlo y usarlo para sus proyectos sin necesidad de configurarlo, de esta manera, evitamos perder días montando la estructura inicial de nuestro proyecto para hacerlo funcionar medianamente bien,esto nos ahorrará días de proyecto lo que permitirá que nuestros clientes se ahorren un pico. + +En este proyecto base, nos hemos enfocado mucho en la experiencia de desarrollo, tener un buen entorno configurado, ágil y donde sentirnos cómodos nos ayudará a ser mas productivos. + +## Aplicación + +Actualmente las librerías que usa el proyecto, están en las últimas versiones para sacar todo el partido a las nuevas _features_ que éstas ofrecen. El proyecto tiene una configuración de **react** + **redux** + **redux-saga** donde podremos usar un **store centralizado** para manejar nuestros datos y generar flujos de nuestra aplicación con la potencia que nos ofrece **redux-saga** y los generadores. Para los reducers, como no somos muy partidarios de los **switch**, hemos usado **redux-act** para tener unos reducers mas simples y legibles. El proyecto base también cuenta con **i18n** que nos permite tener **multilenguaje** en la aplicación con un **lazy loading** sin necesidad de configurar nada. También tenemos un componente **ErrorBoundary** para capturar todas las excepciones que tengamos en el **render**, de esta manera podemos manejar los errores a nuestro gusto. Por último y no por eso menos importante es la parte de estilos, para la cual hemos configurado **css-modules** para poder tener estilos con _scope_ por componente y así poder tratar los estilos como un objeto más de JavaScript. + +La aplicación lleva un **ServiceWorker** para poder trabajar de modo **offline** de esta manera, si perdemos la conexión, todos los ficheros y _assets_ de la aplicación estarán disponibles para su uso. + +## Preprocesador + +Para poder hacer funcionar muchas de las _features_ nombradas anteriormente, nos hemos apoyado en **Webpack** como **preprocesador**. Para ello hemos usado **Webpack 4** que ofrece múltiples avances respecto a la versión anterior, con **Webpack 4** el tamaño del bundle disminuye considerablemente (entorno al 70 - 80%) y las builds necesitan menos tiempo de bundle. En esta configuración hemos añadido todo tipo de **loaders** y **plugins**, para minificar imagenes (Sin perder calidad), minificas css, generar sprites, etc. + +Una de las cosas mas importantes que hemos añadido para conseguir un performance de carga de la aplicación más rápido es la minificacion de los ficheros, permitiéndonos rebajar hasta un **75%** el tamaño del **js** de salida. La minificación se hace a **.gz** y a **.br** (La que mas ganancia genera), estos ficheros seran usados directamente por los navegadores que lo soporten por lo que no es necesario hacer nada mas. + +## Entorno de desarrollo + +Para agilizar un poco el desarrollo y evitar cometer los típicos errores tontos cuando desarrollamos en JavaScript (usar variables no declaradas, escribir mal las variables, etc), hemos añadido un linter, que nos va a permitir identificar errores sintácticos directamente en el IDE, esto nos ayudará a agilizar un poco el proceso. Unificar el estilo de código entre desarrolladores, nos puede ayudar a entender mejor el código en algunas ocasiones, para eso, hemos añadido **prettier** que va a ser un formateador de código, una vez que guardemos, formateará el código de la manera que hayamos definido nuestras reglas, de esta manera todos tendremos el mismo estilo de código. + +Una de las cosas mas importantes a la hora de desarrollar es tener un entorno de desarrollo **perfectamente configurado**, unas veces puede ser por falta de conocimiento de extensiones o por pereza que no tenemos bien configurado el IDE, por ello, hemos creado una [**imagen docker**](https://hub.docker.com/r/nocountryforgeeks/vscode-js/) con **VS Code** totalmente configurado y listo para trabajar. + +El proyecto tiene configurado **HMR** (Hot Module Replacement) que nos permitirá reflejar los cambios en caliente sin perder el estado de la aplicación y con una rapidez inmediata. + +## Docker + +## Overview + +Es una primera versión en la que continuamos trabajando para mejorarla. El objetivo es dar la máxima flexibilidad e ir añadiendo nuevas funcionalidades a medida que se vaya avanzando. Tenemos marcado un **Road Map** de mejora y actualización: + +- Creación de un provider de temas +- Creación de un CLI + - Generar el proyecto a través del CLI + - Opción de crear un proyecto dinámicamente según configuración requerida + - Extraer la configuración webpack como un paquete npm y permitir que sea extensible +- Diferentes opciones de build + - GitHub + - Gitlab + - VSTS +- Service worker + - Permitir usar la aplicación sin conexión. + - Cachear las todas las llamadas +- Notificaciones + - Firebase + - Azure + - Amazon +- Uso de servicios nativos (GPS, Webcam, Micrófono) +- Plash screen +- Soporte para PWA +- SSR + +<h2 align="center">Contributors</h2> + +<table> + <tbody> + <tr> + <td align="center"> + <img width="150" height="150" + src="https://avatars2.githubusercontent.com/u/5735315?s=460&v=4"> + <a href="https://github.com/franmolmedo">Francisco Manuel Olmedo</a> + </td> + <td align="center"> + <img width="150" height="150" + src="https://avatars0.githubusercontent.com/u/22966198?s=460&v=4"> + <a href="https://github.com/IvanRodriCalleja">Iván Rodríguez</a> + </td> + </tr> + <tbody> +</table> + +Puedes contribuir a este proyecto mediante la publicación de [issues](https://github.com/NoCountryForGeeks/react-boilerplate/issues) para problemas detectados o sugerencias para añadir al **boilerplate**, también estaremos encantados de recibir una [pull request](https://github.com/NoCountryForGeeks/react-boilerplate/pulls) para aportar nuevas funcionalidades y solución de posibles bugs. + +<h4 align="center">Esperamos vuestro feedback!!</h4> diff --git a/content/blogs/2018-09-20-destructuring/es.md b/content/blogs/2018-09-20-destructuring/es.md new file mode 100644 index 0000000..77b8dbf --- /dev/null +++ b/content/blogs/2018-09-20-destructuring/es.md @@ -0,0 +1,439 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-09-18-destructuring/header.jpg +navigation: True +title: "Destructuring en Javascript" +date: 2018-09-20 12:00:00 +tags: javascript rambling-javascript +class: post-template +subclass: 'post' +author: aclopez +--- + +"Todo es muy difícil antes de ser sencillo" Thomas Fuller + +Hay una nueva programación que viene de camino. Una programación que da prioridad a una lectura fácil sacrificando un poco de dificultad a la hora de escribir el código. Ya hemos hablado de ello en posts como [La energía del código](https://geeks.ms/windowsplatform/2017/05/10/la-energia-del-codigo/) y el __destructuring__ de Javascript es una de las nuevas características de ES6 que sigue esa tendencia. Recuerda mucho al [Pattern matching de Haskell](http://learnyouahaskell.com/syntax-in-functions); pero programación funcional o no, lo que está claro es que esta nueva opción del lenguaje nos permite escribir código mucho más limpio y claro, y es nuestra obligación hacer uso del _destructuring_ siempre que nos permita dejar un código con más energía. + +## Definición + +_Destructuring_, o destructuración en un lenguaje más hispanizado, es una nueva característica de ES6 para Javascript que nos da la posibilidad de poder coger los datos de objetos o _arrays_ directamente y de manera múltiple, para extraerlos a variables o constantes. + +## Sintaxis + +La sintaxis del _destructuring_ es muy sencilla. Por un lado tenemos el objeto que queremos destructurar. Para extraer sus propiedades usamos las __{ }__, metiendo dentro de ellas sus respectivos nombres y con esto tenemos nuevas variables que contienen estas propiedades: + +```javascript + +const person = { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] +} + +const { name, age, hobbies } = person; + +console.log(name); // result => Pepe + +``` + +Si queremos poner nombres específicos para estas nuevas variables bastará con poner __:__ seguido del nuevo nombre de variable que queramos asignar en las propiedades destructurada: + +```javascript + +const person = { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] +} + +const { name: personName, age: personAge, hobbies } = person; + +console.log(name); // result => undefined +console.log(personName); // result => Pepe + + +``` + +Y de igual manera podemos destructurar los arrays: + +```javascript + +const person = { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] +} + +const { name: personName, age: personAge, hobbies } = person; + +const [ firstHobbie, secondHobbie, thirdHobbie ] = hobbies; + +console.log(secondHobbie); // result => running + +``` + +## Uso Práctico + +Después de haber visto la sintaxis del _destructuring_ seguro que os queda la duda de: ¿dónde podría aplicar yo esto para que mi código fuese más limpio y legible? La destructuración se puede hacer en muchos sitios, pero los más beneficiosos suelen ser: + +* Retornos de funciones +* Parámetros en las funciones +* Funciones de trabajo con _arrays_ +* Destructuring múltiple +* Importación de objetos +* Destructuring en React + +### Retornos de funciones. + +Cuando recuperamos datos de una función solemos recuperar un objeto con propiedades que nos devuelve el resultado de la misma. Para tener que interaccionar con sus propiedades tenemos que ir hasta ellas desde el objeto. Algo que podemos evitar si usamos adecuadamente el _destructuring_. + +```javascript + +function getPerson() { + return { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] + } +} + +const { name } = getPerson(); + +console.log(name); // result => Pepe. + +``` + +Como se puede ver podemos cazar al vuelo las propiedades devueltas por una función. Esto hace que el código sea mucho más limpio y que podamos atacar a las propiedades que de verdad nos interesan de los objetos que nos devuelven las funciones. Como podéis observar no he destructurado ni ```age``` ni ```hobbies``` ya que son propiedades con las que no voy a trabajar. + +### Parámetros en las funciones + +Muchas veces no usamos todos los parámetros de nuestras funciones y solo necesitamos un par de propiedades. En estos casos _destructuring_ también nos puede ayudar a dejar un código más claro. Pongamos una función _filter_ para poder filtrar las personas que tenemos en un _array_. Esta función _filter_ recibe un objeto con las opciones de filtrado de nombre y edad. + +```javascript + +const people = [ + { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] + }, + { + name: 'Juan', + age: 32, + hobbies: [ 'basket' ] + }, + { + name: 'Paco', + age: 45, + hobbies: ['running'] + } +] + +function getPeople(filter) { + return people.filter(person => (!filter.nameFilter || person.name === filter.nameFilter) && + (!filter.minAge || person.age > filter.minAge)); +} + +const peopleBiggerThan40 = getPeople({ minAge: 40 }); + +console.log(peopleBiggerThan40); +// result => [ +// { +// name: 'Paco', +// age: 45, +// hobbies: ['running'] +// } +// ] + +``` + +Esta función queda más limpia aplicando el _destructuring_ en los parámetros de la misma: + +```javascript + +const people = [ + { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] + }, + { + name: 'Juan', + age: 32, + hobbies: [ 'basket' ] + }, + { + name: 'Paco', + age: 45, + hobbies: ['running'] + } +] + +function getPeople({ nameFilter, minAge }) { + return people.filter(person => (!nameFilter || person.name === nameFilter) && + (!minAge || person.age > minAge)); +} + +const peopleBiggerThan40 = getPeople({ minAge: 40 }); + +console.log(peopleBiggerThan40); +// result => [ +// { +// name: 'Paco', +// age: 45, +// hobbies: ['running'] +// } +// ] + +``` + +También podemos añadir a los parámetros destructurados valores por defecto si nos interesase: + +```javascript + +const people = [ + { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] + }, + { + name: 'Juan', + age: 32, + hobbies: [ 'basket' ] + }, + { + name: 'Paco', + age: 45, + hobbies: ['running'] + } +] + +function getPeople({ nameFilter, minAge = 30 }) { + return people.filter(person => (!nameFilter || person.name === nameFilter) && + (!personAge || person.age > minAge)); +} + +const peopleBiggerThan40 = getPeople({}); + +console.log(peopleBiggerThan40); +// result => [ +// { +// name: 'Juan', +// age: 32, +// hobbies: [ 'basket' ] +// }, +// { +// name: 'Paco', +// age: 45, +// hobbies: ['running'] +// } +// ] + +``` + +### Funciones de trabajo con _arrays_ + +También es interesante usar _destructuring_ en las funciones para trabajar con _arrays_. Podéis verlas [en nuestro post de arrays](http://www.nocountryforgeeks.com/rambling-javascript-3-arrays/). En estas funciones la combinación de _arrows functions_ con el _destructuring_ suele generar un código muy legible: + +Por ejemplo la función anterior podría quedar reducida a: + +```javascript + +const people = [ + { + name: 'Pepe', + age: 26, + hobbies: ['chess', 'running', 'basket'] + }, + { + name: 'Juan', + age: 32, + hobbies: [ 'basket' ] + }, + { + name: 'Paco', + age: 45, + hobbies: ['running'] + } +] + +function getPeople({ nameFilter, minAge }) { + return people.filter(({ name, age }) => (!nameFilter || name === nameFilter) && + (!minAge || age > minAge)); +} + +const peopleBiggerThan40 = getPeople({ minAge: 40 }); + +console.log(peopleBiggerThan40); +// result => [ +// { +// name: 'Paco', +// age: 45, +// hobbies: ['running'] +// } +// ] + +``` + +Hemos destructurado el objeto _person_ dentro de la _arrow function_ del _filter_. Así todo queda mucho más conciso y claro y no hace falta usar _person._ para poder acceder a las propiedades de _person_. + +### Destructuring múltiple + +Podemos llevar el _destructuring_ de objetos hasta su máxima expresión, es decir, podemos hacer _destructuring_ dentro de nuestro propio _destructuring_ hecho. + +```javascript + +const people = [ + { + names: { + name: 'Pepe', + surname: 'Gonzalez' + }, + age: 26, + hobbies: ['chess', 'running', 'basket'] + }, + { + names: { + name: 'Pepe', + surname: 'Gonzalez' + }, + age: 32, + hobbies: [ 'basket' ] + }, + { + names: { + name: 'Pepe', + surname: 'Gonzalez' + }, + age: 45, + hobbies: ['running'] + } +] + +function getPeople({ nameFilter, minAge }) { + return people.filter({ names: { name }, age } => (!nameFilter || name === nameFilter) && (!minAge || personAge > minAge)); +} + +const peopleBiggerThan40 = getPeople({ age: 40 }); + +console.log(peopleBiggerThan40); +// result => [ +// { +// name: 'Paco', +// age: 45, +// hobbies: ['running'] +// } +// ] + +``` + +Esta vez el parámetro _name_ ha sido cogido por una doble destructuración. Podemos tener todas las _destructuring_ que tengamos siempre que no lo mezclemos con el _destructuring de arrays_, ya que no serían compatibles uno con otro. + +En cambio, si realizamos el _destructuring_ solo de _arrays_ también lo podemos realizar de manera múltiple: + +```javascript + +const [first, [[second], third]] = ['apple', [['banana'], 'orange']]; + +console.log(first); +// apple +console.log(second); +// banana +console.log(third); +// orange + +``` + +Este _array_ de _arrays_ ha sido destructurado. Si lo analizamos el elemento _'apple'_ ha sido destructurado de una manera simple. En cambio _'banana'_ está dentro de un _array_ que a la vez está dentro de un _array_ y hemos podido hacer el _destructuring_ múltiple sin ningún problema. _'orange'_ finalmente es un elemento dentro de un _array_ por lo que su nivel de destructuración ha sido de 2. + +### Importación de módulos de ES6 + +Otro uso muy interesante del _destructuring_ es en la importación de módulos de es6. En lugar de importarnos el objeto entero podemos destructurarlo: + +Sin _destructuring_: + +```javascript + +import React from 'react'; + +export class App extends React.Component { + render() { + return ( + <div>hello world!</div> + ); + } +} + +``` + +Con _destructuring_: + +```javascript + +import React, { Component } from 'react'; + +export class App extends Component { + render() { + return ( + <div>hello world!</div> + ); + } +} + +``` + +### Destructuring en React + +_Destructuring_ es una técnica usada mucho en los componentes React ya que les da mucha más legibilidad. Las _props_ suelen ser un blanco fácil para poder realizar el _destructuring_ siempre que se use más de una. + +Si comparados dos códigos con o sin _destructuring_ podemos ver la diferencia entre ellos. + +Sin _destructuring_: + +``` + +import React from 'react'; + +import { SearchPanel } from './MasterPage/SearchPanel.js'; +import { SearchResult } from './MasterPage/SearchResults.js'; + +import { root } from './masterPage.scss'; + +const MasterPage = (props) => ( + <main className={root}> + <SearchPanel search={props.search} /> + <SearchResult results={props.results} isLoading={props.isLoading} /> + </main> +); + +export { MasterPage }; + +``` + +Con _destructuring_: + +``` + +import React from 'react'; + +import { SearchPanel } from './MasterPage/SearchPanel.js'; +import { SearchResult } from './MasterPage/SearchResults.js'; + +import { root } from './masterPage.scss'; + +const MasterPage = ({ search, results, isLoading }) => ( + <main className={root}> + <SearchPanel search={search} /> + <SearchResult results={results} isLoading={isLoading} /> + </main> +); + +export { MasterPage }; + +``` + +## Conclusiones + +_Destructuring_ es una nueva característica muy potente del lenguaje que nos permite escribir código más legible, aunque como todo no hay que abusar de ella. Esto han sido pequeños ejemplos, pero seguramente si lo que vas a destructurar solamente tiene una propiedad, la mejor opción no sea usarlo. _Destructuring_ coge mucha fuerza sobre todo cuando se usan varias propiedades de un mismo objeto que se pueden destructurar, ya que al hacer el desglose de las propiedades todo suele quedar más limpio y claro. \ No newline at end of file diff --git a/content/blogs/2018-10-02-yo-he-venido-aqui-a-equivocarme/es.md b/content/blogs/2018-10-02-yo-he-venido-aqui-a-equivocarme/es.md new file mode 100644 index 0000000..d9fc4f0 --- /dev/null +++ b/content/blogs/2018-10-02-yo-he-venido-aqui-a-equivocarme/es.md @@ -0,0 +1,35 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-10-02-equivocarse/header.jpg +navigation: True +title: "Yo he venido aquí a equivocarme" +date: 2018-10-02 12:00:00 +tags: filosofia +class: post-template +subclass: 'post' +author: aclopez +--- + +"Estamos a un fracaso del éxito" + +Los programadores somos gente especial. Al menos eso creo que es lo que nos decimos a nosotros mismos. Demasiados conceptos abstractos rondan nuestra cabeza. Demasiados aprendizajes, demasiados patrones, demasiados cabezazos contra el ordenador. Y, para mí, lo peor de todo esto es que tenemos que disimular ante nuestros jefes que somos gente con la cabeza bien amueblada, que sabe lo que hace. + +- ¿Ese producto que quieres hacer? No te preocupes, dominamos todos los frameworks actuales y podemos hacerlo con una metodología ágil perfecta para que todo salga según tus necesidades. Haremos un producto mínimo viable con solo las características que te hagan falta, con un despliegue continúo y una integración continúa para que tengas todos los avances que vayas haciendo al día. + +¿Que os parece la conversación? ¿Habéis mentido así alguna vez? Ah no, vosotros sois de los que domináis y sabéis gestionar el proyecto perfecto. A mí me gustaría responder de otra manera. + +- ¿Ese producto que quieres hacer? Sí, es posible. Pero va a cambiar mucho a lo largo del tiempo. Hemos venido aquí a equivocarnos. Vamos a poner todos nuestros medios para equivocarnos cada vez mejor, hasta que adaptemos el producto a las necesidades reales. Intentaremos seguir una metodología ágil y aprenderemos a adaptarnos mutuamente. Muchas veces no será posible y, seguramente, no acertaremos con las métricas a la primera; pero pondremos todo nuestro compromiso en crear esa conexión que dará al producto la calidad que busca. + +Sé que no tengo mucho futuro como _manager_; pero tengo que reconocer que cuando me siento en mi silla de trabajo muchas veces pienso que voy allí a equivocarme. Y eso me saca una sonrisa. Porque si no fuera así, estaría en el sitio equivocado. Al fin y al cabo, hemos hablado muchas veces de que somos __eternos aprendices__ y que si a tu nuevo proyecto no has sido capaz de ponerle un nuevo ingrediente con el cual te vas a equivocar, quizás es que has perdido la ilusión y necesitas nuevas metas. + +Os imagináis en la entrevista de trabajo de esa empresa que tanto te gusta. Allí sentado donde el manager te pregunta: + +- ¿Por qué quieres venir a nuestra empresa? ¿qué es lo que te atrae? +- Yo quiero venir a vuestra empresa a equivocarme. + +Siempre he pensado que lo más importante de un programador es su mentalidad. Y yo no soy entrevistador, pero seguramente yo contrararía a ese chico. Estar dispuesto a equivocarse es estar dispuesto a aprender. Estar dispuesto a equivocarse es estar dispuesto a encontrar la solución más correcta. Estar dispuesto a equivocarse, en definitiva, también es una mentalidad ganadora. + +Si os fijáis un día en vuestra oficina del trabajo seguro que hay un montón de compañeros vuestros equivocándose en este preciso momento, buscando la solución al problema de sus proyectos. Y no solo se equivocan a cada minuto, sino que también los puedes escuchar hablar de ello en el café, en los pasillos o en sus conversaciones internas mirando las pantallas de sus ordenadores. Equivocarse viene en el adn de la programación y si te equivocas junto a los mejores, cada vez te equivocaras mejor y tus errores irán subiendo de nivel. Un desarrollador nunca deja de equivocarse; pero conforme se va teniendo experiencia los errores aumentan en dificultad ya que con más experiencia y más conocimientos somos capaces de ver allá de lo que veíamos al principio. + +Por tanto que no te dé miedo. Propón. Propón esos cambios que están en tu cabeza. Y en tu próxima reunión, en tu próxima entrevista o en tu próxima retrospectiva de equipo reconócelo: tú estás allí para equivocarte. \ No newline at end of file diff --git a/content/blogs/2018-10-04-grpc-protocol-buffers/es.md b/content/blogs/2018-10-04-grpc-protocol-buffers/es.md new file mode 100644 index 0000000..61e8681 --- /dev/null +++ b/content/blogs/2018-10-04-grpc-protocol-buffers/es.md @@ -0,0 +1,381 @@ +--- +layout: post +current: post +cover: assets/images/posts/2018-10-04-grpc-protocol-buffers/header.jpg +navigation: True +title: "GRPC: From Zero to Hero (1): Protocol buffers" +date: 2018-10-04 12:00:00 +tags: grpc +class: post-template +subclass: "post" +author: franmolmedo +--- + +Cuando queremos exponer un servicio (o conjunto de servicios) a aplicaciones externas, actualmente pensamos en crear un API REST que pueda ser accedida desde dichas aplicaciones. Esta solución es por la que, generalmente optaríamos. + +También podríamos plantearnos crear un endpoint GraphQL para exponer nuestros servicios a través +de un esquema bien definido. + +Sin embargo, si echamos la vista atrás, vemos que se utilizaban cosas como [CORBA](http://www.corba.org/) o [RMI](https://es.wikipedia.org/wiki/Java_Remote_Method_Invocation). Estos sistemas de +comunicación estaban basados en lo que se conoce como [RPC (Remote procedure call)](https://es.wikipedia.org/wiki/Llamada_a_procedimiento_remoto), imagino a un montón de gente huyendo del post en este momento, jaja. Esta técnica consiste en que desde una máquina podemos ejecutar código (un método por ejemplo) en otra máquina. Centrándonos en el modelo cliente-servidor que estamos considerando, realmente desde el cliente podremos llamar a métodos (o servicios) definidos en el servidor. +Sobre esta idea se construye GRPC. + +## 1.- ¿Qué es GRPC? + +[GRPC](https://github.com/grpc) es un proyecto open source desarrollado por Google , que forma parte de la CNCF (Cloud Native Computation Foundation). Permite llamar desde una aplicación cliente a un método o servicio que se encuentre definido en un servidor (que puede estar localizado en otra máquina) como si fuera un método +que estuviera definido localmente. + +La idea se basa en definir servicios que constarán de una serie de métodos +RPC en los cuales se detallan el mensaje de entrada que reciben y el mensaje de salida que generan. Realmente, solo es necesario definir estos métodos y los mensajes que se van a intercambiar y GRPC se encargará de realizar toda la labor oscura por nosotros. + +![GRPC Basic structure](/assets/images/posts/2018-10-04-grpc-protocol-buffers/grpc-basic.svg) + +GRPC es moderno, rápido y eficiente de baja latencia, construido utilizando HTTP/2, que soporta streaming, es independiente del lenguaje empleado y que, además, hace relativamente sencillo el incorporar autenticación, balance de carga, trazas y monitorización. + +Por defecto, GRPC utiliza Protocol Buffers (también llamados Protobuf) para definir los servicios y los mensajes que se intercambiarán. Estos [protobuf](https://developers.google.com/protocol-buffers/docs/overview) se enmarcan en un protocolo, también desarrollado por Google, para el serializado / deserializado de datos. Si comparamos el uso de Protobuf con otros sistemas de serialización / deserialización de datos (como, por ejemplo XML o JSON) vemos que consigue más eficiencia, mejor mantenibilidad y el tamaño de los mensajes intercambiados es también más pequeño. Como referencia os dejo un [artículo de Auth0](https://auth0.com/blog/beating-json-performance-with-protobuf/) en los que realizan una comporación entre Protocol Buffers y JSON en distintas circunstancias: + +En esta pequeña entrada no vamos a entrar en comparaciones, sino que, nuestra idea, es mostrar cómo trabajar con protocol buffers y desarrollar un ejemplo sencillo de aplicación utilizando GRPC. En posteriores entradas hablaremos en más profundidad de GRPC, detallando los distintos tipos de servicios que podemos definir y cómo podemos implementarlos utilizando diferentes lenguajes de programación. + +## 2. Construyamos un chat + +Creo que es la aplicación de ejemplo clásica que se utiliza en todos los sitios para introducir GRPC y protocol buffers. En nuestro ejemplo vamos a crear un servidor en Java, que será quien reciba los mensajes de los clientes y los reenvíe a todos ellos en tiempo real. + +### 2.1.- Utilizando protocol buffers + +Como hemos comentado anteriormente, lo primero que vamos a realizar es definir los mensajes que se van a intercambiar entre el cliente y el servidor. Posteriormente, tendremos que definir el servicio que utilizará dicho mensaje. +Definiremos estos servicios y mensajes empleando una sintaxis de alto nivel, utilizando protocol buffers. Lo que haremos es crear un fichero con extension **_.proto_** donde utilizaremos la sintaxis de protocol buffers. En concreto, utilizaremos la versión 3 de la misma. La definición de nuestro mensaje quedaría de la siguiente forma: + +```protobuf +syntax = "proto3"; + +message ChatMessage { + string from = 1; + string message = 2; +} +``` + +La primera línea de nuestro archivo **_.proto_** nos define la sintaxis que utilizaremos dentro del lenguaje. Utilizaremos la versión 3, aunque podríamos utilizar la versión 2. +Posteriormente, definimos nuestro mensaje. Para ello, se utiliza la palabra reservada **_message_** seguido del nombre que queramos. Nosotros hemos sido sumamente originales y lo hemos bautizado como _ChatMessage_. Cada mensaje constará de una serie de campos que situaremos entre llaves. Cada campo, a su vez, está compuesto, generalmente, por tres elementos: + +- **_Tipo_**: En nuestro ejemplo los dos campos van a ser de tipo _string_. +- **_Nombre_**: El nombre del campo. +- **_Tag_**: Es ese numerito que situamos tras el signo igual. + +Hagamos un pequeño inciso para comentar detalles sobre los tags y los tipos que podemos utilizar. + +#### Tags + +Como acabamos de comentar, es necesario especificar un tag para cada campo de nuestros mensajes. + +- **¿Por qué?**: Los mensajes que intercambiamos utilizando protocol buffers se transmiten en binario, con lo que se utiliza ese identificador para localizar cada campo dentro del mensaje. Por lo tanto, se podría decir que, para el mensaje en sí, lo realmente importante es el **_tag_** y no el nombre del campo. +- **Consideraciones**: Podremos usar tags desde el número 1 hasta el 2^29 - 1 (excluyendo desde 19000 hasta 19999 que son reservados por **_Google_**). Sin embargo, es interesante constar que, para codificar los tags entre 1 a 15 se empleará solamente un byte, por lo que es recomendable utilizarlos para los campos que se transmitan habitualmente. + +#### Tipos + +Es obvio decir que podremos definir diferentes tipos de campos en nuestros mensajes. Anterormente hemos visto el tipo _string_, pero existen muchos más: + +- Enteros: _int32_, _int64_, _uint32_, _uint64_, _sint32_, _sint64_, _fixed32_, _fixed64_, _sfixed32_ y _sfixed64_. Toda esta gama es provista con el propósito de conseguir el mayor grado de eficiencia en la codificación de los mensajes, dependiendo del rango de valores que queramos representar, si se permiten o no números negativos y de cómo se va a realizar la codificación de los números negativos en el caso de que existan. +- Punto flotante: Tenemos dos tipos principales: _float_ y _double_, según queramos 32 o 64 bits de precisión. +- Booleanos: Con valores _True_ y _False_ y se representa con el tipo _bool_. +- Cadenas de caracteres: Es el tipo _string_ que hemos presentado antes. +- Array de bytes: Se representan como _bytes_. +- Enumerados: Se utiliza la palabra reservada **_enum_** para definir un enumerado y entre llaves se situarían los valores de los que consta. Veremos posteriormente un ejemplo. +- Lista: Para declarar una lista en nuestros mensajes utilizaremos el concepto de campos repetidos. Para ello, emplearemos la palabra reservada **_repeated_**, de la siguiente forma: + +```protobuf +message ChatMessage{ + repeated string messages = 3; +} +``` + +Con lo que, nuestro mensaje **_ChatMessage_** estaría compuesto por una lista de mensajes. + +#### Consideraciones de uso + +Antes de proseguir con nuestro ejemplo del chat, vamos a comentar de forma somera algunos puntos interesantes a la hora de trabajar con protocol buffers: + +- Es posible definir varios mensajes en el mismo fichero. +- Podemos definir mensajes dentro de otros con el fin de establecer "localidad" para un determinado tipo o para evitar conflictos de naming. +- Es conveniente utilizar varios archivos **_.proto_** donde definir la totalidad de nuestros mensajes. Por ello se introduce la palabra reservada _import_ que nos permite importar el contenido de un archivo a otro y poder utilizar tipos declarados en otros archivos. +- Es conveniente definir el paquete donde se van a generar los mensajes una vez que sean convertidos al lenguaje de programación con el que vayamos a trabajar. Para ello, se utiliza la palabra reservada _package_ seguida de un string con el nombre del paquete que deseemos. +- En relación a lo anterior, podemos establecer un conjunto de opciones específicas para cada lenguaje destino, donde nos permite especificar el paquete (para java, por ejemplo) o el namespace destino (para c#)... Estas opciones son: + +```protobuf +option csharp_namespace = "nocountryforgeeks.blog"; +option cc_enable_arenas = true; +option go_package = "nocontryforgeeks/blog"; +option java_package = "com.nocountryforgeeks.blog.grpc.chat"; +option java_outer_classname = "Chat"; +option java_multiple_files = true; +option objc_class_prefix = "GRPC"; +``` + +Todas las opciones explicadas, junto con el resto de especificación del lenguaje lo puedes encontrar [aquí](https://developers.google.com/protocol-buffers/docs/proto3) + +Como reglas de estilo para estos ficheros **_.proto_** recomiendo basarse en la guía publicada por **Uber** en [github](https://github.com/uber/prototool/blob/dev/etc/style/uber/uber.proto), aunque lo podemos resumir en cuatro puntos: + +- Utilizar [**_Camel Case_**](https://es.wikipedia.org/wiki/CamelCase) para el nombre de los mensajes. +- Para el nombre de los campos utilizar [**_Snake Case_**](https://en.wikipedia.org/wiki/Snake_case), básicamente, en minúsculas con guión bajo separando cada palabra. +- Para el nombre de los enumerados también utilizamos **_Camel Case_**. +- Por último, para los valores del enumerado utilizamos Mayúsculas con cada palabra separada por guión bajo. + +```protobuf +message WhatTheFuckAreYouDoingWithThatName { + string this_name_is_long = 1; + int32 this_number_is_long_named_too = 2; +} + +enum ThisEnumHasALongName { + FIRST_VALUE_OF_THIS_ENUM = 1; + SECOND_VALUE = 2; +} +``` + +#### Servicios + +Hasta ahora solamente hemos definido nuestro mensaje para el chat que, recordemos, es el siguiente: + +```protobuf +syntax = "proto3"; + +message ChatMessage { + string from = 1; + string message = 2; +} +``` + +Sin embargo, con el fin de poder comunicar una aplicación cliente con un servidor es necesario definir servicios que intercambien los mensajes. Aquí es donde entra en juego **_GRPC_**. +Por lo tanto, en nuestro archivo **_.proto_** vamos a añadir un servicio que utilice el mensaje que hemos definido. El servicio se especificaría de la siguiente forma: + +```protobuf +service ChatService { + rpc chat (stream ChatMessage) returns (stream ChatMessage); +} +``` + +La sintaxis no es compleja. Definimos un servicio, _ChatService_, utilizando la palabra reservada **_service_**, que tendrá varios métodos que podremos usar (en cada método utilizamos la palabra reservada **_rpc_** indicando que utilizaremos un protocolo rpc). Nosotros hemos creado el método **_chat_** el cual espera un mensaje del tipo _ChatMessage_ de entrada y responde con otro _ChatMesage_. Los más avispados habrán notado la presencia de la palabra reservada _stream_ situada previamente a la declaración del tipo del mensaje desde cliente a servidor y del tipo de mensaje desde servidor a cliente. Y aquí es donde se encuentra uno de los aspectos diferenciadores de **_GRPC_**. Como hemos comentado antes, **_GRPC_** se basa en **_HTTP2_**, lo que permite, entre otras cosas, habilitar el streaming en ambos sentidos (tanto desde servidor a cliente, como desde cliente a servidor). Con ello, podremos definir cuatro tipo de servicios diferentes: + +- **Servicios tradicionales**: Servicios sin streming. El cliente envía una petición y servidor responde. +- **Servicios con streaming desde servidor**: El cliente envía una petición al servidor y se abre un canal de streaming que permite al servidor mandar datos por el canal creado conforme se vayan generando. +- **Streaming desde el cliente**: El cliente puede abrir un canal de streaming hacia el servidor. +- **Streaming bidireccional**: Es el ejemplo que nos ocupa. El canal se establece en ambos sentidos permitiendo al cliente y al servidor mandar mensajes en el momento que consideren oportuno. + +Para declarar uno u otro tipo de mensajes basta con añadir la palabra reservada _stream_ delante del tipo de mensaje de cliente a servidor y/o del tipo de mensaje de servidor a cliente. + +#### Generación del código para cada lenguaje + +Perfecto, ya tenemos la definición de nuestro servicio, conjuntamente con la definición de nuestro mensaje. Ahora necesitamos generar el código en el lenguaje en el que nos sintamos más cómodos para empezar a trabajar en nuestra aplicación. Para ello tendremos que descargar la aplicación [**_protoc_**](https://github.com/google/protobuf/releases) que corresponda a nuestro sistema operativo (en nuestro caso _Windows_). Una vez descargado, descomprimimos el fichero en la carpeta destino deseada e incorporamos la subcarpeta **bin** al **_PATH_**. Podemos verificar si todo funciona correctamente desde el terminal: + +![Protoc](/assets/images/posts/2018-10-04-grpc-protocol-buffers/protoc.PNG) + +Tenemos que especificar el archivo o los archivos de entrada y el lenguaje en los que queremos obtener la salida. Por ejemplo, nosotros tenemos el fichero _chat.proto_ dentro del directorio _proto_ y queremos generar la implementación en **python**, **java** y **c#**. Para ello, utilizaremos los siguientes comandos: + +```console +protoc -I=proto --python_out=proto/python proto/chat.proto +protoc -I=proto --csharp_out=proto/csharp proto/chat.proto +protoc -I=proto --java_out=proto/java proto/chat.proto +``` + +Previamente hemos creado tres carpetas (python, csharp y java dentro del directorio proto que es donde tendremos el fichero **_chat.proto_**). + +- En **_C#_** se genera el fichero **_Chat.cs_** con la implementación de nuestro servicio y el mensaje que podremos utilizar posteriormente en nuestra aplicación. +- En **_Java_**, por el contrario, se genera la estructura de carpetas típica según el nombre del paquete indicado (nosotros hemos usado la opción java\*package y java\*multiple*files) y vemos que se generan 3 archivos: \*\*\_Chat.java**\*, **\_ChatMessage.java**\* y **\_ChatMessageOrBuilder.java*\*\* +- Por último, para **_Python_** se genera, igualmente, un solo archivo **_chat_pb2.py_** + +![Ficheros generados](/assets/images/posts/2018-10-04-grpc-protocol-buffers/generatedFiles.PNG) + +### 2.2. Trabajando en el servidor + +Por fin nos pondemos manos a la obra. Como hemos comentado en el apartado previo vamos a generar el servidor de nuestra aplicación utilizando **_Java_**. +En concreto utilizaremos [**_gradle_**](https://gradle.org/) para construir nuestro proyecto y, gracias al uso de varios plugins, realizar el proceso de generación de los ficheros _java_ a partir de los ficheros _proto_ de forma automática. No vamos a entrar en la descripción punto a punto de como generar el proyecto utilizando _gradle_, pero podéis ver todo el código del proyecto en el [repositorio con el ejemplo](https://github.com/franmolmedo/GrpcChatExample). +Aparte de ello, nuestro proyecto consta de dos ficheros fundamentales: + +- El primero de ellos, _ChatServiceImpl.java_ se encarga de extender la clase abstracta _ChatServiceImplBase_ que se ha generado a partir de nuestro fichero descriptor **_chat.proto_**. El código queda como sigue: + +```java +import java.util.LinkedHashSet; + +import io.grpc.stub.StreamObserver; + +import com.nocountryforgeeks.blog.grpc.chat.ChatMessage; +import com.nocountryforgeeks.blog.grpc.chat.ChatServiceGrpc.ChatServiceImplBase; + +public class ChatServiceImpl extends ChatServiceImplBase{ + + private static LinkedHashSet<StreamObserver<ChatMessage>> observers = new LinkedHashSet<StreamObserver<ChatMessage>>(); + + @Override + public StreamObserver<ChatMessage> chat(StreamObserver<ChatMessage> responseObserver) { + observers.add(responseObserver); + + return new StreamObserver<ChatMessage>() { + + @Override + public void onNext(ChatMessage value) { + ChatMessage messageToSend = ChatMessage.newBuilder() + .setMessage(value.getMessage()) + .setFrom(value.getFrom()) + .build(); + System.out.println("Message received from: " + value.getFrom()); + observers.stream().forEach(observer -> observer.onNext(messageToSend)); + } + + @Override + public void onError(Throwable t) { + observers.remove(responseObserver); + } + + @Override + public void onCompleted() { + observers.remove(responseObserver); + } + }; + } +} +``` + +Básicamente implementamos el método _chat_ definido por nuestro servicio que consta de un _StreamObserver_ de entrada y otro de salida, ambos tipados con nuestro _ChatMessage_. Para cada mensaje entrante lo que hacemos es incorporar al cliente que ha remitido el mensaje al conjunto de observadores (es un conjunto para prevenir que podamos insertar dos veces al mismo observador) y posteriormente tendremos que considerar 3 casos diferentes, para lo cual implementamos los métodos _onNext_ para cuando todo ha ido correcto, _onError_ cuando se haya producido un error y _onComplete_ si se ha terminado correctamente la comunicación. +El caso más interesante es el método _onNext_. En él vemos una de las características más interesantes de la implementación en **_Java_** que consiste en que todo se realiza a través de un **Builder Pattern** (o patrón constructor). En este caso, para cada mensaje de entrada, generamos un mensaje de respuesta copiando el contenido del mensaje y el origen. Posteriomrnete lo que hacemos es recorrer los observadores que tenemos y llamar al método _onNext_ de cada uno de ellos con el mensaje que hemos construido. + +- El segundo fichero (**_ChatServer.java_**) lo utilizamos para crear y arrancar el servidor. El código queda de la siguiente forma: + +```java +package com.nocountryforgeeks.blog.grpc.chat.server; + +import java.io.IOException; + +import io.grpc.Server; +import io.grpc.ServerBuilder; + +public class ChatServer { + public static void main(String [] args) throws IOException, InterruptedException { + System.out.println("Hello! The server is running"); + + Server server = ServerBuilder + .forPort(8080) + .addService(new ChatServiceImpl()) + .build(); + + server.start(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("Received shutdown request"); + server.shutdown(); + System.out.println("Shutdown resolved"); + })); + + server.awaitTermination(); + } +} +``` + +La implementación es aún más sencilla. En el método **main** creamos el servidor utilizando el _ServerBuilder_ (de nuevo el patrón builder) proporcionado, indicándole el puerto en el que queremos que escuche y los servicios que queremos que proporcione. +Tras ello, lo arrancamos y lo dejamos ejecutándose hasta que termine (por error) o se invoque su apagado. + +Para arrancar el servidor nos situamos en la carpeta base del proyecto y ejecutamos **_gradle run_**. + +![Servidor corriendo](/assets/images/posts/2018-10-04-grpc-protocol-buffers/serverRunning.PNG) + +### 2.2.- Clientes + +Nuestros clientes van a estar implementados en **C#**. En este caso, es necesario utilizar el **paquete NuGet** llamado _GRPC.Tools_ para generar los archivos que emplearemos en nuestra solución. Idealmente lo mejor es generar algún ficherito .bat con todos los pasos para generar los archivos e incorporar su ejecución en el proceso de build de la solución. En nuestro caso el **_.bat_** generado queda tal que así: + +``` +setlocal + +@rem enter this directory +cd /d %~dp0 + +set TOOLS_PATH=packages\Grpc.Tools.1.13.1\tools\windows_x64 + +%TOOLS_PATH%\protoc.exe -I ..\proto chat.proto --csharp_out . +%TOOLS_PATH%\protoc.exe -I ..\proto chat.proto --grpc_out . --plugin=protoc-gen-grpc=%TOOLS_PATH%\grpc_csharp_plugin.exe + +endlocal +``` + +Se generan dos archivos: **_Chat.cs_** y **_ChatGrpc.cs_** que incorporaremos a la solución. + +Una vez hecho esto, lo que queda es más simple. Construimos una ventana inicial y en el _code behind_ incluimos el siguiente snippet de código: + +```csharp +using System; +using System.Threading; +using System.Windows; +using Grpc.Core; +using Service; + +namespace ChatClient +{ + public partial class MainWindow + { + private ChatService.ChatServiceClient _chatServiceClient; + private AsyncDuplexStreamingCall<ChatMessage, ChatMessage> _call; + + public MainWindow() + { + InitializeComponent(); + + InitializeGrpc(); + } + + private async void InitializeGrpc() + { + var grpcChannel = new Channel("127.0.0.1:8080", ChannelCredentials.Insecure); + + _chatServiceClient = new ChatService.ChatServiceClient(grpcChannel); + + try + { + using (_call = _chatServiceClient.chat()) + { + while (await _call.ResponseStream.MoveNext(CancellationToken.None)) + { + var serverMessage = _call.ResponseStream.Current; + var displayMessage = + $"{serverMessage.From}: {serverMessage.Message}{Environment.NewLine}"; + ChatContent.Text += displayMessage; + } + } + } + catch (RpcException) + { + _call = null; + throw; + } + } + + private async void ButtonSend_OnClick(object sender, RoutedEventArgs e) + { + if (_chatServiceClient == null) return; + + await _call.RequestStream.WriteAsync(new ChatMessage + { + From = Name.Text, + Message = Message.Text + }); + + Message.Text = string.Empty; + } + } +} +``` + +La implementación, a nuestro gusto, es mucho menos elegante que la que hemos visto en **Java**. En este caso, definimos nuestro canal con la dirección del servidor (consideramos una ejecución en local) y creamos el cliente a partir de dicho canal. Después nos quedamos escuchando a que se produzca la recepción de un mensaje a través del canal y mostramos el mismo por pantalla. +En la parte inferior mostramos el código asociado al evento de pulsar el botón de enviar un mensaje. En él, si tenemos el canal de stream abierto, escribimos un nuevo mensaje en el mismo que llegará a servidor. + +El resultado de nuestra aplicación lo podemos ver en el siguiente gif: + +![Chat](/assets/images/posts/2018-10-04-grpc-protocol-buffers/chatDemo.gif) + +## Conclusiones + +Sólo hemos mostrado un pequeño ejemplo de lo que se puede conseguir. La idea es continuar la serie desgranando verdaderamente todo lo que nos puede ofrecer **_GRPC_** con ejemplos más realistas y usos avanzados. Queremos mostrar cómo usar **_GRPC_** en un desarrollo con microservicios y realizar pruebas de carga para ver como mejora su utilización comparándolo con un enfoque más tradicional (utilizando API REST con JSON), cómo podemos combinarlo con **_GraphQL_** y si merece la pena hacerlo, cómo lidiar con la evolución de los mensajes y servicios a lo largo del tiempo, hablar de los **flatbuffers** y cómo podemos usarlos con **GRPC**... + +Sin más, os dejo con algunos enlaces de referencia y el código del ejemplo: + +- [Página oficial GRPC](https://grpc.io/) +- [Protocol buffers](https://developers.google.com/protocol-buffers/) +- [Ejemplo de chat en github](https://github.com/franmolmedo/GrpcChatExample) + +Happy Coding! diff --git a/content/blogs/2019-01-31-no-mas-condicionales-anidados-abraza-el-patron-estrategia/es.md b/content/blogs/2019-01-31-no-mas-condicionales-anidados-abraza-el-patron-estrategia/es.md new file mode 100644 index 0000000..b7c0a1e --- /dev/null +++ b/content/blogs/2019-01-31-no-mas-condicionales-anidados-abraza-el-patron-estrategia/es.md @@ -0,0 +1,182 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-01-31-no-mas-condicionales-anidados-abraza-el-patron-estrategia/header.jpg +navigation: True +title: "No más condicionales anidados, abraza el patrón estrategia" +date: 2019-01-31 12:00:00 +tags: design-patterns +class: post-template +subclass: 'post' +author: cjdelgado +--- + +Hace algunos días un compañero de equipo formuló la siguiente pregunta en nuestro chat: + +> Tengo un controller que llama a una clase, dependiendo del valor del parámetro que se le pasa al controller el método al que se llama puede hacer una cosa u otra. + +[English version](https://www.carlosjdelgado.com/no-more-nested-conditionals-embrace-the-strategy-pattern/) + +El código que quería refactorizar era algo como esto (solo un ejemplo): + +```csharp +public int DoMathematicalOperation (string @operator, int a, int b) +{ + if (@operator == "+") + { + return Add(a, b); + } + + if (@operator == "*") + { + return Multiply(a, b); + } + + if (@operator == "/") + { + return Divide(a, b); + } + + throw Exception("You must pass a valid operator"); +} + +//METODOS PARA SUMAR, MULTIPLICAR Y DIVIDIR +``` +¿Quien no se ha encontrado nunca en esta situación? El product owner necesita una nueva funcionalidad para la aplicación, pero esta funcionalidad tiene algunas excepciones que cambian todo el comportamiento para algunos casos. Por ejemplo, tienes un servicio de búsqueda de vuelos que usa un servicio externo para devolver todos los vuelos disponibles en un día, pero hay algunas rutas que usan otro servicio para obtener esos vuelos, en este caso el comportamiento cambia completamente. + +Otro caso puede ser si cambias una funcionalidad, pero el product owner quiere que este inactiva en el entorno de producción hasta que el departamento de marketing (por ejemplo) lo apruebe, en ese caso querrás usar una feature flag en tus settings que cambian el comportamiento. + +Volviendo a nuestro ejemplo anterior es obvio que hay más de una implementación de una operación matemática con 2 operadores, el código anterior no respeta la S de los principios SOLID porque está asumiendo más de una responsabilidad al mismo tiempo (sumar, multiplicar y dividir), puede ser refactorizado separándolo para tener una implementación por operación matematica usando una interfaz como molde. + +Esta es la interfaz base de nuestro refactor: + +```csharp +public interface IMathematicalOperation +{ + int DoMathematicalOperation(int a, int b); +} +``` + +Ahora tenemos estas 3 implementaciones: + +```csharp +public class AddOperation : IMathematicalOperation +{ + public int DoMathematicalOperation(int a, int b) + { + return a + b; + } +} +``` + +```csharp +public class MultiplyOperation : IMathematicalOperation +{ + public int DoMathematicalOperation(int a, int b) + { + return a * b; + } +} +``` + +```csharp +public class DivideOperation : IMathematicalOperation +{ + public int DoMathematicalOperation(int a, int b) + { + if (b == 0) + throw new DivideByZeroException(); + + return a / b; + } +} +``` +Sobre este escenario es fácil de implementar un [patrón estrategia](https://en.wikipedia.org/wiki/Strategy_pattern), recuerda que cada operación depende del símbolo de operador que se le pase (+, *, /) entonces, ¿porque no presentar el operador como una nueva propiedad en cada implementación? + +```csharp +public interface IMathematicalOperation +{ + string Operator { get; } + int DoMathematicalOperation(int a, int b); +} +``` + +```csharp +public class AddOperation : IMathematicalOperation +{ + public string Operator => "+"; + + public int DoMathematicalOperation(int a, int b) + { + return a + b; + } +} +``` + +```csharp +public class MultiplyOperation : IMathematicalOperation +{ + public string Operator => "*"; + + public int DoMathematicalOperation(int a, int b) + { + return a * b; + } +} +``` + +```csharp +public class DivideOperation : IMathematicalOperation +{ + public string Operator => "/"; + + public int DoMathematicalOperation(int a, int b) + { + if (b == 0) + throw new DivideByZeroException(); + + return a / b; + } +} +``` + +Es el momento de añadir una nueva clase en la ecuación: el resolver. + +El resolver de este caso tiene la responsabilidad de devolver la implementación correcta de una operación matemática a partir del símbolo de operador pasado como parámetro. + +```csharp +public class MathematicalOperationResolver +{ + private List<IMathematicalOperation> _mathematicalOperationImplementations; + + public MathematicalOperationResolver() + { + _mathematicalOperationImplementations = new List<IMathematicalOperation> + { + new AddOperation(), + new MultiplyOperation(), + new DivideOperation() + }; + } + + public IMathematicalOperation Resolve (string @operator) + { + return _mathematicalOperationImplementations.Single(x => x.Operator == @operator); + } +} +``` + +Ten en cuenta que esto es solo un ejemplo, puedes pasar todas las implementaciones en una lista usando tu sistema favorito de DI. + +Con tu resolver implementado entonces puedes refactorizar el código original, y el resultado quedaría tal que así: + +```csharp +public int DoMathematicalOperation(string @operator, int a, int b) +{ + var resolver = new MathematicalOperationResolver(); + var mathematicalOperation = resolver.Resolve(@operator); + return mathematicalOperation.DoMathematicalOperation(a, b); +} +``` + +¿Has visto? todos los condicionales anidados se han eliminado y el código ahora es más mantenible, ten esto en cuenta cuando vuelvas a encontrarte con esta situación. diff --git a/content/blogs/2019-05-27-no-renderices-mas-de-la-cuenta/es.md b/content/blogs/2019-05-27-no-renderices-mas-de-la-cuenta/es.md new file mode 100644 index 0000000..0e79aba --- /dev/null +++ b/content/blogs/2019-05-27-no-renderices-mas-de-la-cuenta/es.md @@ -0,0 +1,285 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/cover.jpg +navigation: True +title: "No renderices más de la cuenta" +date: 2019-05-27 12:00:00 +tags: react javascript react-window virtualization +class: post-template +subclass: "post" +author: ivanrodri +--- + +Hoy os vengo a hablar un poco de la virtualización y como nos puede ayudar a salvar nuestra aplicación en cuanto a rendimiento cuando tenemos **listados** o **grids** con mucha información. Las aplicaciones cada vez manejan cantidades más grandes de datos, si a esto le sumamos un diseño de nuestro UI +donde tenemos que mostrar una gran cantidad de elementos páginados (100 items por página) o incluso un scroll infinito, tenemos una combinación perfecta para poder cargarnos el rendimiento y la experiencia de usuario de nuestra aplicación. +No vale echar la culpa a los diseñadores por su diseño o luchar para esconder la 💩 debajo de la alfombra, tenemos que conseguir una aplicación que no se degrade en el tiempo a mayor cantidad de información. + +En este caso voy a tratar el tema con **React** ya que no existe un componente nativo del navegador que soporte virtualización. El objetivo es renderizar un listado de 20.000 usuarios de la forma más rápida y eficiente posible. Para ello también he utilizado **React.unstable_Profiler** para saber el tiempo que tarda **React** en renderizar los listados. + +## Renderizando mi lista de usuarios + +```javascript +import React from "react"; + +import { UserItem } from "../shared/UserItem"; +import { Profiler } from "../shared/Profiler"; + +import { getUsers } from "../../services"; + +import { userList } from "./userList.module.css"; + +export const UserList = () => { + const users = getUsers({ startIndex: 0, stopIndex: 20000 }); + + return ( + <Profiler id="noVirtualized"> + <ul className={userList}> + {users.map((user, index) => ( + <li key={index}> + <UserItem user={user} /> + </li> + ))} + </ul> + </Profiler> + ); +}; +``` + +De esta forma tan sencilla podemos renderizar los usuarios en pantalla pero... ¿Que va a suceder exactamente? + +![No Virtualized List Render Time](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/notVirtualized.gif) + +[Ejemplo lista no virtualizada](https://vvgpb.codesandbox.io/no-virtualized) + +Pues como se puede comprobar, el listado funciona pero tiene unos cuantos problemas: + +- El renderizado inicial es muy lento debido a la gran cantidad de trabajo que tiene que hacer **React** **(7.5427s)**. +- La interfaz se queda completamente bloqueada hasta que los elementos son renderizados. +- El hover sobre los items va con mucho retraso. +- Cuando se hace scroll este va muy lageado. +- La prueba se ha hecho en **Chrome** si vamos a navegadores con menos capacidad de computación, estos problemas se agravarán. + +Ahora podemos pensar que si esto lo hacemos con **lazy-load** de datos, no tendríamos este comportamiento **¿es eso cierto?**. Para explicarlo, primero tenemos que entender como funciona **React** y como sería la combinación con **lazy-load**. Si vamos cargando los elementos de 100 en 100, nuestra carga inicial será bastante más rápida pero a medida que vamos cargando más elementos, estamos modificando el estado, lo que significa que el **render** se va a volver a ejecutar. En el caso que usemos **memo** o **PureComponent** en los elementos de la lista, los elementos existentes no lanzaran un re-render **(será necesario hacer la comparación de props lo que puede hacer que el tiempo incremente)**, los nuevos elementos serán creados en el DOM, todo esto implica que nuestro performance inicial va a ser bueno pero a medida que se cargan más datos, nuestro performance va a ir siendo peor, por lo tanto tenemos un listado que se degrada en el tiempo. + +## Virtualización al rescate + +Cuando hablamos de virtualizar una lista o grid nos referimos a renderizar en pantalla solo los elementos que el usuario está viendo en ese momento. La virtualización en listas puede ser tanto horizontal como vertical (dependiendo de la dirección que le indiquemos a la lista) y para los grids la virtualización es tanto horizontal como vertical al mismo tiempo. Para conseguir la virtualización se usan técnicas de **windowing** para calcular los elementos que deben mostrarse y cuales no. + +### ¿Cómo funciona? + +#### Lista + +![Lista virtualizada](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/listVirtualized.png) + +![Lista virtualizada](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/listVirtualized.gif) + +#### Grid + +![Lista virtualizada](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/gridVirtualized.png) + +Para **React** existen varias librerías que nos permiten crear listados virtualizados pero hay 2 que destacan del resto [react-virtualized](https://github.com/bvaughn/react-virtualized) y [react-window](https://github.com/bvaughn/react-virtualized). Ambas librerías son de [Brian Vaughn](https://twitter.com/brian_d_vaughn?lang=es) que es uno de los desarrolladores del equipo de **React**. + +En este caso voy a utilizar **react-window** ya que es una librería muy sencilla de usar, tiene un rendimiento mayor al de **react-virtualized** y el peso de la librería es menor. + +```javascript +import React from "react"; +import { FixedSizeList } from "react-window"; + +import { UserItem } from "../shared/UserItem"; + +import { getUsers } from "../../services"; +import { Profiler } from "../shared/Profiler"; + +export const UserList = () => { + const users = getUsers({ startIndex: 0, stopIndex: 20000 }); + + return ( + <Profiler id="virtualized"> + <FixedSizeList + height={400} + width={300} + itemSize={50} + itemData={users} + itemCount={users.length} + overscanCount={5} + > + {UserListItem} + </FixedSizeList> + </Profiler> + ); +}; + +const UserListItem = ({ style, index, data }) => ( + <div style={style}> + <UserItem user={data[index]} /> + </div> +); +``` + +De esta manera tan sencilla podemos crear una lista vitualizada con **react-window**. En este caso he usado la lista con altura de items fijos (existe una que nos permite items con alturas variables). Las **props** que tenemos que pasarle al listado son bastante simples, podeis verlo en [la documentación](https://react-window.now.sh/#/api/FixedSizeList). + +![No Virtualized List Render Time](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/virtualized.gif) + +[Ejemplo lista virtualizada](https://vvgpb.codesandbox.io/virtualized) + +En este caso el rendimiento de nuestra aplicación ha cambiado por completo, si comparamos con el caso anterior: + +- **Tiempo de renderizado:** Ha pasado de **7.5427s** a **0.0093s**. +- **Bloqueo de interfaz:** Inexistente ya que la carga ha sido directa. +- **Hover:** El hover reacciona instantaneamente. +- **Scroll:** El scroll funciona sin ningún tipo de lag. +- **Browser:** El rendimiento es bueno en todos los navegadores (incluido IE 11). + +### Peticiones de imágenes + +En el ejemplo estamos cargando las imágenes del avatar de los usuarios, esto requiere de hacer peticiones al backend para poder cargarlas. **¿Como está funcionando eso en cada uno de los ejemplos?** + +#### Carga de imágenes en listado sin virtualización + +![No Virtualized List Render Time](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/imageRequests.gif) + +Lanza todas las peticiones de las imágenes del avatar en este caso son **1257** peticiones (**Faker.js** repite imágenes sino, serían unas 20.000) esto puede generar sobrecargas en nuestro servidor y ralentizar otras llamadas. + +#### Carga de imágenes en listado con virtualización + +![No Virtualized List Render Time](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/imageVirtualizeRequests.gif) + +Como vemos, de inicio carga 13 imágenes, 8 de ellas visibles por el usuario más las 5 de **overscan** que le hemos dicho a **react-window** que precargue para dar una mejor experiencia de usuario cuando hagamos scroll. A medida que se hace scroll se van pidiendo las demás. + +En este caso hemos reducido la sobrecarga inicial de imágenes y solo vamos a pedir las que el usuario necesite. Pero esto incluso podemos mejorarlo un poquito más. **react-window** da la oportunidad de saber si se esta haciendo **scroll** sobre la lista. Si hacemos scroll rápidamente, vamos a cargar imágenes que el usuario no ha puesto atención en ellas. Con la **prop** que **react-window** nos ofrece, podemos mostrar un **placeholder** como imágen y cuando se pare de hacer scroll, poner las imágenes reales del avatar. + +```javascript +import React from "react"; +import { FixedSizeList } from "react-window"; + +import { UserItem } from "../shared/UserItem"; + +import { getUsers } from "../../services"; + +export const UserList = () => { + const users = getUsers({ startIndex: 0, stopIndex: 20000 }); + + return ( + <FixedSizeList + height={400} + width={300} + itemSize={50} + itemData={users} + itemCount={users.length} + overscanCount={5} + useIsScrolling={true} + > + {UserListItem} + </FixedSizeList> + ); +}; + +const UserListItem = ({ style, index, data, isScrolling }) => ( + <div style={style}> + <UserItem user={data[index]} showImageAvatar={!isScrolling} /> + </div> +); +``` + +Indicandole a **react-window** la **prop** ```useIsScrolling={true}``` conseguimos que en el render de los items obtengamos la propr **isScrolling** con la que vamos a poder decidir que mostrar. + +![No Virtualized List Render Time](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/isScrolling.gif) + +[Ejemplo lista isScrolling](https://vvgpb.codesandbox.io/virtualized-scrolling) + +De esta manera podemos concretar que las imágenes que pidamos van a ser las que el usuario necesita. + +### virtualización + lazy-load + +Antes hemos comentado que en una lista **no virtualizada** el **lazy-loading** iba a ayudarnos de inicio, pero a medida que tuviésemos más datos, ibamos a ir perdiendo rendimiento. Esto no significa que no haya que usar **lazy-loading**, debería de ser algo que hiciésemos por defecto con los listados de nuestras aplicaciones. + +Implementar un **lazy-load** en **react-*window** es bastante sencillo. Existe una librería a parte llamada [**react-window-infinite-loader**](https://github.com/bvaughn/react-window-infinite-loader) que expone un componente para hacer de forma fácil y sencilla **lazy-load** con **react-window** + +```javascript +import React, { useCallback, useState } from "react"; +import { FixedSizeList } from "react-window"; +import InfiniteLoader from "react-window-infinite-loader"; + +import { UserItem } from "../shared/UserItem"; +import { UserItemSkeleton } from "./userList/UserItemSkeleton"; + +import { getUsers } from "../../services"; + +const TOTAL_USERS = 20000; + +export const UserList = () => { + const [users, setUsers] = useState([]); + const loadMoreItems = useLoadMoreItems({ users, setUsers }); + const isItemLoaded = useCallback(index => !!users[index], [users]); + return ( + <InfiniteLoader + isItemLoaded={isItemLoaded} + itemCount={20000} + loadMoreItems={loadMoreItems} + minimumBatchSize={100} + > + {({ onItemsRendered, ref }) => ( + <FixedSizeList + ref={ref} + onItemsRendered={onItemsRendered} + height={400} + width={300} + itemSize={50} + itemData={users} + itemCount={TOTAL_USERS} + overscanCount={5} + useIsScrolling={true} + > + {UserListItem} + </FixedSizeList> + )} + </InfiniteLoader> + ); +}; + +const useLoadMoreItems = ({ users, setUsers }) => + useCallback( + (startIndex, stopIndex) => + new Promise(resolve => { + setTimeout(() => { + const loadedUsers = getUsers({ startIndex, stopIndex }); + setUsers([...users, ...loadedUsers]); + resolve(); + }, 1000); + }), + [setUsers, users] + ); + +const UserListItem = ({ style, index, data, isScrolling }) => ( + <div style={style}> + {data[index] ? ( + <UserItem user={data[index]} showImageAvatar={!isScrolling} /> + ) : ( + <UserItemSkeleton /> + )} + </div> +); +``` + +Con simplemente 63 lineas de código, tenemos una lista virtualizada con **lazy-load** de datos y en este caso para los items que aún no se han cargado, se muestra un skeleton. Para simular una request, he añadido 1 segundo de retardo a los nuevos elementos cargados. + +![No Virtualized List Render Time](/assets/images/posts/2019-05-27-no-renderices-mas-de-la-cuenta/lazyLoad.gif) + +[Ejemplo lista lazy-load](https://vvgpb.codesandbox.io/virtualized-lazy) + +## react-window + +Esta librería nos permite virtualizar listas y grids de una forma muy sencilla. Nos ofrece funcionalidades que si las tuviésemos que hacer nosotros nos llevarían un buen rato **(navegar a la posición de un item, la posición de scroll inicial en la que comenzar, la dirección de nuestras listas y grids para los idiomas que funcionan con rtl (right to left))**. + +Una de las ventajas que tiene **react-window** sobre otras librerías es que simplemente se encarga de dar un esqueleto y unos cálculos, de este modo es muy sencillo estilarlo como necesitemos, añadir funcionalidades que no soporta por defecto **(stickyheader, drag & drop)**. Y para mí lo más importante es la comunidad que tiene al rededor, donde podemos encontrar muchas librerías que ofrecen funcionalidades como las mencionadas funcionando directamente con **react-window**. + +## Conclusión + +Con la virtualización de listas y grids conseguimos tener vistas más eficientes ya que podemos poner el foco en **solo renderizar en pantalla lo que el usuario está viendo en ese momento.** De esta manera podemos asegurarnos que los listados van a funcionar de una manera muy similar en todos los navegadores y que su rendimiento va a ser el mismo independientemente de la cantidad de datos. + +Cuando trabajamos con virtualización, tenemos que cambiar nuestra manera de pensar, ya que hay ciertos casos en que la manera de hacer algunas cosas no son igual a cuando todos los elementos existen, también hay que tenerlo en cuenta a la hora de dar estilos, ya que todos nuestros tamaños son fijos y las cajas no crecen según su contenido. + +## Ejemplo codesandbox + +<iframe src="https://codesandbox.io/embed/magical-bartik-vvgpb?fontsize=14" title="magical-bartik-vvgpb" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe> \ No newline at end of file diff --git a/content/blogs/2019-06-10-rambling-javascript-6-spread/es.md b/content/blogs/2019-06-10-rambling-javascript-6-spread/es.md new file mode 100644 index 0000000..0fb3251 --- /dev/null +++ b/content/blogs/2019-06-10-rambling-javascript-6-spread/es.md @@ -0,0 +1,424 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-06-10-operador-spread-js-6/header.png +navigation: True +title: "Operador Spread - Javascript #6" +date: 2019-06-10 12:00:00 +tags: javascript rambling-javascript +class: post-template +subclass: "post" +author: aclopez +--- + +"La simplicidad es la gloria de la expresión" Walt Whitman + +# Operador Spread (...js) + +Un operador que ayuda mucho a tener una programación inmutable y que aporta mucha legibilidad a nuestro código es el operador _spread_ o coloquialmente también conocido como "punto punto punto". + +La traducción de _spread_ es propagador, y es que el operador `...` nos permite "propagar" las propiedades de los elementos a los que se les aplica. Propagar es una manera un poco abstracta de verlo. El objetivo es que las propiedades del elemento sobre el que se aplica el operador se expandan donde son esperadas, por ejemplo en los argumentos de una función. + +```js +const array = [ 1, 2, 3 ]; +const result = [ ...array, 4, 5, 6 ]; + +// result: [ 1, 2, 3, 4, 5, 6 ] +``` + +En el ejemplo, el array ```[ 1, 2, 3 ]``` se propaga en un nuevo array al ejecutar la segunda línea y dar valor a la variable _result_ con sus valores y los que se definen a continuación. + +## Operador _spread_ en objetos + +Los objetos también pueden ser expandidos con ```...```. Veamos el ejemplo: + +```js +const obj = { + name: 'pepe', + age: 27 +}; + +const result = { + ...obj, + country: 'spain' +} + +// result: { +// name: pepe, +// age: 27, +// country: 'spain' +// } +``` + +El nuevo objeto ```result``` tiene las dos propiedades que tiene el objeto ```obj```. El objeto ```obj``` se ha expandido dentro de ```result```. Esto también se puede hacer sobreescribiendo alguna propiedad de esta manera: + +```js +const obj = { + name: 'pepe', + age: 27, + country: 'spain' +}; + +const result = { + ...obj, + country: 'france' +} + +// result: { +// name: pepe, +// age: 27, +// country: 'france' +// } +``` + +Al sobreescribir la propiedad ```country``` el uso del operador _spread_ queda reducido a la expansión de todas las propiedades que no están sobreescritas. Así, podemos hacer que un objeto mute solo cambiando una propiedad de un modo sencillo. + +## Operador _spread_ en _arrays_ + +En la definición del operador ```...``` hemos visto una manera de poder concatenar _arrays_. Ahora vamos a ver en profundidad las operaciones que podemos hacer con los mismos. + +### Push en un _array_ + +La función push se hace más sencilla con este operador: + +```js +const array = [ 1, 2, 3 ]; +const arrayPush = [ 4, 5 ]; + +const result1 = Array.prototype.push.apply(array, arrayPush); +const result2 = array.push(...arrayPush); +// result1 & result2 = [ 1, 2, 3, 4, 5 ] +``` + +Al expandir los elementos de ```arrayPush``` en la variable ```array``` podemos usar la función _push_ directamente sin tener que recurrir al _push_ de la clase ```Array``` y todo queda mucho más legible. + +Este es un ejemplo para que veamos la equivalencia del _push_ de la clase _array_ con el uso de ```...```; pero lo que se suele emplear para el _push_ es directamente la concatenación de _arrays_ que veremos a continuación, ya que es equivalente a realizar cualquier añadido al _array_, aunque sea solo un elemento. + +### Concatenación de _arrays_ + +Para concatenar dos _arrays_ se usa la función definida en el prototype de ```Array``` _concat_. Podemos obtener el mismo resultado con _concat_ que usando _spread_: + +```js +const array = [ 4, 5, 6 ]; +const result = [ 1, 2, 3 ].concat(array); + +const resultWithSpread = [ 1, 2, 3, ...array] + +// result & resultWithSpread = [ 1, 2, 3, 4, 5, 6 ] +``` + +En este caso, el resultado de concatenar los _arrays_ es el mismo; pero en mi opinión el operador ```...``` le da mucha más expresividad a nuestro código ya que lo hace mucho más fácil de leer. + +### Operación _splice_ en un _array_ + +La función [_splice_](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Array/splice) combina dos _arrays_. Es una función algo compleja que recibe como parámetros el inicio de donde se inserta el nuevo _array_, el número de elementos que sustituye y el nuevo _array_: + +```js +const numbers = [ 4, 5 ]; +numbers.splice(0, 0, [1, 2, 3]); +// numbers: [ 1, 2, 3, 4, 5] + +const numbers = [ 1, 2, 2, 4, 5 ]; +numbers.splice(1, 2, [3, 3]); +// numbers: [ 1, 3, 3, 4, 5] + +const numbers = [ 1, 2, 3, 5 ]; +numbers.splice(3, 0, [4]); +// numbers: [ 1, 2, 3, 4, 5] +``` + +Estas operaciones usando el operador _spread_ pueden quedar mucho más legibles. Además otra ventaja de usar _spread_ es que se crea un nuevo _array_ y no se modifica el que existe como hace la función _splice_, lo que viene mejor para una programación inmutable. Para ver donde colocar el nuevo _array_ nos ayudaremos de la función [_slice_](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Array/slice), que nos da el subconjunto del _array_ que necesitamos: + +```js +const array = [ 1, 2, 3 ]; +const numbers = [ 4, 5 ]; +const result = [ + ...array, + ...numbers +]; +// result: [ 1, 2, 3, 4, 5] +numbers.splice(0, 0, array); +// numbers: [ 1, 2, 3, 4, 5] + +const array = [ 3, 3 ]; +const numbers = [ 1, 2, 2, 4, 5 ]; +const result = [ + ...numbers.slice(0, 1), + ...array, + ...numbers.slice(3) +]; +// result: [ 1, 3, 3, 4, 5] +numbers.splice(1, 2, array); +// numbers: [ 1, 3, 3, 4, 5] + +const array = [ 4 ]; +const numbers = [ 1, 2, 3, 5 ]; +const result = [ + ...numbers.slice(0, 3), + ...array, + ...numbers.slice(3) +]; +// result: [ 1, 2, 3, 4, 5] +numbers.splice(3, 0, array); +// numbers: [ 1, 2, 3, 4, 5] + +``` + +Si lo pensamos con detenimiento una vez vista la función _slice_ esto es equivalente: + +```js +const array = [ 1, 2, 3 ]; + +const result1 = [ array.slice() ]; +const result2 = [ ...array ]; + +// result1 & result2 = [ 1, 2, 3 ] + +``` + +## Operador _spread_ y los parámetros Rest + +El resultado del operador _spread_ también puede se puede poner al pasarle los parámetros a una función: + +```js +const numbers = [ 1, 2, 3 ]; + +function sum(a, b, c) { + return a + b + c; +} + +const result = sum(...numbers); +// result: 6 +``` + +Esto ya lo habíamos hecho en la función _push_ del _array_. Podemos combinarlo como queramos, usarlo es muy cómodo: + +```js +const dateData = [1989, 1, 7]; +const d = new Date(...dateData); +``` + +En cambio, si queremos poner la expresión ```...``` en los propios parámetros de una función, el comportamiento es el contrario. En lugar de convertir una _array_ en una lista de elementos, convierte una lista de elementos en un _array_. Podemos ver este comportamiento usando la función _suma_ del revés: + +```js +// En su expresión con arrays +function sum(...numbers) { + let total = 0; + for (var i=0; i < numbers.length; i++) { + total += numbers[i]; + } + return total; +} + +// Aunque mejor podemos poner un reduce +const sum = (...numbers) => + numbers.reduce( + (total, nextNum) => total + nextNum, + 0 + ); + +const result = sum(1, 2, 3); +// result: 6 +``` + +Para acordarse de como poder usar _reduce_ podéis visitar [trabajando con arrays](https://www.nocountryforgeeks.com/rambling-javascript-3-arrays/). + +La expresión ```...``` utilizada como parámetros de una función se denomina __parámetros Rest__ y, como hemos dicho anteriormente es justo la expresión contraria al operador _spread_. + +### Destructuring y los parámetros rest + +Se puede combinar los parámetros rest junto con el [destructuring](https://www.nocountryforgeeks.com/destructuring/). + +```js +const numbers = [ 1, 2, 3 ]; + +const { firstNumber, ...otherNumbers } = numbers; + +// firstNumber: 1 +// otherNumbers: [ 2, 3 ] +``` + +Podemos combinarlo de la manera que queramos, usando ```...``` al principio, al final o en medio. + +```js +const numbers = [ 1, 2, 3, 4, 5 ]; + +const { firstNumber, ...otherNumbers, lastNumber } = numbers; + +// firstNumber: 1 +// otherNumbers: [ 2, 3, 4 ] +// lastNumber: 5 +``` + +Y podríamos usar todo esto en los parámetros de una función. + +```js + +const greeting = ({ country, name, ...person }) => country === 'spain' ? `Hola ${name}!` : `Hello ${name}!`; + +``` + +## Objetos inmutables + +El operador _spread_ es muy útil a la hora de trabajar con objetos inmutables, ya que si queremos crear un nuevo _array_ o un nuevo objeto pero cambiando solo una de sus propiedades podemos usarlo con lo visto anteriormente. + +Si por ejemplo tenemos una lista de objetos de tipo persona y queremos cambiar el nombre de una de ellas y hacer un nuevo objetos para pasarlo al estado de nuestro componente en React por poner un caso de uso, podríamos aplicar algo parecido a: + +```js + +const people = [ + { + id: 1, + name: 'pepe', + age: 45, + country: 'spain', + }, + { + id: 2, + name: 'jaime', + age: 34, + country: 'france', + }, + { + id: 3, + name: 'laura', + age: 37, + country: 'spain', + }, + { + id: 4, + name: 'gema', + age: 20, + country: 'usa', + }, + { + id: 5, + name: 'donnald', + age: 18, + country: 'usa', + }, +]; + +const changePersonName(personid, newname) { + const newArray = people.map(person => + person.id === personid + ? ({ + { + ...person, + name: newname, + } + }) + : person); + return newArray; +}; + +``` + +### Deep copy + +El ejemplo anterior es sencillo y tiene un pequeño efecto demo. Hay que tener cuidado cuando lo que queremos hacer es un _deep copy_, es decir, el copiado no solo de propiedades primitivas sino de propiedades que sean objetos. En el caso de que la propiedad que copiemos sea un objeto se estará copiando solamente la referencia: + +```js +let person = { + id: 5, + name: 'donnald', + age: 18, + country: { + name: 'france', + capital: 'paris', + }, +}; +const newPerson = { + ...person, + age: 28, +} +person.country.name = 'spain'; + +console.log(newPerson.country); +// country: { +// name: 'spain', +// capital: 'paris', +// } +``` + +Por ello si queremos hacer un copiado en profundidad (_deep copy_) debemos seguir alguna otra estrategia, por ejemplo serializar un nuevo objeto: + +```js +let person = { + id: 5, + name: 'donnald', + age: 18, + country: { + name: 'france', + capital: 'paris', + }, +}; +const newPerson = JSON.parse(JSON.stringify(person)); +person.country.name = 'spain'; + +console.log(newPerson.country); +// country: { +// name: 'france', +// capital: 'paris', +// } +``` + +## Object assign + +El operador _spread_ y la función ```Object.assign({}, person)``` hacen exactamente lo mismo siempre que el primer argumento de _Object.assign_ sea un objeto vacío. De hecho, _spread_ usa por debajo _Object.assign_ si el navegador lo soporta. + +```js +const person = { + id: 5, + name: 'donnald', + age: 18, + country: 'france', +}; +const newPerson = { + ...person, +} + +const newPersonAssign = Object.assign({}, person); + +// newPerson y newPersonAssign serian una copia exacta de person +``` + +El objetivo de _Object.assign_ es copiar las propiedades de un objeto origen a un objeto destino. El primer parámetro de la función es el objeto destino. Por ello, para usar inmutabilidad y ser idéntico al comportamiento de _spread_, el primer parámetro debería ser ese objeto vacío. Veamos un ejemplo sin este objeto vacío: + +```js +let newPerson = { + id: 6, +}; + +const person = { + id: 5, + name: 'donnald', +}; + +const newPersonAssign = Object.assign(newPerson, person); + +// newPersonAssign: { +// id: 6, +// name: 'donnald', +// } + +// newPerson: { +// id: 6, +// name: 'donnald', +// } + +``` + +_Object.assign_ tiene los mismos problemas que _spread_ con el copiado en profundidad. + +Por tanto, si lo que buscamos es una solución para la inmutabilidad, lo mejor por legibilidad en el código es usar el operador _spread_ en lugar de _Object.assign_. Es verdad que en _perfonmance_ _Object.assign_ tiene un pequeño mejor resultado, pero es casi despreciable. + +Para ver más detalles de _Object.assign_ podéis mirar [aquí](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Object/assign). + +# Conclusiones + +Debemos tener siempre presente un operador como _spread_ para poder conjugarlo en cualquier ocasión y dejar nuestro código mucho más legible. + +Usar ```...``` da [energía](https://geeks.ms/windowsplatform/2017/05/10/la-energia-del-codigo/) a nuestras líneas de Javascript. + +Happy coding! diff --git a/content/blogs/2019-06-12-tutorial-git-flow/es.md b/content/blogs/2019-06-12-tutorial-git-flow/es.md new file mode 100644 index 0000000..4b242ba --- /dev/null +++ b/content/blogs/2019-06-12-tutorial-git-flow/es.md @@ -0,0 +1,393 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-06-12-tutorial-git-flow/header.jpg +navigation: True +title: "Tutorial - Trabajando con ramas" +date: 2019-06-10 12:00:00 +tags: git gitflow +class: post-template +subclass: "post" +author: maktub82 +--- + +¡Hola a todos! Es importante cuando estamos trabajando con Git **tener una política de ramas con la que el equipo se sienta cómodo**. En otro post profundizaremos en [Gitflow](https://es.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow), pero hoy os voy a dar unas nociones básicas de cómo empezar a trabajar con ramas y os dejaré un tutorial práctico para que podáis probarlo vosotros mismos. + +## Política de ramas + +Cuando trabajamos con **Gitflow tenemos varias ramas que nos sirven para diferentes propósitos**: implementar una funcionalidad, corregir un bug en producción, preparar una release... + +![Gitflow](/assets/images/posts/2019-06-12-tutorial-git-flow/gitflow.png) + +Nosotros para este tutorial, vamos a simplificarlo bastante. Vamos a trabajar con una rama principal (master) y vamos a ir implementando funcionalidades mediante *feature branches*. + +![Feature branch](/assets/images/posts/2019-06-12-tutorial-git-flow/branch.png) + +## Introducción + +Planteamos un ejemplo con unos tests unitarios. Vamos a ir implementando la funcionalidad poco a poco, trabajando con ramas y colaborando con nuestros compañeros. El código para empezar el tutorial lo tenéis disponible en [Github](https://github.com/ikeinyyo/Talks.RecursiveGit). + +Para probar el ejemplo, basta con ejecutar el script de python: + +```bash +python main.py +``` + +A medida que vayamos avanzando en el tutorial, iremos completando los métodos, haciendo que pasen todos los tests. + +### Squash + +El Squash es una técnica que nos permite agrupar los cambios de varios `commit` en uno. + +El Squash se hace a través de un `rebase` interactivo, que nos da mayor control de cómo se ejecuta la reorganización del trabajo. + +El comando básico es: + +```bash +git rebase -i HEAD~{number_of_commits} +``` + +Lo que indicamos es el número de `commit` que queremos agrupar. Para que sea más sencillo y no tener que contar cuántos `commit` quiero agrupar, podemos indicar contra qué rama queremos que se haga la resta. De tal forma que nos agrupa aquellos `commit` que están por delante en la rama actual. + +```bash +git rebase -i origin/master +``` + +### Nueva rama de feature + +Estando en `master`, creamos una nueva rama para desarrollar la funcionalidad que afecta a las operaciones de las listas. + +```bash +(master)$ git checkout -b feature/list +``` + +### Count + +Lo primero que vamos a hacer es implementar la función de `count`: + +```python +def count(input): + try: + input[0] + return 1 + count(input[1:]) + except: + return 0 +``` + +Una vez terminada, hacemos un `commit`: + +```bash +(feature/list)$ git commit -a -m "Add count" +``` + +### Sum + +A continuación, implementamos la función `sum` y hacemos `commit`: + +```python +def sum(input): + try: + input[0] + return input[0] + sum(input[1:]) + except: + return 0 +``` + +```bash +(feature/list)$ git commit -a -m "Add sum" +``` + +### Max/Min + +Implementamos las funciones `max` y `min`. A continuación, hacemos `commit`: + +```python +def max(input): + items_count = count(input) + if items_count == 0: + return 0 # Only for 0 items list + elif items_count == 1: + return input[0] + else: + max_other = max(input[1:]) + return max_other if max_other > input[0] else input[0] + +def min(input): + items_count = count(input) + if items_count == 0: + return 0 # Only for 0 items list + elif items_count == 1: + return input[0] + else: + min_other = min(input[1:]) + return min_other if min_other < input[0] else input[0] +``` + +```bash +(feature/list)$ git commit -a -m "Add max and mix" +``` + +### Sort + +A continuación, implementamos la función `sort` y hacemos `commit`: + +```python +def sort(input): + items_count = count(input) + if items_count <= 1: + return input + return sort([e for e in input[1:] if e <= input[0]]) + [input[0]] + sort([e for e in input[1:] if e > input[0]]) +``` + +```bash +(feature/list)$ git commit -a -m "Add sort" +``` + +### Squash + +Una vez hemos terminado la funcionalidad, podemos hacer el `rebase -i` para agrupar los commits. + +**Nota:** Como lo queremos hacer contra `origin/master`, hacemos un `fetch` para asegurarnos que la rama está actualizada. + +```bash +(feature/list)$ git fetch +(feature/list)$ git rebase -i origin/master +``` + +A continuación, nos aparecerá un listado con los `commit` que hay para que podamos elegir cómo queremos que se haga el `rebase`. Debemos dejar el primer marcado como pick y el resto los tenemos que marcar como squash. + +```bash +pick 43fa23 Add count +s 13fa1e Add sum +s 2aff1e Add max and min +s 245eef Add sort +``` + +Simplemente añadimos el mensaje del `commit`: + +```bash +Add list functions +``` + +### Push y Pull Request + +Una vez tenemos nuestra rama preparada para integrarse con la rama principal, en este caso `master`, tenemos dos opciones: o hacer un `merge` en `master` o subir la rama y hacer una Pull Request. + +#### Merge + +Lo que tenemos que hacer es ir a la rama `master`, asegurarnos de que está actualizada, hacer un `merge` y subir `master` al remoto. + +```bash +(feature/list)$ git checkout master +(master)$ git fetch +(master)$ git pull +(master)$ git merge feature/lists +(master)$ git push origin master +``` + +#### Pull Request + +En este caso, únicamente subimos la rama `feature/list` y hacemos la gestión de la Pull Request desde el portal de Azure DevOps o Github. + +```bash +(feature/list)$ git push origin feature/list +``` + +### Rebase + +Otro punto importante cuando trabajamos con Git es poder adaptarnos a que haya código que necesitemos y que se encuentren en otras ramas. + +Para poder traernos ese código utilizaremos el comando `rebase`. + +Al igual que hemos hecho antes, vamos a implementar la nueva funcionalidad en diferentes `commit` y luego haremos un Squash. + +Primero creamos una nueva rama desde `master`. + +```bash +(master)$ git checkout -b feature/calc +``` + +### Mult + +Impementamos el método `mult` y hacemos un `commit`. + +```python +def mult(n1, n2): + if n1 == 0 or n2 == 0: + return 0 + if n1 < 0 and n2 < 0: + return abs(n1) + mult(abs(n1), abs(n2)-1) + elif n2 < 0: + return n2 + mult(n1-1, n2) + else: + return n1 + mult(n1, n2-1) +``` + +```bash +(feature/calc)$ git commit -a -m "Add mult" +``` + +### Div + +A continuación, implementamos la función `div` y hacemos un `commit`. + +```python +def div(n1,n2): + if n2 == 0: + raise ZeroDivisionError + elif n1 == 0: + return 0 + elif abs(n1) < abs(n2): + return 0 + elif n1 < 0 and n2 < 0: + return 1 + div(abs(n1)-abs(n2), abs(n2)) + elif n1 < 0 or n2 < 0: + return -1 + div(n1+n2, n2) + else: + return 1 + div(n1-n2, n2) +``` + +```bash +(feature/calc)$ git commit -a -m "Add div" +``` + +### Squash y crear una Pull Request + +Lo siguiente que hacemos es hacer un Squash de nuestros dos `commit` y subir la rama `feature/calc` para hacer una Pull Request. + +```bash +(feature/calc)$ git fetch +(feature/calc)$ git rebase -i origin/master +(feature/calc)$ git push origin feature/calc +``` + +Hacemos una Pull Request, pero nosotros tenemos que seguir trabajando. + +### Nueva rama y Rebase + +Una vez que hemos hecho la Pull Request, tenemos que dejar tiempo para que nuestros compañeros puedan revisarla y validarla. Pero nosotros tenemos que seguir trabajando. + +Para ello vamos a crear una nueva rama desde `master` y hacernos un `rebase` desde `origin/feature/calc` para poder seguir con el código que en que ya estábamos trabajando. + +```bash +(feature/calc)$ git checkout master +(master)$ git fetch +(master)$ git pull +(master)$ git checkout -b feature/calc-1 +(feature/calc-1)$ git rebase origin/feature/calc +``` + +### Pot + +Implementamos el comando `pot` y hacemos un `commit`. + +```python +def pot(b,e): + if e == 0: + return 1 + elif b == 0: + return 0 + elif e < 0: + return 1 / pot(b, -e) + else: + return mult(b, pot(b, e-1)) +``` + +```bash +(feature/calc-1)$ git commit -a -m "Add pot" +``` + +### Cambios en la Pull Request + +En ese momento, nos sugieren cambios en la Pull Requst. Nos piden implementar nuestro propio método `abs`. Lo primero que tenemos que hacer es volver a la rama `feature/calc` para implementar ahí la nueva funcionalidad. + +```bash +(feature/calc-1)$ git checkout feature/calc +``` + +Añadimos los test unitarios para el método `abs`: + +```python +{'test': lambda: abs(0), 'expected': 0, 'method': "abs of zero"}, +{'test': lambda: abs(5), 'expected': 5, 'method': "abs of positive number"}, +{'test': lambda: abs(-5), 'expected': 5, 'method': "abs of negative"}, +``` + +E implementamos la función `abs`: + +```python +def abs(n): + if n >= 0: + return n + else: + return -n +``` + +Por último, hacemos un `commit`. + +```bash +(feature/calc)$ git commit -a -m "Add abs" +``` + +De nuevo, hacemos un Squash y subimos el código. Esta vez, como hemos cambiado la historia de Git, tendremos que añadir el argumento `-f` al `push`. + +```bash +(feature/calc)$ git fetch +(feature/calc)$ git rebase -i origin/master +(feature/calc)$ git push origin feature/calc -f +``` + +Por fin nuestros compañeros nos aprueban la Pull Request, así que ya podemos completarla. + +### Actualizar mi rama con el trabajo integrado en master + +Lo siguiente que hacemos, es volver a la rama `feature/calc-1` y hacer un `rebase` de `origin/master`, después de hacer un `fetch` para tener la rama actualizada. + +```bash +(feature/calc)$ git checkout feature/calc-1 +(feature/calc-1)$ git fetch +(feature/calc-1)$ git rebase origin/master +``` + +### Fac + +Continuamos con nuestro trabajo de forma normal en la rama `feature/calc-1`. Añadimos ahora, la función `fac` y hacemos `commit`. + +```python +def fac(n): + if n == 0: + return 1 + elif n < 0: + return mult(n, fac(n+1)) + else: + return mult(n, fac(n-1)) +``` + +```bash +(feature/calc-1)$ git commit -a -m "Add fac" +``` + +### Finalizamos nuestro trabajo: Squash, push y Pull Request + +Hacemos el Squash y subimos la rama. + +```bash +(feature/calc-1)$ git fetch +(feature/calc-1)$ git rebase -i origin/master +(feature/calc.1)$ git push origin feature/calc-1 +``` + +Creamos la Pull Request y, tras que nos aprueben nuestros compañeros, la completamos para finalizar nuestro trabajo. + +## Siguientes pasos + +El objetivo de este tutorial es ver el uso de las *feature branches* y poder mostrar cómo se trabaja con ellas y cómo podemos integrar código en las ramas principales. + +Cualquier duda sobre el tutorial, por favor, ponedla en los comentarios. En siguientes post podemos ver más sobre Gitflow y cómo trabajar con otro tipo de ramas. + +¡Nos vemos en el futuro! + + + + + diff --git a/content/blogs/2019-07-06-contenerizacion-de-aplicaciones-en-docker/es.md b/content/blogs/2019-07-06-contenerizacion-de-aplicaciones-en-docker/es.md new file mode 100644 index 0000000..af7c36f --- /dev/null +++ b/content/blogs/2019-07-06-contenerizacion-de-aplicaciones-en-docker/es.md @@ -0,0 +1,290 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-07-06-contenerizacion-de-aplicaciones-en-docker/header.png +navigation: True +title: "Contenerización de aplicaciones en Docker" +date: 2019-07-06 12:00:00 +tags: Docker Containers +class: post-template +subclass: 'post' +author: jgarcia +--- + +# Contenerización con Docker + +Antes de empezar con el tema en cuestión me gustaría agradecer a los amigos de **"No Country For Geeks"** el haberme invitado a realizar una publicación en su blog. Como yo también soy un seguidor de este sitio sé de buena tinta la calidad de los contenidos que se publican, así que espero que no me puedan los nervios y estar a la altura. Para los que se pregunten quién soy, pues decir que no soy más que un Murcianico con la inquietud de aprender continuamente nuevas tecnologías que pueda aplicar a lo que es mi hobby y al mismo tiempo mi profesión, el desarrollo de software. Y sin enrollarme más en contar mi vida comencemos con la contenerización de aplicaciones en Docker, que es a lo que hemos venido. + +La vida del desarrollador es demasiado corta como para perder el tiempo entre compilación y despliegue, en comprobar y mantener estable la perfecta armonía de librerías de código en la que un simple cambio de versión puede destruirte el edificio por los cimientos. Pues aquí vengo a enseñaros la contenerización de aplicaciones usando [Docker](https://www.docker.com/), una herramienta que alargará la esperanza de vida del programador medio al menos un poco. + +Antes de entrar en materia vamos a asentar algunos conceptos base para que no sólo imitemos los comandos que se enseñarán en éste post, si no que además entendamos qué es lo que esta sucediendo por debajo. + +## ¿Qué es contenerizar? + +Simple y llanamente se trata de un método de virtualización que abarca a nivel de Sistema Operativo para implementar y ejecutar aplicaciones distribuidas sin lanzar una [Máquina Virtual](https://www.xataka.com/especiales/maquinas-virtuales-que-son-como-funcionan-y-como-utilizarlas) completa para cada aplicación. Para mantener todo el ecosistema de contenedores que pueden llegar a existir, se habilita un punto central de control o host con el que todos los contenedores se comunican y desde donde son gestionados. + +Dentro de cada contenedor se incluye todo lo necesario para arrancar nuestras aplicaciones, es decir, archivos necesarios como **librerías de código compiladas, variables de entorno e incluso archivos necesarios para la depuración** (Si si, se puede depurar a través de un contenedor). + +Como los recursos están compartidos, ya que los contenedores comparten Sistema Operativo (y pueden compartir también librerías de código), además se pueden crear contenedores de aplicaciones que no ejercerán tanta carga de trabajo en nuestro sistema puesto que no están utilizando los recursos globales, o dicho de otra manera, al no tener ejecutadas las aplicaciones directamente en nuestra máquina si no que se encuentran en una máquina virtualizada a la cual ya se le han asignado unos recursos, las aplicaciones cogerán los recursos ya asignados a dicha máquina. + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/docker-container-architecture-1024x575.jpg) + +Al ejecutarse sobre una máquina virtual, la portabilidad entre distintos tipos de máquina es posible. Por lo que un contenedor de aplicación puede ejecutarse en cualquier sistema sin requerir cambios en el código, tan solo tendríamos inconvenientes al intentar ejecutar un contenedor Windows sobre un sistema operativo Linux. Más o menos con estos detalles ya podríamos entender como funciona la contenerización, a partir de aquí hablaremos específicamente de Docker, por ser el más popular y el que más empresas están adaptando en su sistema. + +## ¿Qué es Docker? + +Se trata de una plataforma libre para desarrollar y ejecutar aplicaciones en contenedores de una forma simple y sencilla. Te permite manejar tu infraestructura como manejarías tu aplicación, y esto es así porque podrías, por ejemplo, crear una instancia de Docker en la que tan solo ejecutarías una imagen de base de datos SQL, con lo que podrías arrancar dicha imagen desde un contenedor y gestionarlo desde éste sin necesidad de tener que tenerlo instalado en local o en algún servidor. + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/descarga.png) + +Esto nos ofrece una versatilidad a la hora de desarrollar increíble, se pueden lanzar tanto nuestras aplicaciones propias en cualquier tecnología y comunicarlas con otros contenedores con diversos servicios de infraestructura sin necesidad de instalarlos en nuestra máquina, y si además te digo que ni siquiera tendrías que decirle o configurar en profundidad (Siempre que no quieras o no lo necesites) la instancia de SQL server que pudieras tener, ya que gracias al repositorio [DockerHub](https://hub.docker.com/), en el cual se encuentran de manera libre miles de **imágenes** **Docker** con diversos servicios, bastaría con indicarle qué imagen queremos usar y el solito se la descargaría, instalaría, y configuraría la base para poder funcionar, y tu solo tendrías que decirle el puerto en el que quieres dicho servicio a través del contenedor. + +Hablamos de una reducción de los tiempos entre los que un desarrollador implementa una nueva funcionalidad y la llegada de dicha _feature_ a producción se reducirían notoriamente. Siempre y cuando nuestro destino final en producción esté contenerizado claro, que si éste es nuestro objetivo y tenemos un sistema grande, distribuido y complejo, no es tarea sencilla seamos sinceros. Esta herramienta es la que antes comentaba que ampliaría la vida media del desarrollador, o por lo menos la hace más llevadera. + +Como bien comentaba Docker nos permite ejecutar **N** aplicaciones simultáneamente en un host en base a contenedores, y además podríamos hacer una jerarquía de dependencias entre aplicaciones, es decir, si nuestra aplicación **A** necesita de otra aplicación **B** para funcionar, podemos configurar mediante Docker y establecer que antes de ejecutar **A** se espere y lance previamente el contenedor **B**. Con ésto se acabo la rutina diaria de alguien está tocando el servidor compartido y mientras realiza su tarea esta dejando sin entorno de trabajo a los demás desarrolladores (Donde seguro que aprovecharán todos para tomarse un café), cada desarrollador podría lanzar su aplicación con una dependencia a una imagen configurada del servidor y cada uno desde su máquina local. + +Para concluir esta sección recapitulemos que características nos aporta Docker: + +1. **Desarrollar** aplicaciones usando contenedores, con todos sus beneficios. +2. **Testear** usando contenedores, mediante imágenes previamente configuradas. +3. **Llegar a producción** con contenedores, donde es recomendable el uso de [orquestadores](https://docs.microsoft.com/es-es/dotnet/standard/microservices-architecture/architect-microservice-container-applications/scalable-available-multi-container-microservice-applications) para aprovechar todo el potencial de los contenedores. + +## Primeros pasos + +Pues para empezar, como es obvio, será descargarnos e instalarnos [Docker Desktop](https://docs.docker.com/docker-for-windows/install/), la aplicación de escritorio de Docker en nuestro ordenador para empezar a trastear con contenedores. En el enlace que he añadido para Docker Desktop se muestran ambas versiones tanto para Windows como para Mac. Todos los ejemplos que mostraré serán bajo un sistema Windows, sobre el que pueden ser ejectuados contenedores tanto Windows como Linux, sin embargo en un Sistema Operativo Linux solo pueden ser lanzados contenedores Linux. Además la instalación base del cliente de Docker puede ser diferente según qué plataforma. Aun así el mismo enlace que he puesto (De la página oficial de Docker) explica detalladamente como instalarnos Docker en nuestra máquina paso por paso y de forma muy sencilla, por lo que no me detendré en todos los detalles de la instalación. + +_**Nota**_: _Existe una versión anterior del cliente de Docker para Windows llamada **[Docker ToolBox](https://docs.docker.com/toolbox/toolbox_install_windows/)**_. _Es posible que tengáis que utilizarla si vuestro sistema operativo Windows no es la versión Pro, Enterprise o Education. ¿Por qué? Pues sencillamente porque la versión de Docker Desktop utiliza como hipervirtualizador [Hyper-V](https://es.wikipedia.org/wiki/Hyper-V)_, _y éste tan solo se encuentra en estas versiones de Windows._ + +**_¡Nota_** **aún más importante!**: _Si por lo que sea os instaláis Docker ToolBox, ya sea por error, por probar o porque no te queda más remedio. Y después os pasáis a Docker Desktop, la instalación previa de Docker ToolBox os creará unas variables de entorno que necesitará para poder funcionar, sin embargo, la versión nueva de Docker Desktop no necesita de dichas variables de entorno, y si por lo que sea pasáis de uno a otro esas variables de entorno convertirán vuestra vida en un infierno. Tenedlo en cuenta y_ **_¡Borradlas!_** + +### ¿Que tenemos instalado? + +Terminado el paso anterior ahora mismo tenemos instalado en nuestra máquina la aplicación cliente-servidor Docker Engine, todo el sistema necesario para desarrollar, contenerizar y ejecutar aplicaciones. El cual se compone de: + +- Servidor arrancado como un demonio ([Daemon](https://es.wikipedia.org/wiki/Daemon_(inform%C3%A1tica))) en nuestro sistema, que sería el encargado de ejecutar los comandos propios de Docker (docker command). +- Un API REST actuando como interfaz para comunicarse con el demonio. +- Entrada de línea de comandos (CLI). + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/docker-CLI.png) + +El CLI interactua con el API REST para ejecutar las acciones deseadas por el usuario, y a su vez el API REST ejecuta las acciones en el demonio (docker daemon). Otras aplicaciones en Docker pueden comunicarse directamente con el API REST. Con ésto tenemos todo lo necesario para manejar elementos de Docker como **imágenes**, **contenedores**, **networks** y **volumenes**. + +#### Imágenes en Docker + +Antes ya había comentado por encima el concepto de imagen en Docker, pues bien, una imagen de Docker representa una instantánea de una máquina virtual, pero siendo esta mucho más ligera. Por ejemplo una imagen podría tener la configuración de un **Sistema Operativo como Windows**, con **nuestra aplicación** instalada y una **BD** como puede ser [MongoDB](https://www.mongodb.com/) de la que se nutrirá nuestra aplicación. + +Estas imágenes las usará Docker para crear los contenedores, además como ya indiqué existe un repositorio público (**DockerHub**) donde habitan decenas de imágenes con aplicaciones pre-configuradas como pueden ser **Redis, Apache, MySQL**… y un largo etc. + +#### Network de Docker + +Los requisitos de red de las aplicaciones y el propio entorno de la red puede resultar algo complejo de gestionar cuando hablamos de contenedores, en Docker se encuentra para facilitarnos la vida la red Docker o también denominado el modelo de red de contenedor ([Container Network Model, CNM](https://docs.docker.com/v17.09/engine/userguide/networking/#default-networks)). El CNM es el encargado de administrar la conectividad entre los contenedores Docker, y todo ello de forma abstracta para nosotros, por lo que nos podemos ir olvidando de la tarea que el mantenimiento de la red conlleva. + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/cables-close-up-connection-159304-300x200.jpg) + +Aun así esto no significa que no podamos configurar **nuestras direcciones de red o aplicar un cifrado en la capa de red o incluso usar descubrimiento de servicios**. Más adelante en otro articulo, es posible que os enseñe algunas de las cosas que se pueden configurar con las interfaces de red, de momento continuemos con lo básico de Docker. + +#### Volúmenes + +Un volumen es un mecanismo que permite persistir datos en contenedores, lo ideal es que los contenedores no contengan estado alguno de nuestras aplicaciones, ya que un contenedor es un ente que está destinado a morir, los contenedores mueren y vuelven a instanciarse con la ayuda de **orquestadores**. Y no solo eso, si no que además es posible arrancar varias instancias de un mismo contenedor y balancear la carga de trabajo entre ellos. Es por esto por lo que no es recomendable que los contenedores contengan estado o configuración de direcciones IP estáticas. + +Así el acceso a los contenedores a través de la red se realiza mediante técnicas de descubrimiento de servicios, con lo que cada vez que arranquemos un contenedor es muy probable que su dirección de red haya cambiado. Aun así en ciertas situaciones es posible que necesitemos almacenar información en los contenedores, por lo que no está de mas que conozcamos esta herramienta. + +### Primera aplicación a contenerizar + +Antes de nada vamos a comprobar que nuestra instalación de Docker funciona correctamente, y para ello vamos a ejecutar el siguiente comando sobre el cmd de nuestra máquina: +```bash + docker run hello-world +``` + + Y si todo ha salido como debería de ser, aparecerán los siguientes mensajes: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/image.png) + +Siguiendo un poco la trazabilidad de los mensajes lo que ha ocurrido es lo siguiente: + +1. Ha buscado si la imagen ‘_hello-world_‘ ya la tenía creada localmente la maquina host de Docker. +2. Al no ser así, ha buscado una imagen con el mismo nombre en el repositorio público **DockerHub**, en este caso si que la ha encontrado y se dispone a descargársela. +3. Una vez finalizada la descarga de la imagen, Docker creará una instancia en un contenedor de esta misma y la ejecutará. Donde los últimos mensajes mostrados provienen de la misma aplicación descargada, en los que se detalla todo el proceso de comunicación en el cliente de Docker entre el CLI y la interfaz que ya os comenté. + +Es importante que se entienda que la imagen ‘_hello-world_‘ es una imagen que yo no he creado y que no está en mi dispositivo local, es una imagen ya creada en el repositorio de **DockerHub** y la cual yo puedo usar a base de un simple comando. De la misma forma que me he descargado esta imagen que tan solo es un ejemplo, podría descargar y utilizar de forma similar una imagen de una caché **Redis** usando el mismo comando pero con el nombre de imagen ‘_redis_‘. + +Continuamos creando una aplicación **.Net Core** muy sencilla en la que solo estableceremos un controlador que devolverá una cadena de texto con un mensaje, esta será la aplicación que se contenerizara a continuación. + +**Program.cs** +```csharp + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup<Startup>(); + } +``` +**Startup.cs** +```csharp + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +``` +**GreetingsController.cs** +```csharp + [Route("api/[controller]")] + [ApiController] + public class GreetingsController : ControllerBase + { + + [HttpGet] + public string Greetings() + { + return "Greetings developer! I´m a contanerized API :)"; + } + } +``` +Como se puede apreciar es un proyecto **.Net Core** sencillo con un simple controlador _**GreetingsController**_, con una llamada GET en la que devolverá un mensaje de texto saludando al desarrollador que realice tal petición. A continuación lo que vamos a hacer es publicar nuestra aplicación en una carpeta, dejándola lista para ser desplegada en servicios de hosting. Para ello ejecutamos el siguiente comando: +```bash + dotnet publish "ContanerizedVisits.csproj" -c Release -o ./publish +``` +Ejecutando el comando anterior en la ruta en la que se encuentre el proyecto, lo que estamos haciendo es compilando la aplicación con la configuración establecida en el perfil de _**Release**_ y dejando el conjunto de librerías compiladas en la carpeta ubicada en la misma ruta denominada _**publish**_. Al ejecutar el comando deberíamos de ver por consola la siguiente salida (Si todo ha ido correctamente): + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/publishContanerizedApp-1024x121.png) + +Y si queremos observar el resultado, podemos ver lo que hay en la carpeta _**publish**_: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/ls-publish-folder-1024x54.png) + +Como vemos genera los ficheros de configuración junto con la librería compilada y el _**web.config**_ necesario para la configuración del sitio donde se hospede. Con todos esos ficheros podemos publicar nuestra aplicación en servidores web como puede ser **IIS ([Internet Information Service](https://es.wikipedia.org/wiki/Internet_Information_Services))**, en nuestro caso procederemos a publicarla en un contenedor Docker. + +### Dockerfile + +En seguida tendremos contenerizada nuestra aplicación y en funcionamiento, pero para ello vamos a necesitar el uso de un fichero de configuración el cual necesita Docker para saber cómo tiene que contenerizar nuestra aplicación. Este fichero se denomina **Dockerfile**, y debe de ser ubicado en la ruta en la que se encuentre nuestro proyecto de arranque. + +Básicamente lo que el Dockerfile contiene es un conjunto de comandos que **podríamos ejecutar directamente desde el CLI de Docker,** pero que como siempre suelen ser los mismos para una aplicación se genera este fichero con toda la lista de comandos a ejecutar en orden, y con esto Docker es capaz de generar automáticamente una imagen de nuestra aplicación. Nosotros utilizaremos un Dockerfile como el siguiente: + + FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-nanoserver-1803 AS base + + WORKDIR /app + + EXPOSE 80 + + COPY ./publish . + + ENTRYPOINT ["dotnet", "ContanerizedVisits.dll"] + +Existe una cantidad variada de comandos de configuración que se pueden establecer en un Dockerfile, pero no los voy a explicar todos porque para eso se encuentran las referencias de documentación oficial como puede ser [Docker Docs](https://docs.docker.com/engine/reference/builder/) en este caso. Si que iré explicando los que se usarán para crear la imagen de la aplicación de ejemplo mostrados. + +**FROM**. Todos los Dockerfile deben de comenzar con éste tipo de instrucción. Debido a que en éste punto estamos especificando qué imagen tomaremos de base para construir la aplicación. ¿Que quiere decir todo esto? Básicamente lo que estamos haciendo es indicarle a Docker que **necesitará el compilador de .Net Core 2.2**, en nuestro caso, para poder crear la aplicación. ¿Recordáis lo que os comenté cuando usamos la imagen de _hello-world_? Pues aquí esta haciendo algo parecido, buscará si tenemos localmente el compilador de .Net Core y si no, se lo descargará del repositorio de **DockerHub**. + +**_Nota_**_: Nosotros ya hemos compilado la aplicación previamente y puesto en una carpeta_ **_publish_** _todos los ficheros resultantes, por lo que realmente lo que haremos será utilizar el_ **[_CLI de dotnet_](https://docs.microsoft.com/es-es/dotnet/core/tools/?tabs=netcore2x)** _para ejecutar la aplicación en el contenedor._ _Pero podríamos compilar nuestra aplicación desde dentro del contenedor para después ejecutarla._ + +**WORKDIR**. Como su propio nombre indica, lo que realiza este comando es la creación del directorio de trabajo dentro del contenedor, en este caso estamos creando el directorio _/app_. Se pueden crear en realidad todos los directorios y subdirectorios que queramos, pero para nuestro objetivo con un solo directorio de trabajo nos basta. + +**EXPOSE**. Con ésta instrucción lo que estamos indicando es que la máquina expondrá en tiempo de ejecución el puerto especificado, es decir, nuestro contenedor podrá ver y comunicarse con lo que le entre por el puerto 80. Si no realizamos esta acción nuestros contenedores estarían ciegos cuando intenten comunicarse entre ellos, así como si nuestra aplicación se encuentra programada para escuchar por el puerto 5000, por ejemplo, pero no lo indicamos desde el contenedor, sería imposible acceder a él. + +**COPY**. Realiza la acción de copiar desde nuestro sistema de ficheros local, partiendo desde la ruta en la que se encuentre el Dockerfile, hacia el sistema de ficheros del contenedor. En este caso estamos copiando todos los ficheros generados en la publicación ubicados en la carpeta _**./publish**_, y transferirlos a la base del directorio de trabajo del contenedor _**/app**_. + +**ENTRYPOINT**. Con ésta instrucción lo que estamos haciendo es establecer que nuestra aplicación pueda ser ejecutada desde el cliente de Docker, y para ello le indicamos el punto de entrada a la imagen generada desde donde se puede lanzar la aplicación como un ejecutable. Por esto se le especifica en nuestra situación que el punto de entrada será **«dotnet»** junto con la librería base de nuestra aplicación **«ContanerizedVisits.dll»**, para que cuando le digamos a Docker que queremos una instancia de ésta aplicación el internamente sepa como arrancar la imagen ejecutando: +```bash + dotnet ContanerizedVisits.dll +``` +Con todas estas configuraciones establecidas **dentro del Dockerfile** ya estaríamos listos para lanzar nuestra aplicación contenerizada. + +### Creando la imagen Docker + +Recapitulemos un poco antes de continuar, en este punto tendremos una estructura de carpetas como la siguiente: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/folder-strucure.png) + +Tenemos la base del proyecto con los clases de **Program.cs y Startup.cs** para arranque y configuración de la aplicación, el controlador **GreetingsController.cs** con la llamada GET que devuelve una cadena de texto, la carpeta **publish** con la compilación del proyecto lista para ser contenerizada y el fichero **Dockerfile** con todas las instrucciones necesarias para que Docker sepa como crear la imagen y ejecutarla. Con todos estos ingredientes listos pasamos a encender los fogones y poner la olla a hervir. + +Para crear la imagen Docker bastaría con ejecutar el siguiente comando: +```bash + docker build -t contanerizedvisitsimage . +``` +Con la opción **«-t»** le estamos indicando que el nombre de la imagen sera el de **«contanerizedvisitsimage»** y el siguiente parámetro será la ruta donde se encuentre el proyecto junto con el Dockerfile, que en mi caso ya estaba ubicado en la misma ruta y por eso se nombra con **«.»**. + +Si todo ha funcionado a la perfección deberíamos de obtener un resultado como el siguiente: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/dockerBuildContanerizedApp.png) + +Como vemos se puede ver paso por paso como va ejecutando todas las instrucciones puestas en el Dockerfile, y el resultado será una imagen Docker con nuestra aplicación a la que hemos denominado «**contanerizedvisitsimage**«. Comprobemos el resultado con el comando: +```bash + docker image ls +``` +El cual nos muestra todas las imágenes que hemos creado con Docker. + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/dockerImageLs.png) + +Tanto las imágenes de Docker como luego los contenedores serán identificados siempre mediante un ID aleatorio por parte de Docker, por lo que si no damos nombre a nuestras imágenes siempre podremos trabajar con ellas en base al ID. + +### Ejecutando el contenedor + +Una vez creada nuestra imagen procedemos al último paso, ejecutar la aplicación en un contenedor, y para ello tan solo nos bastaremos de lanzar el siguiente comando: +```bash + docker run -p 5500:80 contanerizedvisitsimage +``` +Es tan simple como indicarle el puerto al que queremos publicar en el host local el que está utilizando internamente nuestra aplicación para que sea visible desde nuestra máquina, es decir, con **«-p 5500:80»** estamos indicando que todo lo que vaya hacía el contenedor por el puerto 5500 sea dirigido dentro del contenedor al puerto 80. + +Recordemos que nosotros ya habíamos expuesto el puerto 80 en el Dockerfile, lo que sucede es que exponer el puerto significa que **tu contenedor expone ese puerto pero no que desde tu máquina local (localhost o 127.0.0.1) puedas acceder a esa dirección**. Los contenedores cuando se crean se establecen por defecto con una dirección IP aleatoria gestionada por el **CNM de Docker**, así que lo que estamos haciendo es básicamente que desde una llamada en el navegador hacía mi máquina local _**localhost:5500**_ me redirija hacía el contenedor en el puerto 80. + +El resultado por la salida de la línea de comandos que estemos usando será la siguiente: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/dockerRunContanerizedApp.png) + +Indicando que nuestra aplicación se está ejecutando y la salida mostrada es la del ejecutable dentro del contenedor. Si ahora hacemos una llamada desde nuestro navegador hacía la ruta _http://localhost:5500/api/greetings_ obtendremos la respuesta desde el controlador de ejemplo dentro del contenedor: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/exampleContanerizedAPI.png) + +Por si todavía no te lo crees que esta aplicación esta contenerizada vamos a ejecutar el siguiente comando: +```bash + docker ps +``` +El cual te muestra todos los contenedores que existen en ejecución actualmente: + +![](http://mindbodyncode.com/wp-content/uploads/2019/06/dockerPS-1024x53.png) + +Como vemos muestra cierta información de configuración que hemos ido añadiendo durante todo el proceso. Si ahora queremos parar el contenedor en ejecución bastará con ejecutar el comando: +```bash + docker stop recursing_gauss +``` +Se puede apreciar como he utilizado el nombre aleatorio que te ofrece Docker para que una persona la cual está más acostumbrada a memorizar conceptos con significado en el lenguaje antes que Identificadores aleatorios pueda gestionar más fácilmente el uso de contenedores. De todas formas si eres más propenso a parecerte a una máquina también puedes utilizar el identificador para su gestión. + +## Conclusión + +Aquí terminamos este primer ejemplo de contenerización de aplicaciones con Docker, la verdad que ha sido mucho más teórico que práctico, pero personalmente me gusta más aprender los conceptos base de una tecnología en cuestión antes de lanzarme de cabeza a la piscina e ir probando a modo de ensayo y error hasta conseguir el objetivo. + +Tan solo hemos vislumbrado un poco la punta del iceberg en éste post, la contenerización de aplicaciones abarca varios aspectos a parte de tan solo contenerizar una aplicación ya compilada. Y además existen herramientas hoy en día que están muy bien integradas con contenedores Docker, como puede ser el propio **[Visual Studio 2019](https://visualstudio.microsoft.com/es/vs/)**, con el que es posible contenerizar aplicaciones pulsando un simple ‘_click_‘, generando automáticamente el Dockerfile, la imagen y ejecutarla en un contenedor de una forma muy sencilla. Pero por supuesto antes de montar en bici tenemos que aprender a caminar, por eso es por lo que recomiendo que una vez que estés acostumbrado a contenerizar aplicaciones con comandos nos pasemos a la «magia» de los IDE. + +En otros post que seguiré publicando sobre Docker, enseñare esto último que os he comentado con Visual Studio, y el uso de orquestadores. Con esto me despido, ¡Un saludo a todos los aventureros del aprendizaje! \ No newline at end of file diff --git a/content/blogs/2019-07-17-domotizando-nuestra-casa-con-home-assistant/es.md b/content/blogs/2019-07-17-domotizando-nuestra-casa-con-home-assistant/es.md new file mode 100644 index 0000000..e8c7bad --- /dev/null +++ b/content/blogs/2019-07-17-domotizando-nuestra-casa-con-home-assistant/es.md @@ -0,0 +1,105 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-07-17-domotizando-nuestra-casa-con-home-assistant/header.jpg +navigation: True +title: "Domotizando nuestra casa con Home Assistant" +date: 2019-07-17 12:00:00 +tags: homeassistant hassio domotica +class: post-template +subclass: 'post' +author: danimart1991 +--- + +Admitamos algo, el ser humano es vago por excelencia, pero los informáticos lo somos aún más si cabe. Es por este motivo por lo que intentamos automatizar todo lo posible, a veces demasiado. Una frase muy común es: + +> Si haces algo más de dos veces, automatiza para no tener que hacerlo una tercera. + +Podemos extrapolar esta frase a nuestro hogar. ¿Cuántas veces se nos ha olvidado una luz encendida? ¿cuántas hemos perdido el mando a distancia? ¿y si nos vamos unos días de casa y nos entran a robar? + +> Available in English [here](https://www.danielmartingonzalez.com/domotizing-our-house-with-home-assistant/). + +Solucionar todos estos "problemas" es más accesible hoy en día que nunca, pero si empezamos a comprar "cacharros", nos encontraríamos con un móvil repleto de aplicaciones de terceros, y muchos servicios con peligro de [ser espiados](https://www.xataka.com/privacidad/amazon-admite-que-conserva-algunos-algunos-datos-alexa-forma-indefinida-incluso-usuario-elimina-grabacion-voz) o que en algún momento [dejen de dar ese servicio](https://www.xataka.com/seguridad/quedarse-encerrado-calefaccion-imprevisibles-consecuencias-apagon-google-casa-conectada), y con ninguna interconexión entre todos los sistemas. + +Con el concepto de crear un sistema de control [*Open Source*](https://es.wikipedia.org/wiki/Open_Source_Definition) que lleva por bandera la privacidad y el servicio local nace [**Home Assistant**](https://www.home-assistant.io/). + +## Home Assistant + +![Home Assistant](/assets/images/posts/2019-07-17-domotizando-nuestra-casa-con-home-assistant/image01.jpg) + +[Home Assistant](https://www.home-assistant.io/) es un software de gestión de domótica para nuestro hogar capaz de integrar gran cantidad de dispositivos y servicios, tanto de terceros como propios. Lo mejor es, que aunque de evolución lenta, es [un software de código abierto muy vivo](https://github.com/home-assistant), con una comunidad enorme, potente y con una curva de aprendizaje muy buena. + +Podéis probar una *DEMO* [**aquí**](https://demo.home-assistant.io/#/lovelace/0) + +Al ser un software desarrollado en [*python*](https://www.python.org/) es compatible con multitud de sistemas operativos y dispositivos. Es distribuido de diferentes maneras para hacer las delicias de los usuarios principiantes y más avanzados. + +## *Hass.io* vs. *Home Assistant* vs. *Docker* + +Antes de entrar en materia y dado que esto suele ser bastante complejo. Vamos a distinguir las maneras más comunes de hacer funcionar **Home Assistant**. + +- [**Hass.io**](https://www.home-assistant.io/hassio/installation/) es una distribución de *Linux* optimizada para ejecutar **Home Assistant** sobre [*Docker*](https://www.nocountryforgeeks.com/contenerizacion-de-aplicaciones-en-docker/). Es decir, se ha creado una distribución a medida para que todo esté listo para instalar y funcionar. Como ventaja, es sencillo y tiene un sistema de [**Add-ons**](https://www.home-assistant.io/addons/) muy potente. Como desventaja, no tenemos acceso real al sistema operativo que ejecuta el contenedor de *Hass.io*. Es la opción de instalación que veremos en este post. +- **Home Assistant** como hemos mencionado es un software y como tal, puede instalarse en cualquier distribución *Linux* (incluso si te atreves en *Windows*), lo normal es instalar una distribución *Linux* sencilla, como [**Hassbian**](https://www.home-assistant.io/docs/hassbian/installation/) para [*Raspberry Pi*](https://www.raspberrypi.org/) por ejemplo, y luego *Home Assistant* como "aplicación", teniendo toda la potencia y control. +- [**Docker**](https://www.home-assistant.io/docs/installation/docker/) es la opción más ventajosa para algunos, pero también la más avanzada. Consiste en crear un [contenedor de *Docker*](https://www.nocountryforgeeks.com/contenerizacion-de-aplicaciones-en-docker/) con *Home Assistant*, de tal manera que tengamos un control total, y al mismo tiempo aislado de todo *Home Assistant* con las ventajas que ello conlleva. + +Pese a todas estas diferencias, luego se pueden combinar, por ejemplo, instalar *Home Assistant* con *Docker* y meter *Hass.io* por encima. + +## Instalando *Hass.io* + +Tal y como hemos mencionado, vamos a ver como instalar **Hass.io**, la razón, tiene la instalación más sencilla y rápida. Se puede probar *Home Assistant* tras unos minutos y según aprendamos, pasar a los otros sistemas. + +Lo recomendable para empezar, dada la calidad-precio, es utilizar una [**Raspberry Pi 3 B+**](https://amzn.to/2lBMWFL) con [una **MicroSD** de al menos *32Gb*](https://amzn.to/2lBMWFL). Por supuesto, se puede utilizar [*hardware* de lo más variado](https://www.home-assistant.io/hassio/installation/), pero creo que esta es una de las mejores opciones. + +1. Descargamos la imagen más actual de *Hass.io* para nuestro dispositivo. En el momento de escribir este artículo, [**Hass.io 2.12** para *Raspberry Pi 3 B / B+ 32bit*](https://github.com/home-assistant/hassos/releases/download/2.12/hassos_rpi3-2.12.img.gz). + +2. Descargamos e instalamos en nuestro ordenador un quemador de imágenes en tarjetas *SD*. A mí me gusta [**balenaEtcher**](https://www.balena.io/etcher). + +3. Quemamos la imagen en la tarjeta *SD*. Para ello, basta con abrir el programa previamente instalado, insertar la tarjeta *SD* en el lector, seleccionar la imagen descargada en el **paso 1** y hacer clic en *Flash*. No extraigáis la tarjeta todavía. + + ![balenaEtcher](/assets/images/posts/2019-07-17-domotizando-nuestra-casa-con-home-assistant/image01.gif) + +4. **Opcional**, pero que os recomiendo encarecidamente es crear un archivo para configurar la conexión de red del dispositivo, es un paso algo complicado, vamos por partes. + + Mi recomendación personal pasa por asignar una ***IP* estática** a nuestra *Raspberry Pi*. Aunque se pueden reservar *IPs* en el *Router*, creo que una mejor opción es crear un rango de *IPs* dinámicas (*DCHP*) a partir de un número alto de dispositivos, por ejemplo, ``192.168.1.128-255``, de tal manera que todas las *IPs* inferiores a *128* las podremos usar para dispositivos con *IP* estática. Todo ello conectando un cable ***Ethernet*** entre el dispositivo y el *Router*. + + 1. Abrimos el directorio de la tarjeta *SD* recién creada y creamos los directorios *`CONFIG`* y dentro *`network`*. Por último, el fichero *`my-network`* dentro de este último, sin extensión. Quedará **`X:\CONFIG\network\my-network`**. + 2. Editamos el fichero **`my-network`** e incluimos el siguiente código: + + ``` + [connection] + id=my-network + uuid=d55162b4-6152-4310-9312-8f4c54d86afa + type=802-3-ethernet + + [ipv4] + method=manual + address=192.1.1.4/24,192.168.1.1 + dns=8.8.8.8;8.8.4.4; + + [ipv6] + addr-gen-mode=stable-privacy + method=auto + ``` + + Cambiamos el campo `address` por la *IP* y *puerta de enlace* deseada, y el campo `dns`, por aquellas *DNS* que quedamos usar. Apuntad la *IP* que vais a asignar al dispositivo porque es bastante importante. + +5. Expulsamos la tarjeta *SD* del ordenador y la introducimos dentro de la *Raspberry Pi*. La enchufamos a la corriente y esperamos, depende del dispositivo la descarga de última versión e instalación puede llevar **hasta 20 minutos**. + + ![Instalando Hass.io](/assets/images/posts/2019-07-17-domotizando-nuestra-casa-con-home-assistant/image02.jpg) + +6. Mientras tanto, podemos buscar el manual de nuestro *Router* y ver si tiene disponible **mDNS**, en cuyo caso, podremos acceder a la interfaz gráfica de *Home Assistant* desde cualquier otro dispositivo accediendo con el navegador a la siguiente dirección: **http://hassio.local:8123**, en caso contrario, y dado que hemos configurado una *IP* estática en nuestro servidor, podremos acceder a la interfaz con la dirección: **http://192.168.1.4:8123** (siendo la *IP* la puesta anteriormente en el fichero de configuración de red). + +## Configuración básica + +Una vez instalado, se nos mostrará un par de pantallas para realizar la configuración básica de *Home Assistant*, como el usuario y la ubicación del dispositivo. Por último, iniciamos sesión y aparecerá la pantalla principal de nuestro *Home Assistant*. + +![Inicio Sesión en Home Assistant](/assets/images/posts/2019-07-17-domotizando-nuestra-casa-con-home-assistant/image03.jpg) + +> Cabe destacar que, durante la instalación, *Home Assistant* buscará dispositivos inteligentes en la red con los que tenga auto-integración para incluirlos directamente en su configuración. No os preocupéis, podremos añadirlos más tarde. De hecho, los usuarios más avanzados en *Home Assistant* suelen evitar estas integraciones automáticas. + +## Conclusión + +![Página inicial Home Assistant](/assets/images/posts/2019-07-17-domotizando-nuestra-casa-con-home-assistant/image04.jpg) + +En este momento, ya tendremos disponible nuestro propio **Home Assistant** ejecutándose en un servidor local. Y con ello, todo un abanico de posibilidades que descubriremos en próximos artículos. Hemos tardado apenas unos minutos en empezar, pero pronto descubriremos todo el potencial que **Home Assistant** tiene reservado para nosotros. + +Este y otros artículos complementan la explicación del [**repositorio de *GitHub***](https://github.com/danimart1991/home-assistant-config) donde se encuentra disponible la configuración de la domotización de mi casa. diff --git a/content/blogs/2019-08-14-jugando-con-svelte/es.md b/content/blogs/2019-08-14-jugando-con-svelte/es.md new file mode 100644 index 0000000..9c793f7 --- /dev/null +++ b/content/blogs/2019-08-14-jugando-con-svelte/es.md @@ -0,0 +1,406 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-08-14-jugando-con-svelte/header.png +navigation: True +title: "Jugando con svelte" +date: 2019-08-14 12:00:00 +tags: svelte javascript frontend +class: post-template +subclass: "post" +author: franmolmedo +--- + +Sí, otra _[librería / framework / ponga aquí el nombre que quiera]_ para ayudarnos en la tarea de realizar nuestras interfaces de usuario web utilizando javascript. ¿Por qué? ¿Para qué?. Parafraseando a San Agustín: + +> Por muy lejos que hayamos llegado, el ideal siempre estará más allá. + +Tenemos herramientas fiables que han demostrado su capacidad para ayudarnos a realizar esta tarea. Librerías como _React_ o frameworks como _Vue_ o _Angular_ deberían ser suficientes para satisfacer las necesidades de cualquier proyecto. Sin embargo, siempre existen otros enfoques que hay que analizar y que nos pueden aportar otra perspectiva a la hora de encarar nuestros proyectos. En este punto es donde entra **Svelte**. + +## ¿Qué es Svelte? + +Según su [propia página web](https://svelte.dev) _Svelte_ es un framework orientado a componentes que nos ayuda a implementar nuestros interfaces de usuario. ¿Entonces, qué nos aporta con respecto a los que hemos nombrado anteriormente? En este caso el enfoque a la hora de realizar esta tarea es lo que va a marcar la diferencia. En los casos conocidos (_React_, _Vue_ o _Angular_) se nos anima a desarrollar código declarativo que, posteriormente, exige un trabajo extra al navegador para interpretarlo. Igualmente, exigen introducir en los archivos que servimos al compilador la librería correspondiente que estemos utilizando. _Svelte_, por el contrario, introduce una etapa de compilación que va a transformar nuestro código declarativo en un código imperativo con diversas optimizaciones. Además, evita introducir mecanismos como **_Virtua DOM_** para realizar las modificaciones en el DOM de la página; sino que, utilizando este proceso de compilación con sus optimizaciones, puede determinar en tiempo de compilación qué va a cambiar en un determinado componente y generar el código más adecuado que permita realizar estos cambios. ¿Entonces es más rápido o eficiente que _React_, _Vue_ y compañía? ¿Las aplicaciones ocupan menos peso?. Trataremos de verlo en los siguientes apartados. + +## Iniciando un proyecto + +Iniciar un proyecto utilizando _Svelte_ no puede ser más sencillo. Descargamos la plantilla por defecto que nos proporcionan los autores y ya podemos empezar a trabajar. Para ello, ejecutamos en nuestro terminal lo siguiente: + +```bash +npx degit sveltejs/template [nombre del proyecto] +``` + +![Clonando el repositorio](/assets/images/posts/2019-08-14-jugando-con-svelte/cloning-repository.png) + +Se nos habrá clonado el repositorio _sveltejs/template_ en una carpeta con el mismo nombre que el nombre del proyecto que hayamos señalado. Con ello, sólo nos quedaría por irnos a dicha carpeta, instalar las dependencias y ejecutar el proyecto: + +![Ejecutando el proyecto](/assets/images/posts/2019-08-14-jugando-con-svelte/ejecutando-proyecto.gif) + +Ya tendríamos el proyecto funcionando y esperando nuestro cambios para volver a recompilar y mostrar el resultado por pantalla. Si abrimos nuestro navegador favorito en la dirección que se nos indica obtenemos lo siguiente: + +![Hello world](/assets/images/posts/2019-08-14-jugando-con-svelte/hello-world.png) + +### Analizamos la plantilla + +El proyecto que se genera a partir de la plantilla presenta la siguiente estructura: + +![Structure](/assets/images/posts/2019-08-14-jugando-con-svelte/structure.png) + +Si hemos utilizado alguna de las librerías / frameworks que hemos mencionado anteriormente esta estructura de ficheros nos debe de resultar familiar. Destacamos los siguientes puntos: + +- Se utiliza [rollup](https://rollupjs.org/guide/en/) en lugar de [webpack](https://webpack.js.org/) como herramienta para construir el bundle de salida. En esta entrada no profundizaremos en _rollup_, pero no estaría de más adentrarse un poco en cómo podemos ajustarlo para satisfacer nuestras necesidades más allá de la configuración básica que nos ofrece la plantilla de _svelte_. +- La aplicación tiene un punto de entrada en el fichero **_main.js_**. En él, cargaremos el componente de entrada de la aplicación y lo situaremos en el DOM: + +```javascript +import App from "./App.svelte"; + +const app = new App({ + target: document.body, + props: { + name: "world" + } +}); + +export default app; +``` + +La composición de este fichero tampoco debe de extrañar. Se importa el componente de entrada desde el archivo _App.svelte_ que veremos posteriormente y lo instanciamos. Esta instancia la creamos utilizando un objeto de configuración donde le indicamos el elemento del DOM donde queremos que se inserte nuestro componente (utilizando la propiedad _target_), y le pasamos al componente unas _props_ (igual que hacemos en _React_). Estas props consisten en un objeto con una propiedad llamada _name_. + +- Por último, analizamos el fichero _App.svelte_: + +```javascript +<script> + export let name; +</script> + +<style> + h1 { + color: purple; + } +</style> + +<h1>Hello {name}!</h1> +``` + +Esto ya es algo más inusual, aunque los que hayan utilizado _Vue_ sí estarán más familirizados con esta sintaxis. Los componentes _svelte_ se caracterizan por estar estructurados en tres secciones: + +- Una sección donde situamos el código javascript del componente (enmarcada dentro de las etiquetas _script_). +- Una sección donde describeremos el estilo que se utilizará en el componente (dentro de las etiquetas _style_). +- Por último, tenemos el markup HTML que renderizará la estructura de nuestro componente, o sea, como se pintará en el DOM. + +Pero, los más observadores, habrán notado alguna peculiaridad más: + +- **¡¡Se hace un export de una variable _name_ en la parte de los scripts!!**. Sí, y no es casualidad que tenga el mismo nombre que la prop que le pasamos desde el archivo _main.js_. Esta es la notación que utilizamos en _Svelte_ para recuperar la prop que nos hace llegar el componente padre y poder utlizarla luego en nuestro markup. +- **¡¡Qué pasará cuando varios componentes usen el mismo h1!!**. Los estilos son locales a cada componente donde se declaran por lo que evitaremos colisiones. Eso sí, disponemos de un fichero de estilos globales para definir aquellos estilos que vayan a utilizarse a nivel más general dentro de la aplicación. +- **¡¡Esas llaves no son propias del lenguage HTML!!**. Correcto. Esa sintaxis es procesada por _Svelte_ en tiempo de compilación y permite introducir un valor que estemos declarando en la parte de scripts. + +Hay algunas peculiaridades más en la sintaxis que iremos comentando conforme vayamos explicando las bondades de este framework. Sin embargo, en este momento, me gustaría hacer hincapié en el tamaño del bundle generado. Para ello, generamos un bundle de producción con el comando _yarn build_ y observamos que el fichero _bundle.js_ ocupa **3KB**: + +![Bundle](/assets/images/posts/2019-08-14-jugando-con-svelte/bundle.png) + +Si ahora creamos una aplicación _React_ utilizando _create react app_ y generamos el bunlde de salida obtemos lo siguiente: + +![Bundle React](/assets/images/posts/2019-08-14-jugando-con-svelte/bundle-react.png) + +**Ou mama!** La diferencia (abismal) no nos debería de preocupar en estos momentos, pero nadie puede negar que es un buen comienzo. + +## Construyamos una aplicación + +Entramos en faena. Con el fin de ilustrar la sintaxis a usar y ciertos detalles interesantes del framework, vamos a crear una pequeña aplicación. En esta aplicación vamos a mostrar una lista de razas de perros y al pulsar sobre uno de ellos se nos abrirá una ventana modal mostrándonos más información sobre dicha raza. Parece sencillo, pero creo que es una buena manera de mostrar algunas peculiaridades de _Svelte_. + +### Creando la lista + +El primer paso es almacenar en algún lugar nuestra lista de razas. Para ello, en el fichero _App.svelte_ vamos a eliminar la variable _name_ y vamos a generar una constante que contega una lista de 3 razas de perros con los siguientes datos: nombre de la raza, un thumbnail, breve descripción, una foto a mayor tamaño y un identificador. Crearemos la lista dentro de la sección _scripts_ y luego la utilizaremos en nuestro markup utilizando un bloque _each_ que nos provee _Svelte_: + +```javascript +<script> + import DogItem from "./DogItem.svelte"; + + const dogs = [ + { + id: 1, + name: "German Shepherd", + thumbnail: + "https://cdn1-www.dogtime.com/assets/uploads/2011/01/file_23188_german-shepherd-dog-300x189.jpg", + details: + "The German Shepherd Dog is one of America’s most popular dog breeds—for good reason. They’re intelligent and capable working dogs. Their devotion and courage are unmatched. And they’re amazingly versatile, excelling at most anything they’re trained to do: guide and assistance work for the handicapped, police and military service, herding, search and rescue, drug detection, competitive obedience, and–last but not least–faithful companion", + bigPicture: + "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/02.Owczarek_niemiecki_u%C5%BCytkowy_kr%C3%B3tkow%C5%82osy_suka.jpg/1920px-02.Owczarek_niemiecki_u%C5%BCytkowy_kr%C3%B3tkow%C5%82osy_suka.jpg" + }, + { + id: 2, + name: "Siberian husky", + thumbnail: + "https://cdn3-www.dogtime.com/assets/uploads/2011/01/file_22948_siberian-husky-300x189.jpg", + details: + "The Siberian Husky is a beautiful dog breed with a thick coat that comes in a multitude of colors and markings. Their blue or multi-colored eyes and striking facial masks only add to the appeal of this breed, which originated in Siberia. It is easy to see why many are drawn to the Siberian’s wolf-like looks, but be aware that this athletic, intelligent dog can be independent and challenging for first-time dog owners. Huskies also put the “H” in Houdini and need a fenced yard that is sunk in the ground to prevent escapes", + bigPicture: + "https://upload.wikimedia.org/wikipedia/commons/a/a3/Black-Magic-Big-Boy.jpg" + }, + { + id: 3, + name: "Doberman pinsher", + thumbnail: + "https://cdn1-www.dogtime.com/assets/uploads/2011/01/file_22920_doberman-pinscher-300x189.jpg", + details: + "The Doberman Pinscher was developed in Germany during the late 19th century, primarily as a guard dog. His exact ancestry is unknown, but he’s believed to be a mixture of many dog breeds, including the Rottweiler, Black and Tan Terrier, and German Pinscher. With his sleek coat, athletic build, and characteristic cropped ears and docked tail, the Doberman Pinscher looks like an aristocrat. He is a highly energetic and intelligent dog, suited for police and military work, canine sports, and as a family guardian and companion", + bigPicture: + "https://upload.wikimedia.org/wikipedia/commons/c/c0/0Doberman-40172501920.jpg" + } + ]; +</script> + +<section id="dogs"> + {#each dogs as dog} + <DogItem name={dog.name} thumbnail={dog.thumbnail} id={dog.id} /> + {/each} +</section> +``` + +- El primer aspecto a tener en cuenta es que estamos importando un componente llamado _DogItem_ que utilizaremos para mostrar cada una de las razas que queramos pintar en nuestra lista. El código asociado a este componente, que crearemos en un archivo llamado _DogItem.svelte_, es el siguiente: + +```javascript +// DogItem.svelte +<script> + export let id; + export let name; + export let thumbnail; +</script> + +<article> + <h1>{name}</h1> + <img alt={name} src={thumbnail} /> +</article> +``` + +Básicamente recogemos las props que nos llegan desde el componente padre (_App.svelte_) y utilizamos esos valores para mostrarlos en nuestro HTML. + +- Un segundo aspecto importante a comentar es la forma en la que hemos recorrido los elementos de la lista. _Svelte_ nos proporciona bloques que nos permiten implementar funcionalidad diferente: en este caso recorrer la lista con el bloque _each_, también podemos tener condiciones utilizando el bloque _if_ en conjunción con el bloque _else_ o con los bloques _else if_ o incluso tener la capacidad de trabajar con promesas con el bloque _await_. Sinceramente, este tipo de sintaxis es uno de los puntos que menos me gustan de _Svelte_ ya que preferiría un enfoque más directo utilizando solamente javascript. + + En este punto me gustaría trabajar un poco más en nuestro bloque _each_ para ver las distintas variaciones de la sintaxis que podemos usar: + + - Podemos aplicar destructuring en los valores de los elementos de la lista: + + ```javascript + {#each dogs as {name, thumbnail, id}} + <DogItem name={name} thumbnail={thumbnail} id={id} /> + {/each} + ``` + + - Igualmente, al coincidir el nombre de la propiedad con la variable, podemos utilizar una sintaxis más reducida: + + ```javascript + {#each dogs as { name, thumbnail, id }} + <DogItem {name} {thumbnail} {id} /> + {/each} + ``` + + - También podemos obtener el índice de cada elemento de la iteración: + + ```javascript + {#each dogs as { name, thumbnail, id }, index} + <DogItem {name} {thumbnail} {id} /> + {/each} + ``` + +El resultado de este código es el siguiente: + +![Dog list](/assets/images/posts/2019-08-14-jugando-con-svelte/dog-list.png) + +### Añadiendo algo de funcionalidad + +Con el fin de ilustrar algunas características más que nos ofrece _Svelte_ vamos a implementar el detalle de los elmentos de nuestra lista. La idea es que vamos a generar un componente modal que, al pulsar sobre uno de los elementos de nuestra lista, se va a pintar dentro de él el detalle de la raza que hayamos escogido. Implemnetando el modal vamos a poder introducir conceptos como los _eventos definidos por el usuario_ y los _slots_. + +El primer paso de todos es crear un nuevo fichero (llamado _Modal.svelte_) donde vamos a implementar nuestro componente modal genérico (surpimimos la parte de estilos para evitar que el código se alargue demasiado): + +```javascript +<script> + import { createEventDispatcher } from "svelte"; + + const dispatch = createEventDispatcher(); + + const closeModal = () => dispatch("closemodal"); +</script> + +<div class="back" on:click={closeModal} /> +<div class="modal"> + <header> + <slot name="header">TEST</slot> + </header> + <div class="content"> + <slot /> + </div> + <footer> + <slot name="footer"> + <button on:click={closeModal}>Close</button> + </slot> + </footer> +</div> +``` + +En primer lugar vamos a centrarnos en los **slots**. Estos slots forman parte del standard _HTML_ (dentro de la parte destinada a la creación de componentes web) y cuya función es servir de placeholder para poder insertar contenido desde otro lugar, en nuestro caso desde otro componente. Estos _slots_ pueden ser nombrados (como el caso de _header_ o _footer_) utilizando el atributo _name_ o por defecto, como es el que se utiliza dentro del div con la clase _content_. Un compomente sólo puede contener un slot por defecto, pero sí puede tener un número indeterminado de slots nombrados. Otro aspecto a destacar es que podemos proporcionar una implementación por defecto del slot. Por ejmplo, en el caso del slot _footer_ estamos diciendo que si no se provee contenido para el slot actual, queremos que se muestre un botón. + +**¿Cómo pasamos el contenido a los slots?** Para ello, lo mejor es mostrar el código final de nuestro archivo _App.svelte_ (igualmente omitimos los estilos y la implementación del array _dogs_): + +```javascript +<script> + import DogItem from "./DogItem.svelte"; + import Modal from "./Modal.svelte"; + + const dogs = [ +... + ]; + + let selectedDog = null; + + const selectItemHandler = event => { + const { id } = event.detail; + + selectedDog = dogs.find(dog => dog.id === id); + }; + + const closeModalHandler = () => (selectedDog = null); +</script> + +<section id="dogs"> + <header>Our dogs</header> + {#each dogs as { name, thumbnail, id }, index} + <DogItem {name} {thumbnail} {id} on:selectitem={selectItemHandler} /> + {/each} + {#if selectedDog !== null} + <Modal on:closemodal={closeModalHandler}> + <span slot="header">{selectedDog.name}</span> + <div class="detail-content"> + <div>{selectedDog.details}</div> + <img alt={selectedDog.name} src={selectedDog.bigPicture} /> + </div> + </Modal> + {/if} +</section> +``` + +Como vemos, para pasar el contenido a un slot, podemos utilizar otro elemento _HTML_ (en nuestro caso _span_) indicándole que ese _span_ se ha de colocar en el _slot_ cuyo nombre le estamos pasando dentro del atributo _slot_. De esta forma, pasamos los slots nombrados. En el caso del resto del contenido situado entre las etiquetas _<Modal></Modal>_ se pasará al slot por defecto. + +Por último, nos quedaría hablar de la creación de eventos. Como vemos en el código del _modal_ usamos la utilidad _createEventDispatcher_ que importamos directamente desde _svelte_. Esta utilidad nos permite construir una función _dispatch_, a partir de la cual podemos generar este tipo de eventos personalizados. La función _dispatch_ puede recibir un par de parámetros: + +- El primero sería el nombre del evento. Este nombre es muy importante porque en el componente padre podremos asociar manejadores del evento utilizando dicho nombre. Por ejemplo, si creamos un evento personalizado llamado _closemodal_, posteriormente podemos definirle un manejador de la siguiente forma: _on:closemodal={manejador}_, tal y como hemos hecho en el ejemplo. + +Además, estos eventos pueden contener información. Esta información se le indica en el segundo parámetro de la función _dispatch_. Lo vemos en el código definitivo del componente _DotItem.svelte_ (también suprimimos los estilos por concretitud): + +```javascript +<script> + import { createEventDispatcher } from "svelte"; + + const dispatch = createEventDispatcher(); + + export let id; + export let name; + export let thumbnail; +</script> + +<article on:click={() => dispatch('selectitem', { id })}> + <h1>{name}</h1> + <img alt={name} src={thumbnail} /> +</article> +``` + +En la función _dispatch_ del evento _selectitem_ hemos añadido como segundo parámetro un objeto que contiene la propiedad _id_ refiriéndose al identficador del elemento seleccionado. Posteriormente, este valor podemos recuperarlo desde el propio evento, en la propiedad _details_, como vemos en el código del manejador siguiente: + +```javascript +const selectItemHandler = event => { + const { id } = event.detail; + + selectedDog = dogs.find(dog => dog.id === id); +}; +``` + +De esta forma hemos implementado la apertura o no del modal para mostrar los detalles del perro seleccionado. Cuando se selecciona un elmeento de la lista, se manda el evento _selectitem_ que tiene asociado el manejador anterior. Así actualizamos la variable _selectedDog_ con los detalles del perro elegido. Posteriormente en el markup tenemos un senticia _if_ que nos permite pintar el modal si tenemos un perro seleccionado o no pintarlo si _selectedDog_ es _null_. Para volver a establecer _selectedDog_ a _null_, tenemos un evento llamado _closemodal_ cuyo manejador realiza esa acción: + +```javascript +const closeModalHandler = () => (selectedDog = null); +``` + +Nuestra aplicación terminada luce así: + +![Aplicación finalizada](/assets/images/posts/2019-08-14-jugando-con-svelte/app-finished.gif) + +## Routing y Server Side Rendering con Sapper + +Los autores de _Svelte_ están trabajando igualmente en un framework que nos proporciona routing y renderizado de servidor utilizando _Svelte_. Constituye lo que representa [Next](nextjs.org) a _React_. Por lo tanto no es una alternativa a todo lo que hemos contando, sino que nos va ayudar a llevar a cabo estas dos tareas concretas. + +- Sapper implementa el enrutado asignando componentes de _Svelte_ a direcciones URL. +- con el renderizado de servidor, el primer renderizado de la página se realiza en servidor (obviedad!) con lo que ganaremos velocidad en este primer render y también facilitaremos los procesos de SEO. Con ello, al navegador le enviaremos una página HTML con contenido y algo de javascript, en lugar de un HTML prácticamente vacío y gran carga de javascript (modelo de SPA). + **¿Cómo se configura todo esto?** Al igual que ocurre en _Next.js_ es necesario comprender el sistema de carpeta ya que mucha de la configuración se lleva a cabo dependiendo de la localización de los ficheros. + +### Empezando nuestro proyecto con Sapper + +Al igual que con _Svelte_ tenemos varias plantillas con la que podemos empezar. Utilizaremos, igual que antes, la plantilla basada en _Rollup_, pero podemos elegir la basada en _Webpack_ si así lo preferimos. + +```bash +npx degit "sveltejs/sapper-template#rollup" [nombre del proyecto] +``` + +En el siguiente gif vemos cómo llevamos la inicialización del proyecto: + +![Init con sapper](/assets/images/posts/2019-08-14-jugando-con-svelte/sapper-init.gif) + +Y listo, no se tarda prácticamente nada en tener montado el scaffolding básico y listo para trabajar. Lo que obtenemos por pantalla es lo siguiente: + +![Sapper pantalla inicial](/assets/images/posts/2019-08-14-jugando-con-svelte/sapper-initial-screen.png) + +### Hablemos de la estructura del proyecto + +La estructura del proyecto que se nos ha generado es la siguiente (hemos eliminado el fichero _.gitignore_ y el _README.md_ ya que en nuestro repositorio hemos recogido tanto el proyecto básico de svelte como éste y esos dos ficheros los tendremos en la raíz): + +![Sapper estructura](/assets/images/posts/2019-08-14-jugando-con-svelte/sapper-structure.png) + +Destacamos los aspectos más importantes: + +- En la carpeta _src_ tenemos, a su vez, dos carpetas: una carpeta _components_ donde, en principio, se colocarán los componentes que utilizaremos en nuestras páginas y una carpeta _routes_ que contendrá las rutas o páginas de la aplicación. Por lo tanto, cada componente que situemos en esta carpeta se corresponderá, automáticamente, con una ruta. En nuestro ejemplo actual tenemos en esta carpeta cuatro ficheros: _index.svelte_ que se corresponderá a la dirección raíz de nuestra página y _about.svelte_ que se corresponderá a la página about. Tal es el caso, que si navegamos a la página _localhost:3000/about_ se renderizará el componente del fichero _about.svelte_ situado en la carpeta _routes_. El nombre del directorio _components_ podemos modifcarlo, el de _routes_ no. + +![Sapper about](/assets/images/posts/2019-08-14-jugando-con-svelte/sapper-about.png) + +- Dentro de la carpeta _routes_ encontramos también una carpeta _blog_ que contiene, a su vez, una serie de ficheros que pasamos a comentar: + + - _index.svelte_: Componente que se renderizará cuando se navegue a la página _host/blog_. En él se renderizarán los posts que se encuentren disponibles en el blog. + - _index.json.js_: Este es un fichero de servidor. En él establecemos la respuesta a una llamada http preguntando por los posts disponibles. De ellos devolveremos el título y el _slug_ + - _[slug].svelte_: Componente que se renderizará cuando se navegue a la dirección _host/blog/[slug]_ Es importante hacer notar que _slug_ es un placeholder, que puede ser sustituído por cualquier cadena de caracteres (que en este caso serían los slugs de cada post). Así podemos navegar a url personalizadas, como, por ejemplo: + +![Sapper slugs](/assets/images/posts/2019-08-14-jugando-con-svelte/sapper-slugs.png) + + - _[slug].json.js_: Es el equivalente al _index.json.js_ pero para la página de slugs. + - *_posts.js*: Fichero que contiene la información de los posts. Actúa de respositorio y será utilizado por los ficheros de servidor para recuperar la información de los posts. + - Quedarían dos archivos más por comenentar y ambos comienzan por guión bajo: *_error.svelte* es el componente que se mostará cuando haya algún error, sería algo así como la página de error general por defecto y *_layout.svelte* que constituye un esquema general que van a seguir todas las páginas. Es decir, el resto de páginas tomarán lo descrito en este fichero como plantilla para rederizarse de la manera descrita en él. + +- Los ficheros _client.js_, _server.js_ y _service-worker.js_ son ficheros de utilidad que se encargan de preparar al cliente que se utilizará en el navegador, el servidor (que por defecto utiliza _polka_, pero que podemos cambiar a _express_ si se desea) y el último nos proporciona una configuración básica de service worker que podemos modificar para construir una aplicación _PWA_. +- Por último, tenemos el fichero _template.html_ que constituye la estructura básica del _HTML_ que se generará. + +Es importante hacer notar que en la configuración básica tenemos preparado [cypress](www.cypress.io) y que el _live reloading_ está implementado por defecto. Por otro lado, y aunque en esta introducción no ahondaremos en ello, tenemos funcionalidad como el _prefetching_, uso de _stores_ generales y demás que se encuentran igualmente disponibles. + +### Implementando nuestro ejemplo en Sapper + +Una vez conocida la estructura y la disposición de los ficheros que tenemos que usar, para que nuestro pequeño ejemplo funcione con _sapper_ tenemos que adaptarlo a dicha estructura. Así, eliminamos todos los componentes y las rutas que nos interesan y copiamos nuestros componentes _DogItem_ y _Modal_ en la carpeta _components_. Mientras, copiamos el contenido de _App.svelte_ en el fichero _index.svelte_ de la carpeta routes. Con ello, y cambiando las direcciones de los _imports_ debería de bastar para que funcione este ejemplo. Si lanzamos ambas aplicaciones podemos observar la diferencia entre una y otra: + +- Aplicación con _Svelte_: + +![Svelte network](/assets/images/posts/2019-08-14-jugando-con-svelte/svelte-network.png) + +- Aplicación con _Sapper_: + +![Sapper network](/assets/images/posts/2019-08-14-jugando-con-svelte/sapper-network.png) + +Como vemos, mientras en el primer caso se descarga la plantilla del _HTML_ básica para después crear la página a partir del _bundle.js_; en el segundo, la página _HTML_ ya se ha renderizado en servidor y la bajamos ya construida. Eso no quita que se descargue también contenido javascript para poder hacer las interacciones en el cliente. + +## Conclusiones + +Sólo hemos mostrado una pequeña parte de lo que nos ofrece _Svelte_. Si os resulta interesante en siguientes entradas podemos hablar de cómo implementar _stores_ o _contextos_ para almacenar los datos de nuestra aplicación, profundizar en el uso de _sapper_ y comentar funcionalidad como el _prefetch_ de datos o adentrarnos en interacciones más complejas como los distintos tipos de bindings exitentes, transiciones, formas de organizar el código o cómo fomentar el reuso de componentes. + +En definitiva, me _Svelte_ me parece una gran alternativa a las opciones más populares existentes hoy en día, tanto por su versatilidad, por la generación de bundles de tamaño reducido y por la concepción y la idea en general. Además, tenemos ya componentes interesantes elaborados por la comunidad como: + +- Listas virtualizadas utilizando [Svelte virtual list](https://github.com/sveltejs/svelte-virtual-list) +- Podemos utilizar librerías de CSS-in-JS como [emotion](https://svelte.dev/blog/svelte-css-in-js) +- Librerías de testing como [Svelte testing library](https://github.com/testing-library/svelte-testing-library) +- Componentes de material design: [Smelte](https://github.com/matyunya/smelte) + +Así mismo, os dejo el repositorio con el código de los dos ejemplos en: [svelte101](https://github.com/franmolmedo/svelte101) + +¡Mantente curioso! diff --git a/content/blogs/2019-08-22-contruye-un-server-graphql-con-prisma/es.md b/content/blogs/2019-08-22-contruye-un-server-graphql-con-prisma/es.md new file mode 100644 index 0000000..9e7a45b --- /dev/null +++ b/content/blogs/2019-08-22-contruye-un-server-graphql-con-prisma/es.md @@ -0,0 +1,461 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/coverPage.jpg +navigation: True +title: 'Prisma: construye tu servidor GraphQL de una forma rápida y sencilla' +date: 2019-08-21 12:00:00 +tags: mongodb prisma graphql-yoga +class: post-template +subclass: 'post' +author: ivanrodri +--- + +Este es el primer post de una serie donde vamos a ver como podemos crear un servidor **GraphQL** para un carrito de la compra con [**Prisma**](https://www.prisma.io/) y [**graphql-yoga**](https://github.com/prisma/graphql-yoga) de una forma rápida y sencilla + +# Requisitos + +- [yarn](https://yarnpkg.com/lang/en/) +- [docker](https://www.docker.com/) + +# Que es Prisma? + +Antes de empezar a trabajar con **Prisma** vamos a comentar **¿Qué es?**. + +**Prisma** es un conector de base de datos **GraphQL** en tiempo real que convierte la base de datos en un API **GraphQL**, podemos verlo como una especie de **ORM**, pero es mucho más poderoso que los **ORM** tradicionales. + +Con **Prisma**, obtenemos un servidor (servidor **Prisma**) que actúa como un proxy para nuestra base de datos y un motor de consultas de alto rendimiento que se ejecuta en el servidor, lo que genera consultas de bases de datos reales para nosotros. + +El API **GraphQL** de **prisma** proporciona abstracciones para desarrollar backends **GraphQL** flexibles y escalables. + +**Prisma** nos genera un cliente (cliente **Prisma**), que podemos usar para interactuar con el servidor. **Prisma** también agrega un sistema de eventos en tiempo real a nuestra base de datos, para que podamos suscribirnos a los eventos de la base de datos en tiempo real. + +La arquitectura de trabajo con **Prisma** sería la siguiente: + +![Create database](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/prisma.png) + +En este caso estamos hablando únicamente de generar una API **GraphQL** con prisma pero también podemos generar un API **REST** y **GRPC**. + +## Características + +- **Prisma** nos proporciona un API con un **tipado-seguro** desde el back-end hasta el front-end. + +- **SDL** (Schema Definition Language) nos permite escribir nuestros **esquemas GraphQL** que define el API de operaciones **GraphQL** para nuestra base de datos. Este tipo de definición puede jugar el mismo papel que los **protos** en **GRPC**, nos sirve de interfaz para la generación de tipos entre front-end y back-end. + +- **Prisma** maneja las migraciones de base de datos automáticamente. + +- **Prisma** puede trabajar con distintos tipos de base de datos (tanto relacional como documental) y actualmente soporta **MySQL**, **MongoDB** y **PostgresSQL**. + +- **Prisma** puede generar el API para **GraphQL**, **REST** y **GRPC**. + +- **Prisma** funciona con bases de datos existente o **schema first** + +- **Prisma** resuelve el problema N + 1 queries gracias al uso de dataloaders y su motor de queries. + +- **Prisma** permite lanzar queries a través de distintas bases de datos usando **schema stitching** o en el futuro con **Apollo federation** + +- **Prisma** nos permite **real-time** API con **Subscriptions** + +- **Prisma** nos proporciona un sistema de eventos con la base de datos a los que podemos suscribirnos (**Prisma** nos asegura que cualquier base de datos que soporte, nos permitirá suscribirnos a cualquier tipo de evento aún que la base de datos no lo soporte) + +Ahora que tenemos un poquito mas de contexto sobre que es **prisma**, vamos a empezar a construir nuestro servidor **GraphQL**. + +# Instalar prisma + +Para comenzar, necesitamos instalar [**Prisma CLI**](https://www.prisma.io/docs/prisma-cli-and-configuration/using-the-prisma-cli-alx4/) el cual nos va a permitir arrancar nuestro proyecto. + +``` +yarn global add prisma +``` + +# Crear proyecto + +``` +cd ruta_carpeta +yarn init +``` + +En este punto nos preguntará si queremos partir de una base de datos existente o crear un proyecto desde cero, también nos preguntará si queremos alojar nuestro proyecto en desarrollo, en sus servidores o en local con **docker**. Sus servidores son gratuitos para desarrollo y genera un namespace único para cada desarrollador, lo que no genera conflictos entre otros desarrolladores. + +En mi caso voy a elegir la opción **"Create new database"** para tener todo en local. + +![Create database](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/createDatabase.png) + +Después nos va a preguntar la base de datos que queremos usar, en mi caso voy a elegir **MongoDB**. + +![Create database](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/mongoDatabase.png) + +Y por último nos va a preguntar por el tipo de lenguaje en el que vamos a crear el **cliente prisma**, en este caso yo voy a elegir **JavaScript**. + +![Create database](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/prismaClient.png) + +# Echando un ojo al proyecto generado + +Actualmente en el proyecto tenemos 3 ficheros y una carpeta **generated**. + +_docker-compose.yml_ + +```yaml +version: '3' +services: + prisma: + image: prismagraphql/prisma:1.34 + restart: always + ports: + - '4466:4466' + environment: + PRISMA_CONFIG: | + port: 4466 + # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security + # managementApiSecret: my-secret + databases: + default: + connector: mongo + uri: 'mongodb://prisma:prisma@mongo' + mongo: + image: mongo:3.6 + restart: always + # Uncomment the next two lines to connect to your your database from outside the Docker environment, e.g. using a database GUI like Compass + # ports: + # - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: prisma + MONGO_INITDB_ROOT_PASSWORD: prisma + ports: + - '27017:27017' + volumes: + - mongo:/var/lib/mongo +volumes: mongo: +``` + +Este fichero contiene la configuración de los contenedores que vamos a necesitar para ejecutar **Prisma** en local con **docker**, este fichero contiene la imagen para arrancar el **servidor prisma** y la base de datos **MongoDb** + +_prisma.yml_ + +```yaml +endpoint: http://localhost:4466 +datamodel: datamodel.prisma +databaseType: document + +generate: + - generator: javascript-client + output: ./generated/prisma-client/ +``` + +En este fichero tenemos la configuración para el nuestro **servidor prisma** cuando lo despleguemos. + +La propiedad **"endpoint"** es la url donde se va a alojar nuestro servidor, de usar el los servidored de **prisma** para desarrollo, la url sería mas o menos algo así: `https://eu1.prisma.sh/prisma_user/server/dev`. + +La propiedad **"datamodel"** es la localización de fichero _datamodel.prisma_ donde se encuentra nuestro modelo que vamos a generar en la base de datos (mas a delante lo comentaré). + +La propiedad **"databaseType"** es en la cual indicamos que tipo de base de datos que vamos a usar, como en nuestro caso habíamos elegido **MongoDB**, nos pone **documental**. + +Por último, tenemos la propiedad **"generate"** donde le indicamos el tipo de cliente que vamos a auto generar para nuestro servidor, como habíamos elegido **javascript** tenemos **javascript-client**, y el output es donde nos va a generar el cliente. + +_datamodel.prisma_ + +``` +type User { + id: ID! @id + name: String! +} +``` + +En este fichero encontramos la definición de nuestro **esquema GraphQL** que va a manejar **prisma** con la base de datos. Este fichero, usa sintaxis **SDL** y a través de él podemos configurar las referencias/relaciones entre entidades. El modelo no es necesario que esté en un único fichero, se puede separar en múltiples ficheros, en este caso, tendremos que indicar en la propiedad **datamodel** del fichero _prisma.yml_ donde se encuentran nuestros ficheros. + +# Lanzar nuestro servidor prisma + +Ahora tenemos que ejecutar los 2 contenedores **docker** necesarios para correr nuestro servidor prisma en local, uno es para la base de datos **MongoDB** y otro es para el **servidor prisma** + +``` +docker-compose up -d +``` + +Una vez las imágenes de los container se hayan descargado y estén corriendo, deberíais tener algo parecido a esto: + +![Docker images](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/dockerImages.png) + +Por último, tenemos que desplegar nuestro código dentro de los contenedores para poder consumirlo. + +``` +prisma deploy +``` + +Nos indicará que ha generado cambios en el esquema con una entidad **User** con los campos **id** de tipo **ID!** y **name** de tipo **string**. Estos cambios son la creación del modelo que tenemos en el fichero _datamodel.prisma_. También nos indicará la url donde podemos empezar a consumir nuestro **server prisma** que se encuentra por defecto en **http://localhost:4466**. Si entramos en la url, tenemos accesible la interfaz con el **Playground** para poder empezar a lanzar queries directamente contra nuestra base de datos. + +![Grapiql](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/grapiql.png) + +# Repaso + +Hasta ahora no he explicado nada del otro mundo, simplemente hemos instalado el **prisma cli** el cual nos permite arrancar un nuevo proyecto. Despues hemos seguido los pasos eligiendo ejecutar **prisma** en local con **docker**, hemos elegido **MongoDB** como base de datos y que nuestro cliente prisma se genere con **javascript**. Los siguientes pasos nos los ha dado directamente **prisma** tras la generación del proyecto. Primero ejecutamos los contenedores **docker** con un **docker-compose** y despues hemos desplegado nuestro proyecto en los contenedore. + +Con estos simples pasos, tenemos nuestro **servidor prisma** y la base de datos corriendo. Tenemos el **Playground** corriendo en la url `http://localhost:4466` donde podemos empezar a a lanzar **queries** y **mutations** que se han generado para nuestro esquema. + +# API GraphQL + +He comentado que **Prisma** nos genera un API **GraphQL** y también nos genera un cliente **javascript** pero... **¿Cómo?**. + +En nuestro proyecto, tenemos definido un esquema en _datamodel.prisma_ que es el esquema que nos genera por defecto **Prisma**. A partir del esquema, **Prisma** auto genera un cliente **javascript** y el API **GraphQL** (lo que consumimos a través del **Playground**). **Prisma** nos genera todas las operaciones posibles para cada una de las entidades del esquema (tanto en el cliente como en el API). + +## Queries + +- **user**: retorna un usuario filtrado por id +- **users**: retorna un listado de usuarios, se puede incluir clausula **where** filtrando por cada uno de los campos con todo tipo de opciones y también puede paginarse. + +## Mutations + +- **createUser**: permite crear un nuevo usuario +- **updateUser**: permite actualizar un usuario a través de su id +- **updateManyUsers**: permite actualizar varios usuarios que cumplan la condición **where** +- **upsertUser**: permite actualizar un usuario si cumple la condición **where** de lo contrario lo creará. +- **deleteUser**: permite eliminar un usuario a través del id. +- **deleteManyUsers**: permite eliminar múltiples usuarios que cumplan la condición **where** + +Para las clausulas **where**, **prisma** nos genera una inmensa variedad de opciones de filtrado para cada uno de los campos de nuestro modelo, por ejemplo `name_starts_with`, `name_not_starts_with`, `name_in`, etc` + +# Creando nuestro modelo + +La idea es construir el modelo de una tienda donde existen usuarios, que pueden realizar un pedido con productos. Para eso voy a definir el siguiente modelo. + +![database model](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/databaseModel.png) + +Ahora nos toca representarlo en _datamodel.prisma_ mediante lenguaje SDL. + +_datamodel.prisma_ + +```javascript +interface Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt +} + +type Customer implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + name: String! @unique + address1: String! + order: [Order!]! @relation(link: INLINE) +} + +enum OrderStatus { + NotOrdererd, + InProcess, + Delivered +} + +type Order implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + orderStatus: OrderStatus! + customer: Customer! + orderLines: [OrderLine!]! @relation(link: INLINE) +} + +type Product implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + name: String! @unique + price: Int! +} + +type OrderLine implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + quantity: Int! + order: Order! + product: Product! @relation(link: INLINE) +} +``` + +Comentemos un poco lo que hemos hecho con nuestro modelo, hemos definido una interfaz **Model** que van a implementar todos nuestros modelo. En ella estamos obligando a implementar la propiedad `id: ID! @id` esto nos indica que vamos a tener un campo **id** de tipo **ID** que es obligatorio (por el **!**) y que va a ser manejado directamente por **prisma** debido al uso de la directiva **@id**. + +En la interfaz también hemos declarado las propiedades `createdAt: DateTime! @createdAt` y `updatedAt: DateTime! @updatedAt`, estos campos los manejará automáticamente **prisma** cuando la entidad se cree y se actualice gracias a las directivas `@createdAt`y `@updatedAt`. + +Con la directiva `@unique` podemos indicar a **Prisma** que nuestro campo es único, el se encargara de generar el error correspondiente si intentamos insertar un dato duplicado. + +Para las referencias/relaciones, podemos usar directamente un tipo o un array de tipos para declarar 1 o N referencias. + +Nos encontramos una sintaxis `order: [Order!]!` de esta manera indicamos que nuestro campo order, no puede ser nulo y además que la referencia/relación que generemos no puede ser nula. + +Para las referencias/relaciones, usamos una directiva para indicar que tipo de referencia/relación queremos, ya que en mongo, podemos tener documentos embebidos o no, en mi caso he decidido que se genere un documento a parte y que la relación se representa con el id al documento. + +También podemos configurar el tipo de borrado que queremos para nuestras referencias/relaciones (yo no lo he configurado). Todas las opciones de configuración del **datamodel** las puedes encontrar [aquí](https://www.prisma.io/docs/datamodel-and-migrations/datamodel-MYSQL-knul/). + +# Actualizando nuestro modelo + +Ahora que hemos definido un nuevo modelo, tenemos que desplegarlo en nuestro servidor **prisma** ya que ahora mismo tenemos el modelo antiguo, para eso, tenemos que volver a ejecutar el comando: + +`prisma deploy` + +Una vez ejecutado, veremos un log del estilo: + +``` +λ prisma deploy +Deploying service `default` to stage `default` to server `local` 336ms + +Changes: + + User (Type) + - Deleted type `User` + + Customer (Type) + + Created type `Customer` + + Created field `id` of type `ID!` + + Created field `createdAt` of type `DateTime!` + + Created field `updatedAt` of type `DateTime!` + + Created field `name` of type `String!` + + Created field `address1` of type `String!` + + Created field `order` of type `[Order!]!` + + Order (Type) + + Created type `Order` + + Created field `id` of type `ID!` + + Created field `createdAt` of type `DateTime!` + + Created field `updatedAt` of type `DateTime!` + + Created field `orderStatus` of type `Enum!` + + Created field `customer` of type `Customer!` + + Created field `orderLines` of type `[OrderLine!]!` + + OrderLine (Type) + + Created type `OrderLine` + + Created field `id` of type `ID!` + + Created field `createdAt` of type `DateTime!` + + Created field `updatedAt` of type `DateTime!` + + Created field `quantity` of type `Int!` + + Created field `order` of type `Order!` + + Created field `product` of type `Product!` + + Product (Type) + + Created type `Product` + + Created field `id` of type `ID!` + + Created field `createdAt` of type `DateTime!` + + Created field `updatedAt` of type `DateTime!` + + Created field `name` of type `String!` + + Created field `price` of type `Int!` + + OrderStatus (Enum) + + Created enum OrderStatus with values `NotOrdererd`, `InProcess`, `Delivered` + + OrderLineToProduct (Relation) + + Created an inline relation between `OrderLine` and `Product` in the column `product` of table `OrderLine` + + OrderToOrderLine (Relation) + + Created an inline relation between `Order` and `OrderLine` in the column `orderLines` of table `Order` + + CustomerToOrder (Relation) + + Created an inline relation between `Customer` and `Order` in the column `order` of table `Customer` + +Applying changes 541ms +Generating schema 37ms +``` + +Este es el log de cambios que ha generado en la migración respecto a nuestro modelo anterior, como vemos, ha eliminado la entidad **User** existente y ha creado todas las nuevas entidades con sus campos y relaciones. + +Si ejecutamos el **Playground** o inspeccionamos el cliente **javascript** autogenerado, podemos observar que han cambiado, nuestro API **GraphQL** ahora es distinta, ahora tenemos todas las opciones que teníamos para nuestra entidad usuario (user, users, createUsers, updateUser...) las tenemos para cada una de nuestras entidades (updateProduct, createOrder, deleteCustomer...). + +# Semilla de datos + +Si ahora mismo ejecutamos el **Playground** y hacemos una query de productos, no vamos a obtener ningún resultado. + +![empty data](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/emptyData.png) + +**Prisma** nos permite configurar un fichero como semilla para insertar datos en la base de datos, la semilla la creamos lanzando mutations generadas por **prisma** + +_seed.graphql_ + +```javascript +mutation { + product1: createProduct(data: {name: "Product 1", price: 10}){ id } + product2: createProduct(data: {name: "Product 2", price: 20}){ id } + product3: createProduct(data: {name: "Product 3", price: 30}){ id } + product4: createProduct(data: {name: "Product 4", price: 40}){ id } + product5: createProduct(data: {name: "Product 5", price: 50}){ id } +} +``` + +Usando la mutation **createProduct** hemos definido lanzar un **batch** de **mutations** para crear productos de ejemplo. + +En nuestro _prisma.yml_ tenemos que añadir la referencia de nuestro **seed** + +_prisma.yml_ + +```yaml +endpoint: http://localhost:4466 +datamodel: datamodel.prisma +databaseType: document + +generate: + - generator: javascript-client + output: ./generated/prisma-client/ + +seed: + import: ./seed.graphql +``` + +Ahora tenemos que lanzar la semilla a la base de datos, para ello, **prisma** nos ofrece un comando: + +`prisma seed` + +Una vez ejecutado, si nos vamos a al **Playground** y ejecutamos la query de productos, veremos que ahora si tenemos datos. + +![products data](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/productsData.png) + +# Proteger nuestro servidor prisma + +Ahora mismo nuestro **Servidor prisma**, está abierto a cualquier petición sin ningún tipo de autenticación y eso no es bueno si queremos que nuestros datos estén seguros y protegidos, para eso podemos securizarlo de una forma muy sencilla. + +**Prisma** nos permite añadir la propiedad **secret** en nuestro _prisma.yaml_, esta propiedad es un string random con el cual se va a verificar el token en la llamada. + +_prisma.yaml_ + +```yaml +endpoint: http://localhost:4466 +datamodel: datamodel.prisma +databaseType: document + +generate: + - generator: javascript-client + output: ./generated/prisma-client/ + +seed: + import: ./seed.graphql + +secret: mySuperSecret +``` + +De nuevo tenemos que desplegar nuestro servicio para que los cambios se hagan efectivos. + +`prisma deploy` + +Una vez desplegado nuestro cambio en el servidor, si intentamos ejecutar la query anterior de productos, vamos a ver que no estamos autorizados para acceder al **servidor prisma**. + +![no autorizado](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/noAuthorized.png) + +Para poder acceder a nuestro conector, tenemos que generar un token firmado con la misma **secret** que hemos añadido a nuestra configuración. En **prisma** también han pensado en ello, el **CLI** nos ofrece el comando. + +`prisma token` + +Cuando lo ejecutemos, en la consola nos mostrará un **token** totalmente válido para poder usar nuestro conector, ahora tendremos que ir al **Playground** y meterlo en la **header** de la request y con esto ya podremos usar nuestro conector teniendo nuestros datos autenticados. + +![no autorizado](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/authorization.png) + +# Recapitulando + +Con esta serie de pasos tenemos un **servidor prisma** que nos expone un API **GraphQL** con el esquema definido en el **datamodel** y consumiendo una base de datos **MongoDB**. También tenemos un fichero con una semilla para añadir datos de prueba a nuestra base de datos y lo hemos securizado con un token. + +Hay que tener en cuenta que es un servidor con una entrada **GraphQL** securizado y que puede ser totalmente consumido pero eso no significa que tengamos que tirar directamente de el con nuestro cliente (IOs, Android, react...). En este post solo hemos construido una parte de nuestro servidor, la parte de conexión de datos, pero nos falta crear nuestro servidor entre la parte **cliente** y el **servidor prisma**, ahora mismo solo hemos construido lo siguiente: + +![arquitectura prisma](/assets/images/posts/2019-08-22-contruye-un-server-graphql-con-prisma/prismaArquitecture.png) + +El servidor es necesario para meter sistema de autenticación mas flexible usando roles, proveedores de autenticación, etc. También necesitamos el server para meter toda la lógica de negocio que necesitemos ya que eso no lo podemos hacer en el **servidor prisma**. + +Con esto termino por hoy, en el próximo post veremos como crear un servidor en **node** con **graphql-yoga** y como consumir el cliente generado por **Prisma** para poder consumirlo desde nuestros clientes. + +El repositorio **Github** lo puedes encontrar [Aquí](https://github.com/NoCountryForGeeks/prisma-with-graphql-yoga-server/tree/post-1-setup-prisma) + +[Aquí](/segunda-parte-contruye-un-server-graphql-con-prisma) puedes continuar con la segunda parte del post. diff --git a/content/blogs/2019-09-04-dominios-seguros-a-medida-con-github-pages/es.md b/content/blogs/2019-09-04-dominios-seguros-a-medida-con-github-pages/es.md new file mode 100644 index 0000000..e5e9766 --- /dev/null +++ b/content/blogs/2019-09-04-dominios-seguros-a-medida-con-github-pages/es.md @@ -0,0 +1,66 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-09-04-dominios-seguros-a-medida-con-github-pages/header.jpg +navigation: True +title: 'Dominios seguros a medida con GitHub Pages' +date: 2019-09-04 12:00:00 +tags: devops github +class: post-template +subclass: 'post' +author: danimart1991 +--- + +[*GitHub Pages*](https://pages.github.com/) es la posibilidad de crear **un sitio web estático desde un repositorio** que ofrece [*GitHub*](https://github.com/). Esto es útil cuando tienes una página personal, un proyecto con su documentación, o un blog (como este). + +A su vez, gracias a una asociación con [*Let's Encrypt*](https://letsencrypt.org/) de [*Internet Security Research Group (ISRG)*](https://www.abetterinternet.org/), se ofrece una encriptación *HTTPS* totalmente gratuita que incluye además, una red de distribución de contenido (*CDN*) respaldados por [*Cloudfare*](https://www.cloudflare.com/). Podrás conseguir tener un sitio web estático escalable, redundante, rápido y con una encriptación segura. + +> Available in English [here](https://www.danielmartingonzalez.com/custom-secure-domains-with-github-pages/). + +Desde la propia configuración del repositorio de *GitHub* se ofrecen las opciones para configurar el sitio de manera sencilla. Basta con indicar la rama desde la que se va a desplegar el sitio web, activar la opción "*Enforce HTTPS*", y si así lo deseamos un dominio personalizado (que previamente deberemos comprar en nuestro proveedor de dominios y configurar para que redireccione a nuestro sitio web de *GitHub Pages*). + +![Configuración GitHub Pages](/assets/images/posts/2019-09-04-dominios-seguros-a-medida-con-github-pages/image01.jpg) + +> En este artículo no vamos a ver cómo funciona la compilación de sitios web estáticos en *GitHub Pages*, así como de *Jekyll* o sus *themes*. Por el momento con una simple página `index.html` con cualquier texto es suficiente para probar. + +## *CNAME* y sus restricciones + +Si quieres tener más de un repositorio con su sitio web estático y dominios para cada uno, que sepas que no es tarea fácil, de ahí este artículo; puesto que la manera de trabajar de *GitHub Pages* supone que la página principal de tu usuario u organización (por defecto un repositorio con nombre `usuario.github.io`) será de tipo: *https://usuario.github.io/* y el resto de repositorios (considerados proyectos) seguirán un esquema de subdominios de tipo: *https://usuario.github.io/nombrerepositorio/*. Se sigue el siguiente esquema: + +| Repositorio | URL | +|---|---| +| https://github.com/nocountryforgeeks/nocountryforgeeks.github.io | https://www.nocountryforgeeks.github.io/ | +| https://github.com/nocountryforgeeks/proyecto1 | https://www.nocountryforgeeks.github.io/proyecto1/ | +| https://github.com/nocountryforgeeks/proyecto2 | https://www.nocountryforgeeks.github.io/proyecto2/ | +| ... | ... | + +A priori no supone un problema. Si compramos dos dominios (uno para cada repositorio) y según la teoría, bastaría con poner el dominio personalizado en las opciones de configuración del repositorio para *GitHub Pages*. + +![Configuración dominio GitHub Pages](/assets/images/posts/2019-09-04-dominios-seguros-a-medida-con-github-pages/image02.jpg) + +Sin embargo, este cambio lo que crea es un archivo *CNAME* en la raíz de nuestro repositorio, que básicamente lo que le dice a nuestro sitio web es el dominio al que apunta. En la configuración *DNS* de nuestro proveedor de dominios, deberemos hacer lo propio indicando la web a que queremos redireccionar, y aquí es cuando entran en juego los subdominios, ya que una redirección *CNAME* no acepta subdominios (*https://dominio.com/subdominio*), y por tanto, no podremos crear la redirección con el repositorio secundario. Dando errores tanto en la propia redirección como en la activación de la encriptación *HTTPS*. + +![CNAME Github Pages](/assets/images/posts/2019-09-04-dominios-seguros-a-medida-con-github-pages/image03.jpg) + +## Usando registros *DNS* de tipo *A* + +La solución pasa por utilizar otro tipo de registros *DNS* a parte de *CNAME*. Con ello, indicamos que se debe crear una jerarquía que quedará oculta tras los dominios personalizados y ambos repositorios dispondrán de sitios web y dominios propios. + +Esta es la configuración de registros *DNS* para ambos dominios. Observa como en ambos casos se usan registros de tipo *A* apuntando a las *IPs* de *GitHub* y crea un registro *CNAME* apuntando siempre al dominio de repositorio principal (sin *www*). + +![Registros *DNS*](/assets/images/posts/2019-09-04-dominios-seguros-a-medida-con-github-pages/image04.jpg) + +A continuación, configura ambos dominios en la configuración de repositorio de *GitHub* para cada uno. Por último, al crear la redirección de este modo, te permitirá además usar la opción de crear un dominio seguro *HTTPS*. + +| Repositorio | URL | +|---|---| +| https://github.com/nocountryforgeeks/nocountryforgeeks.github.io | https://www.nocountryforgeeks.com/ | +| https://github.com/nocountryforgeeks/proyecto1 | https://www.proyecto1.com/ | +| https://github.com/nocountryforgeeks/proyecto2 | https://www.proyecto2.com/ | +| ... | ... | + +## Conclusión + +Llegados a este punto tendremos tantos dominios personalizados como queramos apuntando a nuestros repositorios con sitios web de *GitHub Pages*. El único problema de este método es que al crear una asociación por jerarquía de subdominio, si accedemos a *https://www.nocountryforgeeks.com/proyecto1*, nos redireccionará al dominio *https://www.proyecto1.com/*, a mi parecer algo asumible. + +Espero que con este artículo os ahorréis los quebraderos de cabeza que he tenido con los dominios personalizados y *GitHub Pages*. diff --git a/content/blogs/2019-10-09-typescript-types-vs-interfaces/es.md b/content/blogs/2019-10-09-typescript-types-vs-interfaces/es.md new file mode 100644 index 0000000..38cb7cb --- /dev/null +++ b/content/blogs/2019-10-09-typescript-types-vs-interfaces/es.md @@ -0,0 +1,58 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-10-09-typescript-types-vs-interfaces/header.jpg +navigation: True +title: 'Typescript: types vs interfaces' +date: 2019-10-09 12:00:00 +tags: react typescript +class: post-template +subclass: 'post' +author: anuez +--- + +[Typescript](http://www.typescriptlang.org/) , es un "superset" del lenguaje Javascript cuyo objetivo es esencialmente añadir un tipado estático. Es un lenguaje en actual evolución, actualmente en la versión 3.6, amado y odiado en partes iguales por los programadores, pero de esta guerra no vamos a hablar por el momento. + +Debido a las requisitos del proyecto en el que se encuentra un servidor, ⚛️ [React](https://es.reactjs.org/) con Typescript para la SPA, me he visto obligado a programar en este lenguaje y me han surgido una serie de preguntas respecto a la utilización de **alias de tipos e interfaces** que me gustaría compartir. + +Los tipos y las interfaces han evolucionado a lo largo de las versiones y actualmente son muy similares, aunque todavía mantienen unas pequeñas diferencias: las interfaces son más ***"extensibles"*** debido a la posibilidad de unir sus declaraciones, y los tipos son más ***"componibles"*** debido a la posibilidad de unir los tipos. 💥 + +Veamos algunas diferencias con código: + +1. Los tipos pueden declararse como tipos primitivos, uniones o tuplas mientras que las interfaces no. + +![diferencia 1](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-1.png) + + +2. Se tarda menos en escribir un tipo que una interfaz. + +![diferencia 2](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-2.png) + + +3. Los tipos pueden crear interseccion con otros tipos, mientras que las interfaces no. + +![diferencia 3](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-3.png) + +4. La unión en la declaración de una interfaz no funciona con los tipos. + +![diferencia 4](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-4.gif) + +5. No puedes extender una interfaz con un tipo si se usa el operador unión en su definición de tipo. + +![diferencia 5](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-5.gif) + +6. La palabra clave **in** puede usarse para iterar sobre todos los elementos en una unión de claves. Podemos utilizar esta función para generar mapped types. Con interfaces no se puede. + +![diferencia 6](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-6.gif) + +7. La sintaxis para **implementar** una clase con un tipo o con una interfaz es la misma. Pero la sintaxis para **extender** una clase no. + +![diferencia 7](/assets/images/posts/2019-10-09-typescript-types-vs-interfaces/diff-7.png) + +## Conclusión + +Debido a estas pequeñas diferencias entre tipos e interfaces, la decisión de usar una sobre otra generalmente depende de su estilo de programación. Si escribes código orientado a objetos, usa interfaces, si escribes código funcional, usa alias de tipo. + +¿Y que pasa con React? React es un lenguaje funcional por naturaleza. Los componentes funcionales generalmente son preferibles a los componentes basados en clases. Los Hooks en React son funciones que sólamente se utilizan sobre componentes funcionales, los HOC, Redux, funciones puras, etc, salen de una programación funcional. + +📢 **Por todas estas razones, se deben usar los alias de tipos en vez de las interfaces en todas tus aplicaciones de React.** 📢 diff --git a/content/blogs/2019-11-13-camara-de-seguridad-con-raspberry-pi/es.md b/content/blogs/2019-11-13-camara-de-seguridad-con-raspberry-pi/es.md new file mode 100644 index 0000000..1c13201 --- /dev/null +++ b/content/blogs/2019-11-13-camara-de-seguridad-con-raspberry-pi/es.md @@ -0,0 +1,149 @@ +--- +layout: post +current: post +cover: assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/header.jpg +navigation: True +title: 'Cámara de seguridad con Raspberry Pi' +tags: homeassistant hassio domotica raspberrypi +class: post-template +subclass: 'post' +author: danimart1991 +--- + +[***Raspberry Pi***](https://www.raspberrypi.org/) (en todas sus versiones) es uno de los micro-ordenadores más potentes y versátiles del momento. Gracias a la comunidad se puede conseguir casi cualquier cosa, y hoy, vamos a ver como convertirlo en una potente cámara de seguridad que nada tiene que envidiar a las disponibles en el mercado a precios mucho más altos. + +> Available in English [here](https://www.danielmartingonzalez.com/security-camera-with-raspberry-pi/). + +## Materiales + +![RPi Zero W + Cámara](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image01.jpg) + +Yo tengo un *Kit* de *Raspberry Pi Zero W* y [una cámara clónica](https://amzn.to/2WLRpEi), pero como es difícil de encontrar hoy en día, podemos buscar otra alternativa. Es recomendable comprar un pack con disipador y/o ventilador, ya que la carga gráfica y de procesador que supone una cámara de vigilancia hace que se caliente. Es recomendable poner la cámara fuera de la caja por el mismo problema de temperatura. Un ejemplo de lo que podemos comprar: + +- *Kit Raspberry Pi 3 B+* con tarjeta, disipador, cargador, caja...: [https://amzn.to/2qn3wLA](https://amzn.to/2qn3wLA) +- Cámara *RPi Noir V2* con visión nocturna (oficial): [https://amzn.to/2rePix1](https://amzn.to/2rePix1) +- Caja para cámara: [https://amzn.to/2NEoM7R](https://amzn.to/2NEoM7R) + +El montaje es bastante sencillo ya que la propia *Raspberry Pi* cuenta con un conector específico para cámaras. + +![Montaje RPi + Cámara](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image02.jpg) + +> Algunas *Webcam USB* son compatibles con el sistema operativo que vamos a instalar. Si tienes alguna disponible, como la instalación son 5 minutos, puedes probar. + +## *motionEyeOS* + +Aunque hay muchos sistemas operativos y software para la tarea que nos concierne. Tras probar varios, en mi opinión, el que tiene una comunidad más activa, más sencillo y a la vez potente es sin duda [***motionEyeOS***](https://github.com/ccrisan/motioneyeos). + +![motionEyeOS](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image03.jpg) + +***motionEyeOS*** es una distribución de *Linux*, que está basada en *BuildRoot*, usa *motion* como *backend* y *motionEye* como *frontend*. Una ventaja pues nos ahorramos configuraciones innecesarias, y tenemos todo lo necesario empaquetado para nuestro proyecto. + +## Preparación + +1. Descargamos la imagen acorde a nuestro dispositivo de [https://github.com/ccrisan/motioneyeos/releases](https://github.com/ccrisan/motioneyeos/releases) + +2. Usando [***balenaEtcher***](https://www.balena.io/etcher/), grabamos la imagen descargada en una tarjeta *microSD*. + + ![Pasos balenaEtcher](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image04.gif) + +3. En el caso de querer usar una conexión *WiFi*, es necesario configurar la red previamente. Una vez grabada la imagen, abrimos el dispositivo con el explorador de archivos (es posible que tengamos que expulsar y volver a introducir la tarjeta en el ordenador) y creamos el archivo `wpa_supplicant.conf`. Dentro incluimos el siguiente código (sustituyendo las variables necesarias): + + ```conf + update_config=1 + ctrl_interface=/var/run/wpa_supplicant + + network={ + scan_ssid=1 + ssid="NOMBRE_RED_WIFI" + psk="PASSWORD_WIFI" + } + ``` + +4. Introducimos la tarjeta en la *Raspberry Pi*, encendemos y esperamos... Una vez pasados unos minutos, buscamos nuestro dispositivo en la lista de dispositivos conectados del *Router* para conocer su *IP*. + + ![Listado dispositivos Router](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image05.jpg) + +## Primer inicio + +Accedemos a la *IP* que acabamos de encontrar con un navegador *Web* desde nuestro ordenador y nos mostrará una imagen parecida a esta: + +![Ventana motionEyeOS](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image06.jpg) + +> En el caso de que no se muestre la imagen de la cámara, es posible que el dispositivo esté alejado del punto de acceso *WiFi*, o que hayamos conectado mal la cámara a la *Raspberry Pi*. + +Para empezar a configurar el sistema, haz clic en el icono de persona que se encuentra arriba a la izquierda e inicia sesión con el usuario ***admin***, dejando el campo ***password*** en blanco. + +![Inicio de sesión](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image07.jpg) + +Si hacemos clic en el menú "hamburguesa" de arriba a la izquierda se nos mostrarán varias opciones. + +## Configuración básica + +*motionEyeOS* necesita pequeñas configuraciones básicas para facilitar accesos posteriores, asegurar el sistema y dejar todo preparado para usos más avanzados. + +1. Cambiar el campo **TimeZone** a la correspondiente a nuestro país. + +2. Añadir una contraseña a los usuarios `admin` y `user`. El primero nos dará acceso a todas las configuraciones, el segundo, es el usuario que debemos usar cuando solo queremos ver la cámara, pero no configurarla. + + ![Configuración usuarios y zona horaria](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image08.jpg) + +3. Cambiamos la configuración de red para asignar una *IP* manual al sistema. Opcionalmente también puede reservarse una *IP* desde el *Router*. + + ![Configuración de red](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image09.jpg) + +4. Reiniciamos. + +5. Una vez reiniciado, entramos de nuevo con la *IP* y usuario `admin` que acabamos de configurar y hacemos clic en *Check* por si existen actualizaciones del sistema. + + ![Actualización motionEyeOS](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image10.jpg) + +## Configuración avanzada + +Ahora vamos a realizar las configuraciones necesarias para optimizar el sistema y el ***Streaming* de vídeo**. + +1. Primero vamos a activar o desactivar solo los servicios que queramos utilizar. Por el momento, a mí solo me interesa conservar los servicios de ***SSH*** (por si falla algo poder acceder por *terminal*), ***Video Device***, ***Text Overlay*** y **Video *Streaming***. + + ![motionEyeOS configuración servicios](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image11.jpg) + +2. Dado que solo tenemos una cámara en el sistema, vamos a poner una sola fila y columna en la interfaz. + + ![motionEyeOS configuración interfaz](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image12.jpg) + +3. Vamos a configurar la imagen de entrada acorde al *hardware* que estamos usando. Dado que depende del dispositivo, así como de la cámara conectada, lo ideal es ir jugando con los valores. Para el caso de mi *Raspberry Pi Zero W* y la cámara clónica que uso he puesto una resolución de *720p* (1280x720 pixeles) y una tasa de cuadros por segundo de 5. + + ![motionEyeOS configuración Video Device](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image13.jpg) + +4. Por último, dentro de *Streaming*, configuramos la misma resolución y tasa de cuadros por segundo que hemos indicado antes (o la que más nos interese), una seguridad básica (*basic*) y un puerto. + + ![motionEyeOS configuración Video Streaming](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image14.jpg) + +Con este último punto podremos acceder al *Streaming* usando la *Url* y puerto desde cualquier navegador usando las credenciales del usuario `user`. + +![motionEyeOS Video Streaming](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image15.jpg) + +## Integración en Home Assistant + +Una vez configurado el *Streaming*, la integración con [***Home Assistant***](https://www.home-assistant.io/) es muy sencilla. Basta con modificar el archivo `configuration.yaml` y añadir las siguientes líneas (sustituyendo según convenga): + +```yaml +camera: + - platform: mjpeg + name: Camara Ejemplo + mjpeg_url: http://XXX.XXX.XXX.XXX:XXXX # Url Streaming + username: user + password: PASSWORD_USER + authentication: basic +``` + +Esto creará la entidad `camera.camara_ejemplo` en nuestro servidor *Home Assistant*. Para mostrarlo en la interfaz, si tenemos [*Lovelace*](https://www.home-assistant.io/lovelace/) en modo edición podemos usar la tarjeta [*Picture Entity*](https://www.home-assistant.io/lovelace/picture-entity/): + +```yaml +type: picture-entity +entity: camera.camara_ejemplo +``` + +![Home Assistant Camera Picture Entity](/assets/images/posts/2019-11-13-camara-de-seguridad-con-raspberry-pi/image16.jpg) + +## Conclusión + +Hemos visto como en unos sencillos pasos hemos configurado de manera básica y barata una cámara de seguridad y su integración con *Home Assistant*. Aún nos queda por ver todo el potencial disponible por *motionEyeOS*, pero eso lo dejamos para más adelante. diff --git a/content/blogs/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/es.md b/content/blogs/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/es.md new file mode 100644 index 0000000..3eb96c6 --- /dev/null +++ b/content/blogs/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/es.md @@ -0,0 +1,320 @@ +--- +layout: post +current: post +cover: assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/cover.jpg +navigation: True +title: 'Prisma 2º parte: construye tu servidor GraphQL de una forma rápida y sencilla' +date: 2020-01-07 12:00:00 +tags: mongodb prisma graphql-yoga +class: post-template +subclass: 'post' +author: ivanrodri +--- + +En el [primer post](/contruye-un-server-graphql-con-prisma) vimos como configurar nuestro **conector prisma** con nuestros modelos sobre una base de datos **MongoDB**, una vez configurado y desplegado, podíamos realizar todas las operaciones **CRUD** sobre nuestros modelos y podíamos lanzar todo tipo de queries. + +Hoy vamos a ver como crear un servidor en **Node** y como conectarlo con nuestro **conector prisma** ya que de esta manera tendremos mas flexibilidad a la hora de generar lógica de negocio, autenticación, etc. + +## Organizar proyecto + +En el post anterior creamos todos los ficheros en la raíz del proyecto pero en mi caso prefiero tenerlos separados entre **server**, **database** y tener los modelos cada uno en un fichero: + +![estructura proyecto](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/fileStructure.png) + +Para ello, primero he creado la carpeta **database** y he metido todos los ficheros relacionados con **prisma** como son _prisma.yml_, _datamodel.prisma_, seed.graphql y _docker-compose_. + +Despues he creado la carpeta _models_ y he creado un fichero por cada uno de nuestros modelos que teníamos definidos en nuestro _datamodel.prisma_: + +_customer.prisma_ + +``` +type Customer implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + name: String! @unique + address1: String! + order: [Order!]! @relation(link: INLINE) +} +``` + +_model.prisma_ + +``` +interface Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt +} +``` + +_order.prisma_ + +``` +type Order implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + orderStatus: OrderStatus! + customer: Customer! + orderLines: [OrderLine!]! @relation(link: INLINE) +} +``` + +_orderLine.prisma_ + +``` +type OrderLine implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + quantity: Int! + order: Order! + product: Product! @relation(link: INLINE) +} +``` + +_orderStatus.prisma_ + +``` +enum OrderStatus { + NotOrdererd, + InProcess, + Delivered +} +``` + +_product.prisma_ + +``` +type Product implements Model { + id: ID! @id + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt + name: String! @unique + price: Int! +} +``` + +Las definiciones de los modelos son exactamente iguales a lo que había, lo único es que ahora cada uno tiene su propio fichero. Los ficheros pueden usar modelos de otros ficheros sin tener que referenciar nada. Para que nuestro modelo siga funcionando como antes, tenemos que modificar el fichero _prisma.yml_ para indicar donde están nuestros modelos + +_prisma.yml_ + +```yaml +endpoint: http://localhost:4466 +databaseType: document +datamodel: + - ./models/customer.prisma + - ./models/model.prisma + - ./models/order.prisma + - ./models/orderLine.prisma + - ./models/orderStatus.prisma + - ./models/product.prisma + +seed: + import: ./seed.graphql + +secret: mySuperSecret + +generate: + - generator: javascript-client + output: ./generated/prisma-client/ +``` + +Si ahora ejecutamos el comando `prisma deploy` dentro de nuestra carpeta _database_, no deberíamos de tener nungún cambio respecto al último despliegue ya que realmente no hemos modificado nada de nuestro modelo. + +![prisma deploy](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/prismaDeploy.png) + +Por último vamos a crear una carpeta _server_ al mismo nivel que la carpeta _database_ y vamos a cambiar la salida del cliente generado por **prisma**, también vamos a aprovechar a añadir otro **hook** para generar el **schema graphql** de nuestro **conector prisma** y así poder usar esas definiciones en nuestro servidor. Para ello voy a borrar el cliente que tenía generado previamente y voy a modificar la propiedad **output** del fichero _prisma.yml_ para cambiar el destino: + +_prisma.yml_ + +```yaml +endpoint: http://localhost:4466 +databaseType: document +datamodel: + - ./models/customer.prisma + - ./models/model.prisma + - ./models/order.prisma + - ./models/orderLine.prisma + - ./models/orderStatus.prisma + - ./models/product.prisma + +seed: + import: ./seed.graphql + +secret: mySuperSecret + +generate: + - generator: javascript-client + output: ../server/generated/javascript-client/ + - generator: graphql-schema + output: ../server/generated/prisma-schema.graphql +``` + +Si ahora ejecutamos `prisma generate` nos va a generar el cliente **prisma** en la carpeta _/server/generated/javascript-client/_ y el fichero _prisma-schema.graphql_ que tiene definido todo el _schema_ que soporta el **conector prisma**. + +Después de estos pasos ya tenemos el proyecto estructurado y listo para continuar. + +## Crear server node + +Lo primero que tenemos que hacer es crear un _package.json_ para poder añadir todas nuestras dependencias **node** que vamos a necesitar, para ello ejecutamos en el raíz del proyecto el comando `yarn init`y rellenamos las preguntas: + +![yarn init](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/yarnInit.png) + +Ahora podemos añadir las dependencias necesarias a nuestro proyecto, de momento solo necesitamos instalar **graphql-yoga** y **prisma-client-lib**, para ello ejecutamos `yarn add graphql-yoga prisma-client-lib`. + +Con las dependencias instaladas ya podemos configurar nuestro servidor con **graphql-yoga** para ello vamos a hacerlo en el fichero _/server/index.js_. + +_index.js_ + +```javascript +const {GraphQLServer} = require('graphql-yoga'); +const {prisma} = require('./generated/javascript-client'); + +const server = new GraphQLServer({ + context: req => ({ + ...req, + prisma, + }), +}); + +const options = { + port: 8000, + cors: { + origin: '*', + }, +}; + +server.start(options, args => + console.log(`Server is running on http://localhost:${args.port}`) +); +``` + +Tenemos que importar el servidor de **graphql-yoga** y el cliente autogenerado de **prisma**, dentro del contexto del servidor, pasamos nuestro cliente de **prisma** para poder consumirlo más adelante. Configuramos las opciones del puerto y **CORS** y ejecutamos el server, para probarlo, tenemos que ejecutar desde nuestro raíz el comando `node ./server/index.js` + +![server no schema error](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/serverNoSchema.png) + +Cuando corremos el comando, nos da un error indicando que no hemos definido un _schema_, esto es debido a que tenemos que definir el **schema graphql** para nuestro servidor y crear sus correspondientes _resolvers_. Por los tanto voy a crear un fichero _schema.graphql_ dentro de la carpeta _server_, en el solo voy a definir una query para obtener todos los productos. + +_schema.graphql_ + +```graphql +# import Product from "./generated/prisma-schema.graphql" + +type Query { + products: [Product!]! +} +``` + +Nuestro **schema graphql** define una query llamada **products** que nos retorna todo el listado de productos, en este caso no admite ningun tipo de filtro. El tipo de objeto que retorna lo importamos del **schema graphql** del **conector prisma**, este lo hemos autogenerado metiendo el hook correspondiente a nuestro \_prisma.yml** y en el tenemos la definición completa del **schema** del **conector prisma\*\*. + +Tenemos que diferenciar entre el **modelo** que tenemos definido para nuestra base de datos (todos los ficheros dentro de la carpeta models), el **schema graphql** autogenerado con `prisma generate`que nos da toda la interfaz que define nuestro **conector** y el **schema graphql** que definimos para nuestro servidor. + +Lo más probable es que no queramos exponer todas las operaciones que nos define el conector o quizá queramos no exponer todos los campos de los modelos o exponer campos computados o incluso meter autorización, por eso definimos nuestro **schema graphql** específico para nuestro servidor. + +Ahora tenemos que indicar al servidor donde está nuestra definición del _schema_, para ello añadiremos la propiedad **typeDefs** con este objetivo. + +_index.js_ + +```javascript +const path = require('path'); +const {GraphQLServer} = require('graphql-yoga'); +const {prisma} = require('./generated/javascript-client'); + +const server = new GraphQLServer({ + typeDefs: path.resolve(__dirname, 'schema.graphql'), + context: req => ({ + ...req, + prisma, + }), +}); + +const options = { + port: 8000, + cors: { + origin: '*', + }, +}; + +server.start(options, args => + console.log(`Server is running on http://localhost:${args.port}`) +); +``` + +Antes de poder ejecutar nuestro servidor, tenemos que crear los **resolvers** correspondientes a nuestro schema graphql de nuestro servidor. + +_index.js_ + +```javascript +const path = require('path'); +const {GraphQLServer} = require('graphql-yoga'); +const {prisma} = require('./generated/javascript-client'); + +const resolvers = { + Query: { + products: (parent, args, ctx, info) => ctx.prisma.products({}, info), + }, +}; + +const server = new GraphQLServer({ + typeDefs: path.resolve(__dirname, 'schema.graphql'), + resolvers, + context: req => ({ + ...req, + prisma, + }), +}); + +const options = { + port: 8000, + cors: { + origin: '*', + }, +}; + +server.start(options, args => + console.log(`Server is running on http://localhost:${args.port}`) +); +``` + +A la configuración de nuestro servidor de **graphql-yoga** tenemos que pasarle la configuración con los resolvers que hemos definido en nuestro schema (tienen que existir todos los resolvers que hemos definido, sino, el servidor nos dará un error al arrancar). En este caso, hemos creado un objeto que contiene la propiedad **Query** como la definida en el schema y esta propiedad a su vez es otro objeto que contiene los resolvers, en este caso **products**. + +El resolver es una función que admite 4 parámetros, entre ellos el contexto, en el que previamente habíamos inyectado el cliente autogenerado de **prisma**, en este caso a través de el cliente, podemos lanzar una query al servidor de **prisma** `ctx.prisma.products({}, info)` el primer parámetro que recibe, son los filtros que vamos a aplicar a esta query (en nuestro caso nada) y el segundo parámetro son los campos que se han pedido en la query (id, nombre, etc). + +Dentro de **ctx.prisma** están todas las operaciones disponibles sobre todos los modelos que tiene definido el **conector prisma** como puede ser **orders**. **orderLine**, **createOrderLine**, **deleteOrderLine**, etc. Son todas aquellas que puedes encontrar en la documentación del playground del **conector** `http://localhost:4466/`. + +Si ahora ejecutamos nuestro servidor `node ./server/index.js`, arrancará en `http://localhost:8000/` donde tendremos disponible el **playgound** de graphql, en este caso, solo tenemos disponible la query de **products** que es lo que hemos definido en el schema del servidor. + +![server no schema error](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/serverRunning.png) + +![server no schema error](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/serverProducts.png) + +El resultado debería de ser el mismo que si lanzamos esta misma query en el **playground** del conector de prisma `http://localhost:4466/` (hay que recordar que ahora tenemos 2 playgrounds, uno de nuestro conector y otro de nuestro server). + +![server no schema error](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/prismaProducts.png) + +**> NOTA: No compareis los ids del primer post con los del segundo post porque he tenido que regenerar todas las imagenes docker y lanzar la semilla de nuevo por lo que no son iguales, pero en las 2 imagenes anteriores si que han de ser iguales**. + +Por si no lo recordabais, a nuestro conector **prisma** lo configuramos con un **secret** en nuestro _prisma.yml_ para que no fuese accesible desde fuera por cualquiera pero nosotros estamos accediendo desde nuestro **servidor node** a nuestro **conector prisma** sin configurar este **secret** y nos está funcionando, esto es debido a que cuando ejecutamos `prisma generate` nos genera el cliente prisma ya configurado con el **secret** para que podamos usarlo directamente, si cambiasemos este **secret** deberíamos de regenerar el cliente (lo hace por defecto) y re-desplegar nuestro **servidor node** para que llame con la nueva **secret**. + +Con la parte del servidor añadida, la foto de la arquitectura de nuestro servicio sería la siguiente: + +![service arquitecture](/assets/images/posts/2020-01-07-segunda-parte-contruye-un-server-graphql-con-prisma/prismaArquitecture.PNG) + +## Resumen + +En este post hemos visto como crear nuestro **servidor node** y como conectarlo a nuestro **conector prisma** con **graphql-yoga**, para ello hemos: + +- Reestructurado el proyecto para separar entre **database** y **server** +- Separar los modelos cada uno en su fichero +- Añadir el hook en el fichero _prisma.yml_ para generar el **schema graphql** del **conector prisma** +- Crear nuestro servidor con **graphql-yoga** +- Crear el **schema graphql** para nuestro **servidor node** +- Crear los resolvers necesarios para nuestro **servidor node** para cumplir con el **schema graphql** + +Ahora que ya sabemos como consumir nuestro conector desde el servidor, podemos generar todas las **queries**, **mutations** y **subscriptions** que necesitemos para crear un flujo completo para realizar un pedido, pero eso lo dejamos ya para el siguiente post 😝 + +Por si te has perdido el post anterior, lo puedes encontrar [aquí](/contruye-un-server-graphql-con-prisma) + +El [código completo](https://github.com/NoCountryForGeeks/prisma-with-graphql-yoga-server) de los posts los puedes encontrar en el repo de **GitHub** y [aquí](https://github.com/NoCountryForGeeks/prisma-with-graphql-yoga-server/tree/post-2-setup-server) puedes encontrar el código de este post. diff --git a/content/blogs/2020-03-18-react-data-fetching-con-suspense/es.md b/content/blogs/2020-03-18-react-data-fetching-con-suspense/es.md new file mode 100644 index 0000000..669b67a --- /dev/null +++ b/content/blogs/2020-03-18-react-data-fetching-con-suspense/es.md @@ -0,0 +1,552 @@ +--- +layout: post +current: post +cover: assets/images/posts/2020-03-18-react-data-fetching-con-suspense/cover.png +navigation: True +title: "React data-fetching con Suspense" +date: 2020-03-18 12:00:00 +tags: react suspense react-cache +class: post-template +subclass: "post" +author: ivanrodri +--- + +Hoy quiero hablaros sobre como **React** nos permite hacer una carga de datos asíncrona de una forma totalmente declarativa y casi convirtiéndolo en una operación síncrona. + +En la actualidad si usais alguna librería de renderizado de **UI** (**React**, **Vue**, **Angular**, etc) casi seguro que habéis tenido que lidiar con carga de datos mediante llamadas asíncronas al servidor. Cuando realizamos una llamada asíncrona al servidor desde el **UI** tenemos que controlar al menos 3 estados posibles de nuestra **UI** estos son: + +**Loading**: Tenemos que controlar un estado de espera de nuestra petición ya que es una llamada asíncrona y dependemos de servidor, network, latencia, etc. En este punto podemos mostrar una pantalla en blanco hasta que nuestros datos carguen, un loading para dar feedback al usuario o un skeleton dando una sensación mas real de los datos al usuario. + +**Error**: En este punto tenemos que controlar como debe reaccionar nuestra app cuando algo falla en el proceso de petición de datos. Puede ser que nos encontremos un error devuelto por el servidor o un problema de conexión, en este caso debemos dar feedback al usuario de que algo ha pasado y mostrar una **UI** consistente a lo sucedido. + +**Datos**: Esto es lo que nosotros veníamos buscando datos para mostrar al usuario una vez obtenidos tenemos que renderizar nuestra **UI** con los datos obtenidos del servidor. + +## Patrón isLoading, error, data + +Muchos de vosotros por no decir todos habréis usado esta manera de controlar una petición al servidor ya sea implementando vuestra propia lógica para controlar los 3 estados o ya se a porque habéis usado una librería de terceros que lo implementa de esta manera ([react-query](https://github.com/tannerlinsley/react-query), [useSWR](https://swr.now.sh/)). + +```javascript +const FetchData = () => { + const [{ data, error, isLoading }, dispatch] = useState({ + data: null, + error: null, + isLoading: false + }); + + useEffect(() => { + dispatch(state => ({ ...state, isLoading: true })); + fetchData() + .then(data => dispatch({ data, error: null, isLoading: false })) + .catch(error => dispatch({ data: null, error, isLoading: false })); + }, []); + + if (isLoading) return <Loading />; + if (error) return <Error error={error} />; + return <Data data={data} />; +}; +``` + +Este sería un ejemplo de lo que solemos hacer normalmente, tener un estado en el componente con los 3 posibles estados de la petición, lanzar la petición en un **useEffect** y dependiendo del resultado de la promesa, actualizar de una u otra manera nuestro estado para renderizar la **UI** que dependiendo de los valores va a renderizar uno u otro estado. + +## Data fetching con Suspense + +Desde el equipo de **React** han estado introduciendo nuevos cambios para poder hacer **data-fetching** de una manera mas declarativa y potente que en el caso anterior para ello vamos a manejar los distintos estados de una petición con **componentes** en vez de variables con estado. + +```javascript +export const FetchData = () => { + return ( + <ErrorBoundary> + <Suspense fallback={<Loading />}> + <Data /> + </Suspense> + </ErrorBoundary> + ); +}; + +const dataResource = createResource(fetchData); + +const Data = () => { + const data = dataResource.read(); + return <RenderData data={data} />; +}; +``` + +En este ejemplo podemos ver que no tenemos ningún estado pero somos capaces de controlar los mismos casos anteriores, esto es debido a 2 componentes que ofrece **React** que nos permite controlar ese estado. + +- **isLoading** -> **Suspense**: Si comparamos los 2 ejemplos anteriores la equivalencia de **isLoading** en este caso sería el componente **Suspense** (un componente importado de **React**). Este componente nos mostrará el **Loading** mientras nuestra petición espera una respuesta. + +- **error** -> **ErrorBoundary**: En caso de error en la petición nuestro componente **ErrorBoundary** mostrará una **UI** acorde al error capturado. + +- **data** -> **Data**: El componente **Data** es el encargado de renderizar los datos en el **UI** cuando nuestra petición nos ha retornado datos, **los obtenemos de una manera sincrona** aun que realmente todo el proceso haya sido **asíncrono**. + +## ¿Que es createResource? + +Este es un código de ejemplo pero lo que realmente estamos haciendo en este caso es crear un recurso, ese recurso recibe la función que va a hacer el fetch a nuestro servidor. + +Si nos fijamos hacemos **dataResource.read()** dentro del render del componente, este **read** va a buscar el recurso en la caché, si existe nos lo retorna (**sin promesa**) y sino, lanza el fetch (que es una promesa) para que **Suspense lo capture** + +## ¿Cómo funciona **Suspense**? + +**Suspense** es un componente que importamos de **React**, este componente captura **cualquier promesa que sea lanzada en tiempo de render**. Es muy importante remarcar el **tiempo de render**, esto no funcionará si nuestra promesa es lanzada en un **callback** o un **useEffect**. Cuando me refiero a lanzar una promesa en tiempo de render me refiero a algo como esto: + +```javascript +export const FetchData = () => { + return ( + <Suspense fallback={<Loading />}> + <Data /> + </Suspense> + ); +}; + +const Data = () => { + const data = throw fetch("/data"); + return <RenderData data={data} />; +}; +``` + +La función **fetch** es la propia del navegador que nos permite hacer peticiones al servidor, esta función retorna una promesa (a la cual podemos atachar la función then y catch). En javascript podemos hacer un **throw** de una promesa, no tiene porque ser un error. De esta manera, cuando lanzamos una promesa, **Suspense** captura ese **throw** y empieza a mostrar nuestro **Loading** hasta que nuestra promesa se resuelve y se lanza el render de nuevo, en ese caso ya tendríamos que mostrar nuestros datos. + +Es muy importante que tengamos en cuenta que **Suspense** tiene que estar por encima de aquellos componentes que lanzan la promesa, por eso en el ejemplo está separado en 2 componentes distintos, si estuviese todo al mismo nivel, no sería capaz de capturarlo. + +## ¿Cómo funciona **ErrorBoundary**? + +**ErrorBoundary** es un componente custom que nos podemos crear usando el método de ciclo de vida que nos ofrece **React** **componentDidCatch**: + +```javascript +class ErrorBoundary extends Component { + state = { + error: null + }; + + componentDidCatch = (error, errorInfo) => { + this.setState({ error }); + }; + + render = () => { + if (this.state.error) return <CatsError error={this.state.error} />; + + return this.props.children; + }; +} +``` + +**componentDidCatch** solo captura aquellos errores lanzados en tiempo de render. Funciona completamente igual que **Suspense** pero en vez de capturar promesas, captura errores. + +**NOTA: Para los ejemplos que he creado yo he usado react-cache que es un paquete experimental (que he tenido que empaquetar yo mismo) en el que está trabajando el equipo de react. Pero para trabajar con suspense podéis usar react-query o useSWR que implementan el flujo con Suspense e implementan su propia cache** + +Para mostrar las diferencias de cargas de datos entre la carga normal y **Suspense** he creado una app de ejemplo llamada [**InstaCat**](https://67gy6.csb.app). El código lo puedes encontrar en [Github](https://github.com/IvanRodriCalleja/instacat) y puedes jugar con ello en [CodeSandbox](https://codesandbox.io/s/festive-fermat-67gy6). + +_app_ + +![instacat](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/app.gif) + +Como podeis observar en el repositorio, los ejemplos de **NormalFetch** y **SuspenseFetch** usan los mismo componentes y servicios **(estos son fake)**, lo único que cambia es la implementación de ellos. + +## Simple Fetch + +_NormalFetch.js_ + +```javascript +import React, { useState, useEffect } from "react"; + +import { CatsSkeleton } from "./shared/CatsSkeleton"; +import { CatsError } from "./shared/CatsError"; +import { CatsList } from "./shared/CatsList"; + +import { fetchCats } from "../service/catsApi"; + +export const NormalFetchData = () => { + const [{ data, error, isLoading }, dispatch] = useState({ + data: null, + error: null, + isLoading: true + }); + + useEffect(() => { + fetchCats() + .then(data => dispatch({ data, error: null, isLoading: false })) + .catch(error => dispatch({ data: null, error, isLoading: false })); + }, []); + + if (isLoading) return <CatsSkeleton />; + if (error) return <CatsError error={error} />; + return <CatsList cats={data} />; +}; +``` + +_SuspenseFetch.js_ + +```javascript +import React, { Suspense } from "react"; +import { unstable_createResource as createResource } from "../packages/react-cache"; + +import { CatsSkeleton } from "./shared/CatsSkeleton"; +import { CatsList } from "./shared/CatsList"; +import { ErrorBoundary } from "./shared/ErrorBoundary"; + +import { fetchCats } from "../service/catsApi"; + +export const SuspenseFetchData = () => { + return ( + <ErrorBoundary> + <Suspense fallback={<CatsSkeleton />}> + <Pets /> + </Suspense> + </ErrorBoundary> + ); +}; + +const petsResource = createResource(fetchCats); + +const Pets = () => { + const pets = petsResource.read(); + return <CatsList cats={pets} />; +}; +``` + +En ambos casos la **UI** se comporta de la misma manera: + +![fetch-data](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/data.gif) + +Es casi el mismo ejemplo que hemos visto al inicio del blog para comparar ambas, los cambios más visibles son la manera en la que se hace el **fetch** de los datos, uno lo hace dentro de un **useEffect** y otro lo hace en tiempo de render con la ayuda de **react-cache**, esta ayuda nos permite usar **Suspense** y **ErrorBoundary** para controlar los distintos estados de nuestra llamada. + +[Ejemplo simple fetch](https://67gy6.csb.app/normal-fetch-data) + +[Ejemplo suspense fetch](https://67gy6.csb.app/suspense-fetch-data) + +## Simple Fetch Error + +_NormalFetchError.js_ + +```javascript +import React, { useState, useEffect } from "react"; + +import { CatsSkeleton } from "./shared/CatsSkeleton"; +import { CatsError } from "./shared/CatsError"; +import { CatsList } from "./shared/CatsList"; + +import { fetchCatsError } from "../service/catsApi"; + +export const NormalFetchError = () => { + const [{ data, error, isLoading }, dispatch] = useState({ + data: null, + error: null, + isLoading: true + }); + + useEffect(() => { + fetchCatsError() + .then(data => dispatch({ data, error: null, isLoading: false })) + .catch(error => dispatch({ data: null, error, isLoading: false })); + }, []); + + if (isLoading) return <CatsSkeleton />; + if (error) return <CatsError error={error} />; + return <CatsList cats={data} />; +}; +``` + +_SuspenseFetchError.js_ + +```javascript +import React, { Suspense } from "react"; +import { unstable_createResource as createResource } from "../packages/react-cache"; + +import { CatsSkeleton } from "./shared/CatsSkeleton"; +import { CatsList } from "./shared/CatsList"; +import { ErrorBoundary } from "./shared/ErrorBoundary"; + +import { fetchCatsError } from "../service/catsApi"; + +export const SuspenseFetchError = () => { + return ( + <ErrorBoundary> + <Suspense fallback={<CatsSkeleton />}> + <Pets /> + </Suspense> + </ErrorBoundary> + ); +}; + +const petsResource = createResource(fetchCatsError); + +const Pets = () => { + const pets = petsResource.read(); + return <CatsList cats={pets} />; +}; +``` + +En este caso el comportamiento del **UI** también es completamente igual: + +![fetch-data-error](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/error.gif) + +Es exactamente el mismo caso que el anterior lo único que cambia en este caso es la función que hace el fetch de los datos (**fetchCatsError**). En este caso siempre retorna un error para que podamos probar el caso de error tanto con el fetch normal o con **ErrorBoundary**. + +## Fetch race condition + +Para aquellos que no sepais que es un **race condition** son aquellos casos en los que lanzamos el mismo **fetch** multiples veces y el orden en que se resuelven las promesas no es el deseado y nos quedamos con una **UI** inconsistente. Para este caso he puesto unos botones de búsqueda y las peticiones tienen un delay random de máximo **3000ms**. Si hacemos 4 peticiones rápidamente y se resuelven en un orden distinto al enviado (esto puede pasar ya que dependemos del servidor) vamos a tener unos datos que no queremos tener. + +_NormalFetchRaceCondition.js_ + +```javascript +import React, { useState, useEffect } from "react"; + +import { CatsSkeleton } from "./shared/CatsSkeleton"; +import { CatsError } from "./shared/CatsError"; +import { CatsList } from "./shared/CatsList"; +import { RadioGroup } from "./shared/RadioGroup"; + +import { searchCats } from "../service/catsApi"; + +const options = [ + "sukiicat", + "albertbabycat", + "smoothiethecat", + "realgrumpycat" +]; + +export const NormalFetchRaceCondition = () => { + const [search, setSearch] = useState(""); + + const [{ data, error, isLoading }, dispatch] = useState({ + data: null, + error: null, + isLoading: true + }); + + useEffect(() => { + dispatch(state => ({ ...state, isLoading: true })); + searchCats(search) + .then(data => dispatch({ data, error: null, isLoading: false })) + .catch(error => dispatch({ data: null, error, isLoading: false })); + }, [search]); + console.log({ data, error, isLoading }); + + return ( + <> + <RadioGroup + selectedValue={search} + onChange={setSearch} + options={options} + name="search" + /> + {isLoading && <CatsSkeleton />} + {error && <CatsError error={error} />} + {!isLoading && !error && <CatsList cats={data} />} + </> + ); +}; +``` + +_Normal fetch race condition_ +![race-condition](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/race-condition.gif) + +Como podemos ver, en este case nuestra **UI** es inconsistente, acabamos mostrando una **card** distinta al filtro aplicado, esto es debido a que no hay control de las peticiones y la última que se resuelva, va a ser la que mostraremos (no tiene por que ser la última petición que hemos hecho). Para solventar este problema tendríamos que cancelar la petición anterior. + +_SuspenseFetchRaceCondition.js_ + +```javascript +import React, { Suspense, useState } from "react"; +import { unstable_createResource as createResource } from "../packages/react-cache"; + +import { CatsSkeleton } from "./shared/CatsSkeleton"; +import { CatsList } from "./shared/CatsList"; +import { ErrorBoundary } from "./shared/ErrorBoundary"; +import { RadioGroup } from "./shared/RadioGroup"; + +import { searchCats } from "../service/catsApi"; + +const options = [ + "sukiicat", + "albertbabycat", + "smoothiethecat", + "realgrumpycat" +]; + +export const SuspenseFetchRaceCondition = () => { + const [search, setSearch] = useState(""); + + return ( + <> + <RadioGroup + selectedValue={search} + onChange={setSearch} + options={options} + name="search" + /> + <ErrorBoundary> + <Suspense fallback={<CatsSkeleton />}> + <Pets search={search} /> + </Suspense> + </ErrorBoundary> + </> + ); +}; + +const petsResource = createResource(searchCats); + +const Pets = ({ search }) => { + const pets = petsResource.read(search); + return <CatsList cats={pets} />; +}; +``` + +_Suspense fetch race condition_ +![race-condition](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/suspense-race-condition.gif) + +En este caso si usamos **Suspense** con **react-cache** no tenemos que preocuparnos de que esto nos pase ya que en este caso dependemos de la **key** con la que hemos pedido nuestro rescurso, como nuestra **key** es la propia búsqueda, siempre vamos a tener los datos de la **key** actual. + +## Listado de peticiones + +Hay ocasiones en las que tenemos que realizar varias peticiones al servidor para cargar distintas partes de una misma vist. En estos casos tenemos que manejar más de una llamada con cada uno de sus estados. Para ello vamos a imaginar que cada una de las cards del listado se carga independientemente. + +_NormalFetchList.js_ + +```javascript +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; + +import { SkeletonCard } from "./shared/catsSkeleton/SkeletonCard"; +import { CatsError } from "./shared/CatsError"; +import { CatCard } from "./shared/catsList/CatCard"; + +import { fetchCat } from "../service/catsApi"; + +const CatsListContainer = styled.section` + margin-top: 56px; +`; + +export const NormalFetchList = () => ( + <CatsListContainer> + <NormalFetchListCat id={0} /> + <NormalFetchListCat id={1} /> + <NormalFetchListCat id={2} /> + <NormalFetchListCat id={3} /> + </CatsListContainer> +); + +const NormalFetchListCat = ({ id }) => { + const [{ data, error, isLoading }, dispatch] = useState({ + data: null, + error: null, + isLoading: true + }); + + useEffect(() => { + fetchCat({ id }) + .then(data => dispatch({ data, error: null, isLoading: false })) + .catch(error => dispatch({ data: null, error, isLoading: false })); + }, []); + + if (isLoading) return <SkeletonCard />; + if (error) return <CatsError error={error} />; + return <CatCard cat={data} />; +}; +``` + +_Ejemplo múltiples fetch_ +![race-condition](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/list-fetch.gif) + +Este sería el comportamiento que tendríamos en el caso normal, no podríamos controlar la resolución de la llamada y las cards aparecerían cada una en un momento distinto, esto puede hacer que nos de una sensación mala de carga. Si quisiéramos mostrar todos a la vez, tendríamos que hacer la carga de todos en el componente padre y resolverlo con un `Promise.all` pero en ese caso si una de las llamadas fallase, nuestra promesa fallaría y no podríamos ver ninguno de los casos que no han fallado. + +Para el caso de **Suspense** también han pensado en ello, en este caso existe un componente **SuspenseList**, este componente coordina todos los componentes **Suspense** que han saltado al primer nivel y nos permite configurar como queremos mostrar el loading y como resolverlo cuando las llamadas se van resolviendo. Este componente admite 2 props: + +- **revealOrder**: Define el order en el que los hijos son mostrados (**forwards**, **backwards**, **together"**) +- **tail**: Define como los Loadings son mostrados (**collapsed**, **hidden**) + +_SuspenseFetchList.js_ + +```javascript +import React, { Suspense, SuspenseList, useState } from "react"; +import styled from "styled-components"; +import { unstable_createResource as createResource } from "../packages/react-cache"; + +import { SkeletonCard } from "./shared/catsSkeleton/SkeletonCard"; +import { CatCard } from "./shared/catsList/CatCard"; +import { RadioGroup } from "./shared/RadioGroup"; +import { + SuspenseListConfigProvider, + useSuspenseListConfig +} from "./suspenseFetchList/SuspenseListConfigContext"; + +import { fetchCat } from "../service/catsApi"; + +const SuspenseFetchListContainer = styled.div` + margin-top: 56px; +`; + +const revealOrderOptions = ["forwards", "backwards", "together"]; +const tailOptions = ["collapsed", "hidden"]; + +export const SuspenseFetchList = () => { + const [revealOrder, setRevealOrder] = useState("forwards"); + const [tail, setTail] = useState("collapsed"); + + return ( + <> + <RadioGroup + selectedValue={revealOrder} + onChange={setRevealOrder} + options={revealOrderOptions} + name="revealOrder" + /> + <RadioGroup + selectedValue={tail} + onChange={setTail} + options={tailOptions} + name="tail" + /> + <SuspenseListConfigProvider revealOrder={revealOrder} tail={tail}> + <SuspenseFetchListContainer> + <SuspenseList revealOrder={revealOrder} tail={tail}> + <Suspense fallback={<SkeletonCard />}> + <Cat id={0} /> + </Suspense> + <Suspense fallback={<SkeletonCard />}> + <Cat id={1} /> + </Suspense> + <Suspense fallback={<SkeletonCard />}> + <Cat id={2} /> + </Suspense> + <Suspense fallback={<SkeletonCard />}> + <Cat id={3} /> + </Suspense> + </SuspenseList> + </SuspenseFetchListContainer> + </SuspenseListConfigProvider> + </> + ); +}; + +const catResource = createResource( + fetchCat, + ({ id, revealOrder, tail }) => `${id}${revealOrder}${tail}` +); + +const Cat = ({ id }) => { + const { revealOrder, tail } = useSuspenseListConfig(); + const cat = catResource.read({ id, revealOrder, tail }); + return <CatCard cat={cat} />; +}; +``` + +_Ejemplo múltiples fetch suspense_ +![race-condition](/assets/images/posts/2020-03-18-react-data-fetching-con-suspense/suspense-list.gif) + +De esta manera tan sencilla podemos coordinar distintas peticiones y mostrarlas de una manera que nos convenga. En este caso podemos controlar los errores de las peticiones por separado (habría que añadir un ErrorBoundary por cada Suspense) + +## Resumen + +Usar **Suspense** para **data-fetching** nor permite hacerlo de una forma declarativa con una gran potencia, hay que tener en cuenta que todavía es experimental aunque hay librerías que ya se integran con **Suspense** para hacer la carga de datos (tanto react-query como useSWR soportan ambas maneras). En este caso yo he hecho el bundle de **react-cache** ya que es un paquete experimental. La ventaja de trabajar con una cache es que en ciertas ocasiones no será neceasio salir al servidor ya que los datos los habremos cargado previamente y serán renderizados de inmediato (si habéis jugado con los ejemplos anteriores habréis visto que si vuelves a una página con **Suspense** en la que ya habíais estado previamente , los datos se muestran instantáneamente). + +Aún que podemos usar **Suspense** para data fetching todavía esta en versión **experimental** y puede ser que nos encontremos con algunos casos en los que no funciona. Respecto al paquete **react-cache** está en un estado **muy experimental**. Actualmente **react-cache** permite hacer **preload** de datos para así poder ir haciendo el fetch de los datos mientras hacemos el fetch de los **chunks**, de esta manera paralelizamos la carga de datos con la carga de código en el browser en vez de hacerlo secuencial (tendríamos que esperar a la carga de código para luego hacer la caraga de datos lo que lleva mas tiempo para dar feedback al usuario). + +## CodeSandbox + +<iframe + src="https://codesandbox.io/embed/festive-fermat-67gy6?fontsize=14&hidenavigation=1&theme=dark" + style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" + title="festive-fermat-67gy6" + allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" + sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" + ></iframe>