ArquiteturaDeComponentes

Uma arquitetura de componentes em Python

Autor: Lennart Regebro - 19/nov/2007

Ao construir grandes frameworks você muitas vezes deseja que tudo seja facilmente plugável e extensível. Os objetos precisam ser capazes de interagir uns com os outros mesmo quando esta interação não está prevista inicialmente. Criar este tipo de flexibilidade não é muito difícil, especialmente em uma linguagem dinâmica como Python, mas se você tem que criar um framework para plug-ins toda vez que precisa de um, você acaba não fazendo isso a menos que seja realmente muito necessário.

Assim, é bom ter uma forma padrão para a interação entre objetos e para que eles possam se inspecionar, e uma forma que você possa usar toda vez que precisar. Resumindo, você precisa de uma arquitetura de componentes. Felizmente, já existe uma para Python, que foi bem implementada, bem testada e vem sendo usada há anos. Seu nome é Zope Component Architecture.

Ah, sim, já posso ouvir você: "Eca! Zope não... Isso só específico para Zope, não é pythonico, só serve para a Web, é cheio de XML. Argh!". Mas você estaria errado. A única coisa não pythonica e "webby" da Zope Component Architecture é que seus módulos se chamam zope.algo. Mas os módulos têm esse nome porque foram escritos pela Zope Corp. Sim, é verdade que o Zope usa XML em sua linguagem de configuração orientada a aspectos. Veja bem, na orientação a aspectos é desejável ter uma linguagem específica para conectar os componentes, e a do Zope é ZCML. Mas a arquitetura de componentes não exige ZCML de nenhuma maneira. É totalmente baseada em Python (exceto por algumas partes escritas em C para maior performance, mas elas também possuem implementações alternativas em Python).

Vamos ver um exemplo realmente estúpido de como usar a arquitetura de componentes. Vamos criar um componente que seja capaz de fazer aritmética com inteiros. Sim, é bem estúpido, porque sempre existiram jeitos de fazer isso em Python, mas foi a melhor idéia que eu tive. Primeiro, a gente define como a aparência externa do componente de aritmética, sua interface:

   1 from zope import interface, component
   2 
   3 class IOperacoesInteiros(interface.Interface):
   4 
   5     def somar(a, b):
   6         """soma dois inteiros"""
   7 
   8     def subtrair(a, b):
   9         """subtrai dois inteiros"""
  10 
  11     def multiplicar(a, b):
  12         """multiplica dois inteiros"""
  13 
  14     def dividir(a, b):
  15         """divide dois inteiros"""

É uma convenção chamar as interfaces de IAlgumaCoisa, por isso temos IOperacoesInteiros. Não é um erro de digitação. ;-) Uma vez que temos uma interface, criamos um componente que a implementa a interface, e podemos instanciá-lo:

   1 class Calculadora:
   2 
   3     interface.implements(IOperacoesInteiros)
   4 
   5     def somar(self, a, b):
   6         return a + b
   7 
   8     def subtrair(self, a, b):
   9         return a - b
  10 
  11     def multiplicar(self, a, b):
  12         return a * b
  13 
  14     def dividir(self, a, b):
  15         return a // b
  16 
  17 calc = Calculadora() # sem usar a AC

Agora podemos efetuar uma divisão com a calculadora diretamente digitando apenas calc.dividir(23,5), mas este não é o estilo de programação de componentes. Para usar a arquitetura de componentes, precisamos registrar o componente:

component.provideUtility(Calculator())

Agora, quem precisar fazer operações com inteiros pode encontrar o componente que presta este serviço:

calc = component.getUtility(IOperacoesInteiros)
print "Divisão:", calc.dividir(23, 5)

Note que você não precisa conhecer o componente, mas apenas a interface. Isto significa que qualquer um pode implementar o componente para você usar. É claro que quando se trata de algo tão banal quanto somar e dividir isto é inútil, mas você pode por exemplo criar uma interface para componentes que conversam com bancos de dados SQL, e então plugar uma utility para cada modelo de banco de dados diferente. Você pode ter quantos componentes quiser, todos implementando a mesma interface, e se eles forem registrados com nomes diferentes você pode escolher na configuração da sua aplicação qual deles deseja usar. Você tem componentes plugáveis, de um jeito muito fácil.

Além disso, a ZCA permite que você extenda outros componentes. Digamos que você precisa calcular o resto da divisão inteira, ou módulo. Primeiro definimos a aparência do componente que fará isso. Poderíamos extender a interface IOperacoesInteiros, mas isso significa que para obter a funcionalidade de módulo, teríamos que criar sub-classes de todas as implementações de IOperacoesInteiros. Porém, como o módulo pode ser calculado usando a divisão, podemos implementar isso usando um adaptador, ou adapter no jargão da ZCA. Primeiro, a nova interface:

   1 class IModulador(interface.Interface):
   2 
   3     def modulo(a, b):
   4         "devolve o resto da divisão entre dois inteiros"

