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".
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
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;
Em seguida nos conectamos ao database criado, digitando o seguinte:
use middleware;
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));
Se não houver nenhum erro, teremos uma tabela com esta estrutura:
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);
Consultando os dados da tabela invoices obteremos este resultado:
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
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
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); } } }
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
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); } }
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']);
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
Voltando ao Visual Studio Code vamos abrir a extensão Thunder, que nos possibilitará realizar requisições REST para nossa api.
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.
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']);
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.
Top comments (0)