viernes, 29 de junio de 2018

No siempre hace falta un Stored Procedure


Los ORM´s suelen facilitarnos mucho el trabajo de interactuar con las bases de datos, ya que nos abstraen de los modelos relacionales al permitirnos trabajar directamente con un modelo orientado a objetos. Permiten además, realizar cambios en los modelos de entidades con un esfuerzo menor. Pero esa abstracción no es gratis. A veces, por olvidar que hay una base de datos detrás, se pueden cometer errores que afecten negativamente la performance de la aplicación.

Alguna vez te debe haber pasado que diseñaste un modelo de objetos impecable y puro, usando un ORM para no contaminarlo con lenguajes profanos como SQL. En resumen, una obra maestra. Se pone en producción y, al tiempo, comienzan a haber quejas por su lentitud y la base de datos se sobrecarga. En ese momento es cuando aparece el DBA y te dice: “¿Están usando un ORM? No…. eso hay que pasarlo a Stored Procedure para que ande rápido”.

Si bien en algunos casos puede ser necesario, la mayoría de las veces el sólo hecho de cambiar la forma en la que se escriben las consultas en el ORM, alcanza para mejorar significativamente la performance sin tener que realizar cambios de alto impacto en la aplicación y en la arquitectura de la solución.
A continuación, comparto una serie de tips que te permitirán mejorar la escritura de consultas en ORMs:

Select new… y lazy loading

Armar la consulta para generar un objeto, únicamente con las propiedades de cada entidad que vamos a utilizar. No tiene sentido levantar 20 columnas de una tabla si solo necesitamos 3 de ellas para la pieza de código que se está ejecutando.
Con esta técnica, además de reducir la cantidad de datos que leemos de la base de datos, evitamos generar una gran cantidad de desencadenadas por el lazy loading (conocido como el problema de select n+1).

Supongamos que estamos obteniendo un listado de productos de la base representados por la entidad “Product”. En un modelo relacional tendría dos tablas: Product y Category.



Una opción para obtener los datos y convertirlos a un view model para mostrar en una vista, podría ser esta:


Pero, ¿qué desventajas tiene esto?

-       Se obtienen propiedades de la entidad Product que luego no se van a utilizar
-    Para acceder a la propiedad Description de la categoría del producto, el mecanismo de lazy loading disparará una consulta por cada producto obtenido de la base. Trabajar con un volumen grande de datos puede ocasionar serios problemas de performance.

Una alternativa es hacerlo de esta manera:



En este caso, el ORM tiene la inteligencia para detectar que solo necesitamos 4 campos. Además, como estamos obteniendo un campo que está en otra tabla, hará el join correspondiente. En resumen, generará un único select que hará el join entre Product y ProductCategory, levantando solo las 4 propiedades que necesitamos.

Count vs any

Cuando se quiere conocer la existencia de una entidad o un registro bajo ciertos criterios, siempre conviene usar “Any” en lugar de un “Count”. Any será traducido a un Exists o a un Select TOP, dependiendo del ORM que estemos usando, mientras que el Count será traducido a la función de agregación de SQL count que tiene más costo de ejecución.

Los ToList

Siempre hay que tener cuidado al utilizar el ToList. Una consulta mediante LINQ no se ejecuta contra la base de datos hasta que no se enumeran los resultados. Esta enumeración puede ocurrir al hacer un foreach sobre los resultados, o al hacer un ToList. Si no se levantaran todos los registros de la base de datos antes de aplicar los filtros,  se deben aplicar los filtros sobre la tabla antes de hacer el ToList.

Por ejemplo, si hacemos algo así:


La primera sentencia va a hacer que el ORM genere un select sin where a la base de datos y una lista de objetos con todos los registros que haya en la tabla. La segunda sentencia va a filtrar los productos en la colección en memoria.

Con un sutil cambio, obtenemos el comportamiento deseado:



Al no usar to ToList en la primera sentencia, la consulta no se ejecuta. El where de la segunda sentencia, se agrega a la expresión de la consulta a construir y recién, en el ToList al final de la segunda línea, se ejecuta la consulta pero con el where correspondiente para obtener de la base solo los registros deseados.

Mapeos de relaciones bidireccionales

