SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 3

filter

Ciência, arte e filosofia se vão fundindo tanto em mim que algum dia certamente vou parir um centauro – Nietzsche.

  1.  Introdução
  2. Flow-Base Programming
    1. Component
    2. Port
    3. Information Packet (IP)
    4. Connection
    5. Network
    6. Initial Information Packet (IIP)
  3. Exemplo: Filter
    1. Introdução
    2. OddFilter
    3. Numbers
    4. MaxNumberIIP
    5. Printer
    6. Dropper
    7. Filter GUI
  4. Portas automáticas
    1. Introdução
    2. Exemplo: Writer e Reader
    3. Usando displays com portas automáticas
    4. Usando portas automáticas no exemplo Filter
  5. Componentes compostos
    1. Introdução
    2. Subnet
    3. Composição
  6. Flow-Based Programming IDE
    1. Introdução
    2. Arquitetura
    3. Protótipo
  7. Flow-Based Programming IDE II
    1. Introdução
    2. Odd filter
  8. Flow-Based Programming IDE – III
    1. Introdução
    2. Writer e Reader

Exemplo: Filter

Introdução

As redes de componentes é uma forma bem comportada de construir sistemas de processamento paralelo. Dependendo do suporte da linguagem cada componente ou grupos de componentes podem ter seus processos atribuídos a cores distintos em um computador ou estação servidora com muitos cores. Um cluster de computadores ou qualquer sistema de utilização de hardware distribuído pode beneficiar o desempenho de uma aplicação construída usando Flow-Based Programming enormemente. Os componentes podem ser desenvolvidos em linguagens diferentes e serem conectados por variados protocolos de comunicação. Flow-Based Programming pode iniciar uma era de sucesso na direção da criação de um mercado de componentes de prateleira, na aproximação dos usuários ao desenvolvimento ativo de sistemas para atender os seus negócios e melhor aproveitamento da infra-estrutura de paralelismo que está despontando cada vez mais. A reutilização de componentes “técnicos”, desenvolvidos pelos profissionais de TI bem como daqueles desenvolvidos pelo negócio deve alavancar uma produtividade nunca vista antes. A reutilização de componentes de negócio sempre foi o calcanhar de Aquiles da indústria de software. Reutilizar widgets da interface com o usuário são bem previsíveis mas entender quais são os “átomos” do negócio (que são os componentes mais reutilizáveis, em princípio) e mesmo os componentes mais complexos, que somente os experts em cada área seriam capazes de detectar, é uma tarefa bem difícil. E se eles puderem “por a mão na massa” com uma linguagem visual e componentes disponíveis para serem conectados os sistemas poderiam evoluir de forma rápida e adaptativa às mudanças nos cenários dos negócios. Uma forma exploratória e menos rígida será gerada pelas novas facilidades no desenvolvimento de sistemas com base em protótipos que se tornam “adultos” (Morrison), que não são jogados fora violando a teoria dos protótipos feitos com “arame preso com chicletes”. Embora isto esteja mudando recentemente. O mundo do software é mais plástico do que o mundo físico ou do hardware. Um modelo de um carro feito de cerâmica ou plástico esculpido para ser testado no túnel de vento não pode depois circular nas ruas mas um modelo em software é análogo ao que pode ser imaginado numa história de ficção científica em que o escritor faz a cerâmica se transmutar em metal. A seguir vamos desenvolver um exemplo que é funcional. Conforme citado no primeiro post o desenvolvimento foi rápido graças ao ambiente do Smalltalk e também devido à simplicidade do exemplo. Apesar da simplicidade a maioria dos conceitos foi exercitado.

Os métodos #run dos componentes abaixo tem seu código em Smalltalk seguindo bem de perto o seguinte pseudo-código:

Repita
Receber pacotes nas portas de entrada
Processar os pacotes recebidos
Enviar os pacotes recebidos ou criados para
as portas de saída ou descartá-los

No caso de ser necessário processar fora do laço o pseudo-código genérico ficaria:

Receber pacotes nas portas de entrada
Processar os pacotes recebidos
Enviar os pacotes recebidos ou criados para
as portas de saída ou descartá-los

Repita
Receber pacotes nas portas de entrada
Processar os pacotes recebidos
Enviar os pacotes recebidos ou criados para
as portas de saída ou descartá-los

Receber pacotes nas portas de entrada
Processar os pacotes recebidos
Enviar os pacotes recebidos ou criados para
as portas de saída ou descartá-los

O laço é opcional em todos os casos a depender da lógica necessária. E não necessariamente é um laço infinito.

