MEF – Import it, Export it, Compose it

Publicado em 15/03/2011

2


Olá pessoal, tudo certo?

O tema de hoje é MEF: um framework, da Microsoft, para construção de aplicações extensíveis.

Com MEF, podemos desenvolver aplicações poderosas com recursos profissionais. Por exemplo: MEF permite o desenvolvimento de aplicativos que suportam plugins.

MEF está disponível, integralmente, no .NET Framework 4 e no Silverlight 4.

Nesse post não pretendo apresentar os fundamentos do MEF. No lugar disso, vou demonstrar algumas implicações e possibilidades arquiteturais que surgem da utilização desse framework.

O que é MEF?

MEF – Managed Extensibility Framework – é uma parte do .NET Framework 4 e Silveright 4 que simplifica o design de aplicações e componentes extensíveis.

MEF permite que aplicações sejam estendias no ambiente de produção. Ou seja, podemos adicionar novas funcionalidades para um programa sem ter a necessidade de “compilãções”. Em casos extremos, podemos “estender” um aplicativo sem que tenhamos necessidade, sequer, de interromper sua execução.

Uma aplicação “extensível” construída com MEF

Um exemplo bacana de aplicação desenvolvida com MEF é o Seesmic Desktop 2. Trata-se de um aplicativo cliente para twitter, inteiramente desenvolvido em Silverlight, que suporta extensões usando MEF.

image

Atualmente, o programa opera como uma plataforma e suporta diversas redes sociais. Entre elas: Twitter, Facebook, Google Buzz e o Foursquare.

Utilizando MEF, o Seesmic provê direcionamento claro para desenvolvimento de plug-ins. Ele define um conjunto de contratos MEF que é utilizado para o desenvolvimento de qualquer plug-in para a plataforma (Seesmic Developer Platform).

A beleza da arquitetura desse programa pode ser resumida da seguinte forma:

“Se você não gosta de como o Twitter é representado no Seesmic, tudo bem… escreva seu plug-in e mofifique isso completamente.”

Isso mesmo, até mesmo a função primoridial do programa foi “isolada” do núcleo e convertida em extensão. Assim, pode ser completamente substituída por uma implementação completamente nova.

Além de poder reescrever completamente a forma como o Seesmic “trata” o twitter, há também a possibilidade de expandir o tratamento default. Observe:

image

Repare, na figura, as duas últimas linhas na representação do tweet. São dois indicadores para a “condição social” do autor:

  1. o nr. Followers, following e tweets.
  2. a pontuação no Klout.

Essas duas linhas são resultado de plug-ins desenvolvidos para “estender” o suporte default para twitter do Seesmic.

Mais uma vez: Todo o suporte a extensibilidade do Seesmic foi desenvolvido com MEF

Arquitetura para uma aplicação com suporte a extensões (plug-ins)

Ao elaborar uma aplicação com suporte a extensões, recomendo o cumprimento das seguintes etapas:

  1. identificar os extension points;
  2. para cada extension point, definir um MEF Contract
  3. para cada extension point, definir os Imports;
  4. para cada MEF Contract, definir, pelo menos, uma Composable Part com o respectivo Export;
  5. defina sua estratégia de descoberta e carga de Composable Parts, definindo Catalogs;
  6. resolva os imports

Extension points

Ao definir a arquitetura de uma aplicação extensível, é fundamental definir seus pontos de extensão.

Pontos de extensão, ou Extension Points, são as “partes” de um software que desejamos tornar extensíveis.

Um exemplo:

Considere que esteja desenvolvendo um programa para tratamento de imagens. Certamente, você desejará adicionar suporte para diversos formatos. Certo? Como fazer isso? Há duas formas evidentes:

  1. adicionar todo o código (rotinas para leitura e gravação) de forma fixa dentro do programa. Dessa forma, sempre que desejar adicionar suporte a um formato, precisará alterar o programa e gerar um novo release.
  2. pensar nas rotinas de leitura e gravação como extension points. Dessa forma, você utiliza um mecanismo de descoberta e carga de plugins, como o MEF, para compor a orquestração dessas operações. Com isso, ao precisar adicionar suporte para um novo formato, bastará escrever e distribuir um plugin.

Evidentemente, a segunda abordagem é superior. As rotinas de leitura e gravação são Extension Points evidentes.

MEF Contracts

Para cada Extension Point identificado em uma aplicação, há a necessidade de definir um contrato.

