🇧🇷 Arquitetura de Workflow para executar seus scripts sequencialmente ou paralelo em NodeJS.
🇺🇸 Workflow Architecture to run your scripts sequentially or parallel in NodeJS. English Readme here
O objetivo geral dessa arquitetura é contemplar a necessidade de execução de scripts de forma controlada. Pode ser utilizada em diversos tipos de cenários e necessidades como por exemplo em aplicações event driven, crawlers, scraping, crons entre outras. A Workflow Architecture atende muito bem necessidades de aplicações de comunicação assíncrona.
Abaixo estão listados os recursos presentes na arquitetura:
- Workflows: São a maior unidade da arquitetura, contém a lógica e controle de execução das pipelines. Os workflows são a camada de entrada para execução dos demais unidades. São responsáveis também por enviar para as camadas filhas os dados a serem processados pelos steps.
- Pipelines: Tem comportamento semelhante aos workflows, porém tem a responsabilidade de decidir a forma e ordem (se aplicável) dos steps. Podem ser de dois tipos:
SequentialeParallel.- SequentialPipeline: São pipelines que executam os steps em sequência, ou seja, um por vez, e seguindo uma ordem.
- ParallelPipeline: São pipelines que executam os steps de forma paralela, ou seja, sem uma sequência ou ordem. São indicados quando um step não há dependencia de execução entre outro.
- Steps: São a menor unidade da arquitetura. Eles são responsável por interagir com o dado enviado e realizar as ações necessárias do script. Podem ser comparados aos UseCases de outras arquiteturas.
- GlobalContext: É uma variável global mutável que pode receber qualquer tipo de dado. Sendo assim se torna o contexto da aplicação, e pode ser acessada e modificada pelos steps de acordo com a necessidade.
Arquiteturas no geral, não são construidas ou criadas sem nenhuma motivação. Em sua maioria, elas evoluem de arquiteturas já existentes ou até mesmo são um complemento, como por exemplo o MVC (Model View Controller) e MVVM (Model View ViewModel). Já a Workflow Architecture é inspirada no funcionamento do Github Actions. Onde se tem vários arquivos de workflows, e dentro desses workflows você tem uma (ou várias) pipelines para serem executadas. E cada pipeline tem seus steps, que são literalmente etapas a serem executadas para a finalização do script. Assim temos uma abstração desse sistema em formato de arquitetura que pode ser utilizado em projetos não só em NodeJS, mas em qualquer linguagem.
.
├── index.js # Arquivo de entrada
└── src
├── core # Camada principal da aplicação, onde se encontra as classes principais
│ ├── ParallelPipeline.js
│ ├── Pipeline.js
│ ├── SequentialPipeline.js
│ ├── Step.js
│ └── Workflow.js
├── pipelines
│ └── # Aqui conter suas pipelines
├── steps
│ └── # Aqui conter seus steps. (Você também pode separá-los por pastas)
│
└── workflows
└── # Aqui conter seus workflowsPara exemplificar a utilização da arquitetura, abaixo temos a implementação de um script básico que roda as pipelines em sequencia, e adiciona um console.log() a cada step executado.
O primeiro passo é criar nossos steps, neste exemplo, eles só vão receber o dado de contexto global da aplicação e mostra-lo no console.
src/steps/ExampleStep1.ts
import Step from "core/Step";
export default class ExampleStep1 extends Step {
async run(context: GlobalContext): Promise<GlobalContext> {
// Your code here
console.log("Step1 running");
return context;
}
}src/steps/ExampleStep2.ts
import Step from "core/Step";
export default class ExampleStep2 extends Step {
async run(context: GlobalContext): Promise<GlobalContext> {
// Your code here
console.log("Step2 running");
return context;
}
}O segundo passo é criar a nossa pipeline, nesse caso vamos utilizar uma pipeline sequencial pois não precisamos que os nossos steps sejam executados de forma paralela.
src/pipelines/ExamplePipeline.ts
import SequentialPipeline from "core/SequentialPipeline";
import ExampleStep1 from "steps/ExampleStep1";
import ExampleStep2 from "steps/ExampleStep2";
export default class ExamplePipeline extends SequentialPipeline {
constructor() {
const step1 = new ExampleStep1();
const step2 = new ExampleStep2();
super([step1, step2]);
}
}O último passo é criar o nosso Workflow, dizer a ele qual a pipeline ele deve executar (lembrando que você pode executar quantas quiser) e executar.
src/workflows/ExampleWorkflow.ts
import Workflow from "core/Workflow";
import ExamplePipeline from "pipelines/ExamplePipeline";
export default class ExampleWorkflow extends Workflow {
constructor() {
const pipeline1 = new ExamplePipeline();
super([pipeline1]);
}
async run(context: GlobalContext): Promise<GlobalContext> {
for (const pipeline of this.pipelines) {
try {
context = await pipeline.run(context);
} catch (error) {
throw error;
}
}
return context;
}
}index.ts
import ExampleWorkflow from "workflows/ExampleWorkflow";
// Variável com o contexto global que será acessado e modificado pelos steps
const data: GlobalContext = {
name: "Workflow Architecture",
};
(async () => {
const exampleWorkflow = new ExampleWorkflow();
console.log("result", await exampleWorkflow.run(data));
})();Gabriel Rabelo
@gabrielrab

