DEV Community

Cover image for Você sabe o que é 'middleware'?
policarpo
policarpo

Posted on • Updated on

Você sabe o que é 'middleware'?

Apresentação

Antes de falar de middleware é importante compreender o conceito de "pipeline", que em tradução livre significa "encanamento".

Em desenvolvimento de software, pipeline representa o caminho percorrido pela informação.

Para ajudar na compreensão, vamos imaginar uma avenida com tráfego intenso. A avenida possui cruzamentos, rotatórias, retornos e desvios que levam a diversos destinos.

Assim que o veículo entra nessa avenida ele pode percorrê-la até o final baseado na sinalização.

Nosso veículo então continua seu caminho até encontrar uma sinalização indicando que apenas veículos com 2 ou mais ocupantes podem seguir. Caso contrário será necessário voltar ao início da avenida.

O cenário que foi mostrado traz os componentes necessários para compreensão do artigo.

Por analogia, temos:

  • avenida -> pipeline
  • veículos -> dado (ou informação)
  • sinalização -> middleware

Mas afinal o que é um middleware?

A palavra middleware foi utilizada pela primeira vez em uma convenção de engenharia de software organizada pela OTAN em Outubro de 1968, na Alemanha.

Conforme observado por Alexander d’Agapeyeff "...não importa o quão bom seja um software de controle de arquivos (por exemplo) ... ainda assim será inapropriado ou ineficiente...".

Outro ponto que merece destaque é o fato de que "...versões novas do mesmo software geralmente não tem como base a versão anterior...".

Essa premissas trouxeram à tona a necessidade de desenvolver uma rotina que servisse de ponte entre o software dos clientes e o software principal.

Podemos ver na pirâmide invertida de d’Agapeyeff que o middleware foi posicionado justamente entre esses extremos, fazendo o papel de "cola".

Pirâmide invertida

Outro cenário comum no uso de middleware está no acesso a instituições financeiras.

A tarefa de autorizar o acesso para quem está se conectando pode ser delegada a um middleware.

Se as credenciais forem válidas o middleware encaminha o usuário para o software principal onde ele poderá realizar quaisquer operações disponíveis para ele.

E se as credenciais não forem válidas, o usuário é redirecionado para a página de login.

Este cenário é particularmente interessante pois mostra a versatilidade do middleware.

Caso a instituição financeira decida reforçar a segurança, ela pode simplesmente modificar as regras contidas no middleware sem afetar a aplicação principal.


Ferramentas necessárias

PHP 8 ou superior
MySQL Server versão 5.7 ou superior
Composer versão 2.0 ou superior
Editor de textos

Existem instaladores que trazem embutidos o PHP, MySQL e Apache (servidor web), como WAMPSERVER, XAMPP e Laragon. Deixo a seu critério a escolha de alguns desses pacotes, ou outro que seja familiar a você.

Para o editor de textos estou utilizando o Visual Studio Code. Além de ser gratuito possibilita a instalação de diversas extensões.


Banco de dados

Se tudo estiver configurado corretamente conseguiremos acessar o banco de dados.

Utilizando o terminal, digite a seguinte instrução:

mysql -u root -p
Enter fullscreen mode Exit fullscreen mode

Basta informar a senha configurada na instalação do MySQL para obtermos acesso ao servidor.

Saberemos que estamos conectados porque a linha de comando do terminal agora virá precedida de mysql>.

A partir deste ponto, todas as instruções digitadas no terminal do servidor MySQL devem terminar com ponto-e-vírgula (;).

Em primeiro lugar precisamos criar um database exclusivo para nosso projeto.

No terminal MySQL vamos digitar a instrução a seguir:

create database middleware;
Enter fullscreen mode Exit fullscreen mode

Em seguida nos conectamos ao database criado, digitando o seguinte:

use middleware;
Enter fullscreen mode Exit fullscreen mode

Em segundo lugar criaremos uma tabela para armazenar alguns registros.

Dentro do terminal do MySQL digitaremos a seguinte instrução:

create table invoices (id smallint not null auto_increment, issuer_date date not null, customer varchar(100) not null, value double default 0, primary key(id));

Enter fullscreen mode Exit fullscreen mode

Se não houver nenhum erro, teremos uma tabela com esta estrutura:

Describe invoices table

Agora que nossa tabela foi criada vamos inserir alguns registros.

insert into invoices (issuer_date, customer, value) values ('2021-11-3', 'George', 1651.68);
insert into invoices (issuer_date, customer, value) values ('2021-7-29', 'Alfred', 834.01);
insert into invoices (issuer_date, customer, value) values ('2022-4-13', 'Lewis', 146.17);
Enter fullscreen mode Exit fullscreen mode

Consultando os dados da tabela invoices obteremos este resultado:

Select invoices table


Projeto

Nosso projeto será uma aplicação Laravel, conhecido framework PHP para desenvolvimento de aplicações web e que no momento em que escrevo este artigo se encontra na versão 9.

