La experiencia de usuario

Me  gustaría aprovechar este espacio para dar mi opinión sobre la experiencia de usuario (UX)  y aportar algunos elementos adquiridos a lo largo de mi trayectoria laboral, así como en lecturas y cursos especializados.

 ¿UX = Usabilidad?

Aclaremos un punto donde suele haber una confusión generalizada. La usabilidad es un atributo más de la experiencia de usuario y es la percepción de cuán eficiente, fácil e intuitivo es el proceso de llevar a cabo una tarea en un contexto determinado.

Pero a menudo en muchos proyectos, por desconocimiento, falta de tiempo o de presupuesto, no avanzamos hasta alcanzar que nuestro producto tenga una experiencia deseable para el usuario y lo adopte como suyo. Nos quedamos en el umbral de lo útil y lo funcional.

Cuántos proyectos quedaron en simplemente buenos o incluso en el olvido por culpa de la tan manida frase de: “Al cliente lo que le importa es que funcione, el resto le da igual.”

Una buena  experiencia de usuario logra provocar en él una determinada emoción cuando usa un producto (por ejemplo un sitio web o una app) y que le sea satisfactorio más allá de lo utilitario, en lo estético, la diversión, la identificación, el branding, etc.

La usabilidad y la UX, no son en cualquier caso, una más importante que la otra, ambas contribuyen a que los usuarios establezcan vínculos satisfactorios con la aplicación o con la web.

¿Qué es UX?

Con frecuencia, los diseñadores nos frustramos cuando estamos ante un cliente o un superior defendiendo una idea  y nos la rechazan por otras que sabemos que están destinadas al fracaso. Esto sucede porque nuestras ideas no están respaldadas por datos, se basan en creencias o suposiciones. Cuando sí lo están, es más fácil que se tengan en cuenta.

No debemos seguir viendo la experiencia de usuario como una etapa más del proceso. Consiste en entender la situación, el contexto y hábitos de uso de un grupo de personas, creando la mejor experiencia de uso posible. Pensar en diseñar un producto por y para los usuarios. Para ello las pruebas con usuarios son fundamentales.

Al realizar pruebas de usuario, incluso, antes de crear el producto con simples bocetos de papel, podríamos llegar a soluciones acertadas que nunca nos hubiéramos imaginado, ahorrando horas de desarrollo en realizar un producto equivocado.

Acá les dejo un video con test a usuarios, realizado con un prototipo de papel:

Podemos decir entonces que el diseño de experiencia de usuario se basa en:

- Pruebas cuantitativas y cualitativas con usuarios, que nos permitan tomar decisiones de diseño basadas e informadas en datos.

- Diseñar un producto que resuelva sus necesidades y se ajuste a sus capacidades, expectativas y motivaciones.

- Poner a prueba lo diseñado con test de usuarios.

¿Por qué debemos apostar en diseñar la experiencia de usuario?

- Es más fácil que crear un producto.

- Por ventaja competitiva.

- Una buena experiencia de usuario produce clientes apasionados y felices, esto permite cobrar más por productos y servicios que la gente ama y considera del más alto valor.

- El costo es casi el mismo y ganas más.

- Todos queremos una vida sencilla, fácil y divertida.


¿Cómo hacer una clase JSON serializable?

Un tiempo atrás quería encontrar una manera simple de serializar y deserializar objetos Java utilizando JSON. Luego de varias pruebas que no resultaron exitosas llegue al siguiente diseño:

Nota: A modo de ejemplo vamos a utilizar la librerialas librerias de Jackson. Este mismo diseño se puede llevar a cabo utilizando otras librerias.

Pasos para crear una clase JSON serializable:

  1. Implementar la interface JsonSerializable


public interface JsonSerializable

{

}

  1. Utilizar las operaciones de JsonSerializationUtilities


public class JsonSerializationUtilities