Há diversas formas de definir MEF Contracs. Podemos por exemplo, definir strings identificadoras ou Delegates (na forma de Actions e Functions). Entretanto, a forma mais recomendada passa pela definição de um tipo base, ou interface.

Voltando ao exemplo iniciado na seção anterior, onde temos dois extension points (ler e salvar), temos a necessidade de definir dois contratos. Simplificadamente, esses contratos poderiam ter a forma das interfaces indicadas a seguir:


              
public interface IDocumentWriter
{
    string FormatDescription { get; }
    string FileExtension { get;  }
    void WriteToFile(Document d, string fileName);
}

public interface IDocumentLoader
{
    string FormatDescription { get; }
    string FileExtension { get; }
    Document LoadFromFile(string fileName);
}

            

Para saber mais sobre como definir MEF Contracts, leia: Defining Composable Parts and Contracts

Um pattern comum, quando definindo contratos usando tipos, é criar um “Assembly Contract”. Ou seja, uma Class Library reunindo todos os contratos da aplicação.

Imports

Os Imports são as partes de nossos aplicativos que “fazem uso” das extensões que escrevemos. Ao declaramos um “import” estamos indicando para o MEF que desejamos recuperar todas as extensões desenvolvidas atendendo um determinado formato.

Voltando ao exemplo desse post. Utilizamos um Import na classe que “orquestrará” o processo de abertura de documentos e outro na classe que orquestrará o processo de gravação. Observe:


              
class LoadDocumentCommand : ICommand
{
    [ImportMany(AllowRecomposition = true)]
    IDocumentLoader[] Loaders;

    // ...
}

            

Seguindo minha diretriz MVVM, o lugar correto para o “Import” do MEF Contract IDocumentLoader é o comando que carrega documentos.

Smiley de boca aberta

Para saber mais sobre Imports, leia: Declaring Imports 

Composable Parts e Exports

Uma Composable Part pode ser entedida, simploriamente, como o lugar onde fazemos a implementação de uma extensão. Tecnicamente, é uma Composable Part toda classe que possui pelo menos um Export.

O Export consiste na “realização” de um MEF Contract.

Seguindo nosso exemplo, seriam Composable Parts:


              
[Export(typeof(IDocumentWriter))]
public class SaveToBmp : IDocumentWriter
{
    // ... contract implementation
}

[Export(typeof(IDocumentWriter))]
public class SaveToPdf : IDocumentWriter
{
    // ... contract implementation
}

[Export(typeof(IDocumentWriter))]
public class SaveToJpeg : IDocumentWriter
{
    // ... contract implementation
}

            

Perceba que definimos, nesse fragmento de código, três Composable Parts. Perceba ainda, que cada uma delas poderia estar “implementada” em um assembly separado.

Novamente, essa é apenas uma “simplificação” de todo o poder do MEF. Para saber mais sobre Exports, leia: Declaring Exports.

Catalogs

Catalog é conceito fundamental para MEF.

Um catálogo representa, em tempo de execução, uma relação de todas as Composable Parts disponíveis para um aplicativo. Há diferentes tipos de catálogos disponíveis:

  • AssemblyCatalog – carga de todas as composable parts de um assembly. É a forma mais simples de catálogo.
  • DirectoryCatalog – carga de todas as composables parts definidas em todos os assemblies do diretório espeficidado;
  • TypeCatalog – composable parts especificadas uma por uma;
  • DeploymentCatalog – composable parts carregados em um Package (arquivo XAP)

Além de todos esses tipos, há ainda a possibilidade de combinar mais de um catálogo usando AggregateCatalog.

Considerando que eu esteja desenvolvendo uma aplicação Desktop, seria uma possível implementação para carga de catálogos:


              
var dirCatalog = new DirectoryCatalog(@".\plugins");
var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var allCatalog = new AggregateCatalog(dirCatalog, asmCatalog);

            

No código, carrego todos as ComposableParts que estejam definidas nos Assemblies presentes na pasta plugins e no Assembly em execução.

Para saber mais sobre catalogs, leia: Catalogs

Mais algumas palavras…

Como disse no início, não tinha como objetivo apresentar fundamentos do MEF. Apenas queria evidenciar alguns pontos a serem planejados na concepção de aplicativos com extensibilidade suportada por MEF.

Vamos discutir um pouco mais? Vamos aos comentários…

Cansei!

Por hoje, era isso!

Etiquetado:MEF, ,
Publicado em: Post