Projetando software com "Pipes & Filters"

Esta é a filosofia Unix: Escreva programas que façam apenas uma coisa, mas que a façam bem feita. Escreva programas que trabalhem juntos. Escreva programas que manipulem streams de texto, pois esta é uma interface universal.
Doug McIlroy

O estilo arquitetural Pipes and Filters emerge, naturalmente, quando arquitetos e desenvolvedores decidem decompor operações complexas em unidades de processamento independentes, mais fáceis de desenvolver e manter. Entretanto, por desconhecimento, com frequência é implementado de maneira descuidada e, em virtude disso, deficiente.

Sistemas construídos utilizando o estilo Pipes and Filters processam streams de dados em etapas. Cada etapa é encapsulada em um componente filter. Dados são “passados” através de pipes entre filtros adjacentes. Eventualmente, a recombinação dos filters permite a construção de sistemas familiares, aprimorados ou mais específicos.

Neste estilo arquitetural, componentes podem ser particionados tanto tecnicamente quanto com base no domínio. Ele favorece atributos de qualidade importantes como performance, escalabilidade e reusabilidade, permitindo que seus componentes sejam implantados e escalados de maneira independente, favorecendo o evolvability. 

Este estilo arquitetural raramente será aplicável a sistemas inteiros, mas sempre é uma boa opção para partes importantes.

Bons exemplos de aplicação desse estilo são compiladores, soluções para renderização gráfica e o padrão MapReduce.

Conceito fundamental

Sistemas projetados utilizando o estilo arquitetural pipes and filters fazem o tratamento de dados, obtidos através de algum mecanismo de ingestão ou produzidos internamente, movimentando-os através de unidades computacionais independentes (os filters), conectadas de maneira planejada (as pipes) para serem processados e enriquecidos, de maneira incremental e contínua.

Uma analogia útil

Imagine um sistema hidráulico responsável por fazer o tratamento de água imprópria para consumo. Internamente, a água é tratada sendo deslocada por meio de dutos e processada em “filtros” específicos, cada um com uma função singular determinada, como, por exemplo, remover impurezas, regular acidez, adicionar cloro e ajustar a temperatura.


Em sistemas implementados utilizando o estilo pipes and filters, a “água” é substituída por dados, os dutos são substituídos por algum mecanismo de comunicação e, finalmente, os filtros, são substituídos por unidades computacionais.

Este estilo arquitetural tem quatro elementos básicos:

  1. filters, que lêem dados a partir de uma ou mais read ports, executam algum tipo de processamento específico, e escrevem os resultados em uma ou mais write ports.
  2. pipes, que conectam dois filters, recebendo input de uma write port e escrevendo dados em uma read port.
  3. read ports, que são pontos de entrada para filters, sendo responsáveis por converter dados em formatos diversos para uma representação interna compatível;
  4. write ports, que são pontos de saída dos filters, convertendo resultados do processamento para representações em formatos específicos.

De maneira genérica, o tipo de processamento executado por um filter inclui gerar, filtrar, transformar (inclusive enriquecendo),agregar e dar finalidade a dados.

Aparência funcional

Desenvolvedores habituados com o paradigma funcional têm mais facilidade para entender o estilo pipes and filters.

Todas as linguagens de programação com suporte ao paradigma funcional oferecem algum mecanismo/operador para criar cadeias de funções, combinadas. Além disso, há sempre funções especialistas em executar as operações de filtermap reduce.

As write ports, por sua vez, conectam o filter com algum tipo de pipe que transportam os dados, sem fazer modificações de qualquer natureza incluído ordenações.

Uma pipe “transporta” dados de uma write port até uma nova read port que serve como entrada para outro filter.

Pipes filters são organizadas de maneira sistemática e organizada, em uma topologia apropriada, com dados sendo coletados de uma origem (Source), submetidos a diversas etapas de processamento, até serem, finalmente, “descartados” em um sink.

Por poderem ter mais de uma porta de leitura e de gravação, filters podem ser envolvidos em “redes” complexas, geralmente sem loops.

Pipes and Filters no Shell dos sistemas Unix-like

As aplicações de linha de comando no Shell dos sistemas Unix-like são projetadas para operar como filters conectando-se com outras naturalmente.

