jueves, 29 de octubre de 2015

TypeScript, lo mejor de dos mundos

Siempre me gustaron los tipos, los conceptos de la POO (clases, herencia, etc) por lo que JavaScript siempre me hizo ruido.

Por supuesto, como herramienta poderosa y popularizada que es, la usé, la uso y la seguiré usando, pero nunca me sentí tan cómodo como utilizando un lenguaje más estructurado.

Entonces me topé con TypeScript. Un lenguaje de código abierto que es un superconjunto de JavaScript (casi cualquier código JavaScript puede usarse como código TypeScript) que tiene la opción de usar nociones como tipos, clases, herencias, interfaces, etc. y que cuando compila genera código JavaScript.

clip_image001

http://aaronpowell.github.io/talks/typescript/img/not-sure-if-typescript.jpg

No es el primer intento de hacer un lenguaje con esas nociones que compile a JavaScript, pero si es uno de los pocos que lo hace ampliando el propio JavaScript, lo cual nos permite incluso usar las librerías ya desarrolladas para JavaScript (JQuery, Backbone, Knockout, etc.) y con un fuerte impulso desde su creador (MIcrosoft) y desde otras empresas (por ejemplo, fue adoptado para hacer Angular 2). También está intengrando con cada build cada vez más conceptos de ECMAScript 6. A su vez, fue creado por Anders Hejlsberg (uno de los creadores de C#, padre de Turbo Pascal y Diseñador en Jefe de Delphi) lo cual es para mí una garantía de buen lenguaje.

Al estar construido sobre JavaSccript, la curva de aprendizaje es realmente suave para cualquier persona que ya conozca JavaScript, pudiendo conseguir grandes resultados desde el primer momento sin demasiado esfuerzo.

Con TypeScript, podemos optar en todo momento, y en cada caso en particular, por usar los conceptos ya citados y así lograr un código más fácil de mantener y escalar. Pero no estamos obligados. De esta manera, podemos elegir usar una variable tipada en una línea, y en la línea siguiente usar una variable sin tipo, para logra la flexibilidad que eso nos da mientras el chequeo de tipos nos proporciona contexto en por ejemplo que se espera en los parámetros de una función a la vez que nos facilita el uso de herramientas, inferencia de tipos, y demás beneficios de los lenguajes tipados.

Abierto e Interoperable

Dado que TypeScript se compila a JavaScript, el código final se puede ejecutar en cualquier browser, en cualquier máquina y en cualquier sistema operativo. Si soporta JavaScript, entonces podrá ejecutar el resultado de nuestro trabajo realizado en TypeScript. Esto nos ofrece una gama de posibilidades enorme, por ser JavaScript un estándar ya aceptado por todos. Es más, se pueden usar los sistemas existentes de construcción, chequeos de links y cualquier otro sistema hecho para operar sobre código JavaScript, lo cual reduce posibles costos de migración en caso de empezar a usar TypeScript si ya teníamos una infraestructura armada para JavaScript.

TypeScript es abierto. El lenguaje TypeScript está disponible a través de la Open Web Foundation´s Final Specification Agreement. La implementación del compilador hecha por Microsoft está subida a GitHub ( https://github.com/Microsoft/TypeScript ) bajo la licencia Apache 2.0. Allí también se pueden encontrar instrucciones de instalación, uso y un roadmap de los próximos pasos que se quieren hacer sobre el lenguaje.


Más información:

http://blogs.msdn.com/b/typescript/

http://www.typescriptlang.org/

http://blogs.msdn.com/b/somasegar/archive/2012/10/01/typescript-javascript-development-at-application-scale.aspx

http://programandonet.com/web/primeros-pasos-con-typescript

https://github.com/Microsoft/TypeScript


Autor:

Fernando Berriel

Software Developer .Net

jueves, 17 de septiembre de 2015

Rational Appscan, una herramienta para hacer pruebas de seguridad

IBM Rational Appscan es una herramienta de escaneo automática que analiza las vulnerabilidades de las aplicaciones web, encontrando fallas de seguridad y generando un reporte de acciones sobre ellas.

Rational Appscan sigue el siguiente flujo de trabajo: 

image

Configuración: Se tendrán que construir la plataforma, el objetivo de las prueba a realizar, el número de páginas involucradas, etc.

Explorar: Se pretende ir buscando todos los enlaces disponibles en el sitio para poder construir una estructura jerárquica.

Se envían peticiones y, en función de las respuestas, identifica los lugares donde hay margen para la vulnerabilidad. 

Prueba: En esta etapa, Rational Appscan ataca el sitio para encontrar las debilidades, buscando principalmente los agujeros de seguridad y clasificando la severidad del riesgo.

Se podrán encontrar en el ciclo de prueba nuevos enlaces en la aplicación, por lo cual Rational Appscan comenzará una nueva etapa de exploración. Esto se repetirá hasta que no se encuentren más conexiones.

El número de análisis lo podrá asignar el usuario en la configuración.

Reporte: Cuando concluyan las pruebas, se podrá generar un informe con todos los resultados, el cual incluirá las medidas para poder corregir los problemas.

Estos informes se pueden personalizar para satisfacer sus necesidades.

Las principales características y ventajas de esta herramienta son:

  • Identifica las vulnerabilidades de las aplicaciones y sitios Web.
  • Ofrece pruebas de seguridad como un complemento a las pruebas tradicionales de funcionalidad y rendimiento.
  • Provee recomendaciones para la eliminación de fallas de seguridad.
  • Genera reportes personalizados de acuerdo a las necesidades del proyecto.
  • Hacer pruebas de seguridad de aplicaciones antes de liberarlas.
  • Permite analizar múltiples aplicaciones.
  • Las pruebas pueden programarse para ejecutarse en horas no productivas
  • Los reportes proporcionan un mejor entendimiento del estado de seguridad.
  • Permite visualizar el progreso de la remediación de vulnerabilidades a lo largo del tiempo.
  • Está alineado con estándares internacionales como OWASP.

Rational Appscan nos ayudará a realizar pruebas de seguridad identificando las vulnerabilidades de nuestra aplicación y sugiriendo recomendaciones de reparación de forma tal que se garanticen ciertos niveles de seguridad de la aplicación que está saliendo a producción.


Autora:

Lic. María Eugenia Spadaro

Analista QA

jueves, 3 de septiembre de 2015

ISO/IEC 27001: Sistemas de Gestión de Seguridad de la Información – Origen | Parte III

La norma BS 7799 de BSI aparece en 1995 (precursora de la ISO/IEC 27001). Su objeto era proporcionar a las organizaciones y/o empresas un conjunto de buenas prácticas para la gestión de la seguridad de su información. La primera parte de la BS 7799-1 es una guía de buenas prácticas, para la cual no se estableció originalmente un esquema de certificación. La segunda parte, BS 7799-2, fue publicada en 1998. Dicha norma originalmente es la que estableció los requisitos de lo que inicialmente se conoció como un ISMS, Information Security Management System, en español Sistema de seguridad de la información, SGSI, para ser certificable por una entidad independiente.

Ambas partes de la BS 7799 se revisaron en 1999 y la primera parte se adoptó por ISO/IEC, sin cambios sustanciales, como ISO 17799 en el año 2000. Asimismo, en 2002, se revisó la BS 7799-2 para adecuarla a la filosofía de normas ISO de sistemas de gestión.
Las Normas “BS” de la British Standards Institution llevan el prefijo “BS” con carácter internacional. Son el origen de normas actuales como ISO 9001, ISO 14001 u OHSAS 18001.

En el año 2005, este esquema se publicó por ISO como estándar ISO 27001, al tiempo que se revisó y actualizó ISO 17799-2.

En el año 2015, a nivel internacional, se estarán terminando de modificar todas las normativas ISO que llevan las mismas secciones de base y cláusulas, como así también han sido actualizadas. La última de las ISO/IEC de Sistemas de Gestión modificadas ha sido la ISO/IEC 9001.

imagen ISO

En 2007 la ISO 17799 se renombra como ISO 27002:2005.

ISO ha continuado, y aún lo hace, desarrollando otras normas dentro de la serie 27000 que sirvan de apoyo a las organizaciones en la interpretación e implementación de ISO/IEC 27001, que es la norma principal y única certificable dentro de la serie.

En Argentina, este año se publica la ISO-IRAM 27001:2015 y las empresas deberán decidir en el momento de la certificación con cuál ISO lo harán. Y allí nace un interrogante, ¿cuál usar y por qué?Cuestión que dejamos para otra serie de artículos futuros.

Autora:
Ing. Lic. Marcela Meyorín Lorenzo
IT Infraestructure & Security
CISA, Lead Auditor 25999/22301, ITIL v.3 Foundation/ISO-IEC 20000/AI 9001.

miércoles, 19 de agosto de 2015

ISO/IEC 27001: Sistemas de Gestión de Seguridad de la Información - La Familia | Parte II.

La familia 27K, conocida como la ISO/IEC 27000 es un conjunto de estándares desarrollados -o en desarrollo en algún caso- por ISO (International Organization for Standardization) e IEC (International Electrotechnical Commission), que proporcionan un marco de gestión de la seguridad de la información que puede ser aplicable en cualquier organización: pública o privada, grande o pequeña.

27001: Es la norma principal de la serie y contiene los requisitos del sistema de gestión de seguridad de la información.

27002: Es una guía de buenas prácticas que describe los objetivos de control y controles recomendables en cuanto a seguridad de la información. No es certificable.

27003: Es una guía que se centra en los aspectos críticos necesarios para el diseño y la implementación exitosa de un SGSI de acuerdo a ISO/IEC 27001.

27004: Es una guía para el desarrollo y utilización de métricas y técnicas de medición aplicables para determinar la eficacia de un SGSI y de los controles o grupos de controles implementados según ISO/IEC 27001.

27005: Proporciona directrices para la gestión del riesgo en la seguridad de la información.

Existen otras muchas ISO de la familia que pueden utilizarse, pero se tendrá en cuenta que se necesita aplicar en cada organización. Asimismo, en los últimos años se han desarrollado normas para industrias y/o servicios: Salud, Entidades Financieras, Telecomunicaciones, Cloud Computing, etc.

En particular, se debe tener en cuenta que la ISO/IEC 27013 une las ISO 27001 y la ISO 20000, unificando así la seguridad de la información, como así también la gestión de servicios de TI.

En 2013 se ha actualizado la norma. La transición se describe en la siguiente figura que las relaciona:

clip_image002

Fuente www.iso27001.es


Autora:
Ing. Lic. Marcela Meyorín Lorenzo
IT Infraestructure & Security
CISA, Lead Auditor 25999/22301, ITIL v.3 Foundation/ISO-IEC 20000/AI 9001.

miércoles, 5 de agosto de 2015

ISO/IEC 27001: Sistemas de Gestión de Seguridad de la Información – Haciendo un poco de historia | Parte I

La ISO 27001 es la norma que establece los requisitos mínimos para implantar, operar, mantener y mejorar un Sistema de Gestión de Seguridad de la Información (SGSI).

Dicho estándar internacional fue publicado como tal por la International Organization for Standardization (ISO) y por la International Electrotechnical Commission (IEC) en octubre del año 2005 (actualmente existe una versión 2013) y es el único aceptado a nivel internacional para la gestión de la seguridad de la información.

La norma, que data de hace casi 20 años, ha sido resultado de la evolución de otros estándares relacionados con la seguridad de la información.

La ISO 27001 es la principal de la serie 27000 y contiene los requisitos del sistema de gestión de seguridad de la información. Su origen primigenio es la norma BS 7799-2:2002, con la cual se certifican los SGSI (Sistemas de Gestión de Sistemas de Información) de las organizaciones a nivel internacional. Pero al no ser obligatoria, algunas compañías deciden implantar los controles y/o el Sistema de Gestión de Seguridad de la Información y no certificar.

Asimismo, la norma enumera en forma de resumen los objetivos de control y controles que desarrolla la ISO 27002, perteneciente a la familia 27k, para que sean seleccionados por las organizaciones en el desarrollo de sus SGSI. A  pesar de no ser obligatoria la implementación de todos los controles mencionados, la organización deberá justificar la no aplicabilidad de los mismos.

En cuanto a su denominación, varios países le asignan distintos nombres. En España está publicada como UNE-ISO/IEC 27001:2007 y puede adquirirse online en AENOR (también en lengua gallega). Aunque hay que tener en cuenta que en el 2009, se publicó un documento adicional de modificaciones (UNE-ISO/IEC 27001:2007/1M:2009). Otros países donde está publicada en español son, por ejemplo, Colombia (NTC-ISO-IEC 27001), Venezuela (Fondonorma ISO/IEC 27001), Argentina (IRAM-ISO IEC 27001), Chile (NCh-ISO27001), México (NMX-I-041/02-NYCE) y Uruguay (UNIT-ISO/IEC 27001).

Actualmente, en los distintos países se está traduciendo la última versión del año 2013. En Argentina, la misma se publicará a fines de este año.


Autora:
Ing. Lic. Marcela Meyorín Lorenzo
IT Infraestructure & Security
CISA, Lead Auditor 25999/22301, ITIL v.3 Foundation/ISO-IEC 20000/AI 9001.

miércoles, 15 de julio de 2015

DevOps al rescate

Motivación/Problemática:

En los últimos años, el desarrollo de software ha cambiado radicalmente, tal vez como nunca antes. La introducción de las metodologías ágiles está modificando la manera en que hacemos y pensamos el software.

Si bien es una realidad que la Agilidad ha acercado sustancialmente al mundo de desarrollo con el mundo del negocio y sus necesidades, y que las entregas incrementales y frecuentes aportan valor de manera sostenida (con todas las ventajas que esto conlleva), también es verdad que este “enamoramiento” entre el negocio y desarrollo ha dejado al descubierto nuevas falencias, o tal vez en realidad solo ha puesto mucho más en evidencia ciertas problemáticas que existían desde siempre.

Aunque las metodologías ágiles pueden ayudar a acelerar el proceso de desarrollo, este es sólo un paso en el largo y complejo camino hacia la verdadera entrega de valor constante y sostenido.

Es que muchas organizaciones, en el afán de generar valor de forma más frecuente, lo único que logran es acelerar el proceso de desarrollo, para luego chocarse con la dura realidad de que llevar esos nuevos desarrollos a producción es un proceso tedioso y burocrático que hace que los nuevos entregables se acumulen en una cola interminable.

Y esto es sólo la parte operativa de la cuestión. Ni que hablar de la tensión y el mal clima que se genera muchas veces entre los equipos de Desarrollo y Operaciones, cuando unos no se sienten comprendidos, valorados o apoyados por los otros.

¿Por qué sucede esto? Por diversas cuestiones, pero si tuviéramos que resumirlas en una sola, esto ocurre porque Desarrollo y Operaciones no tienen un objetivo común.

Mientras los programadores quieren desarrollar y realizar pequeñas entregas lo más rápido posible, de manera de impactar sus cambios en producción de forma frecuente, los administradores de la infraestructura buscan mantener la estabilidad y evitar la disrupción constantemente.

Otro motivo, es la falta de comunicación e interacción entre estos equipos y la consecuente imposibilidad de ponerse en el lugar del otro que esto genera, fomentando la desconfianza y las acusaciones cruzadas ante incidentes o situaciones delicadas.

image

¿Cómo ataca DevOps esta problemática?

Para entender cómo DevOps puede ayudarnos a solucionar esta situación, antes debemos entender que es DevOps.

Algunos dicen que DevOps es un nuevo rol. Otros dicen que es una nueva metodología, mientras que también hay quienes afirman que es una nueva manera de pensar, una especie de “movimiento”. Otros tantos, más bien lo asocian con un conjunto de prácticas y herramientas. Yo creo que DevOps es una mezcla de todo eso, y más. Es una nueva forma de colaboración entre los equipos de desarrollo y operaciones, que por supuesto, involucra nuevos roles o perfiles, con sus respectivos skills, y además es un cambio en la cultura organizacional, y quizás también, en los procesos de la compañía. Y todo esto, soportado por nuevas prácticas y herramientas.

Entonces, podemos decir que DevOps es la práctica de Desarrollo y Operaciones participando juntos en el ciclo de vida completo del proyecto, desde el diseño temprano hasta el soporte a producción.

Si bien DevOps comparte los valores y principios de las metodologías ágiles, donde las personas están por sobre los procesos y las herramientas, lleva todos esos conceptos al siguiente nivel.

image 

Desde esta perspectiva, DevOps simplemente extiende los principios de Agile mucho más allá de los límites del código, alcanzando el proceso de Delivery completo.

DevOps llego para reducir, y en lo posible eliminar, la brecha que hoy existe entre Desarrollo y Operaciones, de manera de incorporar definitivamente la administración de la Infraestructura en la ecuación. Para ello, hace hincapié en conceptos básicos como la comunicación, la colaboración y la integración, entre otras cosas. Si queremos que Desarrollo y Operaciones cooperen como un verdadero equipo, con el objetivo común de satisfacer las necesidades del negocio, entonces es imperativo lograr el involucramiento de Operaciones como actor principal desde el principio de un proyecto, junto a Desarrollo y al negocio, promoviendo una cultura que rompa las barreras existentes y que fomente la visibilidad y empatía entre las distintas áreas. Para lograr esta sinergia necesaria entre equipos, DevOps toma los valores, principios y muchos de los métodos de las metodologías ágiles y los aplica en la administración de los sistemas.

Para conseguir este ambicioso objetivo, DevOps se basa en un conjunto de prácticas, automatización y herramientas. Pero por sobre todo, se basa en que cada uno de los equipos, Desarrollo y Operaciones, reconozca la existencia del otro y trabajen juntos de manera colaborativa entendiendo las responsabilidades y necesidades de cada uno.

Las herramientas disponibles son muchas y muy variadas. Pero aquí, lo importante es que DevOps implica mucho más que implementar algunas prácticas utilizando las herramientas. DevOps implica entender los valores y principios fundamentales que lo conforman y aplicarlos con el convencimiento de que el software no está realmente listo hasta que es exitosamente desplegado en producción y accesible a los usuarios para cubrir sus necesidades reales.

image 

Conclusión

DevOps es un conjunto de conceptos relacionados que promueven la colaboración e interacción entre los desarrolladores y Operaciones para satisfacer las necesidades del negocio.

La iniciativa de DevOps, generalmente, diverge en dos marcadas estrategias:

Algunas organizaciones generan un nuevo rol al que denominan DevOp, que es una especie de mediador y facilitador entre programadores y sysadmins. Estos perfiles DevOp, deben contar con habilidades técnicas mixtas y adicionalmente habilidades blandas como comunicación, resolución de conflictos y liderazgo, más allá de que quizás no terminen ejerciendo autoridad formal sobre los miembros de Desarrollo ni de Operaciones.

Por otro lado, otras ven a DevOps como una transformación en la organización mediante la cual programadores y sysadmins trabajan colaborativamente unos con otros.

Ambas estrategias son válidas y la combinación de ambas quizás se aproxime a la solución ideal, aunque el enfoque definitivo dependerá de las necesidades de cada organización.


Sobre el autor:

Trabajando hace más de 12 años en proyectos de IT como Líder de Proyecto, Project Manager, Scrum Master y Agile Coach, Alejandro Faguaga posee una amplia y variada experiencia en Gestión de Proyectos IT y Metodologías Ágiles.

viernes, 19 de junio de 2015

JqGrid en Aplicaciones MVC con Entity Framework

En muchas aplicaciones web es necesario usar grillas que muestren la información contenida en una base de datos y permitan ordenar, filtrar y paginar los datos. Dependiendo de las herramientas que se usen, esto puede requerir más o menos trabajo. Este artículo presenta una combinación de herramientas para facilitar el manejo de grillas JqGrid de forma sencilla y con buena performance, debido a que el filtrado, ordenamiento y paginado se hace a nivel de base de datos.

Para el ejemplo utilizaremos Entity Framework, pero se puede adaptar fácilmente a NHibernate.

El código de ejemplo está creado con Visual Studio 2013.

 

Creación del proyecto

1- Crear un nuevo proyecto MVC en Visual Studio

clip_image002

2- Seleccionar un proyecto de tipo MVC y configurar la autenticación del proyecto como “No Authentication” (para hacerlo lo más simple posible)

clip_image004

3- Agregar los siguientes paquetes Nuget, en su última versión:

· Entity Framework

clip_image005

· JqGrid

clip_image006

· MvcJqGrid

clip_image007

4- En el Nuget Package Manager, además actualizar todos los paquetes con la opción “Update All”, de manera de trabajar con las últimas versiones.

 

Grilla básica

La aplicación que vamos a crear es una aplicación MVC básica. En una aplicación real sería conveniente crear capas intermedias para las diferentes responsabilidades, pero en este caso vamos a interactuar con el repositorio de datos directamente desde el Controller para simplificar el ejemplo.

1- Creamos dos clases de modelo en la carpeta Models:

Employee

public class Employee

{

[Key]

public virtual int Id { get; set; }

public virtual string FirstName { get; set; }

public virtual string LastName { get; set; }

public virtual decimal Salary { get; set; }

public virtual Organization Organization { get; set; }

}

Organization

public class Organization

{

[Key]

public virtual int Id { get; set; }

public virtual string Name { get; set; }

public virtual string Address { get; set; }

}

2- Creamos un nuevo Controller en la carpeta controllers, con el template de Entity Framework

clip_image008

3- Seleccionamos el modelo y creamos un nuevo context

clip_image009

4- Agregamos un link al nuevo controller en Views/Shared/_Layout.cshtml

<li>@Html.ActionLink("Employees", "Index", "Employee")</li>

Si en este momento ejecutamos la aplicación y vamos a la ruta /Employee, aparece el listado de empleados

5- En este punto deberíamos poder crear algunos empleados de ejemplo

clip_image011

Si abrimos la base de datos desde Visual Studio deberíamos poder ver las tablas que creó Entity Framework.

clip_image012

6- Agregamos algunos datos de prueba en las dos tablas (al menos 6 empleados, así se puede probar la paginación):

clip_image013

clip_image014

Ahora vamos a reemplazar el listado por default que creó el proyecto de MVC por una grilla de JqGrid

7- Borramos la tabla y la cambiamos por este código usando el helper de MvcJqGrid para crear una grilla. También se podría agregar directamente la grilla por Javascript:

@(Html.Grid("employee")

.SetCaption("Employees")

.AddColumn(new Column("FirstName").SetLabel("First Name"))

.AddColumn(new Column("LastName").SetLabel("Last Name"))

.AddColumn(new Column("Salary").SetLabel("Salary").SetSearchOption(SearchOptions.Equal))

.AddColumn(new Column("Organization.Name").SetLabel("Org. Name"))

.AddColumn(new Column("Organization.Address").SetLabel("Org. Address"))

.SetUrl("/Employee/List/")

.SetAutoWidth(true)

.SetHeaderTitles(true)

.SetViewRecords(true)

.SetPager("pager")

.SetSearchToolbar(true)

.SetSearchOnEnter(false)

.SetSearchClearButton(true)

.SetSearchToggleButton(false)

.SetSortName("LastName")

.SetRowNum(5))

8- En el controller EmployeeController agregamos el método List, en donde obtenemos los empleados de la base de datos y los transformamos a un objeto JSON en el formato que espera JqGrid:

public ActionResult List()

{

var employees = db.Employees.ToList();

var jsonData = new

{

page = 1,

records = employees.Count,

rows = (

from e in employees

select new

{

id = e.Id,

cell = new Object[]

{

e.FirstName,

e.LastName,

e.Salary

}

}).ToArray()

};

return Json(jsonData, JsonRequestBehavior.AllowGet);

}

El paquete NuGet de JqGrid no agrega automáticamente bundles para Javascript y CSS, por lo cual hay que agregarlos manualmente.

9- En App_Start/BundleConfig.cs agregamos bundles para JqGrid

bundles.Add(new ScriptBundle("~/bundles/jqgrid").Include(

"~/Scripts/i18n/grid.locale-en.js",

"~/Scripts/jquery.jqGrid.min.js"));

bundles.Add(new StyleBundle("~/Content/cssJqGrid").Include(

"~/Content/jquery.jqGrid/ui.jqgrid.css",

"~/Content/themes/base/all.css"));

10- Y los agregamos en el layout, en la sección <head>en este orden (es necesario mover el bundle de JQuery agregado al crear el proyecto a la sección de <head>)

@Styles.Render("~/Content/css")

@Styles.Render("~/Content/cssJqGrid")

@Scripts.Render("~/bundles/modernizr")

@Scripts.Render("~/bundles/jquery")

@Scripts.Render("~/bundles/jqgrid")

Si ejecutamos la aplicación en este momento ya se deberían ver los datos de empleados en la grilla de JqGrid. En este punto todavía no va a funcionar el ordenamiento, filtros ni paginado.

clip_image016

Agregar acciones en la grilla

Para agregar las funcionalidades de ordenamiento, filtros y paginado tenemos que modificar el método List de EmployeeController para que tome como parámetro un objeto GridSettings y agregar unas clases de soporte.

1- Agregamos las clases PagedQuery, PagedList y JqGridExtensions. Al final del artículo se muestra el código de estas clases, que se encargan de transformar los parámetros de JqGrid en queries que Entity Framework pueda ejecutar.

2- Modificamos el método List del Controller:

public ActionResult List(GridSettings grid)

{

var query = grid.ToPagedQuery<Employee>();

var employees = query.ExecuteOn(db.Employees);

var jsonData = new

{

total = (int)Math.Ceiling((double)employees.TotalItems / employees.PageSize),

page = employees.PageNumber,

records = employees.TotalItems,

rows = (

from e in employees

select new

{

id = e.Id,

cell = new Object[]

{

e.FirstName,

e.LastName,

e.Salary

}

}).ToArray()

};

return Json(jsonData, JsonRequestBehavior.AllowGet);

}

Con estos cambios ya deberían funcionar automáticamente el ordenamiento, paginado y los filtros, todo ejecutado directamente en la base de datos. En la siguiente imagen se puede ver el filtrado por la segunda columna:

clip_image018

Entidades relacionadas

El mismo esquema funciona con entidades relacionadas, simplemente usando la notación EntidadRelacionada.Propiedad

1- Agregamos dos columnas correspondientes a la entidad Organization en la misma grilla, modificando el método List del controller:

AddColumn(new Column("Salary").SetLabel("Salary").SetSearchOption(SearchOptions.Equal))

.AddColumn(new Column("Organization.Name").SetLabel("Org. Name"))

.AddColumn(new Column("Organization.Address").SetLabel("Org. Address"))

.SetUrl("/Employee/List/")

2- Y las agregamos también en el método List del controller:

e.Salary,

e.Organization.Name,

e.Organization.Address

}

Con estos cambios alcanza para que se vean las columnas relacionadas en la grilla y funcionen los filtros y el ordenamiento a través de la relación Employee – Organization.

clip_image020

Clases auxiliares

PagedQuery

Esta clase mantiene y aplica los criterios de filtros, ordenamiento y paginado. Trabaja sobre IQueryable en forma genérica, por lo que se puede aplicar a cualquier fuente de datos. En este caso la usamos contra un repositorio de EntityFramework.

public class PagedQuery<TModel>

{

public Expression<Func<TModel, bool>> Condition { get; set; }

private Expression<Func<TModel, object>>[] SortKeySelectors { get; set; }

private int PageSize { get; set; }

private int Page { get; set; }

private bool[] SortAscending { get; set; }

private PagedQuery(Expression<Func<TModel, bool>> condition)

{

PageSize = 0;

Page = 0;

Condition = condition;

SortKeySelectors = new Expression<Func<TModel, object>>[0];

SortAscending = new bool[0];

}

public static PagedQuery<TModel> All()

{

return new PagedQuery<TModel>(null);

}

public PagedQuery<TModel> FetchPage(int page)

{

Page = page;

return this;

}

public PagedQuery<TModel> Size(int pageSize)

{

PageSize = pageSize;

return this;

}

private PagedQuery<TModel> OrderBy(Expression<Func<TModel, object>>[] sortKeySelectors, bool[] ascending)

{

SortKeySelectors = sortKeySelectors;

SortAscending = ascending;

return this;

}

public PagedQuery<TModel> OrderBy(IEnumerable<string> propertyNames, bool[] ascending)

{

return OrderBy(propertyNames.Select(NestedPropertyGet).ToArray(), ascending);

}

public static IOrderedQueryable<TModel> ObjectSort(IQueryable<TModel> entities, Expression<Func<TModel, object>> expression, bool ascending)

{

var unaryExpression = expression.Body as UnaryExpression;

if (unaryExpression != null)

{

var propertyExpression = (MemberExpression)unaryExpression.Operand;

var parameters = expression.Parameters;

if (propertyExpression.Type == typeof(DateTime))

{

var newExpression = Expression.Lambda<Func<TModel, DateTime>>(propertyExpression, parameters);

return ascending ? entities.OrderBy(newExpression) : entities.OrderByDescending(newExpression);

}

if (propertyExpression.Type == typeof(int))

{

var newExpression = Expression.Lambda<Func<TModel, int>>(propertyExpression, parameters);

return ascending ? entities.OrderBy(newExpression) : entities.OrderByDescending(newExpression);

}

if (propertyExpression.Type == typeof(decimal))

{

var newExpression = Expression.Lambda<Func<TModel, decimal>>(propertyExpression, parameters);

return ascending ? entities.OrderBy(newExpression) : entities.OrderByDescending(newExpression);

}

throw new NotSupportedException("Object type resolution not implemented for this type");

}

return ascending ? entities.OrderBy(expression) : entities.OrderByDescending(expression);

}

public PagedList<TModel> ExecuteOn(IQueryable<TModel> repository)

{

int totalItems = 0;

repository = ApplyFilter(repository);

if (PageSize > 0)

{

totalItems = repository.Count();

}

repository = ApplySorting(repository);

repository = ApplyPaging(repository);

return new PagedList<TModel>(repository.ToList(), Page, PageSize, totalItems);

}

private IQueryable<TModel> ApplyFilter(IQueryable<TModel> repository)

{

if(Condition != null)

{

repository = repository.Where(Condition);

}

return repository;

}

private IQueryable<TModel> ApplySorting(IQueryable<TModel> repository)

{

for (int i = 0; i < SortKeySelectors.Length; i++)

{

repository = ObjectSort(repository, SortKeySelectors[i], SortAscending[i]);

}

return repository;

}

private IQueryable<TModel> ApplyPaging(IQueryable<TModel> repository)

{

if(PageSize > 0)

{

repository = repository.Skip((Page - 1)*PageSize).Take(PageSize);

}

return repository;

}

private static Expression<Func<TModel, object>> NestedPropertyGet(string propertyChain)

{

var properties = propertyChain.Split('.');

var type = typeof(TModel);

var parameter = Expression.Parameter(type, "x");

Expression expression = parameter;

PropertyInfo propertyInfo = null;

foreach (var propertyName in properties)

{

propertyInfo = type.GetProperty(propertyName);

expression = Expression.Property(expression, propertyInfo);

type = propertyInfo.PropertyType;

}

if (propertyInfo != null && propertyInfo.PropertyType.IsValueType)

{

expression = Expression.Convert(expression, typeof(object));

}

return Expression.Lambda<Func<TModel, object>>(expression, parameter);

}

}

PagedList

Es una clase simple, sin lógica, que contiene los resultados de una consulta paginada.

public class PagedList<TModel> : IEnumerable<TModel>

{

public int PageNumber { get; private set; }

public int PageSize { get; private set; }

public int TotalItems { get; private set; }

private IList<TModel> Items { get; set; }

public PagedList(IList<TModel> items, int pageNumber, int pageSize, int totalItems)

{

Items = items;

PageNumber = pageNumber;

PageSize = pageSize;

TotalItems = pageSize == 0 ? items.Count : totalItems;

}

public IEnumerator<TModel> GetEnumerator()

{

return Items.GetEnumerator();

}

IEnumerator IEnumerable.GetEnumerator()

{

return GetEnumerator();

}

}

JqGridExtensions

Se encarga del mapeo entre los parámetros de JqGrid y la clase PagedQuery. Contiene el método ToPagedQuery, que crea un PagedQuery en base a los parámetros enviados por JqGrid, conteniendo los criterios de ordenamiento y filtrado, así como los datos relacionados con el paginado.

public static class JqGridExtensions

{

public static PagedQuery<TModel> ToPagedQuery<TModel>(this GridSettings settings)

{

var query = PagedQuery<TModel>.All().Size(settings.PageSize).FetchPage(settings.PageIndex);

if (settings.Where != null)

{

foreach (var rule in settings.Where.rules)

{

query.Condition = query.Condition.Where(rule.field, rule.data, rule.op);

}

}

var sortColumns = (settings.SortColumn + " " + settings.SortOrder).Split(',');

if (!String.IsNullOrWhiteSpace(settings.SortColumn) && sortColumns.Length > 0)

{

var propertyNames =

sortColumns.Select(x => x.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)[0].Trim())

.ToArray();

var ascending =

sortColumns.Select(

x => x.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)[1].Trim() == "asc")

.ToArray();

query = query.OrderBy(propertyNames, ascending);

}

return query;

}

