martes, 26 de mayo de 2015

Hagamos un buen uso de los ORM

¡Buenas! Quería compartir con ustedes mi experiencia técnica en el uso de los frameworks ORM. Puntualmente, les daré algunos tips que nos van a ayudar a lograr diseños más robustos y performantes.

Desde hace varios años que vengo utilizando estos frameworks en los proyectos en los que he participado, tanto como Technical Leader como Developer. He usado durante mucho tiempo dos ORMs muy conocidos en el mundo .Net: Entity Framework y NHibernate, y la verdad es que en general estoy muy conforme con sus prestaciones.

Más allá del auge de las bases de datos orientadas a objetos, en los proyectos actuales todavía se siguen usando con mayor frecuencia las clásicas bases de datos relacionales, por lo que el uso de un ORM que nos ayude a conectar nuestro modelo de objetos con el modelo de datos, aún es muy valioso, y bien usado, nos puede dar enormes ventajas.

 

Introducción a los ORM

 

clip_image002

ORM es la sigla correspondiente a Object-Relational Mapping (que en español podríamos traducirlo como mapeo objeto-relacional).

Es una técnica de programación mediante la cual se abstrae la comunicación, conversión y adaptación entre nuestro modelo de objetos y el modelo relacional a través del cual se persisten los datos de negocio.

Se implementa como una capa de abstracción individual, y es el último nexo entre nuestros objetos de negocio y el modelo de persistencia de la aplicación, permitiendo obtener y/o guardar fácilmente información en la base de datos.

Actualmente existen numerosos frameworks ORM disponibles en el mercado. Un framework ORM es una implementación particular del modelo ORM por un producto específico. Y si bien hoy en día existen varios de ellos disponibles, muchos desarrolladores aún siguen construyendo sus propias implementaciones para sus aplicaciones, tanto por cuestiones particulares del proyecto, como por desconocimiento de estas herramientas y las grandes prestaciones que pueden ofrecernos.

En el mundo .Net los productos más conocidos son ADO.Net Entity Framework, propietario de Microsoft, y NHibernate, que es open source y deriva del proyecto hibernate para Java.

 

¿Cuáles son las principales ventajas de usar un ORM?

Los frameworks ORM aparecieron en el mercado ya hace tiempo, y si bien siempre generaron polémica en torno a si su uso era una buena decisión de arquitectura o no, actualmente se siguen empleando en muchísimos desarrollos de software.

Básicamente, esto es porque más allá de algunas limitaciones y problemas que comentaremos luego, tienen importantes ventajas que aportarnos:

· Permiten almacenar y obtener información de una base de datos relacional de forma automática. Es decir, no tendremos la necesidad de escribir a mano las consultas SQL necesarias para persistir y recuperar los datos; esto lo hará el framework automáticamente utilizando su motor interno.

· Además, al abstraernos de la base de datos implementada, nos permite que, si el día de mañana tenemos que cambiarla, el impacto para la aplicación sea mínimo, ya que sólo se vería afectada la capa de repositorio y no el resto de las capas de la solución.

· Nos garantizan, a través del Identity Map, que en memoria sólo vamos a tener cargada una única instancia de cada entidad de negocio, sin duplicados. Esto es una gran ventaja, ya que más allá de mejorar la performance, al no tener que recargarla de nuevo si se pide reiteradamente, permite asegurar la robustez del modelo y evitar inconsistencias de datos.

· De lo anterior se desprende la implementación de una caché interna que nos ayudará en lo que se refiere a mejorar la performance de las consultas.

· En complemento con el patrón Unit of Work, el framework se encarga de detectar y persistir los cambios automáticamente, liberándonos de la tarea de tener que guardar los cambios realizados en los datos de forma explícita. Esto no quita que podamos hacerlo o no, pero es una facilidad más que podemos tomar o descartar de acuerdo a nuestras necesidades.

· Agilizan el desarrollo, ya que por un lado nos abstraen de la interacción con la base de datos, pero por otro lado, también nos permiten generar el modelo de datos a partir del esquema de objetos. Esto es lo que se conoce como el approach Code First.

· Por el otro lado, existen los approaches DataBase First y Model First, en los cuales el ORM permite generar un modelo de objetos a partir de un modelo de datos existente previamente.

