Você sabe o que é 'middleware'?
Marcio Policarpo
Posted on July 5, 2022
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.
Posted on July 5, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.