private static Expression<Func<T, bool>> Where<T>(this Expression<Func<T, bool>> query,

string column, object value, string operation)

{

if (string.IsNullOrEmpty(column))

return query;

ParameterExpression parameter = Expression.Parameter(typeof (T), "p");

MemberExpression memberAccess = null;

Expression<Func<T, bool>> finalLambda = null;

foreach (var property in column.Split('.'))

{

memberAccess = Expression.Property

(memberAccess ?? (parameter as Expression), property);

}

if (value == null)

{

return query;

}

foreach (var val in value.ToString().Split(new[] {','}))

{

Expression<Func<T, bool>> lambda = null;

Expression condition = GetCondition(val, memberAccess, operation);

if (condition != null)

lambda = Expression.Lambda<Func<T, bool>>(condition, parameter);

finalLambda = finalLambda == null ? lambda : CombineOr(finalLambda, lambda);

//Si es string vacío también busca por NULL

if (val == string.Empty)

{

condition = GetCondition(null, memberAccess, operation);

if (condition != null)

lambda = Expression.Lambda<Func<T, bool>>(condition, parameter);

finalLambda = finalLambda == null ? lambda : CombineOr(finalLambda, lambda);

}

}

return query == null ? finalLambda : Combine(query, finalLambda);

}

private static Expression GetCondition(string val, MemberExpression memberAccess, string operation)