· También a partir de los dos puntos anteriores, nos facilitan mantener sincronizados ambos modelos (el de objetos y el de datos) ya que facilitan el actualizar el modelo de datos a partir del modelo de objetos, y viceversa.

· Los frameworks ORM actuales suelen implementar mecanismos de seguridad robustos con el fin de evitar ataques del tipo SQL Injection.

· Si respetamos correctamente la separación entre capas, nos facilitan el mantenimiento y la evolución de nuestro código.

· Las aplicaciones pueden funcionar con un modelo conceptual orientado al modelo de objetos, con conceptos como herencia, miembros complejos y relaciones, entre otros.

· Proveen compatibilidad con Language Integrated Query (LINQ), lo que proporciona una validación de la sintaxis de los nombres de entidades y atributos en tiempo de compilación.

 

¿Cuáles son las principales limitaciones de los ORM?

Bien, hasta aquí hemos comentado las ventajas de los frameworks ORM, pero sería injusto si no evaluáramos también las desventajas que presenta su uso como parte del diseño de una solución:

· La desventaja más grande de un ORM es que puede tener un impacto negativo en la perfomance de la aplicación. Esto se debe en parte a que estamos introduciendo una capa más y esto tiene su overhead, y en parte a que, dependiendo de cómo armemos las consultas con LINQ, cuando cargamos datos, el ORM armará automáticamente las consultas SQL que ejecutará sobre la base de datos, y si trabajamos con muchas tablas o condiciones complejas, el query será complejo y demorará mucho más en ejecutarse.

· Basado en lo anterior, el motor interno de un ORM posee una lógica que le permite mapear una consulta en LINQ a una query SQL. Y si bien hace su mejor esfuerzo en generar queries performantes, no siempre lo logra y terminan siendo muy complejas y generando un impacto negativo en los tiempos de carga de datos de la aplicación. Si bien, con el paso de los años las queries generadas por los ORM mejoraron notablemente, si las consultas LINQ son complejas, el ORM no nos puede garantizar un buen query, tal y como lo escribiría un desarrollador por sus propios medios.

· Partiendo de los problemas de performance, podríamos decir que derivamos en problemas de escalabilidad a futuro, sea por un crecimiento de la cantidad de datos como por un mayor uso de la aplicación por parte de los usuarios.

· Añaden una complejidad adicional en nuestro modelo de solución y requiere prepararse para poder utilizarlos correctamente.

· Si ocurre un error de mapeo, es muy difícil debuggear dentro del ORM para detectar la causa real de nuestro problema.

· Al principio, posee un costo de aprendizaje para aquellas personas que nunca han trabajado con un ORM, más que nada acerca de cómo configurar correctamente el mapeo entre las entidades de objetos y las tablas de la Base de Datos.

 

Usar o no usar un ORM

Luego de evaluar ventajas y desventajas de utilizar un framework ORM seguramente les estén surgiendo algunas preguntas como: ¿realmente es válido usar un ORM? y ¿cuándo no es conveniente usar un ORM? Bueno, les cuento que ambas preguntas se responden fácilmente: depende…

Como toda herramienta o framework de desarrollo, existirán situaciones en donde no convenga su utilización. Por ejemplo, el primer punto a evaluar es qué motor de persistencia de datos estemos usando. Si vamos a usar una base de datos orientada a objetos, entonces carece de sentido usar un ORM.

Por otro lado, existen aplicaciones en las cuales un ORM no marcará la diferencia, o peor, terminará siendo un dolor de cabeza. Podemos mencionar, por ejemplo, aplicaciones críticas de planta o aquellas que manejen grandes volúmenes de datos. Recordemos los problemas de performance que pueden presentarse; o que requieran un modelo de seguridad superior al que ofrecen los ORM actuales.

Además, desde mi experiencia, si sólo lo vamos a usar como un DataMapper, entonces no se justifica su uso, ya que el overhead que introduce y los potenciales problemas de performance que puede traer, terminan haciendo que nuestra solución ofrezca una mala experiencia de usuario.

 

Tips para hacer un buen uso de los ORM

En el caso de que optemos por utilizar un framework ORM, evaluemos los siguientes puntos en pos de favorecer el diseño desde etapas tempranas y que esto nos permita lograr una buena solución sorteando las limitaciones enumeradas anteriormente, especialmente las referidas a la performance.