Um exemplo fascinante do poder dessa abstração é narrado no artigo “More Shell, Less Egg“, que conta como Doug McIlroy recriou, combinando poucos aplicativos Linux, um programa de Donald Knuth com centenas de linhas.

tr -cs A-Za-z '\n' |
tr A-Z a-z |
sort |
uniq -c |
sort -rn |
sed ${1}q

Finalmente, filters devem ser independentes um dos outros, sem que qualquer comunicação aconteça, exceto através de pipesFilters tampouco devem assumir que natureza de processamento ocorrerá upstream downstream.

Flexibilidade para recombinar e aperfeiçoar

O estilo arquitetural Pipes and Filters, quando corretamente aplicado, é muito flexível por permitir aprimoramentos através da reordenação das etapas de processamento. Quanto mais “especialistas” forem os filters (consequentemente menores), maior a flexibilidade e as possbilidades de reuso.

Além da ordenação dos filters há também de se considerar a possibilidade adotar novos Sources ao longo do tempo. Em tempos de indústria 4.0 e IoT é importante considerar que não apenas sensores sejam utilizados como inputs em sistemas, mas que eles sejam cada vez mais numerosos.

Pipelines de pipelines

Um exemplo clássico da implementação do estilo Pipes and Filters são compiladores. A análise arquitetural deles nos permite um importante insight para o design: estipular diferentes níveis de design.

Em sua representação mais simples, um compilador pode ser explicado como um único agente computacional, onde código-fonte é submetido para análise e conversão para outro formato, geralmente executável.

Em uma representação mais detalhada, compiladores podem ser entendidos como uma pipeline com três etapas: frontend, optimizer, backend.

Mais próximo da implementação, modernamente, cada uma das três etapas pode se expandir consideravelmente.

Manter e analisar modelos distintos da arquitetura, com diferentes níveis de detalhe, pode facilitar a comunicação e, principalmente, modelar soluções.

Pipelines fixas, mas programáveis

Outro exemplo intrutivo de implantação do padrão Pipes and Filters é o modelo de funcionamento do DirectX 11. Trata-se de uma sequência fixa de Filters, alguns deles programáveis.

Na figura, os filters programáveis estão indicados em verde. Todos os demais, são “fixos”.

Outra curiosidade dessa arquitetura é a adoção de um “quadro negro”, ou seja, um espaço compartilhado de memória utilizado por todos os filters que reduz a transferência de “estado” entre os filtros, melhorando a performance. Essa é uma decisão relativamente segura, dada a configuração “fixa” da pipeline.

MapReduce

MapReduce é um modelo de programação projetado para tratar grandes volumes de dados em paralelo, dividindo o trabalho em um conjunto de tarefas independentes executadas no estilo Pipers and Filters.

O nome MapReduce consiste de duas tarefas: Map Reduce, onde Reduce acontece na sequência da tarefa Map. As “tarefas Map” convertem entradas a pares “chave/valor” que compila resultados intermediários que servem como entrada para as “tarefas Reduce“. As “tarefas Reduce” recebem dados de diveras “tarefas Map” e geram tuplas compatíveis, ou seja, em “chave/valor” que, eventualmente, são processadas por outras “tarefas Reduce.

Abordagens MapReduce são excelentes para produzir e manter de dados consolidados, ao longo do tempo, em cenários distribuídos ou frente a grandes volumes de dados.

Relação natural com mensageria

O estilo Pipes and Filters conecta unidades de processamento independentes (filters) com diferentes demandas e capacidades computacionais. Os filters limitam o throughput dos dados no sistema e, por isso, é comum que dados se acumulem nas pipes anteriores aos filters mais “lentos”. Por essa razão, é essencial que essas pipes sejam projetadas para “gerenciar estado”. A saída comum é utilizar alguma solução de mensageria.

Facilidade para escalar na horizontal

A performance de um sistema com arquitetura no estilo pipes and filters é determinada pelo filter mais lento. 

A força de uma corrente é sempre determinada pelo elo mais fraco.

Elyahu Goldratt

Eventualmente, na medida em que a escala aumenta, um ou mais filters mais lentos podem se converter em gargalo. Nesses casos, é comum “levantar” instâncias paralelas de filtros lentos, permitindo que o sistema espalhe a carga e melhore a taxa de transferência.