{

var filter = Expression.Constant

(

ChangeType(val, memberAccess.Type)

);

Expression condition = null;

switch (operation)

{

//equal ==

case "eq":

condition = Expression.Equal(memberAccess, Expression.Convert(filter, memberAccess.Type));

break;

//not equal !=

case "ne":

condition = Expression.NotEqual(memberAccess, filter);

break;

//begins with !=

case "bw":

condition = Expression.Call(memberAccess,

typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),

Expression.Constant(val));

break;

//string.Contains()

case "cn":

condition = Expression.Call(memberAccess,

typeof(string).GetMethod("Contains"),

Expression.Constant(val));

break;

//not begins with !=

case "ew":

condition = Expression.Call(memberAccess,

typeof(string).GetMethod("EndsWith", new[] { typeof(string) }),

Expression.Constant(val));

break;

//less than <

case "lt":

condition = Expression.LessThan(memberAccess, filter);

break;

//less or equal <=

case "le":

condition = Expression.LessThanOrEqual(memberAccess, filter);

break;

//greater than >

case "gt":

condition = Expression.GreaterThan(memberAccess, filter);

break;

//greater or equal than >=

case "ge":

condition = Expression.GreaterThanOrEqual(memberAccess, filter);

break;

}

return condition;

}

private static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> filter1, Expression<Func<T, bool>> filter2)