Muchas veces, cuando se mapean objetos relacionados, la relación se arma de forma bidireccional. Por ejemplo, si tenemos el objeto Venta con sus Ítems, se podría armar así:



En principio, esta relación no generaría problemas dado que los Ítems en una Venta deberían ser cantidades razonables. Ahora, si a la entidad Producto le agregamos la relación bidireccional con ItemDeVenta, en cuanto la base de datos comience a crecer en cantidad de datos, vamos a tener un problema de performance (y uno muy difícil de encontrar).


En el ejemplo anterior, los ItemDeVenta que referencian a un determinado Producto[AB1] , van a tender a crecer con el tiempo a medida que se procesan las transacciones del negocio. Y si se invoca a Producto.ItemsDeVenta se van a terminar levantando de la base todos los ItemsDeVenta donde el Producto esté incluido. Así mismo, si uno le asigna un Producto a un ItemDeVenta, el mismo ORM puede invocarlo internamente y, si no tomamos las precauciones debidas, no tendremos control sobre la degradación de la performance a medida que la aplicación comience a ser utilizada masivamente.

Una sentencia como la siguiente, puede desatar que el ORM levante millones de registros de ItemDeVenta:
 

Además, sin ni siquiera acceder a la propiedad se puede tener un problema. Por ejemplo, al crear un ItemDeVenta:


El ORM puede internamente acceder a la propiedad ItemsDeVenta de la clase Producto para mantener la consistencia de la relación bidireccional, por lo que, potencialmente estaríamos obteniendo de la base de datos, gran cantidad de registros sin siquiera acceder a una propiedad que es una colección. Acá es cuando el DBA te dice que en su profiler ve que estás haciendo una consulta que levanta un millón de registros y vos le juras que no estás haciendo ninguna consulta.

Value Types Nulleables

Es importante tener cuidado al usar Value Types. Si el tipo es nulleable en la tabla, entonces hay que mantenerlo también a nivel de objetos. Cuando se presenta esta inconsistencia entre modelo de tablas y objetos, los ORM suelen detectar que la entidad tuvo un cambio y tratan de actualizar el campo en la tabla con el valor default del Value Type.

Conclusión

Los tips mencionados son sólo algunos de los casos con los que me he encontrado (y sufrido). Más allá de estos ejemplos puntuales,  lo que siempre recomiendo es verificar qué consulta SQL se está generando a partir del ORM.

Cada ORM tiene su particularidad a la hora de traducir el código a una consulta, por ello, no se debe dejar de controlar que las consultas generadas sean performantes e investigar si hay alguna mejor forma de escribir el código LINQ para generar un mejor SQL.

En la mayoría de los casos se pueden lograr consultas eficientes y performantes sin necesidad de tener que escribir la consulta directamente en SQL o programar un Stored Procedure.

Es fundamental no desentenderse de la base de datos y adoptar la práctica de verificar como terminan traduciéndose a SQL las consultas que escribimos con LINQ, particularmente cuando son complejas. También es imprescindible conocer los detalles y limitaciones que posee el ORM que estamos utilizando y las características del motor de base de datos sobre el cual estamos persistiendo nuestros datos.


Autor:

Diego Tubello

Baufest Technical Expert 



miércoles, 27 de junio de 2018

