Blog de Fernando Machado Piriz

Artículos sobre transformación digital, arquitectura empresarial y temas relacionados

Posts Tagged ‘Extensibilidad

Simple Introducción al Managed Extensibility Framework

with 3 comments

Es muy común que los desarrolladores ocupemos más tiempo modificando aplicaciones existentes que construyendo aplicaciones nuevas desde cero. Nuevos requerimientos de negocio suelen precisar de nuevas funcionalidades en las aplicaciones. La forma como se agregan estas nuevas funcionalidades hoy en día termina cuando generamos, probamos y distribuimos nuevamente la aplicación. Estas actividades generalmente afectan la aplicación completa y no sólo la funcionalidad agregada.

Cuando los requerimientos cambian, los desarrolladores debemos cambiar las funcionalidades de la aplicación. Después de años de evolución, el código que implementa estas funcionalidades suelen tener dependencias indeseables y esas modificaciones pueden tonar la aplicación inestable, o requerir extensas pruebas de regresión que aseguren que no se han introducido errores. También en este caso la historia termina en generar y distribuir de nuevo toda la aplicación.

Cada vez más los negocios cambian más y más rápido, para sobrevivir en un mundo global y competitivo. Y el software que hace que esos negocios funcionen, también tiene que poder cambiar y tiene que poder cambiar rápido.

Supongamos por un instante que cada funcionalidad de la aplicación puede ser descompuesta en una “parte”. Lo que necesitamos es la capacidad de desarrollar partes débilmente acopladas, independientes no sólo en su estructura sino también en su prueba y en su distribución; y que esas partes pueden ser fácilmente compuestas en una aplicación.

Los principios de Ingeniería de software para resolver esta situación se conocen desde hace tiempo y muchas organizaciones los aplican con éxito. Pero las soluciones suelen ser caso a caso y no están disponibles al público en general como nosotros.

Recientemente han aparecido frameworks para desarrollo de aplicaciones en partes. MEF es uno de ellos.

En MEF existen los siguientes roles:

  • Partes exportadas. Declaran que pueden ser usadas para componer una aplicación y el contrato que implementan. Son unidades independientes de desarrollo, compilación y distribución. Estar débilmente acopladas, tanto como las demás partes como la aplicación que compondrán, es decir, una parte no necesariamente conoce a las demás, ni sabe necesariamente en qué aplicación será usada.
  • Puntos de importación. Variables que contienen partes o colecciones de partes importadas que deben implementar cierto contrato. Las partes son creadas automáticamente a partir de la información contenido en catálogos de partes.
  • Catálogos de partes. Contienen definiciones de partes: dónde están y qué contrato implementan.
  • Contenedores de partes. Contienen partes instanciadas y realizan la composición de las partes.

A continuación voy a mostrarles cómo desarrollar una aplicación con MEF paso a paso. Para entender MEF la aplicación debe ser simple, pero debe ofrecer varias funcionalidades que se puedan descomponer en partes.

La aplicación en este caso me permite escribir texto y luego puedo transformarlo aplicando diferentes algoritmos. Cada algoritmo ofrece una funcionalidad diferente y eso es lo que voy a transformar en partes. La aplicación luce así:

image

La lista desplegable muestra las transformaciones que puedo aplicar:

image

El objetivo es desarrollar la interfaz de usuario y cada transformación de forma independiente. Ahí vamos.

El contrato que deben implementar las partes lo declaro con una interfaz IFilter:

public interface IFilter
{
    string Filter(string input);
}

Luego creo una parte (la transformación ToUnicodeFilter) implementando esa interfaz IFilter y declarándola como exportable en MEF con el atributo Export:

[Export(typeof(IFilter))]
public class ToUnicodeFilter : IFilter
{
    public string Filter(string input)
    {
        StringBuilder output = new StringBuilder();
        foreach (char item in input)
        {
            output.AppendFormat(" U+{0:x4}", (int)item);
        }
        return output.ToString();
    }
}