{

var rewrittenBody1 = new ReplaceVisitor(

filter1.Parameters[0], filter2.Parameters[0]).Visit(filter1.Body);

var newFilter = Expression.Lambda<Func<T, bool>>(

Expression.AndAlso(rewrittenBody1, filter2.Body), filter2.Parameters);

return newFilter;

}

private static Expression<Func<T, bool>> CombineOr<T>(Expression<Func<T, bool>> filter1, Expression<Func<T, bool>> filter2)

{

var rewrittenBody1 = new ReplaceVisitor(

filter1.Parameters[0], filter2.Parameters[0]).Visit(filter1.Body);

var newFilter = Expression.Lambda<Func<T, bool>>(

Expression.OrElse(rewrittenBody1, filter2.Body), filter2.Parameters);

return newFilter;

}

class ReplaceVisitor : ExpressionVisitor

{

private readonly Expression from, to;

public ReplaceVisitor(Expression from, Expression to)

{

this.from = from;

this.to = to;

}

public override Expression Visit(Expression node)

{

return node == from ? to : base.Visit(node);

}

}

private static object ChangeType(string value, Type conversionType)

{

if (conversionType == null)

{

throw new ArgumentNullException("conversionType");

}

if (conversionType.IsEnum)

{

return Enum.Parse(conversionType, value);

}

Guid result;

if (Guid.TryParse(value,out result))

{

return Guid.Parse(value);

}

if (conversionType.IsGenericType &&

conversionType.GetGenericTypeDefinition() == typeof(Nullable<>))

{

if (value == null)

{

return null;

}

var nullableConverter = new NullableConverter(conversionType);

conversionType = nullableConverter.UnderlyingType;

}

return Convert.ChangeType(value, conversionType);

}

}

 

