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)