{

/**

* Converts the given object to JSON.

*

* @param obj

* @return

* @throws JSONSerializationException

*/

public static String serialize(JsonSerializable obj)

throws JSONSerializationException

{

try

{

ObjectMapper objectMapper = new ObjectMapper();

return objectMapper.writeValueAsString(obj);

}

catch(IOException ex)

{

throw new JSONSerializationException("JSON serialization exception", ex);

}

}

/**

* Creates an object from the given JSON representation.

*

* @param

* @param json

* @param clazz

* @return

* @throws JsonParseException

* @throws JsonMappingException

* @throws JSONSerializationException

*/

public static T deserialize(String json, Class clazz)

throws JsonParseException,

JsonMappingException,

JSONSerializationException

{

try

{

ObjectMapper objectMapper = new ObjectMapper();

return objectMapper.readValue(json, clazz);

}

catch (IOException ex)

{

throw new JSONSerializationException(ex.getMessage());

}

}

}

Como se puede observar, usaremos JsonSerializable como “marker interface” y seguimos la misma convencion utilizada para la famosa interfaz Serializable. De esta forma JsonSerializationUtilities solo permitira clases “JsonSerializable”.

Ejemplo de su uso:

De acuerdo con los pasos mencionados anteriormente:

public class Foo implements JsonSerializable

{

…

}


Foo foo = new Foo();

String fooAsJson = JsonSerializationUtilities.serialize(foo);

Foo deserializedFoo = JsonSerializationUtilities.deserialize(fooAsJson, Foo.class);

Configuraciones requeridas:

  • Si queres usar la serialización de JSON en tu proyecto, necesitas incluir la biblioteca de jackson:

    • Para aquellos que usan Maven, deben agregar las siguientes dependencias:


<!-- the core, which includes Streaming API, shared low-level abstractions (but NOT data-binding) -->

   com.fasterxml.jackson.core
   jackson-core
   ${jackson-2-version}

 <!-- Just the annotations; use this dependency if you want to attach annotations       to classes without connecting them to the code. -->

   com.fasterxml.jackson.core
   jackson-annotations
   ${jackson-2-version}

<!-- databinding; ObjectMapper, JsonNode and related classes are here -->

  com.fasterxml.jackson.core
  jackson-databind
  ${jackson-2-version}

<!-- JAX-RS provider -->

   com.fasterxml.jackson.jaxrs
   jackson-jaxrs-json-provider
   ${jackson-2-version}

<!-- Support for JAX-B annotations as additional configuration -->

  com.fasterxml.jackson.module
  jackson-module-jaxb-annotations
  ${jackson-2-version}

  • Si no utilizas nada para manejar las dependencias del proyecto:

    • Simplemente agrega los siguientes jars a tu carpeta “libs”:

      • jackson-annotations-${jackson-2-version}.jar

      • jackson-core-${jackson-2-version}.jar

      • jackson-databind-${jackson-2-version}.jar

      • jackson-jaxrs-base-${jackson-2-version}.jar

      • jackson-jaxrs-json-provider-${jackson-2-version}.jar

      • jackson-module-jaxb-annotations-${jackson-2-version}.jar