Código completo

En el archivo zip encontrarán la solución completa del ejemplo en Visual Studio 2013.


¡Gracias Guillermo Vasconcelos por tu contribución!

miércoles, 10 de junio de 2015

¿Un Tester QA debe programar?

clip_image002

El Tester de hoy, por naturaleza, debe ser un profesional con elevada capacidad de comunicación, proactivo, analítico, metódico y con conocimientos técnicos que le permitan la ejecución exitosa del ciclo de vida de prueba del software.

Actualmente, las empresas tienen una mayor demanda del servicio Testing QA y eso ha sido una parte fundamental en la reformulación del perfil que debe tener un Tester.

Este crecimiento en la demanda y el hecho de que hoy por hoy el desarrollo de las aplicaciones se han vuelto más complejas y requieren mayor integración con otros sistemas; hicieron surgir la necesidad de automatizar las pruebas, para disminuir los tiempos empleados en el STLC y lograr mayores niveles de calidad.

La implementación de herramientas de automatización para hacer una gestión más efectiva en el tiempo de prueba ha generado que el Tester deba adquirir conocimientos de programación.

La programación, que siendo una disciplina de la Ingeniería de Software, nos va a permitir aprovechar todo el potencial de nuestra profesión.

Qué beneficios podemos obtener como Tester:

· Mejoras en la organización de las pruebas.

· Mayor capacidad para aplicar pruebas complicadas.

· Entendimiento de los problemas que pueda tener la aplicación en las capas anteriores del frontend y a nivel técnico precisar el bug detectado.

· Mejoras en la comunicación con el equipo, ya que al brindar un sistema para recolectar y diseminar información de manera eficaz, proporcionamos una retroalimentación oportuna al equipo de programación.

· Diseño e implementación de script’s para la automatización de pruebas funcionales.

· Estabilización temprana del código evitando el re-trabajo.

· Habilitación de pruebas de regresión y disminución del tiempo aplicado en los ciclos de pruebas.

· Realización de un mayor número de pruebas. Algunos de los problemas hallados por la automatización, tal vez no hubieran sido encontrados utilizando sólo pruebas manuales, debido a limitantes de tiempo.

· Mayor confiabilidad en los resultados.

· Esto nos facilita analizar cómo nuestra labor complementa y asiste a otras áreas. Somos más valiosos como parte integral de la empresa que como un elemento aislado de la misma.

Como les comenté anteriormente, existe un incremento en la demanda de las áreas de Testing de las empresas. Ahora bien, por un motivo de costos, la búsqueda tiene sus exigencias. El Tester buscado es aquel que tenga conocimientos de programación en herramientas de automatización para las pruebas de regresión. Y sumado a los distintos tipos de pruebas (Sistema, integradas, funcional, no funcional), lo harán un recurso muy valioso.

Este punto me invita a pensar. ¿ El Tester Funcional tiene sus días contados?

clip_image004

Pienso que NO. Conozco a muchos Testers que tienen conocimientos del funcionamiento de una aplicación y sus distintas capas, y junto a los conocimientos del negocio le permiten hacer un test exploratorio o manual más eficiente que un programador.

El Tester debe combinar sus conocimientos para poder aumentar su efectividad de ejecución de los ciclos de pruebas y continuar con el crecimiento profesional, a la par del avance tecnológico que le permitirá ser un recurso más preciado.

Siendo una necesidad el aprendizaje de un lenguaje de programación de acuerdo a la meta del Tester o al objetivo del proyecto. ¿Cuál es el más conveniente (Java, .net, php, ruby, Javascritp python, csharp)?