A documentação oficial oferece um exemplo de middleware que testa um token passado no corpo da requisição e embora seja um exemplo bem didático, vamos fazer diferente: testaremos se o banco de dados está disponível antes de fazer consultas.

Para isso vamos acessar o terminal e digitar a seguinte instrução:

composer create-project --prefer-dist laravel/laravel middleware
Enter fullscreen mode Exit fullscreen mode

O tempo para conclusão desta etapa pode variar conforme a velocidade de conexão à internet e a configuração do computador


Middleware

Ainda dentro do terminal, vamos acessar a pasta do projeto e criar a classe middleware com a seguinte instrução:

php artisan make:middleware EnsureDatabaseIsAvailable
Enter fullscreen mode Exit fullscreen mode

Por padrão os middlwares criados através da instrução acima ficarão na pasta App\Http\Middleware.

Utilizando o editor de textos vamos abrir a classe criada e editar o método handle.

<?php

namespace App\Http\Middleware;

use Closure;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;

class EnsureDatabaseIsAvailable
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        try
        {
            DB::connection()->getPdo();
            return $next($request);
        }
        catch(Exception $e)
        {
            return response()->json([
                'errorCode' => $e->getCode(),
                'message' => 'Database is unavailable. Try again later',
                'timestamp' => now()
            ], Response::HTTP_SERVICE_UNAVAILABLE);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Explicando o código, verificamos se há conexão com o banco de dados através do método getPdo() da classe DB.

Se não houver conexão disponível o método getPdo() lançará uma exceção que será capturada no bloco catch logo abaixo, possibilitando a criação de uma resposta customizada.


Controller

Voltando ao terminal criaremos agora a controller com a seguinte instrução:

php artisan make:controller InvoiceController
Enter fullscreen mode Exit fullscreen mode

Por padrão as controllers criadas através da instrução acima ficarão na pasta App\Http\Controllers.

Para a classe controller implementaremos apenas um método que será responsável por fazer uma consulta simples ao banco de dados.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class InvoiceController extends Controller
{
    public function getInvoices()
    {
        $result = DB::select('select * from invoices');
        return response()->json($result);
    }
}

Enter fullscreen mode Exit fullscreen mode

Rota

Ainda no editor vamos abrir a classe \route\api.php adicionando uma nova rota que acionará o método getInvoices() criado na controller \app\Http\Controllers\InvoiceController.

<?php

use App\Http\Controllers\InvoiceController;
use App\Http\Middleware\EnsureDatabaseIsAvailable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware(
    [
        EnsureDatabaseIsAvailable::class
    ])
    ->get('/invoice', [InvoiceController::class, 'getInvoices']);
Enter fullscreen mode Exit fullscreen mode

Testando

Para obtermos o resultado esperado (retornar uma mensagem caso o banco de dados esteja indisponível) precisamos parar o servidor MySQL.

Agora, acessando novamente o terminal na pasta do projeto vamos iniciar o servidor web embutido no PHP:

php artisan serve --port=8106
Enter fullscreen mode Exit fullscreen mode

Voltando ao Visual Studio Code vamos abrir a extensão Thunder, que nos possibilitará realizar requisições REST para nossa api.

Database unavailable

Podemos ver a mensagem customizada que implementamos na classe
\app\Http\middleware\EnsureDatabaseIsAvailable.php.

Para concluir iniciamos o servidor MySQL realizando uma requisição para nossa api.

Database available

Ordem de execução

Caso a rota ou aplicação precise de mais de um middleware (sim, pode acontecer) podemos determinar a ordem de execução.

O método middleware() na classe \route\api.php recebe como parâmetro um array das classes que serão utilizadas na rota.

Por exemplo, se fosse necessário verificar a conexão com banco de dados antes de validar um token, o código que implementaria esta situação seria este:

<?php

use App\Http\Controllers\InvoiceController;
use App\Http\Middleware\EnsureDatabaseIsAvailable;
use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware(
    [
        EnsureDatabaseIsAvailable::class,
        EnsureTokenIsValid::class
    ])
    ->get('/invoice', [InvoiceController::class, 'getInvoices']);

Enter fullscreen mode Exit fullscreen mode

Conclusão

Quase todas as linguagens de programação trazem alguma implementação de middleware.

Middleware é uma ferramenta poderosa e às vezes, pouco explorada.

Entretanto, dada sua característica de processar todas as requisições da pipeline, a sua criação e utilização devem ser criteriosas para evitar adversidades com performance, por exemplo.

Extensões utilizadas

Em desenvolvimento web, uma boa ferramenta para testar requisições api faz toda diferença.

Por isso disponibilizo aqui o link para a extensão para VSCode Thunder RESTClient, não deixando nada a desejar para outras ferramentas.


Já para aplicações Laravel, a extensão Laravel Extra Intellisense ajuda muito na inclusão automática de referências.


Repositório público

Este projeto está publicado no Github neste repositório.

Discussion (0)