Guía para testear e-commerces y no morir en el intento



    Fuente: https://www.tecnopymes.com.ar/2018/01/17/el-boom-del-e-commerce-favorece-a-las-pymes/

    1. Estructura típica de los e-commerce

    La mayoría de los sitios web de comercio electrónico comparten un mismo tema y una estructura general:
    •  Página principal
    •  Página de resultados de búsqueda.
    •  Página de detalles del producto.
    •  Página de formulario de pedido.
    •  Página de confirmación de pedido.
    •   Página de formulario de inicio de sesión y páginas de cuentas.
    Por supuesto, hay muchas otras páginas en un sitio web de comercio electrónico, pero el viaje principal del usuario implicaría visitar las mencionadas anteriormente, y es aquí donde se deberían centrar las pruebas del comercio: en el flujo de pago.
    Estas páginas "front-ends", probablemente se comuniquen con servicios web de "back-end”, por lo tanto, es importante al probar sitios web de comercios electrónicos, testear los servicios individuales en forma aislada e integrados como un sistema completo.
    Un flujo típico comenzaría en la página de inicio, buscando uno o más productos, revisándolos, agregando productos al carrito de compra, completando los detalles del pedido y de pago y, por último, enviando el pedido.
    2. Descripción y ejemplos de casos de pruebas de algunos elementos

    Fuente: https://www.solostream.com/general-requirements-for-e-commerce/
    A continuación, examinamos algunos casos de pruebas comunes que son específicos para probar sitios web de comercio electrónico. Las ideas acá presentadas, son sólo algunos casos genéricos de prueba que son aplicables a la mayoría de los sitios web de comercio electrónico.
    2.1   Prueba de carrito de compras
    Los carritos de compra son una de las principales características de un sitio web de comercio electrónico y, por lo tanto, son la pieza central de los sitios web de prueba. Estos permiten a los clientes seleccionar y almacenar varios artículos y, posteriormente, comprarlos todos a la vez.
    Hoy en día, los carritos de compras se han vuelto "inteligentes". Los mismos recuerdan los artículos almacenados para que puedan ser recuperarlos en una fecha posterior, o incluso desde otro dispositivo.
    En la mayoría de los casos, las cookies se utilizan para almacenar los datos del carrito o, si el usuario tiene una cuenta activa y está conectado, se puede almacenar una identificación de sesión contra el usuario en la base de datos. 

    A continuación, se detallan algunos casos de pruebas claves para probar un carrito de compras:

    • Agregar un artículo al carrito: el carro debe actualizarse con el artículo, nombre, imagen y precio correcto.
    • Aumentar la cantidad del artículo desde el carrito: el precio debe actualizarse para reflejar la cifra correcta.
    •  Agregar el mismo artículo varias veces: debe haber un artículo en el carrito, pero la cantidad debe reflejar el número de adiciones y el precio total debe reflejar la suma del precio de cada artículo.
    • Agregar varios elementos de diferentes tipos: para cada elemento agregado deberíamos ver el nombre, la imagen, el precio correspondiente y el precio total de todos los artículos.
    • Eliminar algunos elementos del carrito: debería actualizarse mostrando los artículos existentes en el carro y el precio total debería reflejar la nueva suma.
    • Eliminar todos los artículos del carrito: el saldo del carrito debe ser cero y no se deben mostrar artículos en el carrito.
    • Hacer click en un elemento del carrito: deberíamos poder ver más información sobre el producto como una ventana emergente o redirigir a la página del producto.
    • Agregar artículos al carrito, cerrar el navegador y volver a abrir el mismo sitio: lo ideal es que el carro siga reteniendo los artículos. Esto puede variar dependiendo especialmente de los requisitos sobre cómo debe comportarse el carrito.

    2.2   Formularios de búsqueda, clasificación, filtrado y paginación
    El formulario de búsqueda generalmente está presente en varias páginas para permitir que los usuarios busquen productos donde quiera que se encuentren en el sitio. Por lo tanto, es importante que la función de búsqueda se pruebe desde las páginas correspondientes.
    Lo más probable, es que el código para el módulo de búsqueda se reutilice en varias páginas o plantillas, o podría ser parte de la sección del encabezado que se muestra en todo el sitio. Si este es el caso, el comportamiento de la función de búsqueda debe ser el mismo donde sea que ocurra, por lo cual ejecutar los casos de prueba en todas las páginas será una pérdida de tiempo.
    Cuando buscamos un producto, nos redirigen a la página de resultados de búsqueda con todos los elementos relevantes asociados a la misma. Hay mucho que verificar y muchas funciones para probar, pero las tres características más importantes y relevantes son: la clasificación, el filtrado y la paginación.
    A continuación, algunos tips para probarlos:
    • Paginación: compruebe que todos los elementos de la página siguiente sean diferentes a los de la página anterior, es decir, que no haya duplicados.
    • Clasificación: suele ser de selección única, es decir, puede ordenar por un sólo parámetro.
    • Clasificación y paginación: cuando hay productos en varias páginas, ordena por un parámetro. El orden de clasificación debe permanecer como paginado, o más productos cargados.
    • Filtrado: las opciones de filtro son de multiselección, es decir, puede filtrar por múltiples parámetros. Es una buena idea explorar filtros individuales y opciones de filtros múltiples.
    • Filtrado y paginación: una vez más, cuando filtremos en una página, idealmente mientras paginemos, deseamos que el filtro se aplique en todo momento.
    • Clasificación y filtrado: un caso de prueba es mezclar las opciones de clasificación y filtrado. Si bien las características individuales por sí mismas podrían funcionar correctamente, cuando se combinan, la funcionalidad de una o ambas características podría romperse, por lo que es esencial que verifiquemos los resultados al combinarlos.
    • Clasificación, filtrado y paginación: verificar que, cuando se hayan aplicado tanto la ordenación como el filtro, permanezcan en la misma paginación o se carguen más productos.

    2.3   Crear cuenta e iniciar sesión
    Algunos sitios web de comercio electrónico permiten comprar un artículo como invitado, es decir, sin la necesidad de crear una cuenta. Luego, como paso opcional, se ofrece crear una cuenta cuando se realiza un pedido.
    Cuando se crea una cuenta, el usuario puede iniciar sesión en cualquier etapa del flujo de compra, por este motivo, es importante que probemos todas estas variaciones a lo largo del recorrido del usuario.
    Aquí se sugieren las principales pruebas a ser tomadas en cuenta para estas funcionalidades:
    • Comprar un artículo como invitado: si el sitio lo permite, pruebe que pueda comprar un artículo sin tener que crear una cuenta.
    • Cuentas existentes y nuevas: compre un artículo con una cuenta existente y con una cuenta recién creada.
    • Cree una cuenta e inicie sesión antes de comprar: esto es para probar que el artículo que compra se agrega y se conecta a la cuenta correcta. Además, no se le debe pedir que inicie sesión nuevamente una vez que haya iniciado sesión.
    • Redireccionamiento: compruebe el comportamiento de la función de inicio de sesión en diferentes páginas.
    • Sesión de la cuenta: cuando inicie sesión, verifique que permanezca conectado mientras navega por los productos. Además, debe probar el comportamiento cuando el usuario no interactúa con el sitio durante un tiempo y ver si la sesión expira.
    • Inicio de sesión y cierre de sesión: cuando haya iniciado sesión, ciérrela y asegúrese de que no puede acceder a ninguna de las páginas de la cuenta.

    2.4   Pagos
    Los pagos son una parte esencial de las pruebas de sitios web de comercio electrónico. Después de todo, esto es lo que permite a los usuarios comprar sus artículos sin la necesidad de llamar a un número para hacer su pedido. Para testear esta funcionalidad debemos tener en cuenta lo siguiente:
    1.  Medios de pago: se deben probar diferentes tipos de pago, por ejemplo, tarjeta de crédito, PayPal, transferencias bancarias, cuotas, etc.
    2. Datos de la tarjeta del cliente: verificar si son almacenados. Si es así, ver si lo hace de forma segura.

    2.5   Pruebas posteriores a la compra
    Cuando hacemos un pedido, hay muchas cosas que los usuarios pueden hacer en relación con su compra, por ello, es importante probar qué pasa luego que se hace dicha operación. En este sentido, se debe contemplar lo siguiente:
    • Cambiar la cantidad o cancelar el pedido.
    • Revise su pedido reciente y el historial de artículos comprados.
    • Cambios en la cuenta, como dirección de facturación, dirección de envío, cambio de contraseña, cambio de información de perfil como nombre, dirección de correo electrónico e incluso eliminación de una cuenta.

    Sin duda, existen muchas más funcionalidades para probar, pero este artículo es sólo una introducción a todos los casos de prueba relevantes que se pueden ejecutar al probar un e-commerce, pudiendo utilizarse como punto de partida.
    Fuentes:
    https://www.testingexcellence.com/testing-e-commerce-websites/
    https://www.solostream.com/general-requirements-for-e-commerce/
    https://www.tecnopymes.com.ar/2018/01/17/el-boom-del-e-commerce-favorece-a-las-pymes/

    Autor:
    Ricardo Manuel Jigena.
    Tester Técnico.