Alura > Cursos de Programação > Cursos de PHP > Conteúdos de PHP > Primeiras aulas do curso Arquitetura com PHP: escalando uma aplicação monolítica

Arquitetura com PHP: escalando uma aplicação monolítica

Lentidão na aplicação - Apresentação

E aí, pessoal! Boas-vindas a Alura. O instrutor Vinicius Dias vai te guiar nesse curso sobre System Design, arquitetura de sistemas e escalabilidade de aplicações PHP.

Audiodescrição: Vinicius Dias é um homem branco de olhos castanhos. Tem cabelo curto e escuro, usa bigode e cavanhaque e está com uma camisa cinza com as mangas pretas. Ao fundo, uma parede branca sem decorações.

Se você é uma pessoa que trabalha com desenvolvimento há algum tempo ou já tem alguma experiência na área de desenvolvimento, esse curso é para você!

O que vamos aprender?

Vamos aprender a criar aplicações escaláveis, isto é, que aguentam uma demanda crescente. Por exemplo, se for uma aplicação web, vai aguentar muitas requisições.

Nesse curso, vamos começar com uma aplicação de arquitetura mais simples e comum. Aos poucos, vamos evoluir e adicionar novos componentes a arquitetura dessa aplicação.

Por isso, vamos falar muito sobre system design ou arquitetura de sistemas. São termos sinônimos que abrangem as decisões que tomamos em relação ao que envolve a nossa aplicação.

Por exemplo, vamos entender a arquitetura inicial da aplicação com a qual vamos trabalhar por meio de um diagrama. Nesse curso, não vamos ter o front-end, só a parte da API.

Diagrama da arquitetura inicial da aplicação. Cinco retângulos conectados por setas, onde os retângulos de Front-end (Container: SPA Angular), API (Container: PHP Laravel) e Banco de dados (Container: MySQL) estão dentro de um retângulo maior chamado System Name (Software System). Fora do System Name, temos os retângulos de Usuário (person) e Servidor de e-mail (software system). As setas formam o seguinte fluxo: o usuário acessa front-end que realiza chamadas à API (JSON/HTTP). Por sua vez, a API lê e escreve os dados no banco de dados e envia e-mails para servidor de e-mail.

Vamos ter uma pessoa usuária acessando a nossa aplicação, a API recebe os acessos diretamente e lê e escreve dados um banco de dados relacional. Além disso, a API também tem acesso a um sistema externo de um servidor de e-mail.

Repara que é uma aplicação monolítica. É assim que vamos começar o curso. Também vamos terminar o curso com uma aplicação monolítica, mas o diagrama da arquitetura vai ser bem diferente.

Diagrama de arquitetura final da aplicação. Dez retângulos conectados por setas. Dentro de um retângulo maior chamado Avaliador (software system), temos Front-end (Container: SPA Angular), Load Balancer (Container: Nginx), API (Container: PHP Laravel), Message Broker (Container: SQS), Queue worker (Container: PHP Laravel), Armazenamento (Container: S3), Banco em memória (Container: Redis) e Banco relacional (Container: MySQL). Fora do Avaliador, temos usuário (person) e servidor de e-mail (software sytem). As setas formam o seguinte fluxo: usuário acessa front-end que realizada chamadas à API (JSON/HTTP) que passam pelo Load Balancer que distribui o tráfego para a API. Por sua vez, a API armazena dados pré-calculados no banco em memória, lê e escreve dados no banco relacional e publica mensagens no message broker. Por fim, o queue worker consome os dados de message broker, salva relatórios em armazenamento e envia e-mails para servidor de e-mail.

No segundo diagrama, a pessoa usuária novamente acessa a aplicação. Porém, as chamadas para a API vão passar por um load balancer (balanceamento de carga). Com isso, podemos ter vários servidores de API.

Além disso, vamos ter o conceito de message broker (fila de mensageria). Para ler os dados dessa fila, vamos ter outro processo que chamamos de queue worker (trabalhador de fila). Dessa forma, a API e o processo que lida com as mensagens vão estar separados.

Além do banco de dados relacional que vamos acessar para ler e escrever os dados, também vamos ter um banco de dados em memória, por exemplo, para armazenar cache.