Caso não haja portas de entrada ou de saída (para componentes na “borda” do sistema algumas linhas acima são omitidas. Para componentes sem portas de entrada ficaria:

Repita
Enviar os pacotes criados para as portas de saída

Ou no caso de não haver portas de saída:

Repita
Receber pacotes nas portas de entrada
Processar os pacotes recebidos e
descartá-los

E no caso extremo de um componente sem portas:

Repita (opcional)
Processar obtendo ou enviando dados
usando somente efeitos colaterais tais como:
- ler e/ou gravar arquivos
- receber e/ou enviar dados
por uma conexão de rede
- mostrar uma
interface gráfica com o usuário
- configurar e usar uma rede FBP
para processar os dados
- etc

Em cada caso vamos repetir o pseudo-código específico para cada componente antes do código em Smalltalk.

OddFilter

OddFilter é um componente que recebe um pacote contendo um número inteiro na porta IN e o envia para uma das portas ACCEPT ou REJECT. Se o número é ímpar é enviado para a porta ACCEPT. Em caso contrário é enviado para a porta REJECT. Abaixo vamos mostrar o método #run do componente onde é implementado o laço infinito que lê continuamente a sua única porta de entrada e redireciona os pacotes de acordo com o seu conteúdo.

Abaixo segue o pseudo-código:

Repita
Receber pacote da porta IN
Extrair o conteúdo do pacote (um inteiro)
Se o inteiro é ímpar
Envie o pacote para a porta ACCEPT
Senão
Envie o pacote para a porta REJECT

Veja abaixo o código em Smalltalk:

odd-filter-run   Os comentários introduzidos, apenas com fins didáticos, no código acima esclarecem sobre a semântica do método #run. Por causa dos extensos comentários a imagem ficou reduzida. Clique na mesma para ampliar (em outra aba do browser, se preferir). A forma de implementar um componente varia em torno do tema de criar um laço infinito que repete um código que lê as portas de entrada, examina o conteúdo dos pacotes, toma decisões com base neste conteúdo, envia ou descarta pacotes, ou cria novos pacotes e os envia. A maioria dos componentes é funcional no sentido adotado nas linguagens funcionais de programação. Uma função não tem efeitos colaterais. Uma função para calcular a raiz quadrada não manda email, não aciona a impressora, não obtém seus dados de uma interface com o usuário. Apenas recebe um número e devolve a sua raiz. Os componentes não interagem com o exterior a não ser através de suas portas e não tem nenhuma dependência em relação ao meio exterior. A ausência de efeitos colaterais torna mais fácil raciocinar sobre a correção da implementação. É claro que os efeitos colaterais são necessários nas “bordas” do nossa rede de componentes. A origem dos dados está em componentes que muitas vezes não tem nenhuma porta de entrada. Eles obtém os dados de alguma forma e os colocam em pacotes que enviam pelas portas de saída. Podem ser interfaces com o usuário, com a rede de computadores, outros sistemas, bases de dados etc. Um componente destes pode encapsular um web server, uma GUI, uma conexão via socket com outros sistemas, só para dar alguns exemplos. Os dados para serem úteis também precisam retornar ao mundo externo. Aí entram novamente componentes situados na borda e que podem não ter portas de saída, só de entrada. Exemplos seriam componentes de interface com o usuário, impressoras, robots, hardware específico, web server, entre várias coisas. Sem os efeitos colaterais o sistema de componentes seria como uma comunidade de “autistas” incomunicáveis. Na linguagem Haskell de programação funcional devem existir a monadas para dar conta dos efeitos colaterais necessários. Mesmo no nosso exemplo simples veremos componentes funcionais e componentes com efeito colateral. Abaixo coloco a imagem do método #run sem os comentários. odd-filter-run-without-comments

Numbers

Este é um componente que recebe um pacote contendo um número inteiro em sua porta MAX e então ele gera a quantidade equivalente a este número inteiro de números aleatórios entre 1 e 100. Cada inteiro gerado é empacotado e enviado para a sua porta OUT.

Abaixo segue o pseudo-código:

Receber pacote da porta MAX
Extrair o conteúdo do pacote (um inteiro) e guardar em max
Descartar o pacote
Repetir o trecho abaixo para gerar max inteiros
Obter um inteiro aleatório entre 1 e 99 inclusive
Colocar o inteiro num pacote
Enviar o pacote pela porta OUT

Note que neste caso há algum processamento fora do laço.

Abaixo segue o código com comentários. numbers-run   O componente numbers é tipicamente um componente que não tem nenhuma entrada de dados. Apenas uma entrada de configuração vinda da porta MAX, lida uma única vez. Após descartar o o pacote de onde obteve a configuração da quantidade de inteiros a serem gerados passa a executar um laço. Neste laço são gerados os inteiros aleatórios que são empacotados e enviados pela porta OUT. Uma coisa digna de nota é que este laço não é infinito. Termina quando todos os números são gerados. Por isso o processo também termina. O componente numbers faz o seu trabalho e deixa de executar.

MaxNumberIIP

Este é um Initial Information Packet (IIP) que serve de configuração do componente numbers. Os IIP só tem uma porta de saída onde o pacote de configuração é enviado. Foi implementado como um componente. Veja alguns códigos abaixo:

IIP-class

O código acima cria a classe do IIP. Não existe uma classe para cada IIP. Todos IIP são da classe criada acima. As instâncias é que são diferentes. O código do método run é o mesmo (pelo menos nesta versão preliminar).     IIP-run

Note que não há laço nenhum aqui. O IIP é criado com um valor e o coloca imediatamente na porta de saída. Depois o seu processo termina.

Printer

O código abaixo

printer-run

imprime os inteiros que passam pelo filtro num console do Smalltalk chamado Transcript. Novamente foram introduzidos comentários didáticos. Este componente, cujo nome sugere o uso de um hardware de impressão, poderia estar encapsulando um programa para comandar uma impressora. Um exemplo disto é o MicroFlo. O framework NoFlo, em Javascript, inspirou o MicroFlo para lidar com micro-controladores similares ao Arduino, um hardware open source.

O pseudo-código correspondente seria:

Repita
Receber pacote da porta IN
Extrair o conteúdo do pacote (um inteiro)
Descartar o pacote
Imprimir o conteúdo do pacote

Dropper

O laço do dropper é simples. Dropper sistematicamente descarta todos os pacotes que recebe. O OddFilter poderia descartar os pacotes rejeitados sem enviá-los pela porta REJECT. No exemplo isto é um pouco artificial e por enquanto serve apenas para criar o filter com mais de uma porta de saída.

Repita
Receber pacote da porta IN
Descartar o pacote

dropper-run O pseudo-código e o código que configura a rede com suas conexões encontram-se abaixo:

Inicializar com o valor 100 a porta MAX
do componente Numbers
Conectar a porta OUT do componente
Numbers à porta IN do componente OddFilter
Conectar a porta ACCEPT do componente OddFilter
à porta IN do componente Printer
Conectar a porta REJECT do componente OddFilter
à porta IN do componente Dropper

network-config   O código completo que cria os componentes, configura a rede e inicia os componentes segue abaixo: example-code   O código acima é executado num Workspace e o resultado é mostrado num Transcript: running-example-all-windows

Um exercício interessante é tentar construir um código que tenha o mesmo efeito do exemplo Filter usando somente closures e shared queues. É o que mostramos abaixo:

exampleWithClosures

exampleWithClosures2

O código acima, embora tenha o mesmo efeito, é mais difícil de manter do que um código FBP. Isto mostra que muitos adversários de uma nova metodologia estão errados ao atacar uma idéia afirmando que já fazem o que está sendo alegado como uma mudança de paradigma. É claro que há muitas maneiras de se fazer a mesma coisa. O uso de FBP mostra isto de forma mais flagrante pois é possível variar as soluções facilmente e experimentar com elas sem muito esforço.  Mas uma organização diferente das partes do software com desacoplamento no espaço e no tempo como propões FBP promove uma flexibilidade ainda pouco explorada no mundo do desenvolvimento de software.

component-class-example-with-closures

FilterGUI

filter-guiVamos modificar um pouco alguns componentes para introduzir uma interface com o usuário. O componente Numbers passa a receber um sinal na sua porta START e só depois disso começa a gerar os números. O novo componente FilterGUI recebe os números ímpares na sua porta IN e os exibe. Os números gerados por Numbers são positivos, exceto o último que é igual a -1. O componente FilterGUI ao receber o último pacote é que passa a exibí-los. Vejamos os novos códigos para adaptar os componentes ao novo cenário:

network-with-initialize   A network configurada expressa o que está no diagrama. Uma novidade é o método FBPNetwork>>#initialize:toPort:of: que é uma facilidade. Com ele não precisamos criar explicitamente um IIP para configurar numbers. Veja abaixo o código do novo método: network-initialize-inport-of O IIP (que é um componente) é criado e seu processo/thread iniciado. O IIP tem sua porta de saída OUT conectada à uma porta de entrada de um componente.

Note também que o nome do método FBPNetwork>>connect:port:inPort:of: foi alterado para FBPNetwork>>connect:port:toPort:of.

O método #run de Numbers foi alterado para enviar o valor -1 no final (que servirá de flag para FilterGUI saber que a sequência terminou). numbers-run-with-flag O componente OddFilter não precisa ser alterado pois o pacote contendo -1 como flag passará pelo filtro e alcançará o componente FilterGUI onde será testado. O componente Dropper também não precisa ser modificado. Abaixo mostramos o método #run de FilterGUI:   filter-gui-run A interface desenvolvida usando o framework Spec é mostrada abaixo: odd-filter-window

Quando pressionamos o botão Start a lista de números ímpares aparece e o botão é desabilitado.

odd-filter-window-after-button-click

A implementação de interfaces não é assunto desta série de posts. As interfaces implementadas serão bem simples e não exploram todos os recursos do framework Spec. Os códigos podem ser encontrados no repositório do projeto.  No entanto vale comentar que a técnica de uso de interfaces adotada viola o que comumente é usual. Normalmente a interface tem uma referência para o domínio de objetos de negócio que, por sua vez referenciam a camada de dados a qual, finalmente acessa as bases de dados. Esta pilha é construída de tal forma que as camadas são referenciadas do topo para a base e cada camada inferior não referencia as camadas superiores. A comunicação das camadas inferiores com as superiores se dá através de eventos. Este é o modelo em camadas que promove um tipo de desacoplamento que permite desenvolver as camadas inferiores  de forma independente da forma que vai ser usada. Oferece-se uma API para uso das camadas superiores e um conjunto de eventos que servem de estímulos para uma ação reativa das camadas superiores que usam dados porventura contidos no eventos e/ou complementam com acessos através da API ofertada. A imagem abaixo, do artigo The Onion Architecture, ilustra isto.

O estilo que adotamos é inspirado na Hexagonal/Onion Architecture e também em Presenter First e MVP (O proprio framework Spec usa MVP). A interface que foi criada para que o usuário interaja com a aplicação é plugável. É configurável através de uma API (Para atribuir o rótulo do botão, o título da janela, passar uma lista de objetos para ser exibido e um listener para ser executado quando o botão for pressionado). A interface do usuário é totalmente genérica e pode ser definda como “uma janela padrão com uma lista e um botão onde o rótulo do botão e o título da janela podem ser configurados e o evento de presionamento do botão pode acionar um código externo à interface”. É tipicamente uma humble view como é descrita por Martin Fowler (Citado nos meus posts em Hexagonal Architecture in Smalltalk series). Aqui a interface é totalmente plugável e genérica e pode ser usada por quem quiser uma funcionalidade de apresentação totalmente desprovida de uma lógica ou minimamente contendo uma lógica para organizar a si própria como interface, mas nada há sobre o negócio. É facilmente substituída por um mock. O que facilita os testes já que praticamente não é necessário testar uma interface que contém pouca ou nenhuma lógica. Tudo acontece externamente a ela. Neste sentido ela se parece bastante com um componente FBP.

Depurando o exemplo

Aparentemente está tudo certo. Quando eu pressiono o botão “Start” os números ímpares aparecem. Como conheço os código dos componentes também tenho certa segurança de que está tudo bem pela inspeção dos mesmos. Mas mesmo assim vamos continuar com nossa abordagem de caixa preta. Será que todos os números gerado foram usados? O que está acontecendo em cada conexão? Se sou usuário de caixas pretas não posso olhar dentro delas. Meu universo é externo aos componentes e é aí que posso atuar. Desde que tenha ferramentas adequadas…

Vamos criar um componente simples para mostrar o que está acontecendo. O diagrama abaixo mostra o que precisamos. filter-gui-with-displays Os três componentes da classe FBPDisplay interceptando as conexões vão nos dar um panorama do fluxo dos pacotes.

Abaixo segue o método #run de FBPDiplay:

display-run

A configuração da rede é feita pelo código abaixo:

network-with-via

Os componentes FBPDisplay foram introduzidos na rede usando um novo método Network>>#connect:port: toPort:of: via: que facilita a inserção de componentes interceptando uma conexão. No caso acima usamos componentes FBPDisplay. Mas poderia ser outro componente desde que a porta de entrada seja identificada como IN  e a de saída seja OUT. Estas portas serão usadas por default. O código do novo método está abaixo:

network-connect-with-via

A execução da rede faz com que os fluxos sejam mostrados em janelas separadas:

filter-windows-with-displays

E após o pressionamento do botão Start:

filter-windows-with-displays-after-button-press

Anúncios

8 Respostas para “SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 3

  1. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming | Crab Log

  2. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 2 | Crab Log

  3. Pingback: Shared Bounded Queue in Pharo Smalltalk | Crab Log

  4. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 4 | Crab Log

  5. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 5 | Crab Log

  6. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 6 | Crab Log

  7. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 7 | Crab Log

  8. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 8 | Crab Log

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s