Consideraciones generales:

· Un buen ORM es una herramienta compleja de usar, con muchas opciones diferentes de parametrización y varias formas para hacer lo mismo, dependiendo el contexto, algunas mejores que otras.

· Estudiemos el framework, leamos material, investiguemos, consultemos con nuestros colegas sus experiencias previas y qué cosas debemos tener en cuenta.

· No nos quedemos sólo con incluirlo en nuestra solución y que salga andando como pueda, ya que podemos estar haciendo un mal uso de él y eso nos va a generar problemas a futuro.

· Este punto es más genérico, ya que aplica tanto si vamos a usar un ORM como si no: utilicemos un lote de datos de prueba lo más parecido a la realidad. Si en producción una determinada tabla va a tener millones de registros, en nuestro ambiente de desarrollo esa tabla deberá tener al menos esa misma cantidad, e idealmente el doble.

· Muchas veces sucede que no detectamos un problema de performance con el ORM no porque la consulta esté mal, sino porque estamos probando con el lote de datos equivocado.

· Cuando construyamos las bases de nuestra capa de repositorio, pensemos dónde vamos a ubicar los controles de auditoría y profiling. Son dos funcionalidades que, si bien no son requeridas formalmente por los usuarios de nuestras aplicaciones, nos ayudarán a detectar inconsistencias o problemas más adelante cuando nuestro modelo crezca y surja algún inconveniente.

Consideraciones sobre nuestro modelo de objetos:

· Si nuestras entidades de negocio tienen colecciones, tengamos la costumbre de inicializarlas siempre en sus constructores, esto nos va a evitar posibles errores en caso de que se trate de acceder a la propiedad cuando no tiene datos cargados.

· Cuando retornamos una lista de registros desde nuestro repositorio, es preferible hacerlo mediante la colección del tipo IQueryable <T> por sobre el tipo IEnumerable <T>. Y en un nivel más general, dentro de nuestros repositorios manejémonos exclusivamente con IQueryable<T>. Esto se debe a que si usamos IEnumerable<T> , se ejecuta la consulta físicamente sobre la base de datos retornando los registros de la primera condición, y si usamos IQueryable<T>,  irá concatenando las sucesivas consultas hasta que realmente se dispare la consulta en la base de datos, por ejemplo, al loopear dentro de un foreach, o hacer explícito un ToList(). Resumiendo, con IQueryable<T> tendremos mejores prestaciones de performance en nuestra aplicación.

· Utilicemos los patrones Unit of Work y Repository, ya que nos van a facilitar enormemente la gestión del estado de las entidades, la organización de los diferentes métodos de consulta a la base de datos y el manejo de transacciones para garantizar la integridad de los datos.

· Modelar las relaciones de forma explícita, por ejemplo, para representar las Foreign Keys o las entidades Composite. De esta forma podremos acceder a propiedades anidadas en nuestras consultas con una mejor respuesta en performance por parte del ORM.

· Si es posible, utilicemos Primary Keys únicas y no compuestas, esto facilita el manejo de entidades y nos permite crear abstracciones genéricas que nos ahorrarán esfuerzo de desarrollo.

· Prestemos atención en los casos en los que una propiedad de una entidad se complete llamando a una función externa, por ejemplo, ejecutando una consulta a otro sistema. Si yo voy a obtener una única entidad es una cosa, pero otra totalmente distinta es si tengo que ejecutar esa consulta sobre un listado grande de entidades. Por ende, prestemos atención, ya que el problema de performance no siempre será del ORM, sino que puede darse por una regla de negocio en un nivel superior.

· Sobre el punto anterior, evaluemos si ese dato externo es algo que siempre vamos a necesitar o si en determinados casos no, con lo cual podríamos tener dos tipos de carga a fines de evitar los problemas de performance. Seamos flexibles.

· Al margen de que el ORM implemente una caché interna, es una muy buena práctica definir  nuestra propia estrategia de caché, la que puede tener diferentes niveles y tiempos de vida de acuerdo a las necesidades particulares de cada proyecto.

Consideraciones respecto a las consultas que podemos ejecutar a través del ORM:

· Un ORM es muy bueno con las operaciones CREATE, UPDATE y DELETE del modelo CRUD. Específicamente hablando de performance, son instrucciones simples, que pueden ser codificadas utilizando el modelo LINQ, y que el ORM las mapeará correctamente y no generarán problemas de performance al respecto.

· En cuanto a las operaciones del tipo READ, recomiendo usar el modelo LINQ de los ORM para los casos simples, como por ejemplo los clásicos GetAll y GetById; o consultas en donde no tengamos una gran complejidad en la query final generada.

· Ahondando en lo anterior, ¿dónde no les recomiendo usar el modelo LINQ con un ORM?

o En consultas destinadas a cargar un listado de entidades con múltiples opciones de ordenamiento.

o En consultas que obtienen listados de registros considerando una cantidad muy grande de filtros condicionales y/o relacionados.

o En consultas en donde debamos paginar los resultados para mostrar sólo un subconjunto.

o En consultas en donde queden involucradas varias tablas de la base de datos. Cuando más tablas participen, más grande será el universo de datos que deberá manejar el ORM, más compleja será la consulta a realizar, y por ende, más costo a nivel performance.

o Definitivamente, en consultas en donde se combinen las opciones anteriores. Para mapearlo con algo funcional, es típicamente el estilo de consultas que se requieren cuando tenemos que llenar un listado en pantalla con opciones de filtros, ordenamiento y paginado. Ya vamos a ver más adelante que existen otras opciones para resolver esto.

o En consultas que posean subconsultas. Si bien en algunos casos el ORM las puede resolver de forma simple, la realidad es que en la mayoría de los casos vamos a tener problemas de performance graves. Esto se da especialmente con las subqueries dependientes.

o En consultas que se utilicen dentro de procesos Batch o Jobs, ya que, si bien suelen correr en horarios en que no afecten a la aplicación, suelen consumir grandes cantidades de información de la Base de Datos y hacen un uso muy intensivo de ella.

· Entonces ¿cómo resolveríamos las consultas que no recomendamos hacer mediante el modelo LINQ de un ORM? Como generalmente sucede, suele haber más de una opción.

o La que yo recomiendo es utilizar Stored Procedures e invocarlos a través del mismo ORM. Sí, los ORM también permiten ejecutar Stored Procedures definidos en la Base de Datos y manejar sus resultados, y esto es una muy buena noticia ya que nos va a permitir utilizar un esquema u otro dependiendo de la complejidad de la consulta a construir.

o ¿Y la otra opción? Es generar el SQL dentro de nuestra capa de repositorio y ejecutarlo en la Base de Datos a través del ORM. Sinceramente no lo recomiendo ya que es una mala práctica, más que nada porque atenta contra el concepto de Separation of Concerns y dificulta el posterior mantenimiento de nuestra solución.

o Si necesitamos traernos todos los campos de una entidad, usemos un SELECT explícito y no el SELECT * ya que es una muy mala práctica. Además, si no necesitamos todos los campos y tenemos este tipo de SELECT vamos a estar trayéndonos datos de la Base de Datos demás, afectando la performance de la aplicación.

Bien, espero que la lista de tips anteriores les sea de utilidad en sus proyectos. Y recuerden, un ORM es una herramienta más, y hay que saber usarla correctamente para tener buenos resultados.

Usemos el criterio, busquemos un balance. No todo es LINQ, pero tampoco lo condenemos en pos de los Stored Procedures. En mi experiencia, la clave está en usar el sentido común para definir la mejor forma de implementar cada una de las consultas a construir, y basarse en la amplia documentación disponible para definir un conjunto de reglas claras que ayuden a todo el equipo a ir en la misma dirección y utilizar correctamente el ORM elegido.

Autor:

Ing. Ariel Martín Bensussán

.Net Practice Manager

viernes, 8 de mayo de 2015

Desde colaborar con la investigación del cáncer de mama hasta la integración continua

 

clip_image002

Imaginemos la siguiente situación: investigadores médicos necesitan una donación, nada extraño hasta acá, pero en vez de pedir plata piden capacidad de procesamiento. Esto fue lo que hicimos apenas iniciada la Global Azure Bootcamp 2015. Nos repartieron créditos para las cuentas de Azure y nos pidieron deployar un paquete en un "Cloud Service". Este paquete contenía un proceso que analizaba datos de casos de cáncer de mama y los procesaba. Según MS, en esas pocas horas, el tiempo de procesamiento donado fue del equivalente a 23 años de análisis de datos en un solo ordenador.