La transformación en este caso es convertir cada letra del texto recibido en su correspondiente representación Unicode.

Puedo tener tantas partes como quiera, mientras también implementen la interfaz IFilter y estén decoradas con el atributo Export.

Ahora vamos a la aplicación a programar la composición de las partes en la aplicación. Necesito un catálogo que describa las partes. En este ejemplo voy a tener todos los ensamblados con las partes en una carpeta llamada Extensions, así que uso un catálogo DirectoryCatalog.

DirectoryCatalog catalog = new DirectoryCatalog("Extensions");

También necesito un contenedor para las instancias de las partes y para armar la composición. Al contenedor le paso el catálogo para que tenga la información de dónde encontrar las partes:

CompositionContainer container = new CompositionContainer(catalog);

Ya casi estamos. Lo que necesito ahora es un punto donde importar las partes. En este caso puede haber varias transformaciones, así que declaro una IList<IFilter> que decoro con el atributo Import:

[ImportMany(typeof(IFilter))]
private IList<IFilter> filters = new List<IFilter>();

Lo último que hago es componer las partes, indicándole al contenedor dónde están los puntos de importación:

container.ComposeParts(this);

Luego puedo recorrer la lista para poblar los filtros en la lista desplegable:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    foreach (IFilter filter in filters)
    {
        comboBoxFilters.Items.Add(filter.GetType());
    }
}

Cuando el usuario hace clic en el botón Apply, se busca el filtro a ejecutar en la lista de filtros a partir del elemento seleccionado en la lista desplegable:

private void buttonApply_Click(object sender, RoutedEventArgs e)
{
    if (comboBoxFilters.SelectedIndex != -1)
    {
        int index = comboBoxFilters.SelectedIndex;
        IFilter filter = filters.ElementAt(index);
        textBoxOutput.Text = filter.Filter(textBoxInput.Text);
    }
}

Tres ensamblados intervienen aquí. El primero es donde está declarado IFilter. El segundo es donde está implementada la clase ToUnicodeFilter. El tercero es el de la propia aplicación. Lo interesante aquí es que para agregar una nueva transformación o para modificar una existente, lo único que tengo que hacer es distribuir el ensamblado que la contiene en la carpeta Extensions. El resto de los ensamblados no se ven afectados, ni siquiera el de la aplicación.

¿Cómo se logra que esto funcione casi de forma máquina? Cuando invoco el método ComposeParts del contenedor pasando como parámetro la propia aplicación, el contenedor busca todas las variables decoradas con ImportMany (o con Import) en la aplicación. En cada caso aparece también la interfaz requerida por cada punto de importación.

El contenedor también conoce el catálogo, pues lo paso como parámetro en el constructor. Como el catálogo tiene la información de todas partes, incluyendo qué partes implementan qué interfaz, puede encontrar que parte o partes implementan la interfaz de cada punto de importación. Como el catálogo también conoce en qué ensamblados están las partes, puede crear instancias de las partes apropiadas y asignarlas a las variables correspondientes a los puntos de importación.

Al final, la composición “se arma sola”, el trabajo lo hace MEF y no el programador. El programador solo “decora” los tipos con Export, las variables con Import, crea uno o más catálogos y el contenedor, y finalmente compone las partes. Simple, ¿no?

Recuerden que el desafío era desarrollar partes débilmente acopladas, independientes no sólo en su estructura sino también en su distribución; y que esas partes pudieran ser fácilmente compuestas en una aplicación. ¿Objetivo cumplido? Yo creo que sí.

Pueden descargar el código de esta aplicación de aquí. El ejemplo contiene más partes todavía de las que estoy mostrando aquí.

Vean también MEF Home y MEF Overview (en inglés).

En un próximo post voy a hablar de cómo asociar metadata a las partes, por ejemplo, para que en lugar de poner el nombre del tipo en la lista desplegable, aparezca un nombre más adecuado. Nos vemos.

Written by fmachadopiriz

15/04/2010 at 10:46