Estou trabalhando no design de um aplicativo que consiste em três partes:
- um único encadeamento que observa a ocorrência de certos eventos (criação de arquivo, solicitações externas etc.)
- N threads de trabalho que respondem a esses eventos processando-os (cada trabalhador processa e consome um único evento e o processamento pode levar tempo variável)
- um controlador que gerencia esses encadeamentos e manipula erros (reinicialização de encadeamentos, registro de resultados)
Embora isso seja bastante básico e não seja difícil de implementar, estou me perguntando qual seria a maneira "correta" de fazer isso (neste caso concreto em Java, mas também são apreciadas respostas de abstração mais altas). Duas estratégias vêm à mente:
Observador / Observável: O segmento de observação é observado pelo controlador. No caso de um evento acontecer, o controlador é notificado e pode atribuir a nova tarefa a um encadeamento livre a partir de um conjunto de encadeamentos em cache reutilizável (ou aguardar e armazenar em cache as tarefas na fila FIFO se todos os encadeamentos estiverem ocupados). Os threads de trabalho implementam Callable e retornam com êxito o resultado (ou um valor booleano) ou retornam com um erro; nesse caso, o controlador pode decidir o que fazer (dependendo da natureza do erro que ocorreu).
Produtor / consumidor : o thread de observação compartilha um BlockingQueue com o controlador (fila de eventos) e o controlador compartilha dois com todos os trabalhadores (fila de tarefas e fila de resultados). No caso de um evento, o thread de observação coloca um objeto de tarefa na fila de eventos. O controlador pega novas tarefas da fila de eventos, as revisa e as coloca na fila de tarefas. Cada trabalhador aguarda novas tarefas e as leva / consome da fila de tarefas (primeiro a ser atendido, gerenciado pela própria fila), colocando os resultados ou erros de volta na fila de resultados. Finalmente, o controlador pode recuperar os resultados da fila de resultados e executar as etapas necessárias em caso de erros.
Os resultados finais de ambas as abordagens são semelhantes, mas cada uma tem pequenas diferenças:
Com os Observadores, o controle de threads é direto e cada tarefa é atribuída a um novo trabalhador gerado específico. A sobrecarga para criação de threads pode ser maior, mas não muito graças ao conjunto de threads em cache. Por outro lado, o padrão Observer é reduzido para um único Observer em vez de múltiplo, o que não é exatamente para o qual foi projetado.
A estratégia de fila parece ser mais fácil de estender, por exemplo, adicionar vários produtores em vez de um é direto e não requer nenhuma alteração. A desvantagem é que todos os encadeamentos seriam executados indefinidamente, mesmo quando não estiver fazendo nenhum trabalho, e o tratamento de erros / resultados não parece tão elegante quanto na primeira solução.
Qual seria a abordagem mais adequada nessa situação e por quê? Achei difícil encontrar respostas para essa pergunta on-line, porque a maioria dos exemplos trata apenas de casos claros, como atualizar muitas janelas com um novo valor no caso Observer ou processar com vários consumidores e produtores. Qualquer entrada é muito apreciada.