El segundo Lab, aunque un poco más banal, pero no por eso menos interesante, consistió en realizar una web app que analizaba los datos de telemetría generados por un juego de carreras de autos (un ejemplo de desarrollo usando XNA). Estos datos generados en forma asincrónica a la carrera permiten determinar el funcionamiento de los parámetros del juego en general permitiendo evaluar mejoras para próximos juegos.

Otra situación: tenemos una idea para una aplicación mobile, pero nos queremos dedicar de lleno al frontend y no complicarnos en como resguardar los datos o como generar las conexiones con los servidores web que funcionan de backend. Bueno, los mobile services, presentan la solución; una aplicación web configurada para responder con REST api's y conexión directa con una base de datos. Al crear este tipo de servicios nos genera un proyecto con la estructura completamente configurada para con sólo tocar un par de líneas ya tenemos un backend completamente funcional, y altamente escalable. Recomiendo ver los fuentes que están en las referencias para poder ver la simpleza y la capacidad de expansión de la solución.  

Al iniciar la tarde, las charlas se centraron en las bases de datos. Primero sobre el servicio de base de datos brindado por Azure con SQL Database  (que, aunque parecido, no es igual a SQL Server) el cual brinda la disponibilidad de una base completamente configurada y a disposición del desarrollador en cuestión de minutos. Los que hemos trabajado con datacenters sabemos los días, semanas y meses que estas tareas pueden llevar. Azure nos asegura que siempre va a estar actualizada a nivel de seguridad y con nuevos features (por ejemplo: data masking, entre otros)   que muy probablemente tarden algún tiempo en estar disponibles para las bases SQL Server.          

Segundo, con el ejemplo de la experiencia de Autocosmos (un sitio de publicaciones de venta de autos) donde se tenía la necesidad de dar mayor velocidad de respuesta a las búsquedas, se mostró como con la utilización de los servicios de Redis para hacer una cache trasversal a los servers de las bases. Como en definitiva no solucionaba la demora en la búsqueda dentro de los SQL server, decidieron hacer una reingeniería y modificar la búsqueda para que la haga sobre la base noSQL Azure Search, la cual entre otros beneficios les permitía:

· Utilizar un scoring definido por ellos para ,por ejemplo, beneficiar las publicaciones pagas

· Utilizar geo-localización para beneficiar los resultados más cercanos al usuario

· Realizar por medio de facets la sumatoria de registros que se encontraron para todas las categorías

Además de brindar por la plataforma propia de Azure una alta escalabilidad.

Al caer la tarde, presentaron la solución de Azure Media Servicies que brinda una importante cantidad de features listos para ser utilizados como Video-On-Demand (VOD), Live Streaming, Dynamic Packaging, Dynamic Encryption, Azure Media Player, Live Encoding, Dynamic Manifests y Rendered Sub-Clips. Aunque para un nicho de público muy determinado, la posibilidad de que estos servicios se puedan contratar y que nuestra empresa sea la próxima Netflix, es por lo menos interesante.

El evento se cerró con la presentación de algunas características del portal de Azure y su  integración con Visual Studio Online, donde se mostró como hacer deploys continuos a partir del fuente en VSO.

La plataforma Azure permite, con una inversión inicial muy baja, disponer de un gran abanico de servicios listos para consumir y con una alta escalabilidad. Podemos tener una VM con Windows desde $111 ARS/Mes, o con Linux, pasando por un SQL Server desde $111 ARS/Mes, MongoDB u  Oracle, siguiendo con servidores Web (gratuitos) y llegando a almacenamiento y CDN, sin dejar de lado las más de 3000 soluciones que se ofrecen en el market de esta plataforma. Se presenta como una gran solución para pymes y startups, pero también como solución a los clásicos datacenters onsite permitiendo hacer PDR (Plan de recuperación de desastres), backups, contingencias, pruebas de concepto, etc., con una inversión mucho menor que con las soluciones, hasta ahora, tradicionales.

clip_image004

Referencias:

http://argentina.azurebootcamp.net/

http://global.azurebootcamp.net/

https://github.com/southworkscom/GAB-Arg-2015

 

¡Gracias Marcelo Mosquera por tu contribución!