Agora, criamos e registramos o adapter:

   1 class AdaptadorDivisorModulador:
   2 
   3     interface.implements(IModulador)
   4     component.adapts(IOperacoesInteiros)
   5 
   6     def __init__(self, context):
   7         self.context = context
   8 
   9     def modulo(self, a , b):
  10         return a - (b*self.context.dividir(a, b))
  11 
  12 component.provideAdapter(AdaptadorDivisorModulador)

Note que desta vez não instanciamos a classe. Os adaptadores são instanciados quando você adapta alguma coisa.

Agora podemos obter um componente que implementa IOperacoesInteiros:

calc = component.getUtility(IOperacoesInteiros)

e agora podemos adaptá-lo à interface IModulador e usar a funcionalidade extendida:

mod_calc = IModulador(calc)
print "Módulo:", mod_calc.modulo(23, 5)

Note que isto vai funcionar para qualquer componente IOperacoesInteiros. Basta que ele saiba dividir. Desta forma eu extendi todos os componentes IOperacoesInteiros ao mesmo tempo. Não precisei extender cada uma das implementações de IOperacoesInteiros, pois usando adaptação eu forneço a qualquer implementação existente a funcionalidade modulo.

Será que é assim tão fácil? Se não acredita, faça easy_install zope.component, copie todo o código abaixo em um arquivo, e execute. Funciona mesmo. Tudo o que você precisa para ter a arquitetura de componentes mais bacana do planeta é fazer from zope import interfaces, component, criar interfaces para seus plugins, e passar a acessá-los através de getUtility.

Agora, é claro que há muito mais coisas e funcionalidades e operações interessantes que podem ser feitas com a arquitetura de componentes. Existe um novo projeto de documentação da ZCA que foi iniciado pelo Baiju Muthukadan. O livro que ele está escrevendo dá mais detalhes.

Traduzido por LucianoRamalho com autorização do autor, Lennart Regebro. Thanks, Lennart!

Documento original: http://regebro.wordpress.com/2007/11/16/a-python-component-architecture/

Existe um vídeo com a apresentação deste assunto no GrupySP no "GoogleVideo: http://video.google.com/videoplay?docid=5303962278151147403"

   1 #!/usr/bin/env python2.4
   2 # coding: utf-8
   3 
   4 from zope import interface, component
   5 
   6 class IOperacoesInteiros(interface.Interface):
   7 
   8     def somar(a, b):
   9         """soma dois inteiros"""
  10 
  11     def subtrair(a, b):
  12         """subtrai dois inteiros"""
  13 
  14     def multiplicar(a, b):
  15         """multiplica dois inteiros"""
  16 
  17     def dividir(a, b):
  18         """divide dois inteiros"""
  19         
  20 class Calculadora:
  21 
  22     interface.implements(IOperacoesInteiros)
  23 
  24     def somar(self, a, b):
  25         return a + b
  26 
  27     def subtrair(self, a, b):
  28         return a - b
  29 
  30     def multiplicar(self, a, b):
  31         return a * b
  32 
  33     def dividir(self, a, b):
  34         return a//b
  35 
  36 calc = Calculadora() # sem usar a AC
  37 
  38 # para usar a AC, registrar o componente
  39 component.provideUtility(Calculadora())
  40 
  41 calc = component.getUtility(IOperacoesInteiros)
  42 print "Divisão:", calc.dividir(23, 5)
  43 
  44 # uso de um adaptador para extender um componente
  45 
  46 class IModulador(interface.Interface):
  47 
  48     def modulo(a, b):
  49         "devolve o resto da divisão entre dois inteiros"
  50 
  51 class AdaptadorDivisorModulador:
  52 
  53     interface.implements(IModulador)
  54     component.adapts(IOperacoesInteiros)
  55 
  56     def __init__(self, context):
  57         self.context = context
  58 
  59     def modulo(self, a , b):
  60         return a - (b*self.context.dividir(a, b))
  61 
  62 component.provideAdapter(AdaptadorDivisorModulador)
  63 
  64 calc = component.getUtility(IOperacoesInteiros)
  65 
  66 mod_calc = IModulador(calc)
  67 print "Módulo:", mod_calc.modulo(23, 5)

ArquiteturaDeComponentes (editada pela última vez em 2008-11-17 14:20:03 por LucianoPacheco)