Nota: En todos los casos ${jackson-2-version} es la versión que prefieras utilizar.

  • Todos los atributos de una clase que querramos serializar deben respetar alguna de las siguientes reglas:

  1. Contener su “getter” correspondiente: todos los métodos públicos sin argumentos que devuelven un valor, y se ajustan a la convención de nomenclatura de "getXxx"  (o "isXxx", si el tipo de respuesta es boolean, llamado "is-getter") se consideran para inferir la existencia de una propiedad de nombre "xxx" (dónde el nombre de la propiedad se infiere utilizando la convención bean. Por ejemplo, la letra mayúscula inicial se cambia a minúscula.

  2. Ser público: todos los atributos públicos son considerados para representar propiedades, utilizando el campo nombrado tal como está.

  • Se requiere que la clase cuente con un constructor sin parámetros. Si no lo tiene, se debe agregar un constructor privado sin parámetros.

  • Si se utilizan tipos polimórficos, se debe habilitar el manejo de tipos polimórficos para un tipo y sus subtipos utilizando las siguientes anotaciones en la súper clase:


@JsonTypeInfo(

use = JsonTypeInfo.Id.NAME,

include = JsonTypeInfo.As.PROPERTY,

property = "type")

@JsonSubTypes({

@Type(value = Dog.class, name = "dog"),

@Type(value = Cat.class, name = "cat") })

public abstract class Animal

{

…

}

Esto significa que estamos agregando información tipo/subtipo al agregar una nueva propiedad llamada “type” de la cual el valor será “dog” cuando serializamos un objeto “Dog”. Por ejemplo si serializamos una lista de Animales que contienen “Cat” y “Dog”, la representación JSON sería la siguiente:


[

{

"type" : "dog",

… // other field members ...

}, {

"type" : "cat",

… // other field members

}

]


Tips útiles:

Ignorando explícitamente propiedades: @JsonIgnore, @JsonIgnoreProperties

  • @JsonProperty (y @JsonGetter, @JsonAnyGetter) pueden ser utilizadas para indicar que un campo o método debe ser considerado como una propiedad o método, incluso si no es detectado automáticamente.

  • @JsonIgnore puede ser utilizado para prevenir la inclusión de manera forzada, sin importar la detección automática (u otras annotations).

Además, existe un @JsonIgnoreProperties (per-class annotation) que puede ser utilizado alternativamente para listar nombres de propiedades lógicas que NO se incluyan en la serialización; puede resultar más simple utilizar anotaciones via mix-in, en lugar de anotaciones por propiedad (aunque ambas pueden ser utilizadas a través de anotaciones mix-in). Entonces podrías hacer:

@JsonIgnoreProperties({ "internal" })
  public class Bean {
    public Settings getInternal() { ... } // ignored
    @JsonIgnore public Settinger getBogus(); // likewise ignored
    public String getName(); // but this would be serialized
}

Links útiles:

Using Jackson2 with Maven

Jackson Full Data Binding (our JSON serialization implementation)

Jackson In Five Minutes

Jackson Documentation

Deserialize JSON with Jackson into Polymorphic Types - A Complete Example

Jackson - Filtering Properties

¡Espero que les resulte útil!


MongoDB: ¿Qué?, ¿cómo?, ¿cuándo?, ¿dónde?

En uno de los primeros post de este blog, Matias Blasi planteó la curiosidad que despierta una arquitectura Javascript & JSON end to end.

Sin dudas JS & JSON se han masificado en la capa de presentación. Por otro lado con node.js a la vanguardia, en la capa de negocio vienen ganando un lugar preponderante. Por último, por tener una tecnología que supere, o al menos sea comparable a los motores SQL, en la capa de datos los desafíos son muchos.

El primer de estos desafíos es pasar a una base de datos NoSQL para no tener que mapear y transformar mis objetos JSON a registros en tablas. El mundo NoSQL es enorme y amerita muchos otros posts pero por ahora vamos a empezar con un tipo particular de base de datos NoSQL: las orientadas a Documentos.

Las Bases de datos orientadas a documentos son básicamente un store key-value, donde la key es un hash generado a partir de los datos y los valores son documentos.  A partir de esta definición, podemos pensar en las ventajas de este tipo de base de datos:

1. Son horizontalmente escalables. Clusterizar los datos almacenados como key-value es mucho más sencillo que en SQL.

2. No poseen un esquema, por lo cual mis datos pueden ser más dinámicos, me olvidaría de migraciones complejas, podría agregar o quitar atributos o “columnas” sin downtime, echaría al DBA.

Este nuevo escenario me planteó varios interrogantes: ¿cómo impacta este cambio en el tipo de base de datos en el diseño de mi modelo?, ¿debo dejar a un lado mis conocimientos de SQL?, ¿tengo soporte para transacciones?, ¿qué tan seguras son? y muchos otras cuestiones más. Para empezar a responder estas preguntas , decidí enfocarme en la base de datos NoSQL orientada a documentos más popular de estos días: MongoDB.

En MongoDBlos documentos se formatean como JSON. Si vengo de SQL tengo que dejar de pensar en tablas para pensar en collections, olvidarme de las rows y pensar en documentos. Así, podría tener almacenados una estructura compleja de objetos con formato JSON, accesibles con una clave simple. Por lo tanto, podría recuperar todos los datos a mostrar en una página a partir de un id. Si quisiera, podría usar un diseño normalizado y guardar las relaciones como referencias entre documentos. Eso dependerá de cuán fuertes sean ciertas entidades en mi universo de objetos.

MongoDB ofrece una API para operaciones CRUD muy simple y amigable, además, puedo definir índices (simples o compuestos) e indexar campos de texto. Sin embargo, no soporta constraints  sobre los atributos ni foreign keys constraints y tampoco soporta transacciones, al menos por ahora. Obviamente soporta backups, import, exports, administración de usuarios, control de acceso a nivel de colección y demases.

Entonces, ¿cuándo usar MongoDB?

En Prototipos o aplicaciones simples, la velocidad de desarrollo debería aumentar si persistir mis objetos no me requiere excesiva configuración, mapeos y transformaciones. Por eso, si necesito resultados rápidos MongoDB es una buena opción.

Si debo manejar un enorme volúmen de datos o necesito soportar un crecimiento exponencial de mis datos, MongoDB cuenta con la ventaja de estar pensado para escalar horizontalmente. La combinación de los métodos de replication y sharding que soporta MongoDB permiten manejar un gran volumen de lecturas y escrituras de datos.

Si necesito almacenar datos no estructurados, que varían constantemente (comportamiento de usuarios por ejemplo) una base de datos NoSQL como MongoDB también sería una buena opción.

¿Cuándo no usar MongoDB?

No recomiendo migrar  una aplicación que usa una base de datos SQL a MongoDB.

Aconsejaría poner atención a la cantidad de foreign keys que pienso mantener en mi estructura de datos, si son muchas y complejas: el paradigma relacional es la mejor opción ya que en MongoDB no existe algo como un JOIN.

La ausencia de transacciones y esquema son dos cosas que deben tenerse muy en cuenta. Si la base de datos no me brinda rollbacks ante un error en un conjunto de escrituras, el problema pasa a ser mío. Por lo tanto, si el core de mi aplicación pasa por hacer bulks de inserts pensaría dos veces antes de decidir por MongoDB. La ausencia de esquemas es un aspecto seductor pero, por otro lado, si necesito validaciones las voy a tener que escribir yo.

Los invito a seguir aportando acerca de los buenos y malos usos de MongoDB y les dejo un par de links que nos dan otros puntos de vista:

Anti-RDBMS: A list of distributed key-value stores

Why you should use MongoDB. Not always though 

When to Use MongoDB Rather than MySQL (or Other RDBMS): The Billing Example

Why You Should Never Use MongoDB 

MongoDB Use Cases

Common MongoDB use cases 


Hacia un modelo de Arquitectura emergente (parte 1)

Nos encontramos ante el mejor de los mundos, un proyecto nuevo, Greenfield se abre ante nuestro futuro. Pocos requerimientos no funcionales que nos limiten la utilización de nuestros conocimientos para generar la mejor arquitectura posible y los mejores diseños.

Buen momento para aplicar técnicas de Arquitectura Ágil y Diseño Emergente y no ponernos demasiados condicionantes antes de empezar.

En su muy buen webinar sobre Arquitectura Ágil, Martín Salias nos habla sobre cómo en los proyectos ágiles la arquitectura tiene que emerger y nos tiene que ayudar a diferir lo máximo posible las decisiones arquitectónicas para no agregar limitaciones en forma temprana que nos condicionen el proyecto. También podés acceder a las slides del webinar haciendo click acá.

En nuestro caso teníamos limitantes con respecto a la tecnología a utilizar. Se nos pedía utilizar tecnologías Microsoft.

Eliminando el primer condicionante

Descartado el uso de Asp.Net WebForms, nos encontramos con la decisión de utilizar una arquitectura monolítica Asp.net MVC vs Asp.net Web API + Javascript framework.

K.Scott Allan  representa en su blog éstas distintas aproximaciones y nos ayuda  a entender esquemáticamente éstas tecnologías.

Además de las claras ventajas que nos provee la separación de nuestro backend y la exposición de nuestra API por medio de una Web API (ya veremos un poco en profundidad los conceptos ReST), nos encontramos con desventajas del uso de Razor como frontend versus frameworks Javascript:

1- Múltiples lenguajes en el frontend (razor, javascript) vs. Javascript

2- Soluciones complejas para el binding y mixtas

3- Ajax con limitaciones que requieren de soluciones mixtas (razor + javascript)

4- Acoplamiento con el backend (ViewBag, ViewModel)

5- Complejidad extra para interactuar con Diseñadores Gráficos, helpers de razor, sintaxis desconocida, complejidad para manejar clases css

6- El uso HTML + CSS + Javascript abre la posibilidad que más desarrolladores puedan trabajar en el frontend

Representational State Transfer (ReST)

Con todas esta ventajas vemos que la posibilidad de trabajar con un modelo REST API + frontend nos da la posibilidad que nuestra arquitectura evolucione sin necesidad de que las distintas capas se vean afectadas.

ReST  es una simple abstracción de la arquitectura de la Web, está basado en los RFC fundacionales del protocolo HTTP.

Hypertext Transfer Protocol — HTTP/1.0

Hypertext Transfer Protocol — HTTP/1.1

TDD

Muy influenciados por el Maestro Angel Java Lopez (@AJLopez), TDD se incorpora en nuestra cultura y nos permite diseñar una interfaz  de nuestra REST API consistente, independiente del frontend.

A pesar de la larga discusión que se suscitó este año por DHH, el creador de Ruby on Rails,  sobre  la muerte de TDD, TDD está más vivo que nunca.

Identidad

Uno de los primeros problemas que nos topamos al diseñar nuestra API es el de Authentication y Authorization. Una solución sencilla que no va a condicionar nuestra arquitectura es la provista por los Bearer Tokens, especificados en el framework de OAuth 2.0. Esto nos permite intercambiar un token una vez autenticados y luego utilizarlo en los headers HTTP cada vez que necesitamos autorización para acceder a un recurso.

OAuth 2.0 Bearer Token Usage

The OAuth 2.0 Authorization Framework

Generar una capa de datos reemplazable

Nuestra arquitectura puede comenzar sin necesidad de persistencia. Para lo cual generaremos una capa testeable, prototiteable que nos permita representar la posible persistencia. En la evolución de la arquitectura tenemos que tener la posibilidad de que nuestra capa de datos permita el reemplazo de la persistencia, primero desde una versión prototipada a una primera aproximación de capa de datos, de forma que la capa de servicios no se vea impactada.

Generar una capa de servicios de dominio que me permitan abstracción

La interfaz de la API nos puede ayudar a interactuar con una capa de servicios de dominio que nos abstraiga de la persistencia. Ésta será una capa generada utilizando TDD, por lo que será testeable, prototiteable e independiente.

Test de integración utilizando POSTman

Fácilmente podemos testear nuestra API utilizando herramientas como por ejemplo POSTman o RestConsole.

Eso nos permite validar nuestra API pero no es automatizable. Por lo que debemos generar los tests de integración necesarios para mantener nuestra interfaz de API válida en todo momento.

Conclusión

Una arquitectura que emerge a partir de la evolución de los requerimientos, con métodos que nos permitan validarla constantemente, con piezas fácilmente reemplazables, elimina condicionamientos tempranos y nos permite efectuar todas las mejoras que el cliente necesite.

En el próximo post veremos cómo interactúa el diseño del frontend con nuestro modelo arquitectónico.

Mariano Koldobsky (@koldobsky)