Os filters podem ser executados em diferentes computadores, permitindo que sejam escalados de forma independente e aproveitem a elasticidade que muitos ambientes de nuvem fornecem.

Um filter que é computacionalmente intensivo pode ser executado em máquinas melhores, enquanto outros filtros menos exigentes podem ser hospedados em máquinas mais simples, permitindo melhor gestão de custos.

Intimidade com “Actors Model

A natureza dos filters , desacoplados e coesos, combinados em ambientes necessariamente resilientes, dá ao estilo arquitetural Pipes and Filters ajuste natural ao Actors Model.

O modelo de atores é uma abstração matemática para computação distribuída, paralela e concorrente, onde “ator” é a primitiva essencial. Atores atuam sempre a partir de mensagens recebidas, executam algum tipo de processamento, modificam seu estado privado e se comunicam com outros atores, novamente, a partir de mensagens.

O modelo de atores é a abstração fundamental para tecnologias como ERLANG, Elixir, Akka, Akka.net, Orleans e mais.

A implementação do estilo Pipes and Filters ocorre através da construção de um ator para cada filter.

Intimidade com “FaaS

Recentemente, tem ganho destaque um modelo computacional orientado a eventos, com unidades mínimas de processamento (funções) executadas em contêineres stateless, que são executadas em ambientes gerenciados. Essas “funções” são acionadas sempre que há a ocorrência de um “evento”, com escalabilidade automática.

FaaS é o modelo computacional que fundamenta tecnologias como AWS Lambdas e Azure Functions.

A implementação do estilo arquitetural Pipes and Filters tem ajuste natural com FaaS. A solução é adaptada com filters sendo implementados como funções e as pipes como mecanismo para coleta e gestão de eventos.

Intimidade com plataformas de stream 

Nos últimos anos, plataformas para tratamento distribuído de streams, como Apache Kafka e NiFi têm permitido a construção de sistemas robustos capazes de tratar fluxos gigantes de informações.

Apache NiFi, uma ferramenta visual (com API REST),  permite a configuração e conexão entre processadores (filters) de maneira dinâmica. Além disso, permite monitoramento e controle dos fluxos de execução, autorizando ajustes de escala e pausas controladas.
Apache Kafka,  uma implementação distribuída do padrão publish-subscribe, permite aos desenvolvedores conectar programas (filters) entre si, em diferentes linguagens e em um grande número de máquinas. Trata-se de uma alternativa poderosa para implementação de pipes.

Soluções como Kafka e NiFi autorizam escalar de maneira segura soluções projetadas seguindo o estilo Pipes and Filters.

Conway, outra vez!

O desenvolvimento de soluções utilizando o estilo arquitetural Pipes and Filters conduz ao estabelecimento de pelo menos três tipos de times diferentes:

  • Desenvolvedores de filters – preocupados com determinada demanda de negócio pontual e implementação direta em código.
  • Supervisores de pipes – que desenvolvem e especializam plataformas ou soluções para gestão de streams de dados.
  • Gestores de Pipelines – que combinam diversos filters e tecnologias de pipes para o desenvolvimento de uma solução completa para o negócio.

Eventualmente reativo!

O estilo arquitetural Pipes and Filters habilita o desenvolvimento de sistemas reativos, que é algo desejável. Tais sistemas, são responsivos, resilientes, elásticos e orientados a mensagens.

A responsividade é obtida pelo desacoplamento lógico e operacional das diversas filters. Eventualmente, operações mais pesadas são postergadas para processamento posterior enquanto feedback rápido é dado aos usuários ou agentes externos.

A resiliência, por sua vez, é garantida pela estabilidade dos pipes que “seguram” os dados caso exista alguma instabilidade de processamento nos filters.

A elasticidade, em sistemas baseados em Pipes and Filters é possível desde que cada filter opere realmente de forma desacoplada com o fluxo quanto com outros filters. Em princípio, quando a demanda sobe, filters mais pesados podem ter novas instâncias criadas e descartadas conforme a escala oscila.

Por fim, o “fluxo dos dados” indica clara orientação a mensagens.

Indicações e contraindicações

