En este tutorial veremos cómo desarrollar pruebas cuando construimos componentes que interaccionan con Elasticsearch gracias al componente de testing que nos ofrecen desde el equipo de Elastic.
Como ya sabéis el testing es algo esencial en el desempeño día a día de nuestro trabajo como desarrolladores, ya hemos hablado en diversas ocasiones sobre su importancia.
Si bien ya existe algún tutorial sobre el testing de elasticsearch, como este de Daniel Rodríguez, en este tutorial veremos como desarrollar pruebas cuando construimos componentes que interaccionan con Elasticsearch gracias al componente de testing que no ofrecen desde el equipo de Elastic.
Este componente, utilizado internamente en Elastic para el desarrollo de Elasticsearch, permite configurar infraestructuras de cluster formadas por múltiples nodos facilitándonos el camino para la codificación de las pruebas que validen la existencia de errores en nuestro código.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
A continuación mostramos el código fuente de un test de integración muy sencillo que nos servirá a modo de ejemplo sobre los puntos que necesitamos cumplir para crear nuestros test de integración.
ExampleElasticSearchIT.java
public class ExampleElasticSearchIT extends ESIntegTestCase {
@Test
public void someExampleTest() {
createIndex("test");
ensureGreen();
client().prepareIndex("test", "type", "1").setSource("field", "xxx").execute().actionGet();
refresh();
SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
assertNoFailures(searchResponse);
assertFirstHit(searchResponse, hasId("1"));
}
}
Haciendo que nuestros tests hereden de ESIntegTestCase no tendremos que preocuparnos por montar la infraestructura necesaria para dar soporte a nuestros tests puesto que será la propia biblioteca la que se encargue de proveernos de todo lo necesario.
En el snipet de código anterior se muestra cómo crear un índice (createIndex(“test”);) y asegurarnos de que es correcto (ensureGreen();) a través de un cliente (client()) se dará de alta un nuevo documento dentro del índice con un mapping concreto:
Para finalizar realizaremos las aserciones correspondientes que validen nuestro código.
4.2. Configuración
El grado de configuración de la infraestructura de pruebas dependerá de nosotros en todo momento, pudiendo establecer cualquier tipo de particularidad necesaria para nuestros tests.
La configuración “out-of-the-box” genera un cluster que dispondrá de Scope SUITE, entre 1 y 3 o 6 nodos de datos (dependiendo del tipo de ejecución), entre 1 y 3 nodos maestros y 1 nodo coordinador.
Los distintos Scope (SUITE o TEST) marcarán el ámbito para el cual se va a crear el cluster y se pueden configurar mediante la anotación @ClusterScope(scope=SUITE).
SUITE: Se creará un cluster nuevo por suite de tests, borrando todos los índices y templates entre cada test.
TEST: Se creará un cluster nuevo por cada test.
Esta anotación también nos permite configurar otros aspectos relativos al cluster como pueden ser: número de nodos de datos, número máximo y mínimo de nodos de datos (por si queremos utilizar un valor aleatorio acotado), si damos soporte a nodos master dedicados, número de nodos cliente…
Podemos utilizar otras anotaciones existentes dentro de la biblioteca a nivel de test para establecer otras configuraciones, como son:
@Nightly: Establece que el test se ejecuta solo en construcciones nigthly. Esta anotación tiene consecuencias a nivel de configuración del cluster .
@Backwards: Establece que se trata de un test de retrocompatibilidad.
@AwaitsFix: Establece que el test esta pendiente de resolver algún bug.
@BadApple: Establece que el test falla de manera random.
Si bien en el ejemplo del punto anterior hemos decidido que la biblioteca nos devuelva un cliente conectado a un nodo aleatorio (a través de client()), dentro InternalTestCluster disponemos de un amplio conjunto de métodos para recuperar clientes conectados a nodos dedistinta naturaleza dentro del cluster:
client()
coordOnlyNodeClient()
dataNodeClient()
masterClient()
nonMasterClient()
smartClient()
transportClient()
4.3. Helpers
La clase ESIntegTestCase también nos proveerá de una serie de metodos (helpers) que nos serán de gran utilidad en nuestros tests. El conjunto de helpers se puede categorizar de acuerdo a la funcionalidad que otorga pudiendo encontrar aserciones, métodos de configuración del cluster, métodos de explotación del cluster, etc. Si bien la documentación de estos helpers puede consultarse aquí, a continuación veremos en detalle algunos de ellos:
refresh(): dispara el proceso de refresco de los índices que existan en el cluster.
waitForDocs(numDocs): espera hasta que un número dado de documentos (numDocs) se encuentra visible para búsquedas.
ensureYellow() y ensureGreen(): comprueba el estado del cluster y si no es Amarillo o Verde, según el caso, lanza un error de tipo AssertionError.
waitNoPendingTasksOnAll(): espera hasta que no haya tareas pendientes en ningún nodo.
waitForRelocation(): espera hasta que todos los shards reubicados se encuentren activos.
Gracias a la jerarquía establecida, también dispondremos de los métodos helpers que exponen ESTestCase y LuceneTestCase.
4.4. Aserciones
Como en el caso de los Helpers, al extender la funcionalidad de ESIntgeTestCase tendremos acceso a una serie de asserts que pueden facilitarnos la generación de nuestros tests. Algunos ejemplos pueden ser:
assertAllShardsOnNodes(index, pattern): esta aserción comprueba que todos los shards se asignan a nodos con un patrón dado.
assertResultsAndLogOnFailure(expectedResults, searchResponse): comprueba que el número de documentos devueltop es el esperado, en caso contrario deja un log con la información.
assertPathHasBeenCleared(path): comprueba que el path especificado no contiene ningún fichero.
Como en el punto anterior, la jerarquía establecida nos añade todas las aserciones que se exponen tanto en ESTestCase y LuceneTestCase
5. Caso de uso
Para mostrar un caso no tan trivial como el del ejemplo en el punto 4.1.
@ClusterScope(scope = Scope.SUITE)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class BookSearchIT extends ESIntegTestCase {
private static Client client;
private BookSearch bookSearch;
@Before
public void init() throws Exception {
super.setUp();
client = dataNodeClient();
bookSearch = new BookSearch(client);
bookSearch.createType();
bookSearch.add(new Book("1", "Luis Cernuda Bidou", "Los placeres prohibidos", "Libro de poemas publicado en 1931"));
bookSearch.add(new Book("2", "Luis Cernuda Bidou", "Vivir sin estar viviendo", "Libro de poemas publicado en 1949"));
bookSearch.add(new Book("3", "Rafael Alberti", "Marinero en tierra", "Libro de poemas publicado en 1925"));
refresh("resources");
indexExists("resources");
ensureGreen("resources");
}
@Test
public void shouldIndexAndSearchBooks() throws Exception {
List books = bookSearch.findByAuthor("Alberti");
assertEquals(1, books.size());
Book book = books.get(0);
assertEquals(book.getTitle(), "Marinero en tierra");
}
@Test
public void shouldIndexAndSearchBooks2() throws Exception {
List books = bookSearch.findByAuthor("Cernuda");
assertEquals(2, books.size());
}
}
6. Conclusiones
Como hemos visto, disponer de la infraestructura necesaria para realizar tests de integración de nuestros componentes de interacción de elasticsearch puede suponer una tarea ardua, sin embargo, gracias a la biblioteca de testing de Elastic, podemos lidiar con la mayoría de las situaciones de forma rápida y elegante.
En este tutorial les presentaré una mecanismo que suele utilizarse para facilitar la labor del Product Owner al momento de escribir historias de usuario.
Scrum es una de las metodologías ágiles más populares dado que cuenta con una serie de principios que tienen el propósito de asegurar una implementación efectiva del marco de trabajo, en pro de la confianza y transparencia en la gestión de proyectos.
De este modelo una de las prácticas que ha resultado más favorable tiene que ver con el trabajo que se realiza en las fases iniciales de la ejecución de un proyecto. En esta, la recomendación que nos da Scrum es que se dividan las iniciativas en elementos más pequeños, para reducir la incertidumbre que los rodea y acotar el alcance de la solución propuesta. Con este modelo lo que se pretende promover es que la entrega de valor sea continua, incremental y basada en las prioridades del negocio.
Dicho esto, la herramienta más utilizada para dividir un proyecto en elementos más pequeños son las llamadas Historias de Usuarios que nos permiten estandarizar la especificación de los requisitos funcionales de una iniciativa.
Dado que la formulación de historias de usuario requiere de técnica y experiencia, en el presente tutorial les hablaré de algunos modelos que se suelen utilizar para facilitar la labor del Product Owner al momento de enfocar la formulación de un proyecto.
2. Enfocando las historias de usuario
Según lo comentado anteriormente, es en la formulación de las historias de usuario que se sienta las bases para su correcta ejecución y existen algunos enfoques que nos facilitarán el proceso.
El primero de ellos ha sido heredado de las metodologías tradicionales (o en cascada) y propone que la división de los proyectos se haga en base a historias de usuario orientadas por las capas o componentes técnicos que estén involucrados. El problema de este enfoque horizontal es que existen algunas incongruencias desde el punto de vista de las metodologías ágiles, por ejemplo:
Las historias de usuario no aportan valor de manera independiente.
Se aumenta la dependencia entre equipos según su grado de especialización de los mismos.
La priorización no sólo se basa en el valor que aportan las historias al negocio sino en la capacidad de desarrollo de los equipos involucrados.
Aunque esta división horizontal genera historias pequeñas, limita la capacidad de los equipos a ofrecer componentes de software que por sí solos no aporta una funcionalidad, lo cual ocasiona que los plazos de entrega crezcan y aumenten el riesgo de fallar.
El siguiente enfoque, y el que en mi opinión propone un división más acertada, plantea que las historias de usuario se dividan de manera vertical independientemente de los componentes técnicos que estén involucrados.
Para explicar mejor este modelo podemos utilizar una metáfora en la que cortamos un pastel con varias capas de distintos sabores. Si se desea cortar el pastel horizontalmente puede que la división no aporte demasiado dado que los invitados no podrán probar todas las capas. En tal sentido, la solución más eficiente sería cortar el pastel en rebanadas verticales en el que cada trozo aporte el valor esperado.
Un ejemplo podría ser la siguiente historia que debemos dividir por su complejidad:
‘Como cliente quiero poder pagar por mis pedidos para conseguir mis productos’
En este caso podemos dividir la funcionalidad en varias historias de usuario que reduzcan el alcance, pero sin dejar de entregarle valor al negocio:
‘Como cliente quiero poder pagar por transferencia bancaria mis pedidos para conseguir mis productos’,
‘Como cliente quiero poder pagar con tarjeta de crédito mis pedidos para conseguir mis productos’
y ‘Como cliente quiero poder pagar manualmente mis pedidos para conseguir mis productos’.
3. Gestionando las dependencias de una historia entre equipos
Aunque los principios de gestión de proyectos ágiles presuponen que los equipos son multidisciplinarios y han logrado eliminar las dependencias, también es cierto que en la práctica no siempre se logra evitar las dependencias entre los equipos y según sea el caso se deben gestionar las mismas para el cumplimiento de los objetivos planteados.
En estos escenarios, una buena práctica es que alguno de los equipos involucrados asuma la responsabilidad de la integración de las piezas que componen una historia y el resto de equipos figuren como colaboradores en la ejecución de tareas técnicas o historias “habilitadoras” que otorgan valor de forma integral.
4. Conclusiones
Tras algún tiempo como colaborador en distintos proyectos ágiles, puedo dar fe de que el esfuerzo que se realice en la creación de historias de usuario es clave para obtener buenos resultados en la implementación de la solución. En tal sentido, un enfoque adecuado puede ser crucial para la ejecución de un proyecto dado que determinará la cadencia con la que aportamos valor a negocio y cómo minimizamos las dependencias entre los equipos.
En Autentia proporcionamos soporte a la implantación corporativa de metodologías ágiles ayudando a la transformación digital de grandes organizaciones. Te invito a que te informes sobre los servicios profesionales de Autentia y el soporte que podemos proporcionar a tu empresa.
Una vez en el trabajo me preguntaron si podía hacer que unos videos pesaran menos. De primeras, ante una pregunta tan fácil dije que sí, claro que sí. Trabajo con videos y todos los días tengo que comprimir. En este caso, los archivos que debía comprimir eran archivos .mp4, .webm y .ogv.
El primer formato me resultaba muy familiar, evidentemente, pero de los otros dos sinceramente no había oído mucho. Cuando he abierto el programa que uso para comprimir videos sin perder calidad (Handbrake, Open Source Video Transcoder) me he dado cuenta de que con el .mp4 podía trabajar sin problemas, pero con los otros no, la única posibilidad que me daba ese programa era convertirlos a .mp4 o a .mkv y no quería tres archivos iguales.
Me puse a buscar en internet, navegué por muchos convertidores de video online pero la finalidad de todos era pasar de un formato a otro. No buscaba eso. Pero tenía que haber una solución. Investigando me he topado con algo llamado “FFmpeg”.
¿Qué es FFmpeg? Para los nuevos en esto como yo, “FFmpeg es una potente herramienta con la que podemos convertir entre formatos de video, rotar, reducir tamaño, calidad o resolución, y muchas otras operaciones, todo ello automatizado desde una terminal.” Una terminal. Ok.
No estoy muy familiarizada con el mundo de la informática. Cuando uso cualquier software me fijo en la apariencia externa, la interfaz que tiene pero no pienso que eso lo forma mucho código programado por detrás. FFmpeg lleva toda la vida y yo lo he descubierto ahora…
Por suerte trabajo en una empresa que desarrolla software y hay ingenieros informáticos que me ayudan a entender cómo funcionan las cosas.
1. Introducción
La herramienta ffmpeg es multiplataforma, por lo que puede instalarse en cualquier sistema ya sea Mac, Windows o Linux. Aunque su uso no es excesivamente complejo, la gran cantidad de parámetros, combinaciones y el inmenso abanico de formatos y sus características, hacen que el uso de ffmpeg sea poco intuitivo cuando empezamos a utilizarlo.
Cambiar formato
Resolución video
Comprimir, extraer imágenes y video
Insertar marcas de agua (texto o tipografías en un video)
Etc.
2. Instalación
Este tutorial lo he realizado en un entorno Mac por lo que algunos de los comandos no servirán en entorno Windows.
Una manera que aconsejo es crear una carpeta llamada ffmpeg y dejarla en el escritorio, dentro de esa carpeta meteremos el archivo ejecutable y pondremos todos los archivos que queramos convertir/editar.
También existe otra opción si no queremos mover cada video a la carpeta de ffmpeg en el escritorio, sino que queremos hacer la acción desde la ubicación donde ya se encuentre el archivo que sería la siguiente:
desde la terminal miro si tengo el archivo .bash_profile. Este archivo lo que hace es dejar órdenes grabadas para que se ejecuten siempre y no solo durante la sesión. Al ser un archivo oculto si listo el contenido con ls no me aparecerá, tengo que escribir ls -a.
Si no lo tenemos podemos instalarlo desde la Terminal escribiendo nano .bash_profile. Nos aparecerá una ventana nueva y en ella escribimos lo siguiente:
export PATH="/Users/$USER/Desktop/ffmpeg:$PATH"
Yo he puesto Desktop porque el ejecutable de ffmpeg lo tengo en una carpeta en el escritorio, si lo tenéis en otra ubicación, sustituís Desktop por la carpeta correspondiente.
Para los siguientes ejemplos voy a ejecutar ffmpeg con la opción del .bash_profile. Yo lo tengo en el escritorio porque me he acostumbrado a trabajar con él desde ahí pero recordad que si lo hacéis con esta opción, podéis tener el ejecutable en cualquier carpeta y solamente tendréis que indicar la ruta correcta.
Abrimos la Terminal y nos situamos dentro de la carpeta ffmpeg. Para ello introducimos este comando: cd /Users/Desktop/ffmpeg
4. Comandos
Antes de empezar con los comandos debemos tener clara la diferencia entre contenedores de video y códecs:
4.1. Contenedores y códecs
Códecs: un códec es un programa que codifica o decodifica la información multimedia según cierto formato. Una vez codificados los datos pueden ser tratados, por ejemplo almacenados o transmitidos. Se utilizan para evitar archivos imposibles de manipular en un ordenador común. MPEG en todas sus variantes, como así también DivX, 3ivx y Xvid, entre otros.
Para audio, los más populares son el formato MP3, el Ogg Vorbis, el cual se caracteriza por ser un códec perteneciente al software libre, y el AC3 que suele utilizarse en compresiones de DVDs ya que se trata de un códec multicanal (5.1).
Contenedores: un formato contenedor es un formato de archivo que puede contener varios tipos de datos, comprimidos mediante una serie de códecs. El archivo contenedor es usado simplemente para identificar e interpolar los diferentes tipos de datos que contiene.
Los formatos contenedores más populares son:
AVI (contenedor estándar de Windows)
MOV (contenedor estándar de Quicktime)
MP4 (contenedor estándar para MPEG-4)
Ogg (contenedor estándar de Xiph.org códecs)
MKV (Matroska, estándar abierto)
Ver información de nuestro video
Si ejecutáramos ffmpeg desde la carpeta del escritorio y fuéramos pasando ahí todos los videos con los que vamos a hacer algo desde Terminal, estando dentro de la carpeta ffmpeg escribimos el comando así:
En mi opinión:
Pros de mover todos los videos a la carpeta de ffmpeg, convertirlos ahí y luego moverlos a otra ubicación si lo deseamos: tan sólo hay que poner un ./ delante de ffmpeg al inicio del comando.
Contras: tienes que andar copiando y pegando archivos en directorios.
Pros de hacerlo a través de .bash_profile: da igual en qué ubicación tengas el video, tan sólo indicas la ruta al ejecutar
Contras: tienes que escribir cada vez una ruta distinta si coges videos de varias ubicaciones.
Conversión a otros formatos
DE AVI A MP4
En este ejemplo indicamos a ffmpeg que el formato de entrada es videomuestra.mov (mediante el parámetro -i) y que lo convierta a un archivo de destino al cual le hemos indicado la extensión .mp4. De esta forma, ffmpeg busca los codecs de video y de audio apropiados para este formato (automáticamente selecciona h264 para video y aac para audio).
DE MP4 A MKV
Evitar pérdidas de calidad
¿Cuántas veces nos ha pasado que tenemos un video que queremos subir a youtube y no hemos podido porque era muy pesado? O que necesitamos enviarlo por WeTransfer y ocupa más de 2GB o tenemos el dropbox lleno para subirlo…
Podemos utilizar el contenedor .webm con video codificado en VP8 y audio con theora ogg vorbis
Con el valor -qscale 0 el video de salida conserva la calidad del original. Importante seleccionar siempre para evitar pérdidas.
CONVERTIR UN VIDEO PARA VER EN LA PSP
HACER UN VIDEO DE 5 SEGUNDOS POR EJEMPLO CON UNA IMAGEN
CAMBIAR EL FORMATO DE UN VIDEO Y MANTENERLO EN LA MISMA CALIDAD
Otra opción alternativa a la opción vista más arriba con el parámetro -qscale 0:
UNIR VARIOS VIDEOS EN UN ÚNICO VIDEO EN DOS PASOS
Primero se pasan a mpeg para que estén en un formato común:
Después se unen los videos:
5. Resumen comandos
Opciones de audio:
– ar: frecuencia de sambleado del audio por segundo.
–acodec: codec de audio. Por ejemplo: wav, mp3, mp2, ac3… (si no te funciona mp3, prueba a poner: libmp3lame)
-ab: tasa de bits de audio por segundo (por defecto 64k).
Opciones de video
–vcodec: codec de video. Por ejemplo: mpeg4, copy, flv, wmv1, libxvid, etc. Para consultar todos los codecs disponibles puedes escribir en la consola: ffmpeg -formats.
r: Ajuste de velocidad de frames (número de ciclos por segundo (Hz), (por defecto = 25).
–s: tamaño del video. Por ejemplo: vga (640×480), svga (800×600)…
–b: tasa de bits de video por segundo (por defecto 200kbits por segundo). De este parámetro depende mucho la calidad del video y el tamaño.
–qscale 0: para obtener la misma calidad de video que el original.
–aspect: para fijar el aspect ratio (4:3, 16:9).
Otras opciones:
–i: nombre del fichero de entrada.
–pass: el número de veces que se va a recodificar el video (para conseguir una mayor compresión), por defecto 1.
–y: sobreescribir el fichero de salida.
6. Conclusiones
Hay un montón de comandos para manipular el video y el audio con ffmpeg. Es una herramienta que ofrece un amplio abanico de posibilidades y para los que no estáis acostumbrados a trabajar con comandos y la Terminal, con un poco de paciencia, buscando por internet los comandos que queramos y referencias de otros ejemplos se pueden hacer muchas cosas.
Aquí dejo varias páginas donde vienen más ejemplos para que completéis:
Spring Boot nos permite “olvidarnos” de ciertas cuestiones de configuración que con Spring pueden ser algo tediosas o que simplemente queremos aprovechar todo el potencial de las anotaciones, ya que nos quita el tener que estar trabajando con archivos XML de configuración, aunque esto no quita el hecho de que podamos seguir usándolo de la manera antigua.
Todo esto surge con mi necesidad de levantar un contexto de una versión muy antigua de Spring en un Spring Boot actual, y ya de paso aprovechar el Tomcat embebido y no tenerlo por separado.
Como ejemplo intentaremos configurar un contexto tal como podríamos hacer en el “server.xml” de un Tomcat cualquiera, pero lo haremos completamente en Java.
Nuestro reto será configurar una variable de entorno que será un path que use un bean ajeno y un recurso que será la configuración de nuestro data source.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
En primer lugar crearemos una clase que nos servirá de configuración y se llamará AppConfiguration.java. El nombre puede ser el que queramos, ya que no afecta a Spring Boot en ningún sentido, podría llamarse TomcatConfiguration o como se desee. En ella configuraremos el data source y Tomcat.
Nuestra clase de configuración quedaría tal que:
@Configuration
@EnableTransactionManagement
public class AppConfiguration {
}
La anotación “@Configuration” es la que le permite a Spring Boot entender que esta clase aporta configuración que puede ser distinta a la que él autoconfigura en un principio.
La anotación “@EnableTransactionManagement” es relevante con respecto al data source.
4. Configurando nuestro contexto de ejemplo
Una vez tenemos creado un punto de partida procederemos a configurar el data source y Tomcat creando un par de beans, uno de ellos será el data source y el otro nos permitirá crear la configuración del Tomcat embebido.
Debe tenerse muy en cuenta que, dependiendo de las características del proyecto y de cómo debe configurarse Tomcat según su versión, el siguiente código puede variar bastante del ejemplo a vuestro proyecto, ya que al fin y al cabo es configuración y hay un amplio abanico de posibilidades y casos.
Nuestra clase de configuración tomaría la siguiente forma:
@Configuration
@EnableTransactionManagement
public class AppConfig {
// La anotación Primary hace que este bean tenga prioridad sobre otros iguales, no es
// necesario. Además, el data source es muy peculiar y no condiciona nuestra configuración
// de Tomcat.
@Primary
@Bean
public DataSource dataSource() {
// Configuración de JNDI que necesita un bean del proyecto ya que requiere unos
// argumentos.
final JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
final Properties jndiEnvironment = new Properties();
jndiEnvironment.setProperty("name", "java:comp/env/customDirExample");
bean.setJndiEnvironment(jndiEnvironment);
bean.setJndiName("java:comp/env/jdbc/exampleDB");
bean.setProxyInterface(DataSource.class);
// Como comentaba anteriormente, el caso del lookup fue necesario por peculiaridades del
// proyecto.
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
// Devuelve el data source con los cambios necesarios.
return (DataSource) bean.getObject();
}
// Este bean nos permite configurar Tomcat, dependiendo de las necesidades puede variar
// bastante la implementación, por lo cual recomiendo encarecidamente revisar la documentación
// de Spring Boot e ir incrementalmente añadiendo configuración, según sea necesario.
@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
@Override
protected void postProcessContext(Context context) {
// Nos permite cambiar el contexto una vez levantado automáticamente por Spring
// Boot.
// Creamos la variable que empleará uno de los beans del proyecto.
ContextEnvironment environment = new ContextEnvironment();
environment.setName("customDir");
environment.setType("java.lang.String");
environment.setValue("/Users/username/...");
// Configuramos el data source.
ContextResource resource = new ContextResource();
resource.setName("jdbc/exampleDB");
resource.setAuth("Container");
resource.setType("javax.sql.DataSource");
resource.setProperty("maxTotal", "100");
resource.setProperty("maxIdle", "30");
resource.setProperty("maxWaitMillis", "1000");
resource.setProperty("driverClassName", "com.mysql.jdbc.Driver");
resource.setProperty("username", "root");
resource.setProperty("password", "root");
resource.setProperty("url", "jdbc:mysql://localhost:3306/exampledb?autoReconnect=true&useSSL=false");
resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
// Una vez creada la variable de entorno y el recurso del data source los añadimos
// al contexto.
context.getNamingResources().addEnvironment(environment);
context.getNamingResources().addResource(resource);
}
};
}
}
Como puede verse, la implementación está plagada de peculiaridades que no tienen por qué ser así en todos los proyectos, pero ya que nos basamos en un caso real es necesario tenerlo en cuenta.
Lo que realmente configura Tomcat son unas pocas líneas y realmente simples; es como la etiqueta “Context” de un server.xml pero en código Java.
Dependiendo de nuestras necesidades podemos sobreescribir el método que más nos convenga. En este ejemplo sobreescribimos el método postProcessContext, pero hay otros para configurar el contexto como configureContext. También podemos cambiar el baseDir entre otras cosas, aunque la mayor parte depende de sus los componentes del contexto como ContextEnvironment o ContextResource.
5. Una pequeña mejora
Una vez que tenemos bien configurado nuestro Tomcat hay algo que podemos mejorar. En lugar de escribir directamente la configuración del datasource y otros parámetros, ¿por qué no llevárnoslo todo a un YML?, ¡aprovechemos que Spring Boot puede tomar configuración de un YML!.
Para tomar la configuración de un YML necesitamos primero crear una carpeta en la raíz del proyecto y la llamaremos “config”, dentro de esta crearemos el archivo “application.yml” con la siguiente configuración:
Mediante el YML le estamos diciendo a Spring Boot que queremos que su Tomcat use el puerto 8008 con la configuración del datasource que le estamos poniendo.
Todas las propiedades que podemos decirle a Spring Boot que configure las podemos encontrar aquí.
Pero Spring Boot aún no sabe nada de nuestro archivo de configuración, para que lo reconozca y lo use hay que ir a nuestro Eclipse y añadírselo en el classpath. Para esto configuramos el classpath de “Ejecutar como” o “Run as” -> “Run configurations”. En el perfil que empleamos para ejecutar nuestra aplicación vamos a la pestaña classpath, hacemos click sobre “User Entries” o “Entradas de Usuario” y le damos al botón “Avanzado”, seleccionamos añadir una carpeta y le decimos que añada nuestra carpeta config.
Una vez añadida la carpeta config al classpath no debería haber ningún problema, pero tenemos que acceder a estos valores desde el código con la anotación @Value, añadiendo la siguiente pieza de código al principio de nuestra clase de configuración:
Una vez conseguidos los valores que necesitemos procedemos a reemplazar las string que antes metíamos directamente por nuestros parámetros obtenidos del YML. Ahora mejor, ¿verdad?
6. Conclusiones
Como hemos visto, configurar nuestro Tomcat embebido es realmente sencillo, el verdadero reto es ir configurando poco a poco nuestro Spring Boot y Tomcat para satisfacer las necesidades de nuestro proyecto. Para ello recomiendo paciencia, tener cerca la documentación de Spring Boot y un motor de búsqueda.
Configurar el Tomcat embebido nos permite incluir la configuración general de nuestra aplicación y dejar en archivos externos sólo lo que queramos parametrizar simplificando, en algunos casos, enormemente la instalación de nuestra aplicación Spring Boot en cualquier sistema.
En este mini tutorial aprenderemos a instalar e interpretar los informes de Page Analytics. Esta herramienta es una extensión de Google Chrome que nos permite ver datos del comportamiento de nuestros usuarios mientras están dentro de nuestro site.
2. Definición
Page Analytics es una extensión de Chrome (antes podíamos encontrarla como menú en Google Analytics) que permite ver el comportamiento de los usuarios en tiempo real en una página concreta.
La información aparece por defecto en la parte superior de la página y muestra métricas (número de vistas, tiempo de permanencia, tasa de rebote…), número de usuarios activos en tiempo real y un análisis con porcentajes dentro de la página que indica dónde se hace click.
3. Funcionamiento
In-Page analytics se descarga desde la Chome Web Store y funciona teniendo instalado Analytics. Como cualquier otra extensión podemos activarla o desactivarla desde la barra de extensiones de Chrome.
Una vez activada, en la parte superior aparecerán las métricas con los datos básicos y el número de personas que están en tiempo real visitándo nuestro site. Fuera de ese panel, en la propia web, nos da información de cuánta gente de la que está en tiempo real navegando hace clic en los links de nuestra página.
Cuando dos links (por ejemplo una imagen y un texto) llevan al mismo sitio, Page Analytics los cuenta como si fueran uno.
Si queremos diferenciarlos, en la url debemos añadir al final un parámetro distintivo como ‘?origen=texto’ quedando una dirección parecida a esta: https://www.autentia.com/servicios/metodologias-agiles-transformacion-digital/?origen=texto
Los resultados también podemos verlos a través de una leyenda de color, donde el azul indica las zonas menos clicadas y el rojo las zonas más clicadas o zonas calientes.
Otra herramienta útil es el filtro con el mínimo porcentaje con el que queremos que nos muestre los resultados. Por defecto aparecen aquellos por encima del 0,10%.
4. Conclusiones
Page Analytics es un complemento de Google Analytics fácil de instalar y muy intuitivo de utilizar. Tiene las ventajas de ser más visible al mostrar los datos sobre la propia página y ofrecer el comportamiento de los usuarios en tiempo real.
Este tutorial está escrito usando el siguiente entorno:
Hardware: Portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
Sistema Operativo: macOS Sierra
@angular 4.3
2. Introducción
Una de las cosas que menos me gustaban de Angular era la forma en la que teníamos que extender de la clase BaseRequestOptions para modificar la petición para, por ejemplo, añadirle el token en la cabecera “Authentication” a todas las peticiones y no tener que hacerlo uno por uno. El método merge me resulta muy confuso y tiene una serie de bugs reportados. Yo decía: “Esto con interceptores en AngularJS era trivial :-(“
Y, por fin, a partir de la versión 4.3 de Angular y la nueva implementación del servicio HttpClient, ya podemos hacer uso de ellos,
entre otras novedades que iré comentando.
3. Vamos al lío
Como comentaba, vamos a resolver el caso de uso de tener que modificar la cabecera “Authentication” para incluir el token de acceso.
Simplemente tenemos que crear una clase que implemente la interfaz HttpInterceptor. Esta implementación requiere de implementar el método intercept, el cual recibe la petición y el siguiente interceptor en la cadena.
Es muy importante que mantengamos la petición inmutable, por lo que cualquier cambio lo haremos en una petición clonada, la cual enviamos al siguiente interceptor de la cadena.
Teniendo esto en cuenta, aplicamos nuestra lógica, que es recuperar el token del servicio de autenticación y establecerlo en el clon de la petición.
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent> {
// Obtenemos el token
. const token = this.auth.getToken();
// Importante: modificamos de forma inmutable, haciendo el clonado de la petición
const authReq = req.clone({headers: req.headers.set('Authorization', token)});
// Pasamos al siguiente interceptor de la cadena la petición modificada
return next.handle(authReq);
}
}
Una vez tenemos implementada la clase, es hora de convertirla en provider para que el framework sepa de su existencia y pueda hacer uso de ella.
Para ello vamos al módulo principal (o al secundario que haga uso del servicio HttpClient) y en el array de providers añadimos el interceptor al token de Angular “HTTP_INTERCEPTORS” que, como ves, tiene la propiedad multi a true para aceptar más de un elemento y formar la cadena de interceptores.
import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';
@NgModule({
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
}],
})
export class AppModule {}
Es importante resaltar que el orden en la declaración de providers va a determinar el orden de la cadena de interceptores.
¡Y ya está!
4. Conclusiones
De una manera muy mantenible e intuitiva hemos resuelto un caso de uso muy común que de otro modo nos obligaría a implementar mucho código repetitivo. Ya he dicho que esto ya estaba resuelto con la extensión de BaseRequestOptions pero ahora es mucho más intuitivo; lo que demuestra que la gente de Angular mantiene muy vivo el proyecto con importantes mejoras. Ganas tengo de ver qué nos trae la versión 5.
Cualquier duda o sugerencia en la zona de comentarios.
El pasado junio, Apple presentó iOS 11 en su conferencia para desarrolladores WWDC 2017. A pesar de ser para desarrolladores, la presentación inicial se dirige a la prensa. Este año las novedades fueron ARKit, multitarea en iPad, y nuevos productos de consumo: iMac Pro, HomePod, y iPad Pro.
Cuando Apple se dirige al público en general, habla de qué puedes hacer con sus productos y pasa de puntillas por el cómo.
Por ejemplo, iOS 11 comprimirá fotos y videos a mitad de tamaño manteniendo la calidad. Esto implica: doble de espacio en tu teléfono, mitad de discos duros en la mesa, mayor calidad de streaming en Internet.
Esta brujería es posible gracias a HEIF (High Efficiency Format), un nuevo formato con el que Apple quiere reemplazar a JPEG en dispositivos Apple.
Hay más comparativas en esta web de Nokia dedicado a HEIF. En esta web puedes ver auténticas imágenes HEIF renderizadas mediante un plugin de JavaScript.
2. ¿Qué es HEIF?
HEIF no es un formato de compresión sino que es un contenedor de archivos, esto quiere decir que permite almacenar secuencias de imágenes. Son imágenes fijas tomadas del formato de vídeo HVEC.
HVEC es el sucesor de códec H.264. Por eso se llama H.265, (o High Efficiency Video Coding si te gustan los nombres largos). Tanto HEIF como HVEC han sido desarrollados por el MPEG Group, el grupo de expertos que ha publicado los estándares MPEG, que incluyen formatos como MP3 o AAC.
HVEC no es gratis (igual que casi todos los formatos). Su uso require una licencia del MPEG LA, una empresa a través de la cual se licencian formatos que incluyen patentes creadas por múltiples empresas. Por ejemplo, para que podamos usar VP8 sin pagar patentes, Google tuvo que licenciar su uso a MPEG LA para luego ofrecerlo gratuitamente.
HVEC no es nuevo. Fue estandarizado en el verano de 2015, pero no había sido adoptado antes porque requiere mucha potencia de proceso. El motivo por el que Apple lo soporta es que sus dispositivos móviles tienen los chip más potentes del mercado, y que además soporta decodificación acelerada de HVEC. iOS 11 y MacOS High Sierra son las primeras plataformas en soportar HEIF de forma nativa.
3. HEIF vs JPEG
Hasta ahora el formato más popular para compresión de imágenes digitales ha sido JPEG, que lleva con nosotros 25 años (desde 1992). JPEG hizo posible la fotografía en la web, porque comprime típicamente 10x sin pérdida perceptible de calidad. Sin embargo se quedará fuera de juego frente a HEIF. Continuará como formato de compatibilidad durante muchos años, pero JPEG desaparecerá gradualmente a medida que aumente el soporte y la potencia de compresión. Por ahora, los dispositivos Apple exportarán automáticamente a JPEG cuando detecten que el destino no es Apple.
JPG vs HEIF al mismo tamaño.
4. Características de HEIF
Opción de compresión con y sin pérdida.
Edición sin pérdida. Ciertas ediciones como rotación, recorte, títulos y overlays se almacenan como un añadido, lo que nos permite volver al original.
Almacenamiento de imágenes, audio y texto sincronizados con el vídeo. Esto lo hace un buen formato para mostrar meta-información (como hacen en primevideo).
Mitad de peso. Un iPhone de 128GB podrá contener 100.000 fotos.
Soporte para colores de 16 bits frente a los 8 bits de JPEG lo que da un color más rico y realista.
Live Photos en contenedor único. Thumbnail y vídeo se guardan en un mismo fichero.
5. Conclusiones
HEIF será el nuevo formato estándar en iOS a partir de iOS 11 que vendrá en septiembre 2017.
Y esto, nos guste o no, hará que HEIF coja semejante impulso como para acabar con JPEG, o por lo menos quitarle una buena porción de mercado. Supondrá también un paso importante para la web pues será posible cargar media más rápido.
En este tutorial sobre el motor de búsquedas Elasticsearch vamos a ver cómo implementar diferentes políticas que nos permitirán crear buscadores que vayan más allá de la simple correspondencia léxica.
Cuando nos enfrentamos a un problema de búsquedas relacionado con el lenguaje natural estamos lidiando con un problema de interpretación de las entradas que se reciben a la hora de realizar la búsqueda. Este proceso de interpretación puede enfocarse desde múltiples puntos de vista:
Normalización de términos.
Reducir los términos a su lexema.
Sinónimos.
…
En el presente tutorial vamos a realizar una serie de aproximaciones sucesivas sobre esta problemática con Elasticsearch para ver cómo podemos ir mejorando un sistema de búsquedas de manera iterativa e incremental.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
Disponemos de un índice en Elasticsearch que almacena informes con un texto relativo a la intervención que realizó un veterinario y, sobre esos documentos, queremos realizar búsquedas por términos relacionados, por ejemplo perros.
PUT interventions/report/1
{
"description": "El caballo presenta un estado perfecto de salud."
}
PUT interventions/report/2
{
"description": "Se ha detectado enrojecimiento y sequedad en los ojos del border collie."
}
PUT interventions/report/3
{
"description": "El bull terrier tiene síntomas de hiperactividad."
}
PUT interventions/report/4
{
"description": "El gato de origen alemán tiene perdida de pelo."
}
PUT interventions/report/5
{
"description": "Se trata de un dogo alemán con caracter violento."
}
POST interventions/_search
{
"query": {
"match_phrase": {
"description": "perro"
}
}
}
Como podemos comprobar, al realizar una búsqueda por “perro” no obtenemos ningún resultado, puesto que la coincidencia exacta de valores no se da en ningún caso, tampoco obtenemos ningún valor relacionado.
3.2. Aproximación basada en sinónimos
La primera aproximación que podemos realizar sobre el problema consiste en definir un conjunto de sinónimos que haga que el buscador nos devuelva correspondencias entre términos como sinónimos.
Gracias a al filtro que hemos establecido el resultado será el esperado y aparecerán aquellas coincidencias con los sinónimos definidos. Sin embargo, aparece un comportamiento que puede resultar no adecuado dependiendo de la precisión que se requiera.
Por ejemplo:
Este comportamiento se produce debido a la forma en la que Elasticsearch procesa la información. A muy alto nivel, podemos decir que Elasticsearch, a través de los analizadores, se encarga de generar tokens por separado para cada término que encuentra de acuerdo a un conjunto de reglas.
En este ejemplo concreto, establecemos un analizador basado, por un lado, en un generador de tokens standard y por el otro lado un filtro (que también procesará tokens) ad-hoc basado en sinónimos.
Así, sobre nuestro ejemplo podemos ver que, al establecer el texto “border collie” nuestro analizador va a generar dos conjuntos de tokens “border” y “collie” y, con posterioridad, aplicará los conjuntos de sinónimos dejando los conjuntos como “perro, chucho, border, bull, dogo, labrador, pinscher” y “collie, terrier, alemán, miniatura”. Y son estos conjuntos de tokens los que se tendrán en cuenta a la hora de realizar la búsqueda posterior, pudiendo generar valores correctos (“border collie” o “bull terrier”) y valores incorrectos (“labrador alemán”, “chucho terrier”).
3.3. Aproximación basada en sinónimos mejorada
Una posible mejora sobre la solución basada en sinónimos pasa por considerar algunos términos compuestos como un único token, de esta manera eliminamos la posibilidad del cruce de términos indebido que veíamos en el punto anterior.
Esto puede conseguirse simplemente estableciendo un generador de tokens distinto a nivel de nuestro filtro de sinónimos, en concreto el tipo keyword. Sin embargo, estableciendo este tipo de tokenizador perdemos la potencia de la que disponíamos a través de los sinónimos.
Es en este punto donde entra el filtro keep, que nos permitirá mantener ciertos términos dentro de las búsquedas retomando esa potencia perdida por el tokenizador.
Todo esto lo estableceremos como un subcampo dentro del campo descripción para que, a la hora de realizar búsquedas, podamos aumentar la prioridad de ciertas correspondencias frente a otras.
3.4. Aproximación basada en taxonomías y vocabularios controlados
Si queremos realizar una aproximación más cercana al lenguaje natural necesitamos centrar nuestros esfuerzos en las taxonomías y los vocabularios controlados.
Las taxonomías son clasificaciones jerárquicas de cosas, por ejemplo la categoría taxonómica: taxones o grupos en que se clasifican los seres vivos: Dominio > Reino > Filo > Clase > Orden > Familia > Género > Especie
Un vocabulario controlado es una extensión de una taxonomía que tiene en cuenta sinónimos. Ejemplos de vocabularios controlados son: Tesauros, encabezamientos de materias u ontologías.
En concreto, un tesauro es un instrumento de control terminológico que traduce a un lenguaje sistémico o documental el lenguaje natural empleado en los documentos y por los usuarios. Consiste en un vocabulario controlado y dinámico de términos relacionados semántica y jerárquicamente, que se aplica a un campo específico del conocimiento.
Los términos que conforman el tesauro se interrelacionan entre ellos bajo tres modalidades de relación:
Relaciones jerárquicas: establecen subdivisiones que generalmente reflejan estructuras de TODO/Parte. Hiperónimos e hipónimos.
Los hiperónimo: aquel término que puede ser utilizado para referirse a la realidad nombrada por un término más específico.
Los hipónimos: palabra que posee todos los rasgos semánticos, o semas, de otra más general —su hiperónimo— pero que en su definición añade otras características semánticas que la diferencias de ésta.
Relaciones de equivalencia: controlan la sinonimia, homonimia, antonimia y polisemia entre los términos.
Relaciones asociativas: mejoran las estrategias de recuperación y ayudan a reducir la poli-jerarquía entre los términos.
Como hemos visto, con Elastisearch podemos implementar múltiples políticas de análisis enfocadas en las búsquedas permitiéndonos así responder a un amplio abanico de necesidades.
Sinónimos, vocabularios o taxonomías son distintos enfoques para resolver un mismo problema, afrontar la tarea (nada trivial) de intentar comprender qué información es la que realmente el consumidor quiere recibir en base a un “lenguaje natural”.
Queda en nuestra mano, como profesionales del software, razonar cuál de las soluciones es la que mejor se adapta a las necesidades a cubrir siempre optando por la más sencilla.
La técnica del duotono o mapa de degradado es un recurso muy utilizado ya que es sencillo de realizar y genera unos resultados muy creativos. Podemos verlo, por ejemplo, en las imágenes que utiliza Spotify.
En este tutorial aprenderemos a aplicarlo en unos sencillos pasos.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
Hardware: portátil MacBook Pro 15′ (2,5 Ghz Intel i7, 16GB).
Sistema operativo: MacOS Sierra 10.12.
Adobe Photoshop CC Versión 2017.1.1.
3. Importación
En primer lugar importamos la imagen en Photoshop. En este caso hemos utilizado una imagen de la página Picjumbo en la que podemos disponer de manera gratuita de fotografías para uso personal o comercial.
Una vez importada, nos vamos a capa – nueva capa de ajuste – mapa de degradado o pulsamos el icono de capa de ajuste en la paleta capas.
4. Ajustes del efecto
Una vez aplicada la capa veremos que la imagen queda con los colores invertidos, por lo que seleccionamos la capa de ajuste y seleccionamos la opción invertir.
Una vez hecho esto la imagen ya tendrá una apariencia normal por lo que el siguiente paso es cambiar los colores de nuestra imagen.
En Photoshop podemos encontrar varias bibliotecas de degradados a las que podemos acceder pulsando encima del degradado en la ventana del efecto y después en la rueda que despliega el menú.
Si probamos los degradados predefinidos y no nos convence, lo mejor es que elijamos nuestros propios colores, desde el mismo menú utilizamos los selectores de color que aparecen debajo del degradado.
Los mejores resultados los obtendremos si utilizamos un color claro y uno oscuro, si elegimos aproximadamente la saturación y el brillo en la paleta de colores y desplazamos la barra lateral de la derecha hacia arriba y hacia abajo, podemos ver los distintos tonos con esas características e ir probando cual nos gusta más.
Al haber utilizado una capa de ajuste, si importamos cualquier otra imagen podemos reutilizar el mismo efecto.
5. Vídeo explicativo
En el siguiente vídeo puedes ver el proceso paso a paso:
6. Otros ejemplos
Con esta técnica podemos conseguir multitud de efectos diferentes, dándole a nuestras fotografías un toque más creativo.
Cuando hablamos de geofences nos referimos a un perímetro virtual para un área geográfica del mundo real el cual nos interesa monitorizar.
En iOS, el framework de CoreLocation nos permite definir regiones mediante una coordenada geográfica y un radio en metros, y comprobar mediante la ubicación del usuario, es decir, la coordenada geográfica en la que este se encuentra en un determinado momento, si dicha coordenada está dentro o no de esa determinada región.
Lo mejor de todo esto es que este trabajo de obtener periódicamente la ubicación y procesar si está dentro o no del área se realiza a nivel del sistema, disparando un evento que captura la App cuando se entra o sale de la región.
Como ejemplos del uso de geofences, podemos poner recordatorios asignados a una ubicación, o en cierto juego que estuvo de moda el verano pasado en el que al encontrarse en una determinada ubicación aparecían determinados tipos de pokémon salvajes…
Apple se toma muy en serio la privacidad de sus usuarios, de hecho la promociona como una de las características estrella de sus productos frente a la competencia.
Para pedir permiso para utilizar la App, lo podemos hacer por ejemplo en nuestro AppDelegate
import UIKit
import CoreLocation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// Important: location manager is declared as a class attribute in order to keep a strong reference. Otherwise, if it was deallocated all delegate method wouldn't be triggered
var locationManager : CLLocationManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
locationManager = CLLocationManager()
locationManager?.requestAlwaysAuthorization()
return true
}
}
Esto mostrará un diálogo modal pidiendo permiso al usuario para utilizar la ubicación. Solo se le preguntará una única vez, tanto si el usuario acepta como si no. En caso de que se arrepiente de su decisión, deberá cambiar el permiso desde el menú de preferencias del sistema.
Como cosa a tener en cuenta, para poder utilizar geofences en nuestra aplicación necesitamos solicitar el permiso para utilizar la ubicación siempre, en caso contrario los eventos de entrada/salida de una geofence no se dispararán.
Para que esto funcione y poder utilizar la ubicación del usuario en nuestras apps, además debemos declarar con qué intención se va a utilizar la ubicación del usuario.
Para ello utilizaremos la key "NSLocationAlwaysUsageDescription" y como valor le daremos una explicación que será la que se muestra en el diálogo que hemos hablado antes.
4. Crear la geofence
Crear la geofence en iOS es muy sencillo. Para la prueba de concepto voy a crear desde el view controller principal del proyecto una vista muy sencilla con un mapa que muestra la ubicación actual, un label para indicar si está o no activo la geofence y un botón que desactive el botón.
Para ello lo primero que voy a hacer que el view controller principal sea el delegate del location manager
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
@IBOutlet weak var geofencesLabel: UILabel!
@IBOutlet weak var mapView: MKMapView!
var locationManager : CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
locationManager = appDelegate.locationManager
locationManager?.delegate = self
}
}
}
El centro de la geofence serán las oficinas de Autentia, y el radio 100 metros.
Una vez creada la región, tan solo tengo que indicarle al locator manager que empiece a monitorizar esa región. Para ello haré que el action asociado al boton inicie o detenga la monitorización de la región.
@IBAction func toggleGeofences(_ sender: Any) {
if let locationManager = self.locationManager {
let region = self.regionToMonitor()
if(locationManager.monitoredRegions.count > 0) {
locationManager.stopMonitoring(for: region)
} else {
locationManager.startMonitoring(for: region)
}
setupView()
} else {
notify(msg: "Unable to set geofence")
}
}
Y por último creo los métodos delegados que se dispararán cuando se produzca un evento de entrada o salida en una región.
Y ya está, tan simple como eso. Cuando el usuario esté dentro del radio o salga de él se le mostrará una notificación. Podemos crear tantas geofences como queramos, ya que como hemos visto al crear la región se le debe asignar un identificador para distinguirlas (si es que necesitamos diferenciarlas claro).
Una cosa que hay que tener en cuenta es que las geofences perduran aunque se cierre la app (da igual que seamos nosotros los que la cerremos o el sistema decida matarla en cualquier momento al estar en background), y cuando entre o salga en una geofence se disparará el evento y la app se ejecutará de nuevo.
5. Probando su funcionamiento
Para probar que todo funciona como esperamos, en el simulador de iOS en el menú "Debug->Location->Custom location…" podemos jugar con ubicaciones simuladas
Con la longitud de la imagen estaríamos bastante cerca de la geofence pero fuera del radio, si la establecemos a -3.509220, estaríamos justo en el centro de la geofence y habríamos provocado un evento de entrada.
6. Conclusiones
Establecer una geofence en iOS es increíblemente sencillo y rápido. La única pega es que se debe solicitar permiso para utilizar la ubicación en todo momento.
Esto puede hacer que algún usuario más preocupado por su privacidad sea reticente a dar su consentimiento a la app, que acabe borrándola o simplemente se queje amargamente en la App Store si no se le deja claro para que se va a utilizar su ubicación (y con razón…).
Por esta razón hay que plantearse si establecer geofences puede darle una funcionalidad útil para el usuario o por el contrario solo va a ser un consumo de batería inútil.
Apiary es un editor para APIs que te permite usar tanto API Blueprint como Swagger, los cuales son lenguajes que nos permiten describir nuestras APIs, y gracias a ellos podemos realizar algunas tareas como mockear una API de forma sencilla y rápida.
Aunque ya las enumeraremos más detalladamente, Apiary no solo nos permite editar texto como cualquier otro editor, sino que también nos ayuda a pasar tests a nuestra API empleando Dredd. También analiza las requests con su inspector, nos permite previsualizar el documento final y compila constantemente con los últimos cambios permitiéndonos ver errores al validarlo todo (aunque puede ser molesto, por eso puede desactivarse la previsualización).
En un principio se ve bastante completo, pero ¿es fácil de usar? Parece que Apiary se ha centrado en la comodidad para el usuario, ya que la interfaz tiene la información justa y necesaria, no te abruma desde un principio como puede ocurrir con algunas herramientas con mucha opciones disponibles; mucho botón no siempre ayuda.
2. Funciones a nuestro alcance
Antes de ver las funcionalidades que nos provee hay que tener en cuenta que tiene una parte de pago.
A la hora de crear APIs puedes hacerlo como un usuario normal o como un Team o equipo. Un equipo lo forman un conjunto de usuarios y el grupo es el que tiene las APIs, no un usuario en concreto. Lamentablemente los equipos tienen una barrera que es el dinero: 99$ mensuales.
Aunque hacer un equipo no sea gratuito, podemos usarlo personalmente para diseñar nuestras propias APIs con un editor muy cómodo, además de que nos brinda un abanico de opciones para facilitarnos la tarea.
Ahora que tenemos claro qué no podemos hacer, vamos a ver lo que sí podemos hacer. Apiary nos deja a mano las siguientes posibilidades:
Un editor que cuenta con dos estilos y varios tamaños de letra, además de una previsualización muy cómoda y una consola que permite hacer ejemplos y ver la especificación.
Genera la documentación de forma que pueda compartirse para ver y editar entre unos pocos, ya que se encuentra limitado, para aumentar el número de invitaciones hay que pagar.
Permite vincular la documentación con tu proyecto de GitHub.
Permite probar el diseño sobre tu implementación real de la API.
Tiene un inspector de llamadas sencillo de usar.
Aporta una interfaz limpia para hacer tests con Dredd, con un serie de pasos para poderlo usar.
3. Una API de ejemplo
Crear una nueva API es tan sencillo como pulsar un botón, generando así una API de ejemplo con la que empezar.
Mediante este ejemplo se muestra cómo trabaja Apiary y cómo se vería el documento final. Cabe destacar que Apiary nos obliga a tener como mínimo una API.
Como puede verse, Apiary nos genera rápidamente una documentación bonita y limpia, con desgloses que abren la consola que es la responsable de mostrar la request en detalle.
El inspector también es interesante, ya que podemos observar todas las llamadas de ejemplo que vamos haciendo, no solo su resultado final. No escatima en detalles y nos muestra el mensaje completo.
También es interesante el apartado de tests, ya que nos permite ver los que realizamos en local, en integración continua y en post-deployment y además nos añade un pequeño tutorial para emplear Dredd, que lo usa para ejecutar los tests sobre nuestra API, mostrando dónde y por qué ha fallado, además de comparar el mensaje que espera y el que ha recibido realmente para que puedas ver el problema fácilmente.
Como puede verse, Apiary es una herramienta muy completa, y es web, con lo cual es muy cómoda de usar.
4. Conclusiones
Podemos ver que Apiary, sin tener en cuenta la barrera de pago ante equipos, es muy completo y su simplicidad sorprende, con conocimientos básicos puedes usarlo sin problemas. Con solo tener que aprender la sintaxis de API Blueprint puedes obtener resultados muy buenos.
El poder usar Dredd con unos sencillos pasos le da más puntos si cabe, además de que si queremos hacer pruebas con Drakov y Dredd es tan sencillo como copiar todo y pegarlo en un archivo que creemos para poder usarlo localmente.
En definitiva, una herramienta con muchas posibilidades y que merece la pena probar, aunque los límites de pago pueden incomodar el trabajo para algunos.
En este tutorial voy a explicar una técnica de video marketing que consiste en creas animaciones de video sobre pizarra blanca (conocido también como whiteboard animation), con el programa VideoScribe.
Lo primero es saber qué es el video marketing: conjunto de técnicas que usan el video con el fin de promocionar un producto o servicio para lograr objetivos dentro de una estrategia de marketing.
Con la aparición de internet esta técnica ha crecido y evolucionado rápidamente porque facilita la labor de difusión y acceso a contenidos audiovisuales desde distintas plataformas, gracias a la aparición de portales gratuitos para compartir videos como Youtube, Vimeo y otros. El 40% del tráfico de internet es consumido por el visionado de videos y la publicidad online aumenta cada vez más su inversión en creación de videos para su difusión online.
El componente social de la estrategia del video marketing se diseña para aumentar la participación de la audiencia en redes sociales en relación a un video determinado.
Algunas herramientas de animación whiteboard:
VideoScribe
After Effects
Doodly
Explee
TTS Sketch Maker
Las ventajas de este tipo de publicidad:
Ahorro de tiempo a la hora de buscar clientes.
Aumenta el número de espectadores.
La gente se queda en la página web más tiempo.
Las ventas aumentan hasta 4 veces.
Llama la atención y divierte.
Método perfecto para anunciar el producto.
2. VideoScribe
2.1. Descarga e instalación
La idea en la que se basa VideoScribe es en la de un lienzo único a través del cual discurre el proceso de presentación eligiendo en todo momento la situación de la cámara que captura el video. Es un programa de pago pero dispone de una versión de prueba de 7 días que se puede adquirir desde su página web.
Nos descargamos la versión de prueba desde la página de Sparkol o bien podemos ir a la página de VideoScribe. Las cuotas van en función de la modalidad escogida.
2.2. Registro e inicio de sesión
Al darle a iniciar prueba/ sign up se descargará el programa automáticamente. Cuando lo tengáis se os abrirá esta ventana y con vuestro correo iniciáis sesión.
3. Crear el lienzo
Lo primero que veréis será la ventana de proyectos. El primero con el símbolo de + se usa para crear un lienzo nuevo. El segundo te permite acceder a tu cuenta online. Y en caso de tener varios proyectos guardados, aparecerán a continuación.
Creamos un lienzo en blanco. Arriba tenemos la barra de herramientas (guardar proyecto, importar imagen, importar texto, importar música, importar audio…). En la parte inferior tenemos el timeline.
3.1. Añadir imágenes
VideoScribe viene con un montón de imágenes para usar. Están organizadas por carpetas según la categoría: “people, business, buildings…”. Hay que dedicar un tiempo a ver todas las imágenes que hay para tener una idea en la cabeza de qué podemos usar en nuestra animación. Para previsualizar cualquiera de ellas hacemos click derecho encima de la imagen.
En la parte inferior podemos pinchar en la carpeta para importar nuestras propias imágenes o pinchar en SVGStudio.com para descargarnos más (son de pago).
Al importar una imagen, por defecto la duración de la animación son 8 segundos, pero podemos indicarle el tiempo que queramos nosotros que tarde en dibujarlo. Para abrir las propiedades de la imagen pinchamos en el icono de la hoja que tenemos abajo a la izquierda. Desde ahí también podemos indicarle el tiempo de la animación.
Debajo tenemos el % de opacidad (100% sólido) y al lado los grados que queremos inclinar la imagen.
Dentro de las propiedades podemos elegir si lo queremos dibujado solamente con trazos o no. La diferencia la podéis ver clara:
En la pestaña INCORPORAR que aparece al lado de dibujar y transformar se pueden incorporar elementos desde fuera del lienzo.
La flecha azul está indicando por donde va a entrar el dibujo pero como vemos podemos elegir desde qué punto del lienzo queremos que entre (la flecha roja estaría indicando que el elemento va a entrar por la parte central de la derecha).
En el botón de la mano podemos elegir con qué mano queremos que se dibuje la animación o por el contrario si no queremos mano elegimos sin mano.
En el timeline, veréis cada dibujo que vais poniendo. Si queréis cambiar de orden alguno le dais click al botón derecho y elegís entre las opciones:
3.2. Configuración de zoom y posición de cámara
Cada elemento que agregamos tiene una posición de cámara. Por defecto VideoScribe dibuja cada elemento en el centro del lienzo y esa es la primera posición de cámara pero se puede personalizar.
Paso 1: selecciona el elemento y colócalo donde quieras en el lienzo.
Paso 2: usa los botones + y – o la rueda del ratón para ajustar el lienzo a nivel de zoom que quieras.
Paso 3: con la imagen seleccionada ajusta la posición de cámara usando el icono de abajo a la derecha. Cuando la tengas colocada donde quieras, dale al botón de la cámara con un check para grabar la posición.
La imagen ahora se dibujará en el tamaño y posición que hemos ajustado.
3.3. Añadir texto
En la barra superior aparece una T para texto. Si pinchamos en la F inferior que nos sale a la izquierda podemos importar cualquier tipografía que tengamos instalada en el ordenador.
4. Añadir archivos de audio
Voz en off y música.
Hay dos maneras de combinar en el lienzo audio con imagen. Puedes importar tu voz en off primero y después ir construyendo el lienzo o puedes grabarte la voz en off después de haber creado el lienzo. Lo recomendable es grabarse la voz primero porque resulta más fácil después ir encajándola con los dibujos.
5. Guardar proyecto
Captura de pantalla: click en el botón guardar en la esquina superior derecha y elegimos el tercer icono. Nos generará una captura en un PDF.
Guardar en local: volvemos a darle al botón de guardar y le ponemos un nombre. Elegimos la carpeta o creamos una y pinchamos en el check.
Guardar online: abrimos la carpeta creada si lo hemos hecho en el paso anterior. En la ventana guardar hacemos click en el icono de la nube.
6. Exportar
Publicar en redes sociales.
Publicar en powerpoint.
Crear un archivo de video: se abrirá una ventana para elegir el formato de salida y la calidad.
7. Conclusiones
¿Por qué deberías usar el video marketing para promocionar tu empresa o negocio?
Contribuye a mejorar el posicionamiento SEO. Si subes tus videos a Youtube y los optimizas tendrás más posibilidades de aparecer en los primeros resultados de una búsqueda.
Aumenta el tráfico a tu web.
Te ayudan a diferenciarte de la competencia. Utiliza el video para mostrar aquello que te hace diferente y cuéntalo de manera que capte la atención de tu clientela potencial.
Contribuye a captar la atención y despertar interés de tus visitantes que hará que las visitas a tu web aumenten.
Generan emociones.
Facilitan la viralidad. Los videos son los contenidos que más se ven y se comparten en redes sociales. Siempre es más probable que un usuario comparta un video donde se presenta alguno de tus productos a que comparta un enlace a una página de descripción.
Ayudan a fomentar la transparencia y la confianza. Si presentas a tu equipo de trabajo, las instalaciones de tu empresa, tu negocio, generas más confianza en el espectador que con un simple texto.
Puede ser gratuito si aprendes a manejar alguna herramienta de edición de video.
Se muestra a personas que buscan información sobre un tema concreto.
Hace algunas semanas se anunció la actualización de SAFe a la versión 4.5 como resultado de la investigación en cientos de implementaciones, la retroalimentación de clientes y la aportación de miembros de la comunidad agile.
Posters de SAFe 4.5
Entre algunas de las características más destacadas de esta nueva versión se pueden mencionar:
La definición de varias configuraciones del framework con el objetivo de poder satisfacer las necesidades de cualquier tipo de empresa.
La inclusión de conceptos Lean-Agile relacionado con la construcción incremental del producto.
La definición de un modelo de entrega basado en DevOps & Continuous Delivery.
Adicional a estos aspectos, se incluyeron algunos cambios en lo referente a la guía de implementación y las recomendaciones asociadas a la adopción de SAFe como marco de escalado Agile.
2. Configuraciones de SAFe
La nueva versión de SAFe está pensada para ser un marco configurable y orientado a satisfacer las necesidades de cualquier producto o servicio. Ahora, a medida que las necesidades de una empresa crezcan, se dispondrá de una versión de SAFe para atender estos desafíos.
Partiendo de este hecho se definieron cuatro configuraciones que permiten proporcionar un enfoque ajustado a las características de cada empresa.
2.1. ESSENTIAL SAFe
Es la configuración más básica del framework y provee de los elementos mínimos necesarios para una implementación efectiva de SAFe.
Proporciona un punto de partida para implementar SAFe y describe los elementos más críticos necesarios para obtener la mayoría de los beneficios del marco. Contempla básicamente la inclusión de los niveles de Equipo y Programa como punto de partida.
Es la configuración pensada para empresas que construyen soluciones grandes y complejas, pero que por sus características no requieren de las bondades del nivel Portfolio para gestionarlas.
Es la configuración ideada para corporaciones con múltiples productos con un mínimo número de dependencias, pero que requiere de un nivel de Portfolio para la coordinación, estrategia, inversión y gobernanza.
Representa la configuración más compleja de SAFe dado que es compatible con la construcción de grandes soluciones que normalmente requieren integrar a cientos de personas para su desarrollo y mantenimiento.
Con la inclusión de enfoques como Lean Startup, Lean UX, Lean Portfolio Management (LPM) y Lean Budgets, SAFe 4.5 pretende ayudar a las organizaciones a innovar en la implementación de una estrategia orientada a la obtención resultados.
En base a estos conceptos algunos de los puntos que se pueden mencionar son:
Lean Startup. Busca brindar a las organizaciones un enfoque iterativo para la inversión y construcción incremental del denominado Producto Mínimo Viable (MVP).
Lean UX. Propone un enfoque orientado a la retroalimentación constante del usuario para la optimización de los resultados tras cada ciclo de trabajo.
Lean Portfolio Management (LPM) y Lean Budgets. Proponen un modelo autonómico para la financiación y gobernanza de la inversión sobre los productos.
4. DevOps & Continuous Delivery
Juntos, los conceptos de DevOps & Continuous Delivery proponen un cambio de mentalidad para la inclusión de un conjunto de prácticas técnicas que promueven la comunicación, integración, automatización y estrecha cooperación de los elementos necesarios para planificar, desarrollar, probar, implementar, liberar y mantener una solución.
Como parte de este modelo se promueven las siguientes recomendaciones:
Fomento de una cultura basada en la responsabilidad compartida de los equipos para el desarrollo y despliegue.
Automatización del ciclo de entrega continua.
Flujo Lean orientado a limitar el trabajo a lotes pequeños.
Mediciones a lo largo del flujo de trabajo para poder evaluar el desempeño.
Inclusión de medidas para la rápida recuperación, reversión y una solución.
5. Conclusiones
Como hemos podido apreciar, en esta nueva versión de SAFe se buscó ganar la flexibilidad de acomodarse a casi cualquier tipo de organización con la inclusión de conceptos importantes como Lean-Agile y DevOps para fomentar el desarrollo iterativo e incremental de productos orientados a satisfacer las necesidades del negocio.
En Autentia proporcionamos soporte a la implantación corporativa de metodologías ágiles ayudando a la transformación digital de grandes organizaciones. Te invito a que te informes sobre los servicios profesionales de Autentia y el soporte que podemos proporcionar a tu empresa en la implantación de frameworks de escalado de agile como SAFe y LESS.
Muchos pensarán sobre la utilidad de la realidad virtual, unos para bien y otros para mal, pero hay una cosa muy clara cuando vemos la facilidad con la que nuestro compañero Enrique Rubio nos presenta el framework A-Frame, y es que tenemos que poner a prueba la madurez de su desarrollo. Y no hay nada más divertido y completo que crear un videojuego.
Para nuestro objetivo no nos iremos muy lejos, volveremos al clásico dungeon/mazmorra donde por arte de cursor haremos frente a enemigos estáticos y poco animados. Con esta base y los conocimientos que aplicaremos, veréis que sois capaces de llevarlo más allá de lo que os muestro, implementando vuestras propias ideas y mejoras.
Antes de comenzar, debemos echarle una ojeada al inspector que nos proporcionan los desarrolladores de A-Frame y, aunque en mi opinión prefiero usarlo como una forma rápida de ver cambios y de editar, es cierto que puede seros de mucha utilidad.
AVISO: he experimentado muchos problemas en navegadores Chrome en varios ordenadores, aunque en mi teléfono Android con Chrome sí funciona. Desde problemas con las colisiones hasta no poder cargar la escena. Recomiendo probar varios navegadores con el ejemplo que os dejo en las referencias antes de poneros a probar cosas.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
El inspector de A-Frame, como su nombre indica, nos permite ver y crear gráficamente escenas de A-Frame. Es como un inspector de páginas web pero orientado a A-Frame.
3.1. Instalación local
Para instalarlo en local es tan sencillo como ir a su repositorio en GitHub y clonarlo en donde queramos. Una vez clonado, abrimos la terminal dentro del proyecto y ejecutamos lo siguiente:
npm install
npm start
Una vez que se haya iniciado el inspector, podremos acceder a él mediante la url http://localhost:3333. En caso del ejemplo sería http://localhost:3333/example/.
También podemos añadir nuestros proyectos de A-Frame tal como se hace con el ejemplo, se copia la carpeta del proyecto en la del proyecto del inspector y tan solo habría que ir a la dirección http://localhost:3333/miproyecto/
3.2. ¿Qué nos permite hacer?
Para ver las posibilidades que nos aporta el inspector, usaremos el ejemplo que viene por defecto.
Hay un atajo de teclado para poder cambiar entre el inspector y la escena: Ctrl + Alt + I.
Desde el principio podemos observar la estructura de la escena en la parte izquierda de la ventana. Aquí podemos crear nuevas entidades o seleccionarlas. También tiene unos iconos para poder copiar o eliminar la entidad; estos aparecen si seleccionamos la entidad.
Como ya podéis haber visto, tenemos el editor gráfico donde es representada toda la escena con sus texturas correspondientes. Conviene mencionar que los objetos con animaciones se encuentran inmóviles.
Finalmente, y no lo menos importante, si hacemos click sobre alguna entidad, nos aparecerá a la derecha un menú con todas las propiedades de dicha entidad. También aparecen unas flechas apuntando a las 3 dimensiones, con las que podremos mover el objeto por la escena.
Mediante las propiedades podemos editar completamente el comportamiento y apariencia de las entidades, desde componentes hasta texturas y efectos.
4. ¡Manos a la obra!
Una vez que hemos visto el inspector, ya podemos comenzar a crear nuestro minijuego. Nuestros objetivos serán los siguientes:
Añadiremos un fantasma (enemigo) que está creado con una herramienta externa y lo animaremos un poco.
Trataremos el problema del movimiento por el mapa.
Crearemos una interfaz para poder matar al enemigo que hemos creado y para ver nuestra vida.
Crearemos la interacción entre entidades para poder atacar y ser atacados o incluso morir.
Crearemos las colisiones tanto del fantasma como del resto de elementos
Una vez tenemos claros nuestros objetivos, pasamos a prepararlo todo. Para ello, según hemos visto anteriormente, iniciaremos el inspector de A-Frame y usaremos el ejemplo que nos aporta como base; así podremos hacer nuestros experimentos y probarlos en una escena ya creada y que tenemos la certeza de que funciona. También podemos duplicar la carpeta “examples” que se encuentra en el inspector (que es el proyecto de ejemplo) y le cambiamos el nombre; de esta manera podremos modificar el nuevo proyecto copiado del ejemplo y tener el ejemplo original intacto. Otra opción es crear una nueva carpeta y empezar un proyecto desde cero, pero hay que tener en cuenta que los proyectos tienen que encontrarse dentro de nuestra carpeta del inspector. Accederemos a ellos con la URL correspondiente, como por ejemplo http://localhost:3333/miproyecto.
Puede ocurrir, y en mi caso ocurrió, que el inspector o la aplicación de A-Frame que queramos iniciar con el comando “npm start” no funcione, haciendo que en la consola nos salgan errores. Una solución común es actualizar node.js y npm, ya que quizás no tengas versiones compatibles.
Una vez abierto el inspector en el proyecto que vamos a cambiar o crear, ya podemos empezar a añadir un modelo creado externamente con sus texturas.
4.1. Añadiendo modelos propios con texturas
En mi caso creé el modelo usando MagicaVoxel (es gratuito), pero pueden usarse otros programas como Blender por ejemplo, que es open source y completamente gratis.
Aquí tenemos terminado a nuestro nuevo amiguito:
Algunos quizás ya sepan que es necesario exportar a nuestro fantasma y muchas herramientas nos permiten hacerlo a varios formatos. Nosotros exportaremos nuestro modelo a “.obj”, y en consecuencia se generará un archivo “.mtl” y otro “.png” aunque solo usaremos el “.obj” y el “.png”.
Aprovechando el inspector, añadiremos el modelo y sus texturas mediante este. Comenzaremos abriendo el inspector —en caso de que no lo tengamos ya— pulsando “Ctrl+Alt+I” y creando una nueva entidad (arriba a la izquierda del inspector encontramos un “+” para crearla). A esta nueva entidad le daremos el ID “Ghost01” y añadiremos 2 componentes: Material y OBJ-Model.
Con el componente OBJ-Model podemos añadir un mtl y un obj, así que en una carpeta llamada “resources”, que crearemos dentro de nuestro proyecto en caso de que no exista, meteremos el obj del modelo que hemos creado y lo referenciaremos en el componente de la entidad escribiendo “resources/ghost.obj”. Esto debería hacer visible en el inspector, en la posición donde se encuentre la entidad, a nuestro fantasma, pero sin textura y de un tamaño gigantesco, cosa que solucionamos variando su escala a 0.1. El componente debería quedarnos de la siguiente forma:
Para añadir la textura tenemos el componente Material en el cual buscaremos la propiedad “src”, que si hacemos click en el rectángulo de su derecha nos abrirá la siguiente ventana para subir las texturas:
Finalmente ya tendremos nuestro fantasma con modelo y texturas propiamente aplicadas. Cabe destacar que no hemos añadido el archivo mtl, ya que no lo coge bien, quitando la textura. La aplicación de los distintos archivos dependerá muy probablemente del programa que los ha exportado, ya que es posible que los que uséis Blender sí tengáis que añadir el mtl. Podemos modificar otras propiedades como la posición y la rotación para colocarlo donde queramos.
CIUDADO: antes de continuar hay que tener presente que, al igual que un inspector de HTML, el inspector de A-Frame no toca tu proyecto, es decir, hay que darle al botón copiar de la entidad, ubicado en la esquina superior derecha de las propiedades de la misma, tal como se muestra en la siguiente imagen:
Una vez copiado, pegaremos el código en nuestro index.html, que será el que tenga todo el código HTML. Todas las entidades de A-Frame deben encontrarte dentro de “a-scene”
He aprovechado también para cambiar la escena de tal forma que he añadido muros (entidad con forma de box o caja) con texturas y he cambiado la textura y la forma del suelo para asemejarlo un poco más con los muros. Aunque por el momento podamos atravesar los muros, lo resolveremos más adelante. La luz también la modifiqué pero realmente configurarla a vuestro gusto. El código HTML resultante:
4.2. Una animación sencilla
Ahora tocaremos un poco el tema de las animaciones, pues en A-Frame estas pueden resumirse como un movimiento entre dos puntos, con propiedades como el número de repeticiones, solo de ida, etc.
Ya que tenemos un fantasma vamos a hacer que este suba y baje un poco para que de la sensación de levitación. Esto sería una animación en el sitio, pero también puede usarse para moverlo de un punto A a un punto B de forma progresiva, sin teletransportarlo de repente.
Esta vez vamos a hacerlo a mano, sobre el index.html, añadiendo dentro de nuestra entidad con ID “Ghost01” un tag con el cual especificaremos que la posición B sea un poco más arriba de su posición original. Especificar solo el “to” sin el “from” hace que la posición from o A sea la original, es decir, la posición en la que tenemos a nuestro fantasma.
Con el atributo repeat especificamos que nunca acabe, con direction=”alternate-reverse” especificamos que sea de ida y vuelta (de A a B y de vuelta a A) y el easing nos permite decir cómo va a ser ese movimiento, en nuestro caso, lineal. Con la duración (dur) podemos regular la velocidad con la que recorre ese camino entre ambos puntos, ya que la modifica para poder recorrer esa distancia en el tiempo especificado.
Una vez añadido volvemos al inspector y entramos a la escena pulsando “Ctrl+Alt+I” de nuevo, miramos a nuestro fantasma y lo veremos levitando arriba y abajo sin parar.
Las animaciones pueden activarse y pararse mediante eventos e incluso existe un componente que permite encadenarlas, pudiendo ser usado para movimientos encadenados entre otras cosas.
5. El problema del movimiento en VR
Habiendo visto cómo podemos hacer que una entidad se mueva, viene el siguiente paso: ¿cómo se moverá el usuario? Esto, sin duda alguna y por el momento, es un problema muy serio de la realidad virtual, ya que solo las gafas (que sin contar Cardboard no es que sean baratas actualmente) no nos permiten movernos. Se han realizado algunos avances, como darle funcionalidad a un mando en el HTC Vive para teletransportar al usuario de un lugar a otro o moverlo progresivamente, pero no todos los usuarios tienen por qué usar el HTC Vive.
Pensándolo y buscando posibles soluciones encontré dos opciones: mover al usuario mediante un sistema de esferas que con mirarlas lo muevan y dejar el sistema de movimiento WASD para los que no usan gafas de VR o los que pueden usarlas junto a un teclado.
Los controles WASD ya vienen incluidos en la cámara de ejemplo, que además viene con un cursor y un raycaster para poder interactuar con otras identidades. En el primer caso va a ser algo más complicado, ya que tendremos que posicionar nuestras esferas por toda la escena y dotarlas de dicha funcionalidad.
Primero crearemos, usando JavaScript, un componente de AFRAME desde cero, el cual generará un eventListener activado al hacer click que tiene como función teletransportar al usuario (la cámara con ID “player”) a su posición.
AFRAME.registerComponent('static-movement', {
schema: {default: ''},
init: function () {
var el = this.el;
el.addEventListener('click', function () {
document.querySelector('#player').setAttribute('position', el.getAttribute('position'));
console.log("Click: Player moved");
});
}
});
Mediante este código podemos aprender varias cosas. Todo componente tiene un nombre, en este caso es “static-movement”. El “schema” es una lista con las propiedades del componente que pueden tener especificados valores por defecto. En nuestro caso, “init” es un método que especifica lo que se realiza al crearse la entidad que tiene el componente añadido. Pueden crearse métodos con cualquier nombre, pero hay que tener en cuenta que hay algunos que se ejecutan de forma especial como “init”, “update”, “destroy”, “tick”, etc.
El objeto “el” es la entidad y a esa entidad le añadimos el listener que, como observamos, obtenemos, como en HTML, la cámara con ID “player” y editamos su posición para que esta sea la de la entidad que tiene el componente asociado. También imprimimos en consola un mensaje para saber que funciona y que es esa función la que se ejecuta.
Ahora que tenemos el componente creado lo guardamos como “movement.js” y lo añadimos en nuestro index.html. Para poder asignar un componente a una entidad tan solo hay que añadir un atributo con el mismo nombre que el del componente seguido de un “=” y sus propiedades correspondientes, en caso de no tener, solo con poner su nombre bastaría.
Ya que queremos poder movernos por toda la sala que hemos creado con los muros, creamos un total de cuatro esferas de reducido tamaño:
Ahora ya podemos entrar de nuevo a nuestra escena a probar nuestras nuevas esferas.
Si lo probáis veréis que nos teletransporta, pero… ¡no nos deja en el suelo! De momento lo dejaremos así, ya que añadiremos físicas más adelante que nos devolverán al suelo por nuestro propio peso.
6. Interfaces
Tenemos un fantasma y unas esferas que nos permiten movernos por el mapa, pero ¿cómo lo matamos? Aquí entra en juego el sistema de “combate”, en el que hacemos daño al fantasma para matarlo y el fantasma nos responde quitándonos vida. Empezaremos creando las interfaces tanto del fantasma como de nuestra vida.
Para el fantasma crearemos tres botones (Atacar, Defender y Evadir) y una interfaz para mostrar la vida del fantasma. Para ello crearemos entidades de texto y, ya que no todo lo que ocupe dicho texto es clickable, añadiremos por cada texto un plane que los dote de mayor visibilidad de un área completamente clickable. Haremos también otra entidad que sirva de wrapper para toda la interfaz del fantasma, ya que la posición de todos los componentes pasaría a estar ligada a este wrapper y moviendo el wrapper moveríamos toda la interfaz como conjunto.
Cabe destacar el ID del texto de la vida del fantasma, ya que para poder cambiar su valor para actualizarlo es necesario acceder a este, pero nos vemos obligados a establecer un ID a cada texto de vida de cada enemigo y mediante JavaScript programarlo para cumplir con DRY (Don’t Repeat Yourself), ya que si no habría que programar un componente para cada enemigo, y eso no es fácilmente mantenible.
Solo implementaremos más adelante la lógica del botón “Atacar”, por eso se muestra el atributo “attack-button”.
También añadiremos una interfaz para ver nuestra vida, al igual que hacemos con nuestro fantasma:
Como la interfaz de la vida se encuentra “dentro” de la cámara (el usuario), esto hace que su posición sea relativa a la de la cámara, fijándose así en la pantalla del usuario. Debido a que la vista en modo VR vería nuestra perspectiva con respecto al inspector y al modo fuera de VR, se deberá ajustar posteriormente la posición de todos los elementos.
7. Interacción entre entidades
Tenemos interfaz, pero ahora queda darle vida. Mediante JavaScript crearemos componentes para crear interacción entre las interfaces con las entidades que las contienen.
Para comenzar crearemos el archivo “enemy.js” y lo añadiremos al index de nuestro proyecto. En este nuevo archivo implementaremos la lógica de los enemigos; en nuestro caso solo el fantasma.
Este componente nos da un buen ejemplo de cómo añadir propiedades, que en este caso nos permiten decir cuánta vida tendrá el enemigo y el id de dicho enemigo (si es Ghost01 tomará el valor 1).
La propiedad id nos permite, como puede verse en el método updateHealthText, obtener el texto que muestra la vida de dicho enemigo de forma general, es decir, cualquier enemigo podrá actualizar su vida sin problemas y sin duplicar código, pero claro, a coste de seguir una regla de nomenclatura dándoles ids con el esquema “text-health-{ID}”.
También es destacable la función “tick”, que se ejecuta en cada frame, con lo cual se ejecuta constantemente. Esta función la hemos implementado para que en cuanto su vida llegue a 0 se elimine la entidad, haciéndola desaparecer.
Añadimos este componente a nuestra entidad fantasma, añadiendo enemy=”health: 30; id: 1″ como atributo.
Ahora haremos un componente en el archivo “player.js” y se lo asignaremos a la cámara con id “player” tal como hemos hecho con el fantasma.
Finalmente, dotaremos de funcionalidad al botón atacar de nuestro fantasma en nuestro nuevo archivo “handleEvents.js” y la vida del jugador:
AFRAME.registerComponent('attack-button', {
schema: {
targetEntity: {type: 'string', default: '#Ghost01'}
},
init: function () {
var el = this.el;
var data = this.data;
el.addEventListener('click', function () {
var entity = document.querySelector(data.targetEntity);
entity.components.enemy.reduceHealth(numAleatorio(1,5));
console.log(entity.components.enemy.getHealth());
entity.components.enemy.updateHealthText();
});
function numAleatorio(min, max) {
return Math.round(Math.random() * (max - min) + min);
}
}
});
La propiedad targetEntity sirve para decirle al componente a qué entidad hay que restarle vida y debe atacar al usuario.
Ahora solo nos queda añadir el componente “attack-button” al botón de atacar del fantasma y probarlo.
El resultado lo podemos ver mediante el siguiente gif:
8. Sonidos
Vamos a añadirle algún detalle más, y en este caso trataremos el sonido, que tiene su “truco”.
Leyendo en páginas de internet se ve que hay problemas con el sonido, que no se reproduce o cosas así. Principalmente se refieren a atributos de “sound” como el autoplay. En mi caso surgió un ¿bug? bastante extraño que no he sido capaz de solucionar (además de que solo pude reproducir el audio programáticamente), quizás por cómo trabaja A-Frame con el audio o simplemente sea mi navegador, así que lo veremos más adelante.
Añadiremos una música de fondo para dar algo de ambiente y un efectillo para cuando nos demos de tortas con un enemigo. La música que empleo de fondo la podéis encontrar aquí y el efecto de golpe lo podéis encontrar aquí. Empezaremos por la música de fondo.
Primero hay que cargar el asset de audio para que precargue correctamente y no surjan problemas de sincronización.
A continuación, ya que la música de fondo la escucha el jugador y no puede haber efecto Doppler, lo colocaremos como atributo de la cámara para que se fije al usuario.
Solo nos queda empezar a reproducir la música en cuanto aparece el usuario, con lo cual la función “init” de nuestro componente “player” (player.js) quedaría de la siguiente forma:
init: function () {
var el = this.el;
el.components.sound.playSound();
}
Y os preguntaréis, ¿por qué dos veces el mismo audio? Por algún motivo el audio no se reproduce si no tenemos la entidad de debajo del comentario “BG Music”, solo la del atributo de la cámara se escuchará, sin importar cuál de las dos reproduzcamos. La verdad es que es muy extraño cómo pueden afectarse entre ambas entidades, incluso sin tener relación padre-hijo.
Es un apaño “feo” pero por lo menos sabemos cómo reproducir audio en bucle y desde el comienzo. Ahora viene el efecto de sonido que se reproducirá cuando ataquemos al fantasma. Para ello realizaremos lo mismo que para la música de fondo, pero esta vez no pondremos ni loop ni autoplay, ya que lo controlaremos mediante JavaScript. Cuando ataquemos al fantasma reproduciremos el sonido de la siguiente forma en la función “reduceHealth” de “enemy.js”:
Sí, hay que volver a duplicar la entidad del sonido, cosa que me parece muy molesta ya que es innecesario. Supongo que se tratará de algún bug que corregirán en versiones siguientes.
Probemos nuestros sonidos y ¡disfrutemos de matar fantasmas a puñetazos!
9. Colisiones
Hemos avanzado mucho, hasta el punto de tener algo jugable, en donde podemos matar a enemigos repartidos por un mapa sin necesidad de tener piernas. Todo esto está muy bien, pero hay algo que aún falta, ¡atravesamos paredes!
Aunque para los que no usan teclado realmente no debería implicar nada (¿cómo vas a atravesar una pared si no puedes moverte?), esto es un problema en caso de alguien que pueda moverse. Lo resolveremos creando colisiones y para ello emplearemos los componentes Aframe-extras y aframe-physics-system o sistema de físicas. El sistema de físicas, basado en Canon.js, no sólo dotará de colisiones sino también de gravedad lo que nos permitirá volver al suelo cada vez que nos teletransportemos a una esfera.
Para poder usar este componente simplemente descargamos la versión minificada y la añadimos como cualquier archivo JavaScript. El sistema de físicas contiene varios componentes que añadiremos para poder dotar al usuario y al resto de entidades de colisiones.
Primero añadiremos el componente kinematic-body (de aframe-extras) a nuestra cámara con un radio de 0.5 (además de una altura de 1.6) ya que por defecto nos vuelve muy anchos.
El componente kinematic-body habla por sí mismo, pero en resumidas cuentas convierte nuestra cámara en un cuerpo sólido con físicas propias (se ve afectado por la gravedad).
A continuación, para el resto de entidades añadiremos el componente static-body, el cual por defecto elige automáticamente la mejor forma para su “caja” de colisiones. Estas entidades serán el suelo, las paredes y el fantasma.
Si habéis probado la escena solo habiendo añadido el componente “kinematic-body”, veréis como caéis al infinito, ya que ni siquiera el suelo es considerado sólido, y al haber gravedad la cámara ya no flota “por arte de magia”. Para el suelo y las paredes simplemente hay que añadir el atributo “static-body” (de aframe-physics-system), no es necesario especificar nada más. Si queremos ver que funciona y cómo son las cajas de colisiones, tendremos que añadir el atributo physics=”debug: true”. Si volvemos a entrar a la escena, podremos ver las cajas de colisiones con líneas rojas.
Ahora ya sí que estamos encerrados entre los muros que hemos creado, con un fantasma delante que podemos matar, ¿o no? Vamos a hacer que el fantasma bloquee el camino y para ello hay que dotarlo de su propia caja de colisiones, pero este es un caso algo especial, ya que es un modelo que hemos cargado y nos un problema.
Si añadimos “static-body” a nuestro simpático fantasma veremos cómo de forma automática elige “box” como forma y el cálculo de esta caja es erróneo, ya que solo rodea a la interfaz. Para solucionar este problema de cálculo añadiremos una caja que colocaremos detrás del fantasma, pero que este la incluya, para que la caja de colisiones se calcule para incluir tanto a toda su interfaz como a dicha caja, creando una caja de colisiones que engloba todo, incluido al fantasma. Si hacemos esta caja invisible, será como si nada hubiera pasado, tendremos una caja de colisiones correcta.
Nuestra entidad fantasma quedaría de la siguiente forma:
Ahora que ya hemos creado las colisiones de todo lo necesario, solo queda probarlas y ajustar valores para que quede todo a nuestro gusto.
A partir de este punto podemos hacer crecer nuestra pequeña prueba y añadir más muros, más fantasmas, más tipos de enemigos o todo lo que queramos hacer.
10. Conclusiones
Como hemos estado viendo a lo largo de este tutorial, las posibilidades que nos ofrece A-Frame son, como mínimo, decentes. Es verdad que se encuentra lejos de estar completo, aunque no es algo que no se pueda solucionar.
Uno de los principales inconvenientes, que espero que amplíen, son las animaciones. Las animaciones son algo básicas, fáciles de usar para algo simple pero no para tareas más sofisticadas. Un ejemplo lo encontraríamos a la hora de hacer que nuestro fantasma se moviera, si queremos que levite y se mueva a la vez nos resulta imposible ya que sólo se ejecuta una animación simultáneamente, habría que moverlo según un vector que sea la combinación de ambos movimientos, cosa que complica la tarea. Lo bueno de su simplicidad es que se pueden crear animaciones de forma dinámica mediante el método “setAttribute”.
Otra mejora sería el crear una entidad interfaz o algo similar que nos permita componer interfaces de forma más rápida, sin tanto ajuste.
Aunque lo anterior se base en mi experiencia, no es absoluto. A-Frame es flexible y nos permite realizar cualquier idea rápidamente, aunque tenga sus posibles inconvenientes. Espero que evolucione más y nos muestre buenos resultados.
En el tutorial “express” de hoy voy a mostrar cómo cifrar passwords con Maven de una manera fácil y sencilla para que nunca más tengáis una contraseña en claro en vuestro fichero de configuración de usuario de Maven.
Una de las cosas que llama la atención a la hora de usar Maven para desplegar software en repositorios remotos e interactuar directamente con sistemas de control de código fuente es que iremos recopilando una serie de contraseñas cada vez más amplia en la configuración de Maven. Sin un mecanismo para cifrar estas contraseñas, el ~/.m2/settings.xml se convertirá rápidamente en un riesgo de seguridad, ya que contendrá contraseñas de texto sin formato necesarias para acceder a los distintos repositorios de código y comunicarnos con el resto de sistemas configurados.
A partir de la versión 2.1, Maven introduce un mecanismo que facilita el cifrado de contraseñas en la configuración Maven de un usuario (~/.m2/settings.xml). Para ello, primero debemos crear una contraseña maestra y almacenarla en un archivo security-settings.xml en ~/.m2/settings-security.xml. A continuación, podremos utilizar esta contraseña maestra para cifrar contraseñas almacenadas en la configuración de Maven del usuario (~/.m2/settings.xml).
Además de resolver un claro riesgo de seguridad, este mecanismo de cifrado también facilita la compartición de estos archivos de configuración entre los distintos integrantes de un equipo de desarrollo, algo muy habitual.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
5. Cómo mantener la contraseña maestra en una unidad extraíble
Si queremos disponer de todavía más seguridad, podemos crear la contraseña maestra del mismo modo que se describe en los puntos anteriores pero la vamos a almacenar en una unidad extraíble montada como /Volumes/mySecureUsb y almacenamos el siguiente contenido:
Si ya conocíais este mecanismo de Maven, lo seguiréis usando y, para todos aquellos que no, a partir de ahora no hay ninguna excusa para seguir utilizando nuestras contraseñas de servidor en claro y sin ningún formato en la configuración de Maven para nuestros proyectos.
Espero que os sirva de utilidad. Saludos cordiales.
Angular es un framework en JavaScript para generar aplicaciones webs SPA.
El framework usa la arquitectura MVVM (model, view, view-model):
El modelo representa los datos como suele ser habitual.
La vista representa la interfaz gráfica. Lo habitual es definir las vistas con archivos XML.
La Vista-Modelo contiene la lógica de negocio y expone los datos a la vista.
La ventaja de esta arquitectura es que permite aprovechar el concepto two-way data binding que permite que los cambios en el modelo sean reflejados de forma inmediata en la vista.
Firebase es una plataforma móvil que permite desarrollar el backed de tus aplicaciones. Ofrece aplicación multiplataforma con API integradas a SDK individuales para Android, iOS y JavaScript. Pasa a una plataforma diferente sin modificar tu infraestructura.
Este apartado se divide en varios puntos: generar proyecto, instalar Firebase y configurar Firebase.
Entre los servicios de backend, la autenticación de usuarios es una parte esencial, y Firebase la gestiona de una manera muy simple.
Todos los datos de Firebase Realtime Database se almacenan como objetos JSON. A diferencia de bases de datos SQL, no existen tablas ni registros. Cuando se agregan datos al árbol JSON, estos se convierten en un nodo en la estructura JSON existente.
3.3.1. Generar proyecto en Firebase
Nos dirigimos a la página de Firebase, accedemos a la consola y generamos un proyecto nuevo.
3.3.2. Configurar acceso
En el menú de Firebase, tenemos la opción de autentificación:
Para que los usuarios puedan acceder a nuestra aplicación, podemos configurar la forma de acceso de diferentes formas: mail, cuenta de Google, Twitter…
Si se decide permitir acceso mediante correo electrónico, tenemos incluidas unas plantillas (¡en varios idiomas!), de esta forma enviará un mail al usuario cuando éste realice el alta (verificación), cuando quiera cambiar la contraseña (restablecimiento) o cuando quiera cambiar la dirección de email (cambio de dirección). La opción de verificación por SMS,permite que los usuarios accedan con una contraseña de un solo uso que se envía al móvil mediante SMS.
En nuestro caso, hemos elegido que permita acceso, únicamente, mediante cuenta de Google. Luego veremos cómo desarrollar la pantalla de login y cómo, con una llamada a Firebase, nos autenticamos rápidamente.
3.3.3. Database
En la sección database podremos dar de alta nuestro modelo de datos:
Pulsando el boton ‘…’ situado en la parte derecha de la pantalla, nos sale el siguiente menú y, haciendo clic en importar JSON, se nos abrirá una pantalla donde podremos subir nuestro modelo.
Podemos subir un modelo con datos, pero para ello debemos tener información (registros). Como carecemos de registros, iremos subiendo datos y generando la BD según vayamos codificando los metodos de alta de nuestras clases.
Una vez configurado el acceso, ya podemos instalar en local Firebase y configurarlo en nuestra app.
3.3.4. Instalar Firebase en el proyecto
Para instalar Firebase ejecutamos en consola:
$ npm install -g angularfire2
Ahora, en nuestro module.app, importamos los módulos de Firebase:
3.3.5. Configurar Firebase
Seguimos en nuestro module.app y añadimos la constante “firebaseConfig” con los datos de tu proyecto en Firebase:
Añadimos los módulos en el “imports” de “NgModule”
4. Nuestra aplicación
Vamos a crear una aplicación donde podamos autenticarnos en Firebase mediante la cuenta de Google. También realizaremos un CRUD contra la base de datos en la nube. Todo esto lo dejaremos bonito con material. Cool, ¿no? Vamos a ello.
4.1. Autentificación
Hasta aquí se podría decir que tenemos todo configurado. Lo primero que tenemos que hacer es ver si funciona todo el tinglado, para ello vamos a probar la conexión mediante la autentificación de nuestro usuario mediante el login.
Nos creamos un servicio, fireService, donde incluiremos todos los metodos que conecten con Firebase:
Con la línea del método “loginWithGoogle”, llamaremos al proceso de autentificación de Firebase mediante el proceso elegido (cuenta de Google). Más abajo está el “logout” que nos servirá para salirnos de la sesión.
Para poder llamar a estos métodos necesitamos generar una vista con el cuadro de login, pero para llevar al cuadro de login, primero le decimos a nuestra aplicación que cuando entre se dirija a ella. En app.component.ts nos suscribimos al estado de autentificación y, si no está autenticado, nos envía a la pantalla de login y, si lo está, nos envía a la pantalla home:
Creamos login-page.component.html y añadimos:
Y su fichero typescript correspondiente, login-page.component.ts:
Quedando nuestra pantalla de login de la siguiente forma:
Ahora, cuando pulsemos el botón de login, nos solicitará la cuenta de Google con la que deseamos entrar en nuestra aplicación, junto a su contraseña.
Después de terminar con el proceso de login, si ejecutamos la aplicación, veremos como en firebase se da de alta nuestro usuario.
Ahora ya tenemos el usuario de firebase, con la interfaz UserInfo. Aquí podemos ver el “uid” que será nuestra clave única y que usaremos para dar de alta cualquier dato en cualquier “tabla”.
4.2. Creación de “tablas”
Para crear tablas con datos, únicamente debemos dar de alta un registro, pasando el nombre de nuestra tabla:
En nuestro servicio, añadimos un método “setUserProfile” que nos creara nuestro perfil en la “tabla” profiles:
Nuestra clase profile:
En Firebase nos dará de alta la “tabla” “profiles”, con nuestro registro. Si nos fijamos, la raíz del registro es nuestro uid:
4.3. Recuperar datos
Para obtener datos de nuestra BD en la nube, realizamos un método en nuestro servicio que nos permita realizar esta tarea, este método llevara la siguiente linea:
De esta forma tan sencilla recuperamos un registro de la “tabla” que deseemos, pero, y si necesitamos recuperar una lista, ¿cómo lo haríamos?
firebase.database().ref('/foods').once('value');
Easy piece!
¡Ya sabemos enviar y recibir datos!
Como veo que esto se alarga, lo dejamos aquí y nos vemos en la segunda parte de este gran tutorial, donde abordaremos el resto de la aplicación.
Saludos
5. Conclusiones
Ya sabemos que todo lo que Google toca se convierte en oro, así que Firebase tiene muchas papeletas de convertirse en el próximo estándar de base de datos. A mí me ha encantado.
En este tutorial vamos a hablar de cómo gestionar el estado de nuestras aplicaciones Angular sin tener que recurrir a la complejidad de la librería ngrx.
Este tutorial está escrito usando el siguiente entorno:
Hardware: portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
Sistema operativo: macOS Sierra
@angular/cli 1.2.6
@angular 4.3.3
2. Introducción
No es que yo tenga nada en contra del uso de la librería ngrx para la gestión del estado en aplicaciones con Angular, de hecho se está convirtiendo en un estándar de facto. Lo que pasa es que por más blogs que leo y más charlas hasta del mismísimo Victor Savkin, menos intuitivo me parece su uso y más separado de la implementación “normal” de una aplicación de Angular lo veo. Para mí es como un cambio completo de paradigma que te obliga a desarrollar como dicta la librería y lo veo poco intuitivo y mantenible (opinión personal).
Lo que es innegable son las ventajas que aporta tener separado el estado de la aplicación como una única fuente de verdad absoluta a la que todos consultan y donde todos modifican. Es por ello que leyendo un poco más sobre el tema me encontré con el Angular Model Pattern que consta de un único fichero de 1.07 Kb que haciendo uso de la programación reactiva y más concretamente del BehaviorSubject permite mantener el estado de la aplicación de una forma que a mi parecer es más intuitiva y que se adapta mejor a la forma de implementar las aplicaciones con Angular.
3. Vamos al lío
El propio autor de la librería nos ofrece dos opciones para su uso: bien instalando la dependencia ngx-model
$> npm install --save ngx-model
o simplemente creando nosotros el siguiente servicio:
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
export class Model {
private _data: BehaviorSubject;
data$: Observable;
constructor(initialData: any, immutable: boolean, clone?: (data: T) => T) {
this._data = new BehaviorSubject(initialData);
this.data$ = this._data.asObservable()
.map(data => immutable
? clone ? clone(data) : JSON.parse(JSON.stringify(data))
: data);
}
get(): T {
return this._data.getValue();
}
set(data: T) {
this._data.next(data);
}
}
export class ModelFactory {
create(initialData: T): Model { return new Model(initialData, true); }
createMutable(initialData: T): Model {
return new Model(initialData, false);
}
createWithCustomClone(initialData: T, clone) {
return new Model(initialData, true, clone);
}
}
export function useModelFactory() {
return new ModelFactory();
}
export const MODEL_PROVIDER = {
provide: ModelFactory, useFactory: useModelFactory
};
Lo que hace es, a través de genéricos, generalizar la creación de BehaviorSubject en base al tipo de datos que necesitemos, proporcionando métodos para la consulta y modificación de los datos del modelo y mejoras para hacer el clonado de los objetos y mantener la inmutabilidad.
De esta forma podemos instanciar en cualquier módulo el provider:
@NgModule({
providers: [MODEL_PROVIDER]
})
o si hacemos uso de la librería:
import { NgxModelModule } from 'ngx-model';
@NgModule({
imports: [NgxModelModule]
})
Y ya podemos hacer uso de esta clase de instanciación de modelo. Por ejemplo, si queremos almacenar el estado de un usuario, podríamos crear la interfaz con sus datos:
Y crear un servicio de modelo donde utilizar esta interfaz para instanciar el modelo de User e implementar todos los métodos que cambian y consultan los datos del modelo.
import { Injectable } from '@angular/core';
import { Model, ModelFactory } from 'ngx-model';
import { User } from './list-users/user';
@Injectable()
export class CurrentUserModelService {
private model: Model;
user$: Observable;
constructor(private modelFactory: ModelFactory) {
this.model = this.modelFactory.create({});
this.user$ = this.model.data$;
}
setUser(newUser: User) {
this.model.set(newUser);
}
getUser(): User {
return this.model.get();
}
}
Ahora en los componentes o servicios que se quiera consultar/modificar el estado del usuario solo se tiene que inyectar el servicio CurrentUserModelService y hacer uso de los métodos creados. A continuación vemos un ejemplo de componente que consulta los datos para mostrarlos por pantalla:
Nota: fíjate que estamos haciendo uso del observable para suscribirnos dentro del método ngOnInit a los cambios que se produzcan en el objeto, de forma que si otro componente hiciera uso del método setUser para modificar la información, inmediatamente este componente mostraría esa información de forma reactiva, es decir, sin tener que refrescar la aplicación. Y, si hubiera más de un componente suscrito, toda la información cambiaría al mismo tiempo.
4. Conclusiones
Como ves la programación reactiva vuelve en nuestra ayuda para facilitarnos de una forma muy cómoda, limpia y elegante un elemento nada trivial en nuestras aplicaciones como es la gestión del estado y la actualización de todos los componentes una vez se cambia el estado.
ngrx te proporciona esto mismo y muchas más cosas pero de una forma mucho más encorsetada y a mi modo de ver más confusa y menos mantenible.
Por defecto, en Kibana los tiempos de carga en la pestaña del Discover son muy altos cuando buscas información general sin filtros. Vamos a ver cómo podemos modificar la búsqueda que realizamos a Elasticsearch desde la configuración del Kibana para disminuir el tiempo de búsqueda.
Damos por hecho que ya tenemos instalado Kibana y Elasticsearch. Este tutorial te ayudará a conocer el funcionamiento de las herramientas.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
Con las herramientas del desarrollador del navegador, podemos ver que la petición que más tarda durante la carga del Discover es _msearch.
Si vemos la petición que se realiza cuando se hace una llamada a _msearch (herramientas del desarrollador –> Network –> click derecho –> copy as cURL), vemos la siguiente estructura:
En esta petición hemos omitido las cabeceras propias del navegador, pero hemos dejado la del content-type, ya que este es necesario para poder hacer una petición correcta al endpoint _msearch.
Este endpoint recibe varios JSON en varias líneas, donde las impares son cabeceras y las pares son los cuerpos de la misma, de forma que puede hacer búsquedas múltiples iterando entre cabeceras y cuerpos. En nuestra petición solo tenemos dos líneas (marcadas con \n), aunque la última la hemos separado para mayor claridad.
Toda esta información saldrá correctamente formateada cuando copies la información del cURL.
La cabecera contiene la información sobre el índice en el que vamos a realizar la búsqueda y el cuerpo tiene la búsqueda propiamente dicha. Vamos a analizar con detalle esta búsqueda para ver qué información podemos eliminar.
Vemos que por defecto esta búsqueda nos trae 500 documentos. El Discover no suele ser una herramienta que se visite de forma plana, sino que sueles acceder a ella tras filtrar la información que quieres buscar, ya sea por dashboards o filtros de búsqueda. Podríamos reducir su valor para que la consulta tardara menos en procesarse.
El factor más relevante de la consulta a eliminar es el apartado highlight que se hace en todos los campos. Esta propiedad incluye en la respuesta de cada campo un campo ‘highlight’ que incluye los campos sobresaltados. Esto ralentiza muchísimo la carga del Discover y no es necesario para nada, pero viene habilitado por defecto.
4. Cambio de las propiedades en Kibana
Para realizar el cambio, vamos a acceder a Management –> Advanced Settings. Aquí salen un montón de propiedades que podremos actualizar, de las cuales vamos a cambiar las siguientes:
discover:sampleSize. Vamos a reducir este valor de 500 a 50, de forma que obtengamos menos datos y la búsqueda sea más rápida. Esto disminuye los tiempos de carga bastante, pero es posible que en algún caso queramos tenerlo con un valor superior al de por defecto, por lo que es un cambio más opcional.
doc_table:highlight. Vamos a poner el check a False. Si leemos la descripción de esta propiedad, vemos que pone que puede hacer las peticiones lentas, y realmente lo hacen. Tras cambiar esta propiedad, las búsquedas que antes me tardaban 5 minutos, ahora me tardan 1 segundo. Maravilloso.
5. Conclusiones
Mirar cómo funcionan las herramientas por debajo puede darnos conocimiento de comportamientos innecesarios o inadecuados. Como recomendación general, cuando veas que hay algo que no funciona como debería, saber qué es lo que hace realmente nos ayuda a solventar el problema. Y, si la solución es muy fácil, mejor.
Firebase Cloud Messaging es el servicio de mensajería en la nube de Firebase, la plataforma de servicios
de Google para el desarrollo de aplicaciones móviles que provee de una serie de servicios entre los que se
encuentran autenticación, mensajería, un API para guardar y sincronizar datos en la nube en tiempo real… todo
lo que a priori necesitaríamos para el desarrollo de nuestras aplicaciones; es lo que se conoce como una plataforma
“SaaS” Software as a Service que proporciona esos servicios esenciales para arrancar cualquier proyecto.
Firebase es la evolución de una plataforma que ha ido mejorando poco a poco y en concreto Firebase Cloud Messaging
es una evolución de Google Cloud Messaging.
En este tutorial vamos a ver cómo configurar y hacer uso del servicio de mensajería de Firebase
tanto desde el cliente como desde el servidor, porque aunque Firebase provea de todos estos servicios,
la lógica de negocio seguirá formando parte de una aplicación de backend.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
Asumiendo que tenemos una cuenta de Google, solo debemos acceder a la web de Firebase
y crear un nuevo proyecto.
No tenemos más que darle un nombre para crearlo.
Y desde la consola podremos acceder a todos estos servicios:
Pero antes de nada debemos configurar una aplicación, vamos a registrar un cliente de todos esos servicios,
en realidad, para el objetivo de este tutorial el tipo de cliente nos da un poco igual, esto es,
cada uno tiene su propio SDK para hacer uso de los servicios y usaremos el mismo con el lenguaje del propio
cliente.
Nosotros, para hacer una prueba rápida, vamos a crear un cliente web que consumirá los mensajes desde
javascript.
Tras pulsar sobre “Añade Firebase a tu aplicación web” se mostrará un código como el siguiente que permitirá
copiarlo para añadirlo a nuestro cliente.
Dentro de las propia documentación de Firebase podemos encontrar un ejemplo de integración desde javascript
https://github.com/firebase/quickstart-js/tree/master/messaging
que es el que hemos usado para este tutorial. Sobre el código de dicho ejemplo solo tenemos que pegar el código que se ha generado en la pantalla
anterior en el espacio reservado para ello.
Nosotros hemos incluido las siguientes importaciones y configuración para realizar nuestras pruebas en el index.html
del proyecto:
<!-- Firebase -->
<!-- ********************************************************
* TODO(DEVELOPER): Update Firebase initialization code:
1. Go to the Firebase console: https://console.firebase.google.com/
2. Choose a Firebase project you've created
3. Click "Add Firebase to your web app"
4. Replace the following initialization code with the code from the Firebase console:
-->
<!-- START INITIALIZATION CODE -->
<!-- PASTE FIREBASE INITIALIZATION CODE HERE -->
<script src="https://www.gstatic.com/firebasejs/3.6.6/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.6.6/firebase-messaging.js"></script>
<script>
var config = {
apiKey: "AIza ..... Cs",
authDomain: "tnt-poc.firebaseapp.com",
databaseURL: "https://tnt-poc.firebaseio.com",
storageBucket: "tnt-poc.appspot.com",
messagingSenderId: "10 ... 78"
};
firebase.initializeApp(config);
</script>
<!-- END INITIALIZATION CODE -->
<!-- ******************************************************** -->
Además de insertar el código anterior en el index.html debemos asignar el mismo messagingSenderId en la inicialización
del worker, dentro del fichero firebase-messaging-sw.js.
La configuración de Firebase necesita cargar ese fichero y dicha configuración será usada cuando la página pierda el
foco, para recibir las notificaciones en segundo plano.
Una vez hecho esto podemos ejecutar la aplicación bajo cualquier servidor web y comprobar que podemos acceder a la
página de index.html:
Pulsando sobre el botón “REQUEST PERMISSION” se ejecutará el código javascript que solicitará del navegador permisos
para recibir notificaciones.
Si permitimos el uso de notificaciones la aplicación solicitará un token que se mostrará en pantalla
y en los logs podemos ver algo como lo siguiente.
4. Nuestros primeros envíos de mensajes a dispositivos.
Lo primero que necesitamos es la clave del servidor para hacer uso del servicio de mensajería.
La clave del servidor la podemos encontrar en la configuración de Firebase dentro de la opción “Mensajería en la nube”
Adicionalmente, para los objetivos de esta prueba, necesitamos el token del dispositivo al que queremos enviar el mensaje.
El token que identifica al cliente lo hemos podido ver en la propia página tras permitir el uso de menjsaría por parte del navegador.
4.1. Desde cualquier terminal
Vamos a hacer una primera prueba de envío y recepción de mensajes a nuestra aplicación web, para ello,
podemos realizar una prueba rápida a través del servicio REST de mensajería del propio Firebase.
curl --header "Authorization: key=AIz ... qJ8" --header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send
-d "{\"to\":\"d2n8awzwFNs:APA91bEnyeP6hRCF15caUWOKboV7oRh7ClVUVKapsjtvBAL37tX5-LgjZyAsSJz5TBXnI_AUHbMp2bhcLpS13OF-ga0252L1N8-G056sblJQ0g3tFER9Dj0Dy2WU2p9Hz0-fHNNNZdRN\",
\"notification\":{\"body\":\"Ahi va mi primer mensase...\"},\"priority\":10}"
Una vez enviado el comando de CURL recibiremos una notificación del sistema como la siguiente.
4.2. Desde la consola.
Lo mismo podríamos hacer desde la consola pulsando sobre nuevo mensaje
Asignando un texto, un id de dispositivo y podemos enviar el mensaje ahora o encolarlo para enviarlo en una fecha determinada
Podemos acceder al listado de mensajes enviados o pendientes de enviar también desde la consola.
También es interesante la opción de enviar mensajes a una aplicación entendiendo como tal un proyecto IOS o Android registrado,
lo que significa enviar mensajes a todos los distitivos de este tipo.
5. Gratuito.
Si, a día de hoy, es gratuito; dentro de los precios de uso de la plataforma la mensajería en la nube no tiene costes.
Sin necesidad de hacer uso de más servicios de Firebase podemos tener cubiertas nuestras necesidades
de mensajería en la nube de una manera muy sencilla desde el punto de vista de desarrollo y arquitectura.
En este tutorial vamos a ver cómo podemos manipular el bytecode de una aplicación Java con el soporte de la librería javassist, también dentro del ciclo de vida de una aplicación Spring Boot.
Javassist es una librería que facilita la modificación del bytecode de una aplicación java. Para ello hace uso de reflexión,
permitiendo modificar la implementación de una clase en tiempo de ejecución.
A diferencia de otras librerías del mismo estilo, javassist proporciona dos niveles de api:
a nivel de código fuente: que permite editar una clase sin tener conocimientos específicos del bytecode de java, solo
necesitas conocimientos del API java, pudiendo insertar bytecode como código fuente normal puesto que javassist lo compila
al vuelo,
y a nivel de bytecode: que permite editar la clase ya compilada.
Frameworks como Spring e Hibernate han usado librerías como javassist o cglib para generar proxies dinámicos desde sus
inicios y desde hace tiempo la propia JDK incluye soporte para la generación de proxies dinámicos. Este tipo de librerías son
las que permiten que tanto estos frameworks, como las arquitecturas que se basan en los mismos, puedan hacer su parte de magia,
facilitando la vida a los programadores:
abriendo una transacción con base de datos cuando un método está anotado con @Transactional o
accediendo a base de datos con la simple invocación a un método getUsers de una entidad, si está marcado como lazy.
En este tutorial vamos a ver un ejemplo muy sencillo de su uso que nos va a permitir modificar en tiempo de ejecución el
comportamiento de un método de una clase. Después veremos también cómo hacerlo dentro del ciclo de vida de una aplicación
Spring Boot.
Estando bajo el paraguas de un framework que soporte Spring o CDI siempre tendremos otras alternativas a nivel de AOP
que nos permitirán no tener que llegar a tan bajo nivel. E incluso sin dicho soporte, siempre podríamos hacer uso de AspectJ,
modificando el código de nuestra clase para manipularla en tiempo de compilación; pero ¿y si lo que queremos manipular no está bajo nuestro contexto o
no lo hemos compilado nosotros?…
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
3. Manipulando el código de un método en tiempo de carga.
Vamos a ver un ejemplo muy sencillo, un método que recibe una cadena y devuelve un resultado concatenando el parámetro de entrada:
package com.sanchezjm.tuto.javassist;
public class HelloWorld {
public String sayHi(String name){
return "Hi " + name+ "!";
}
}
A continuación vamos a hacer un test, también muy sencillo:
package com.sanchezjm.tuto.javassist;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.junit.Test;
public class HelloWorldTest {
@Test
public void mustSayHi(){
HelloWorld helloWorld = new HelloWorld();
String text = helloWorld.sayHi("Tip");
assertEquals("Hi Tip!", text);
}
@Test(expected=IllegalArgumentException.class)
public void mustThrowsIllegalArgumentExceptionIfTheName(){
HelloWorld helloWorld = new HelloWorld();
helloWorld.sayHi("Coll");
fail("must throw IllegalArgumentException");
}
}
En este punto el primer método del test pasa pero el segundo no,
el método no devuelve nunca una excepción, siempre saluda del mismo modo al argumento de entrada.
A continuación vamos a manipular el bycode del método para lanzar una excepción concreta
en función del parámetro de entrada, en un método anotado con beforeClass
package com.sanchezjm.tuto.javassist;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import org.junit.BeforeClass;
import org.junit.Test;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
public class HelloWorldTest {
@BeforeClass
public static void setUp() throws NotFoundException, CannotCompileException, IOException{
final ClassPool pool = ClassPool.getDefault();
final CtClass cc = pool.get("com.sanchezjm.tuto.javassist.HelloWorld");
final CtMethod m = cc.getDeclaredMethod("sayHi");
m.insertBefore("System.out.println(\" param: \" + $1); if (\"Coll\".equals($1)){ throw new IllegalArgumentException(); }");
cc.toClass();
}
@Test
public void mustSayHi(){
HelloWorld helloWorld = new HelloWorld();
String text = helloWorld.sayHi("Tip");
assertEquals("Hi Jon!", text);
}
@Test(expected=IllegalArgumentException.class)
public void mustThrowsIllegalArgumentExceptionIfTheName(){
HelloWorld helloWorld = new HelloWorld();
helloWorld.sayHi("Coll");
fail("must throw IllegalArgumentException");
}
}
Además de lanzar la excepción hemos lanzado a consola el parámetro de entrada; ahora los dos tests pasan.
Para manipular el bycode debemos recuperar la clase del pool del classloader en una instancia de una clase CtClass
(compile-time class), acceder al método en cuestión, manipular el código e invocar al método toClass para convertir
la instancia de la clase CtClass a una clase compilada de nuevo.
$1 es un identificador para hacer referencia al primer argumento de un método.
El API es mucho más extesa que este simple ejemplo que hemos hecho, podemos modificar la interfaz de una clase, añadir un constructor, añadir un método, un atributo,…
4. Manipulando el código dentro de una aplicación Spring Boot.
Si en el ejemplo anterior alguien hubiese hecho cualquier tipo de referencia a la clase HelloWorld con anterioridad
a los métodos de tests, el compilador de javassist indicaría que la clase ya ha sido cargada y el ciclo de vida de la
manipulación del código sería más complejo, puesto que una vez cargada la clase queda marcada como “congelada”.
Si la clase que necesitamos manipular es cargada dentro de ciclo de vida del arranque de una aplicación Spring Boot
a continuación mostramos un ejemplo basado en la configuración de un listener de arranque.
package com.sanchezjm.tuto.javassist.listener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
public class JavassistRunListener implements SpringApplicationRunListener {
private static boolean isExecuted; // because can be executed twice, in different instances, is static
private final SpringApplication application;
public JavassistRunListener(SpringApplication application, String[] args) {
this.application = application;
}
public void markAsIllegalArgumentException() {
try {
final ClassPool pool = ClassPool.getDefault();
pool.appendClassPath(new LoaderClassPath(application.getClassLoader()));
final CtClass cc = pool.get("com.sanchezjm.tuto.javassist.HelloWorld");
final CtMethod m = cc.getDeclaredMethod("sayHi");
cc.defrost();
m.insertBefore("if (\"Coll\".equals($1)){ throw new IllegalArgumentException(); }");
cc.toClass();
cc.detach();
} catch (NotFoundException | CannotCompileException e) {
throw new IllegalStateException(e);
}
}
public void starting() {
if (isExecuted){
return;
}
markAsIllegalArgumentException();
isExecuted = true;
}
public void contextLoaded(ConfigurableApplicationContext arg0) {
// do nothing...
}
public void contextPrepared(ConfigurableApplicationContext arg0) {
// do nothing...
}
public void environmentPrepared(ConfigurableEnvironment arg0) {
// do nothing...
}
public void finished(ConfigurableApplicationContext arg0, Throwable arg1) {
// do nothing...
}
}
La clave aquí está en la recuperación del classloader de spring boot y su asignación al class path de javassist.
Sin esa asignación, fuera del contexto de tests, ejecutándose como una aplicación Spring Boot,
no encontraría la clase a manipular.
El listener hay que declararlo en un fichero spring.factories dentro de src/main/resources/META-INF/