Si bien el tester funcional seguirá activo en los proyectos, cada vez toman más fuerza los testers que manejan distintas herramientas y que conocen lenguajes de programación capaces de automatizar tareas.

clip_image006

 

 

 

 

Autor:

Álvaro Guaramato

Líder Técnico de QA asignado al proyecto del Assessment de Testing & QA en Banco Hipotecario.

viernes, 5 de junio de 2015

Trunks, tags & branches

¡Hola! Les acerco mi experiencia en la organización más eficiente de un directorio de control de versiones de código fuente; especialmente en los casos en donde tenemos diferentes desarrollos en paralelo, o tenemos que realizar diversos releases intermedios, y mantenerlos, mientras seguimos desarrollando una nueva funcionalidad aparte.

Si bien mi experiencia se centra en TFS y SVN, con los cuales vengo trabajando ya desde hace ya varios años, estos conceptos pueden aplicarse también a otros sistemas como GIT, CVS, Mercurial, etc.

Introducción a las herramientas de SCM

Sin ahondar mucho en hechos históricos, podemos inferir que cuando surgieron los equipos de desarrollo de software, nacieron los problemas de tener que sincronizar el trabajo que cada uno de los miembros realizaba. Integrar los cambios y modificaciones, volver atrás una versión, trackear un cambio para detectar cuando se hizo (o quien lo hizo), entre otras cosas, se volvieron actividades comunes en los diferentes proyectos que se realizaban.

Y así surgieron las primeras herramientas de repositorio, que nos brindaron la posibilidad de almacenar nuestro código fuente, versionándolo, resguardándolo y agregándole la metadata necesaria para poder realizar el tracking correspondiente. Con el paso del tiempo fueron evolucionando, convirtiéndose en las hoy llamadas herramientas de SCM (por Software Configuration Management) con una mayor cantidad de características y beneficios, ya que no sólo se encargan de administrar los elementos de nuestro software, sino que además ofrecen soporte a la gestión y administración de los proyectos.

¿Qué beneficios nos aportan las herramientas de SCM?

Actualmente es casi imposible pensar en llevar adelante un proyecto con éxito sin contar con una de estas herramientas para el equipo. Y aún en el caso de que la solución fuera desarrollada por una única persona, el contar con una herramienta de SCM trae numerosos beneficios, más allá de la sincronización de nuestro código fuente:

· Mantenimiento de la integridad de los componentes de nuestra solución (sea código fuente o no)

· Evaluación y tracking de cambios

· Auditoría de la configuración

· Respaldo de versiones anteriores de cada uno de los elementos

· Gestión de actividades y tareas

· Planificación y seguimiento del estado de avance

· Trazabilidad horizontal (tareas asignadas vs. changesets de código)

· Reducción de costos del proyecto como producto de mantener todo más ordenado

Definiciones de Trunk, Branch y Tag

Ahora bien, pasemos a definir tres de los conceptos más importantes que deberemos manejar a la hora de trabajar con una herramienta de SCM y sobre los cuales luego vamos a discutir diferentes estrategias de organización y algunas buenas prácticas de trabajo para que apliquemos en nuestros proyectos.

clip_image001

Trunk (Tronco): Es la línea principal de desarrollo, en donde se realizan los cambios menos importantes o con un menor impacto en el día a día del proyecto. Además, es el lugar en donde se integran los cambios realizados en otros branches una vez que están finalizados y validados. Suele haber un único trunk por proyecto.

Branch (Rama): Es una copia del código fuente, derivada del trunk u otro branch, en un determinado instante. Permite trabajar en paralelo sobre sí misma sin afectar el trunk o el branch de origen. Básicamente existen de dos tipos: los que se utilizan para introducir cambios, correcciones o mejoras significativas en nuestra aplicación, y los que se generan como producto del lanzamiento de un nuevo release.

Tag (Etiqueta): Permite identificar un cierto momento en nuestro ciclo de desarrollo que deseamos preservar para luego poder reconstruir nuestra aplicación tal cual estaba en dicho instante. En otras palabras, un tag es un snapshot. Generalmente se los crea para identificar y preservar un nuevo release o un punto de estabilidad ante futuros cambios.

Otro concepto muy usado actualmente en la gestión de nuestros repositorios de código fuente, especialmente en herramientas más modernas, es el concepto de fork (bifurcación), que es cuando se crea un nuevo proyecto a partir de otro replicando el código fuente en dicho instante, pero con una dirección distinta a la original. En los casos de los proyectos open source se suele dar con mayor frecuencia, especialmente cuando surgen diferencias entre los desarrolladores, o cuando se quiere partir del producto actual para realizar una versión independiente (como por ejemplo sucede con las distribuciones de Linux). En el ámbito de los proyectos de software propietario se suelen dar con menor frecuencia, por ejemplo, cuando se quiere llevar una versión de una aplicación a otro país y hay que aplicar cambios importantes, pero independientes a los del producto original.

Buenas prácticas en la organización del trunk, los branches y los tags

En lo que respecta a la interacción que los desarrolladores y arquitectos tenemos con las herramientas de SCM, quiero enfocarme en esta parte del artículo en algunas buenas prácticas y estrategias acerca de cómo podríamos organizar mejor nuestro repositorio para responder satisfactoriamente al desarrollo de actividades en paralelo, o tener disponibles múltiples versiones de un sistema en el mismo instante. Por ejemplo, para que puedan mantenerse y evolucionarse mientras dichas versiones que viven en los diferentes Stages Pre-productivos de nuestros clientes.

En lo que respecta al manejo del trunk, les propongo las siguientes buenas prácticas para aplicar en sus proyectos de desarrollo:

· Cada uno de nuestros proyectos deberá tener un único trunk como línea base de desarrollo. Si tenemos más de un trunk, entonces algo no estamos haciendo bien… o tenemos un fork y no lo estamos viendo, o son branches tratados como trunk.

· En el trunk no deberemos hacer cambios con un impacto alto, o larga duración, ya que en caso de tener que pensar en armar una entrega, la solución podría no compilar o no ser estable, afectando la calidad del producto final. Los grandes cambios requieren tiempo de estabilización y ejecutar pruebas para validar que todo siga funcionando correctamente, y esto no es algo que podamos hacer de un día para el otro.

· El trunk siempre debe compilar y pasar todas las pruebas unitarias, de integración y de regresión en todo momento. Para lograr este objetivo podemos apoyarnos en alguna herramienta de Integración Continua, como Jenkins por ejemplo, para que nos ayude a correr los tests.

· Si algún miembro del equipo rompe el trunk, es decir, que la solución deja de compilar, se lo suele penalizar con el castigo de tener que traer algo para desayunar al día siguiente… por ejemplo, unas ricas facturas. Esto se suele arreglar con el equipo al comenzar el proyecto, y es interesante ya que todos vamos a estar pendientes de que el trunk esté estable en todo momento, tanto para no tener que traer facturas para todo el equipo, como para cazar a la persona que lo rompió, exigirle el pago y disfrutar de un buen desayuno al día siguiente.