O estilo Pipes and Filters é uma abstração poderosa que autoriza o desenvolvimento de sistemas robustos e potencialmente reativos. Entretanto, como é sempre o caso, não é uma solução “bala de prata”.

A aplicação de Pipes and Filters deve ser considerada sempre (e apenas quando) que o processamento de uma aplicação puder ser dividido em conjunto de etapas independentes, não dependentes de uma única unidade transacional. Eventualmente, isso não acontece para o software como um todo, mas, será o caso para partes. O ganho será mais percebido quando etapas diferentes tiverem demandas de escalabilidade diferentes.

Outro aspecto interessante do estilo, quando ele for aplicável, é a separação dos processos de desenvolvimento dos filters e o projeto das pipelines indica maior flexibilidade para permitir reordenação das etapas de processamento executadas, além, obviamente, de sofisticação dos processos, adicionando ou removendo etapas.

Tratar erros é importante, mas difícil

Tratamento de erros é, provavelmente, o calcanhar de aquiles do estilo arquitetural Pipes and Filters.

É muito importante que a arquitetura defina uma estratégia comum para reportar erros em produção. Entretanto, a possibilidade de implantar mecanismos de recuperação pode ser difícil, ou até mesmo impossível, dependendo da natureza do problema.

Em grandes jornadas, dividir para conquistar

Boa parte das implementações “chamadas” microsserviços são, na verdade, realizações de Pipes and Filters. O problema disso é que, muitas vezes, abstrações importantes do estilo acabam ignoradas, causando dificuldades desnecessárias.
0
Você concorda?x

Pensar sistemas em termos de Pipes and Filters é um começo importante para quem deseja criar arquiteturas desacopladas e com bom evolvability. Afinal, o estilo conta com familiaridade com mecanismos poderosos, admirados por muitos desenvolvedores. Além disso, acomoda muitas das “tendências tecnológicas”.

// TODO

Antes de avançar para o próximo capítulo, recomendo as seguintes reflexões:

  • Você conseguiria decompor seu sistema em um conjunto de “atividades” desacopladas, implementáveis como Pipes and Filters?
  • Você já utiliza Kafka, NiFi ou FaaS em seus sistemas? Há semelhanças, na sua solução, com o padrão Pipes and Filters?

Referências bibliográficas

BONÉR, Jonas; FARLEY, Dave; KUHN, Roland; THOMPSON, Martin. O manifesto reativo. 2014. Disponível em: https://www.reactivemanifesto.org/pt-BR. Acesso em: 16 maio 2021.

BUSHMANN, Frank; MEUNIER, Regine; ROHNERT, Hans; SOMMERLAD, Peter; STAL, Michael. Pattern-oriented software architecture: a system of patterns. Rexdale, Ontário: John Wiley & Sons, 1996. (Software Design Patterns).

COOPER, Keith D.; TORCZON, Linda. Engineering a Compiler. 2. ed. Houston, Tx: Elsevier, 2012.

DRANG, Dr (org.). More shell, less egg. 2011. Disponível em: https://leancrew.com/all-this/2011/12/more-shell-less-egg/. Acesso em: 14 maio 2021.

GOLDRATT, Eliyahu; COX, Jeff. A meta: teoria das restrições (toc) aplicada a indústria. 4. ed. São Paulo: Nobel, 2015. 400 p. Edição comemorativa de 30 anos.

LUNA, Frank D.. Introduction to Game Programming with DirectX 11. Dulles, Va: Mercury Learning And Information, 2012.

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
0 Comentários
Feedbacks interativos
Ver todos os comentários

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mentoria

para arquitetos de software

Imersão, em grupo, supervisionada por Elemar Júnior, onde serão discutidos tópicos avançados de arquitetura de software, extraídos de cenários reais, com ênfase em systems design.

Consultoria e Assessoria em

Arquitetura de Software

EximiaCo oferece a alocação de um Arquiteto de Software em sua empresa para orientar seu time no uso das melhores práticas de arquitetura para projetar a evolução consistente de suas aplicações.

ElemarJúnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

+55 51 99942-0609 |  contato@eximia.co

+55 51 99942-0609  contato@eximia.co

0
Quero saber a sua opinião, deixe seu comentáriox
()
x