Por fim, vamos ter um serviço de armazenamento. Vamos conversar sobre a possibilidade de ter um serviço de externo para armazenamento de arquivo para armazenar relatórios que teremos na aplicação.

Perceba como o primeiro diagrama é bem simples e com poucos componentes, enquanto o segundo diagrama é bem mais completo e escalável.

Pré-requisitos

Para você aproveitar melhor esse curso, é importante que você tenha conhecimentos sólidos sobre Docker.

Como temos vários componentes nessa arquitetura (como load balancer, servidor da API, banco de dados relacional e em memória), vamos disponibilizá-los através do Docker Compose. Assim, cada pedaço da arquitetura vai ser um contêiner diferente.

Existem cursos na Alura específicos sobre Docker. É importante que você os faça antes de continuar nesse curso.

Sobre linguagens de programação e framework, esse curso é de PHP. Porém, não tem problema ser uma pessoa desenvolvedora de outra linguagem, como C# ou Java.

Boa parte do que vamos aprender nesse curso é genérico e relacionado a system design e arquitetura de sistemas. Por isso, não importa qual a linguagem de programação você conhece. Mas, é preciso que você tenha um bom domínio dos conceitos de desenvolvimento web.

Agora, vamos falar especificamente sobre PHP. Para você entender sobre o que vamos abordar, é interessante que você tenha feito os cursos de Laravel ou Symfony. Lá, vamos ter aprendido boa parte dos conceitos que vamos aplicar sobre uma nova perspectiva nesse curso.

Se você não completou os cursos sobre os frameworks Laravel e Symfony, saiba que não são obrigatórios, mas vão te ajudar bastante a acompanhar o que faremos. Contudo, você pode fazer esse curso primeiro para entender sobre a escalabilidade de uma aplicação e depois estudar detalhes específicos dos frameworks.

Além desses cursos, recomendamos outros conteúdos para incrementar seu aprendizado:

Já temos cursos específicos na Alura sobre cada parte da nossa aplicação para você se aprofundar na ferramenta.

Comunidade

A ideia desse curso é você focar no aprendizado de escalabilidade de aplicações, conceitos de arquitetura de sistemas e system design.

Se você ficar com alguma dúvida, te convidamos a fazer parte do servidor do Discord da Alura. Lá, você pode levantar questionamentos e ajudar outras pessoas que possam estar estudando o mesmo tema. Também te convidamos a participar do fórum da Alura, onde você pode postar e responder dúvidas de outras pessoas.

Esperamos que tirem muito proveito desse conteúdo. No próximo vídeo, vamos conhecer a aplicação na qual vamos trabalhar durante esse curso.

Lentidão na aplicação - Conhecendo a aplicação

Agora que você já tem o sistema rodando e a aplicação funcionando, vamos te explicar o que é essa aplicação.

Conhecendo a aplicação

A ideia dessa aplicação é ser uma API para um consultório médico que permite cadastrar especialistas e avaliações (reviews) para cada especialista.

Na atividade "Preparando o ambiente", disponibilizamos os passos para preencher o banco de dados com essas informações. Dessa forma, já temos muitos dados nessa aplicação inicial.

O endereço dessa API é:

http://localhost:8123/api

Nesse endereço, você vai acessar todos os endpoints. Dentre eles, os endpoints principais que teremos acesso são:

A aplicação está em inglês, porque é uma língua comum no dia a dia de uma pessoa desenvolvedora.

Lista de especialistas e avaliações

Vamos tentar acessar http://localhost:8123/api/specialists para recuperar a lista de especialistas. Faremos isso utilizando o Postman, mas você pode utilizar qualquer cliente HTTP.

Após apertar o botão "Send" (ou atalho "Ctrl + Enter"), tentamos realizar essa requisição GET e recebemos o status 401 na resposta, informando que não estamos autorizados a fazer essa requisição.

{
    "message": "Unauthenticated."
}

No corpo da resposta, temos um JSON com a chave message e o valor Unauthenticated, ou seja, não autenticado. Isso significa que ainda não fizemos o login.

Para corrigir esse problema, vamos acessar http://localhost:8123/api/login também com o Postman, mas utilizando o verbo POST.

No corpo da requisição, vamos passar um JSON contendo o e-mail e senha de uma pessoa usuária foi criada automaticamente para você:

{
    "email": "email@example.com",
    "password": "12345678"
}

OK

Quando enviamos essa requisição, recebemos um status 200 OK e no corpo da resposta também recebemos um "OK".

Agora, estamos logados. Com isso, uma sessão foi armazenada no Postman e, portanto, podemos realizar a requisição GET do endoint /specialists.

Trecho da resposta:

{
    "current_page": 1,
    "data": [
        {
            "id": "99438af9-8f33-4b97-bd34-d677cbd70854",
            "created_at": "2023-05-27T02:15:34.000000Z",
            "updated_at":"2023-05-27T02:15:34.000000Z",
            "crm": "37.855.dg",
            "name": "Thaddeus Koepp",
            "specialty": "ortopedia",
            "email": "kuhic.jamar@example.org"
        },

Desse modo, conseguimos visualizar a lista de especialistas. Repare que a lista já está paginada. Na página 1, temos os dados de ID, data de criação e atualização, CRM do especialista, nome, especialidade e o e-mail.

Cada objeto vai ter todas essas chaves do array do JSON e seus respectivos valores.

    ],
    "first_page_url": "http://localhost:8123/api/specialists?page=1",
    "from": 1,
    "last_page": 67,
    "last_page_url": "http://localhost:8123/api/specialists?page=67",
    "links": [
        {
            "url": null,
            "label": "« Previous",
            "active": false
        },
        {
            "url": "http://localhost:8123/api/specialists?page=1",
            "label": "1",
            "active": true
        },

No final do JSON, temos os detalhes de paginação. Temos a URL para a primeira página, para a última página, para a próxima página, para a quantidade de páginas, etc.

Nessa lista de especialistas, vamos pegar um especialista qualquer e copiar seu ID, por exemplo, o segundo especialista. Agora, vamos fazer uma nova requisição para buscar duas avaliações.

Vamos acessar http://localhost:8123/api/specialists/ seguido do ID copiado e /reviews.

http://localhost:8123/api/specialists/99438b05-4575-4644-a38b-04dadb69afcd/reviews

Após fazer uma requisição de tipo GET para esse endereço, temos a lista de avaliações para esse segundo especialista.

Trecho da resposta:

{
    "current_page": 1,
    "data": [
        {
            "id": "99438b05-52ca-4761-9337-63299b898280",
            "created_at": "2023-05-27T02:15:42.000000Z",
            "updated_at": "2023-05-27T02:15:42.000000Z",
            "specialist_id": "99438b05-4575-4644-a38b-04dadb69afcd",
            "rating": 5,
            "comment": "Veritatis nostrum quam in eius saepe totam."
        },

Novamente, temos o ID, data de criação e atualização, ID do especialista, nota da avaliação de 1 a 10 e um comentário em texto.

No contexto do nosso sistema, temos uma lista de especialistas e lista de avaliações.

Contexto da aplicação

Para você entender melhor, vamos analisar um pequeno diagrama que explica o contexto da nossa aplicação.

Diagrama do contexto aplicação. Três retângulos conectados por setas. As setas formam o seguinte fluxo: o usuário (person) acessa o avaliador (software system) que usa (SMTP) o servidor de e-mail (software system).

A pessoa usuária vai acessar esse avaliador, que é o sistema de avaliação de médicos. Nesse sistema, vamos usar um servidor de e-mail para notificar a pessoa especialista que ela recebeu uma nova avaliação.

Vamos acessar o Postman para criar uma nova avaliação, usando o mesmo endereço com o ID do especialista que copiamos anteriormente. Porém, vamos fazer uma requisição do tipo POST.

http://localhost:8123/api/specialists/99438b05-4575-4644-a38b-04dadb69afcd/reviews

Passaremos o conteúdo como um JSON, tendo uma chave rating com uma nota 8. Também passaremos uma chave comment, tendo um valor como string de um comentário da avaliação em português.

{
    "rating": ,
    "comment": "Comentário da avaliação em português"
}

Ao clicar em "Send" para realizar o cadastro dessa avaliação, temos um status 201 Created, informando que a avaliação foi criada.

{
    "specialist_id": "99438b05-4575-4644-a38b-04dadb69afcd",
    "rating": 8,
    "comment": "Comentário da avaliação em português",
    "id": "99913c0c-3901-4bcd-9d59-e5565a94ee8e",
    "updated_at": "2023-07-04T17:09:27.000000Z",
    "created_at": "2023-07-04T17:09:27.000000Z"
}

Quando acessamos o endereço localhost:8025 no navegador, temos acesso a uma aplicação de e-mail chamada Mailpit. Essa aplicação que já fornecemos junto com o sistema inicial vai interceptar todos os e-mails que foram enviados para a aplicação.

No inbox, já temos acesso ao e-mail da nova avaliação, enviado para o e-mail do especialista ygerlach@example.net:

You have a new Review

Rating: 8

Comment: Comentário da avaliação em português

Em inglês, nos é informado que temos uma nova avaliação com a nota e o comentário. Assim, conseguimos avaliar o conteúdo desse e-mail.

Essa é a nossa aplicação. Repare que temos o sistema avaliador e temos um sistema externo que utilizamos para enviar e-mails e notificar a pessoa especialista que recebeu uma nova avaliação.

Agora que entendemos o contexto, vamos efetivamente conhecer problemas desse sistema, falar sobre sua arquitetura e aumentar sua escalabilidade no próximo vídeo.

Lentidão na aplicação - Notificando o usuário

Já entendemos como é a API onde vamos trabalhar. Recapitulando o diagrama mostrado anteriormente, temos a figura de uma pessoa usuária que acessa o sistema avaliador que usa o servidor de e-mail. Por exemplo, podemos utilizar o protocolo SMTP para enviar esses e-mails.

Agora que entendemos o contexto, vamos utilizar outro cliente diferente do Postman. É só uma questão de preferência do instrutor, mas você pode continuar a utilizar o Postman.

Notificando a pessoa usuária

Vamos abrir o PHP Storm, onde já temos as requisições configuradas. Vamos realizar a requisição do tipo POST de login para armazenar a sessão e ter a pessoa usuária logada, clicando no botão de play à esquerda da requisição.

### Login
POST http://localhost:8123/api/login
Accept: application/json
Content-Type: application/json

{
    "email": "email@example.com",
    "password": "12345678"
}

Response code: 200 (OK); 197ms (197 ms); Content length: 2 bytes

Temos a mesma resposta de "OK".

Em seguida, vamos tentar acessar a lista de especialistas.

### All Specialists
GET http://localhost:8123/api/specialists
Accept: application/json

Temos novamente aquela longa resposta que já havíamos visualizado.

Response code: 200 (OK); Time: 84 ms (84 ms); Content length: 5132 bytes

Mas também temos algumas informações como o código da resposta, que foi 200 (OK). Nesse computador com suas configurações, o tempo da resposta foi de aproximadamente 84 milissegundos.

E se buscamos as reviews de algum especialista? Vamos pegar o ID do segundo especialista e substituir no endereço para buscar todas as avaliações desse especialista. É a mesma requisição que fizemos no vídeo anterior.

### Specialist's review
GET http://localhost:8123/api/specialists/99438b05-4575-4644-a38b-04dadb69afcd/reviews
Accept: application/json

Response code: 200 (OK); Time: 82 ms (82 ms); Content length: 6168 bytes

Tivemos um tempo de resposta de 82 milissegundos, bastante semelhante ao anterior.

Agora, vamos adicionar outra avaliação para aquele mesmo especialista, assim como fizemos anteriormente. Adicionamos uma nota 9 e o comentário como Nova avaliação.

### New review for Vinicius
POST http://localhost:8123/api/specialists/99438b05-4575-4644-a38b-04dadb69afcd/reviews
Accept: application/json
Content-Type: application/json

{
    "rating" : 9,
    "comment": "Nova avaliação"
}

Ao enviar essa requisição, sabemos que vamos salvar essa avaliação no banco de dados e também enviar um e-mail. Repare que isso vai fazer a aplicação demorar mais para responder.

Response code: 201 (Created); Time: 181 ms (181 ms); Content length: 224 bytes

Recebemos o status de sucesso, 201 que informa que foi criado. O tempo que a API levou para devolver foi de 181 milissegundos. Isso é mais do que o dobro do que demorávamos normalmente.

Já sabíamos que salvar uma avaliação no banco de dados demora mais do que buscar uma avaliação no banco de dados. Quando inserimos uma informação em um banco, ele vai precisar verificar a consistência dos dados, atualizar tabela de índices, conferir se todos os checks estão conferidos.

Contudo, mais do que o dobro do tempo é uma demora muito grande.

Mais uma vez, no diagrama da aplicação, temos uma seta do sistema avaliador se comunicando com um sistema externo que é o servidor de e-mail. O envio de e-mail também demora e consome tempo.

Estamos enviando esse e-mail na hora em que adicionamos a avaliação. Será que a pessoa que está adicionando a avaliação precisa esperar que o especialista receba seu e-mail? Não precisa.

Ao invés do avaliador mandar o e-mail na hora, ele poderia salvar uma informação de que tem um e-mail para enviar, já responder à pessoa usuária e só depois fazer esse envio. Em outras palavras, queremos tornar o envio desse e-mail assíncrono.

Envio de e-mail assíncrono

O que queremos fazer? Queremos pegar a aplicação e adicionar algo para conseguir armazenar tarefas para serem executadas no futuro. No nosso caso, a tarefa é enviar um e-mail. Fazer isso no Laravel é muito simples.

Vamos abrir o projeto no PHP Storm. Dentro da pasta "app > Mail", vamos acessar a classe ReviewCreated.php.

Nessa classe, após extends Mailable, vamos implementar o ShouldQueue. Essa interface diz para o sistema do Laravel que o e-mail não precisa ser enviado na hora. Assim, vai ser armazenado em uma fila que depois vai ser processada.

ReviewCreated.php:

use Illuminate\Contracts\Queue\ShouldQueue;

class ReviewCreated extends Mailable implements ShouldQueue
{
    // código omitido…
}

Antes de continuar, vamos abrir aquele sistema de e-mail Mailpit no navegador. Vamos voltar para a lista de e-mail e apagar todas os e-mails do inbox, clicando em "Delete all" na lateral esquerda.

Agora, vamos refazer a requisição de adicionar uma nova avaliação. Quando enviamos essa requisição novamente, o tempo de resposta já diminuiu. Foi de 181 para 136 milissegundos.

Response code: 201 (Created); Time: 136 ms (136 ms); Content length: 224 bytes

Ainda demora mais do que buscar dados, porque inserir um dado no banco demora mais do que buscar. Isso é esperado.

Além disso, só temos um e-mail enviado em um cenário local, ou seja, na nossa máquina. Em um sistema real, esse servidor de e-mails vai estar em outro computador e até em outra rede em outro continente. Essa demora seria ainda maior.

O simples fato de deixar o envio assíncrono faz com que a resposta seja bem mais rápida.

Porém, ao abrir o sistema de e-mail no navegador, o e-mail não está no inbox. Se você fez os cursos de Laravel ou Symfony, você sabe que falta uma parte crucial do processo: ter um código que vai ler a fila de tarefas onde está armazenado a tarefa de enviar o e-mail.

No cenário do Laravel, vamos abrir um terminal e passar o comando php artisan queue:work.

No nosso caso, como utilizamos o Docker, queremos rodar esse comando dentro do contêiner app que contém a nossa aplicação. Portanto, antes de php, vamos digitar docker compose exec app.

docker compose exec app php artisan queue:work

Ao executar, está sendo processado a tarefa de envio de e-mail ReviewCreated:

2023-07-04 17:19:33 App\Mail\ReviewCreated

Feito isso, quando acessamos o sistema de e-mail no navegador, já temos o e-mail informando que uma nova avaliação foi criada.

You have a new Review

Rating: 9

Comment: Nova avaliação

Conclusão

Agora, temos dois processos diferentes. Em um processo, a API recebe e responde às requisições, salva no banco de dados e faz todo a lógica de negócios. Já o outro processo consome as mensagens na fila.

Essa separação nos trouxe algumas vantagens. No próximo vídeo, vamos entender melhor quais são as vantagens, além de ter uma resposta mais rápida.

A seguir, vamos falar sobre conceitos como performance e escalabilidade. Te espero lá!

Sobre o curso Arquitetura com PHP: escalando uma aplicação monolítica

O curso Arquitetura com PHP: escalando uma aplicação monolítica possui 139 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de PHP em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda PHP acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas