HeadlinesBriefing favicon HeadlinesBriefing.com

Arquitetura de Núcleo Determinístico e Shell

Hacker News •
×

Há catorze anos, Gary Bernhardt popularizou o termo Functional Core, Imperative Shell. Como a maioria das boas ideias em computação, ele não era completamente novo, mas sua concepção tinha grande clareza e constitui uma excelente base para falar sobre testes e determinismo em sistemas existentes. Brevemente, a arquitetura Functional Core Imperative Shell divide o código em duas partes.

O Functional Core é puramente funcional - ou seja, sem I/O e sem atualizações de estado destrutivas. Ele se preocupa com a lógica de negócios do aplicativo. O Imperative Shell tem menos caminhos, mas mantém o estado, coordena dependências externas e lida com o mundo exterior - ou seja, I/O.

Seu trabalho é consultar o núcleo com valores, receber valores de volta como resultado de alguma decisão blackbox, e usá-los para interagir com o mundo exterior; seja escrevendo em um banco de dados, enviando uma solicitação, ou atualizando uma interface gráfica. O núcleo e o shell neste modelo têm características distintas: núcleo shell toma decisões coordena dependências muitos caminhos de execução ramificados mais execução linear isolado do mundo integra com o mundo Isso torna o núcleo muito adequado para testes. Como é puramente funcional, as mesmas entradas sempre darão os mesmos resultados.

Como está isolado, não há nada a ser mockado ou stubado. E como lida com lógica de negócios complexa, os testes podem nos dizer muito sobre como o sistema se comporta. Pureza funcional e determinismo Uma forma mais curta de descrever as propriedades que tornam as funções puras adequadas a testes é que elas são determinísticas.

Ou seja - dados um fluxo de entradas, uma função pura sempre retorna o mesmo fluxo de saídas; seu comportamento é repetível. Mas a programação funcional pura não é o único caminho para chegar lá. Se inclinarmos levemente a cabeça, podemos ver que um fluxo de valores e uma sequência de atribuições são formas diferentes de expressar a mesma coisa, e as Máquinas de Estado podem nos trazer os mesmos benefícios.

Considere o seguinte código: function add(ns) {return ns.reduce((a, b) => a + b, 0)} class Add Machine {#state = 0 transition(input) {this.#state += input} get state() {return this.#state}} a função add é fácil de racionalizar; é pura e, portanto, determinística. Mas a Add Machine também é determinística - dados a mesma sequência de chamadas à função transition, a Add Machine retornará o mesmo estado. Ser imperativo não muda isso. const output = add([1, 2, 3]) const a = new Add Machine() a.transition(1) a.transition(2) a.transition(3) const output = a.state A programação funcional pura é um bom paradigma, mas devido a considerações de linguagem ou desempenho, nem sempre é prático - não queria tentá-lo em C! Mas ao enfraquecer os requisitos de programação funcional pura para apenas determinística, mantemos os benefícios de testabilidade de Functional Core Imperative Shell, ao mesmo tempo que ampliamos sua aplicabilidade.

E assim o título desta postagem: Núcleo Determinístico, Shell Não Determinístico. Determinismo pode parecer um conceito mais abstrato do que a pureza funcional. Como você sabe quando o vê? Encontro que é mais fácil começar com o que não é determinístico e trabalhar para trás.

Aqui estão alguns exemplos comuns de comportamento não repetível: Chamar geradores de números aleatórios que não estão seedados Operações assíncronas e multi-threaded Comunicação sobre a rede Comunicação com outros processos Leitura Escrita em armazenamento local Interações com bancos de dados Pedir ao sistema operacional a data ou hora Todos esses pertencem ao shell não determinístico. Sempre que você os encontrar em sua lógica de negócios, você tem um alvo natural para fragmentação - seja dividindo a função em duas ao redor deles, ou levantando-os uma camada e injetando seu resultado como um parâmetro. É ilustrativo pensar na metáfora do shell de forma literal; ele deve cercar a lógica, consultando o coração do aplicativo para obter o que ele precisa. Trabalhando com o que você tem Tudo isso é bem legal, você pode pensar, mas qual é o uso disso para mim, trabalhando na codificação legada...