A la hora de gestionar y administrar los diferentes branches, vale la pena aclarar que existen tres grandes estrategias a considerar. Elegir entre una u otra dependerá en gran medida del tipo de proyecto, y de la fase de vida en la que se encuentre (no es lo mismo uno que recién arranca que otro que ya se encuentra operativo y evolucionando continuamente). Las tres estrategias que podemos adoptar son:

· The Never-Branch Strategy

o Generalmente se emplea en proyectos que recién están comenzando y aún no tienen una aplicación funcionando en un ambiente del cliente (productivo o pre-productivo).

o Los desarrolladores trabajan directamente sobre el trunk, lo cual a veces genera que el mismo deje de compilar y/o pasar las pruebas definidas.

· The Always-Branch Strategy

o En el otro extremo, se encuentra la estrategia de generar siempre un branch por cada nueva tarea de codificación que haya que realizar, no importa su complejidad, no importa su duración o esfuerzo. Solamente cuando la tarea se haya realizado completamente y esté correctamente validada, se pasarán los cambios al trunk.

o Se la suele utilizar en proyectos complejos, o en donde el equipo no ha participado en la fase primaria del desarrollo y existe el riesgo de que un cambio desestabilice el resto de la aplicación, o en proyectos en donde existe un management muy exigente.

· The Branch-When-Needed Strategy

o En el medio de las dos estrategias anteriores, se encuentra la estrategia de realizar un nuevo branch sólo si es necesario, generalmente para los cambios más complejos o con un alto impacto en el resto de la aplicación.

o Es la estrategia favorita para los proyectos que ya se encuentren operativos y sobre los cuales tengan que convivir diferentes versiones del mismo: la versión productiva, las versiones pre-productivas y las versiones futuras con nuevos desarrollos o modificaciones.

o Desde mi punto de vista, es la más sensata y la que suelo usar en mis proyectos a partir del primer release de la aplicación a un ambiente del cliente, independientemente de si vaya a producción o simplemente quede en un ambiente de QA previo.

Si optamos, entonces, por implementar las estrategias que poseen branches, es bueno que sepan que existen tres diferentes tipos de branches que podemos crear en el repositorio:

· Feature Branches: O también conocidos como Ramas de Soporte o Ramas de Funcionalidad. Son aquellos que se crean de forma puntual para desarrollar una funcionalidad específica; y una vez estabilizada se sincroniza con lo que está en el trunk.

· Hotfix Branches: Se utilizan en los casos en que tenemos que generar un hotfix para corregir un error de forma urgente, y que suponemos que va a tener un alto impacto en nuestro código y por eso no lo hacemos en el trunk.

· Release Branches: Se crean para poder dar soporte a la salida de una nueva versión de producción de la aplicación. Permiten tener bajo control los entregables de la versión y poder realizar el mantenimiento de estabilización y de corrección sobre ella.

Considerando los dos puntos anteriores, entonces paso a compartirles algunas buenas prácticas para que trabajemos correctamente con Branches sin morir en el intento:

· Idealmente, para cada tarea compleja, que tenga un alto impacto, que sea de larga duración, y/o que aún no sabemos cuándo debemos disponibilizarla al cliente, deberemos trabajarla en un branch aparte para no perjudicar la estabilidad del trunk y de nuestra aplicación en general.

· Integrar en el trunk una funcionalidad desarrollada en un branch únicamente cuando esté totalmente desarrollada y testeada. No hacer integraciones parciales, ya que esto sólo va a terminar afectando la calidad final del producto.

· Tener la funcionalidad desarrollada en branches aparte nos permite, más allá de tenerla completamente desarrollada y validada, la posibilidad de elegir cuándo deseamos integrarla al trunk para liberarla en un próximo release.

· Si vamos a experimentar con nuestra aplicación, por ejemplo realizando alguna PoC (Proof of Concept) probando un nuevo componente o validando un upgrade de algún framework, hagámoslo sí o sí en un branch aparte para no ensuciar el trunk.

· Por cada release que necesitemos preparar para entregar al cliente, creemos un branch aparte, de forma de poder trabajar tranquilamente sobre él, realizando la estabilización final del producto antes de realizar el delivery.

· Sobre los branches de release no se deberán realizar tareas como refactor, code cleaning, etc. Sólo estarán permitidas pequeñas correcciones en código, pero no grandes modificaciones. Esto es en pos de facilitar luego el merging de los fuentes con el trunk.

· Otra de las ventajas de tener branches de release es que nos van a permitir efectuar correcciones en paralelo al desarrollo que se encuentre en el trunk y liberar versiones intermedias antes del siguiente major release a producción.

· Dado que con el tiempo nuestro repositorio va a tender a llenarse de branches, es bueno mantenerlo organizado desde un comienzo. Una buena práctica consiste en agrupar los diferentes tipos de branches. Para ello, se podrán nombrar los diferentes branches agregando un prefijo que indique su tipo; o se podrán crear subcarpetas por cada uno de los tipos (por ejemplo, en TFS esto es posible y queda mucho más limpio). Otra recomendación es seguir una nomenclatura estandarizada y conocida por todo el equipo del proyecto.

Y por último, en lo que respecta al armado de los tags, les propongo seguir las buenas prácticas que enumeraremos a continuación, lo que les facilitará enormemente encontrar y bajarse un determinado hito de la aplicación en un futuro:

· Primero y antes que nada, sobre un tag no deberemos realizar jamás cambios y subirlos al repositorio. Un tag es una versión congelada de la aplicación y no deberá modificarse.

· Realizando una analogía, veamos al tag como un branch en el que los archivos correspondientes al código fuente no evolucionan, sino que permanecen congelados.

· Realizar un tag por cada release de la aplicación que se realice, independientemente de si se trate de un major release o de uno intermedio.

· Realizar un tag siempre que consideremos que vamos a necesitar recuperar el estado actual del repositorio en un futuro, por ejemplo, es útil si en un branch queremos implementar una mejora, pero no estamos seguros de si finalmente la vamos a conservar.

· Definitivamente, es una muy buena práctica crear un tag antes y después de realizar un merge entre diferentes branches o desde un branch al trunk.

· Definir una nomenclatura estándar y darla a conocer a todo el equipo para nominar los diferentes tags en nuestro repositorio.

Bueno, espero que les haya resultado interesante el artículo, que les sean útiles los tips anteriores y que puedan aplicarlos en el día a día en sus respectivos proyectos.

De la misma forma que les comentaba en artículos anteriores, no existe una solución mágica, ni una única forma de resolver las cosas. El manejo del repositorio SCM no es la excepción, y si queremos mantenerlo organizado y funcional, deberemos utilizar nuestro criterio para determinar cuál es la mejor estrategia de branching, y principalmente, cuándo branchear y cuándo no, dependiendo en gran medida del contexto del proyecto y de las tareas que tengamos que realizar.

Ing. Ariel Martín Bensussán

.Net Practice Manager

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