thesis: expand architecture and technology rationales
Pedro Lucas Porcellis porcellis@eletrotupi.com · 2 days ago
c901c35af9c84b6a29e5185545ced1129f33b8f4 Browse filesParents
31d5e0aChanged files
3
Additions
+200
Deletions
-19
thesis/chapters/architecture.tex
1 1
\chapter{Arquitetura e Funcionamento do Sistema}
2 2
3 3
Como delineado as tecnologias no capítulo anterior, aqui se apresenta a
4 4
arquitetura, demonstrando como todas as tecnologias apresentadas acima se
5 5
conectam e conversam entre si.
10 10
\label{fig:arch}
11 11
\includegraphics[width=0.9\linewidth]{arch-v2.png}
12 12
\source{Elaborado pelo autor (2026)}
13 13
\end{figure}
14 14
15 +
Três planos compõem a arquitetura representada na Figura~\ref{fig:arch} ---
16 +
cliente, \textit{back-end} e infraestrutura ---, lidos da esquerda para a
17 +
direita. O aplicativo móvel comunica-se exclusivamente com a API por
18 +
HTTPS/JSON e mantém no dispositivo apenas o estado efêmero da interface e um
19 +
cache local dos dados já consultados; validação, autorização e regras de
20 +
domínio ficam concentradas no servidor. A API é a única porta de entrada para
21 +
os dados: acessa o PostgreSQL por meio do Prisma, grava avatares no
22 +
armazenamento de objetos e delega o trabalho que não precisa bloquear a
23 +
resposta ao usuário a filas no Valkey, consumidas por \textit{workers}
24 +
independentes. São esses \textit{workers} que enviam e-mails e notificações e
25 +
recalculam os indicadores, mantendo a API livre de tarefas de longa duração.
26 +
27 +
Essa organização tem como principal força a separação de responsabilidades em
28 +
torno de um contrato único: como o cliente só conhece a API, mudanças internas
29 +
no banco, nas filas ou nos serviços externos não o afetam, e a autenticação por
30 +
JWT permite que a API permaneça sem estado, reiniciável sem perda de sessão. O
31 +
desacoplamento por filas também torna o registro de um humor uma operação
32 +
curta, independente do custo de recalcular indicadores ou disparar
33 +
notificações. Em contrapartida, a topologia é deliberadamente monolítica e
34 +
vertical: todos os componentes são implantados em um único servidor por meio do
35 +
Docker Compose, sem replicação horizontal da API ou separação física entre
36 +
banco, fila e \textit{workers}. Isso significa que o trabalho pesado dos
37 +
\textit{workers} compete por CPU e memória com o atendimento das requisições, e
38 +
que a disponibilidade do conjunto equivale à disponibilidade desse único
39 +
\textit{host}. Trata-se de um \textit{trade-off} coerente com o porte do
40 +
projeto, mas que delimita seu principal limite de escalabilidade.
15 41
16 42
Este capítulo detalha o funcionamento de algumas funcionalidades
17 43
desenvolvidas durante o projeto. Apenas algumas funcionalidades mais
18 44
chaves são abordadas neste capítulo, como o registro de humor, o vínculo
19 45
de um gatilho, os indicadores, o registro de atividades e, por fim, a
409 435
complementa, mas não substitui, a exportação integral: para uma visão
410 436
completa de todos os tipos de dado, o usuário ainda recorre ao
411 437
\textit{zip} de CSV.
412 438
413 439
\FloatBarrier
thesis/chapters/tech.tex
1 1
\chapter{Tecnologias}
2 2
3 3
Este capítulo apresenta as principais tecnologias empregadas no desenvolvimento
4 4
do aplicativo, organizadas de acordo com a camada da arquitetura em que atuam:
5 5
\textit{front-end}, \textit{back-end} e infraestrutura/persistência de dados.
111 111
export type CreateMoodInput = z.infer<typeof CreateMoodSchema>;
112 112
\end{lstlisting}
113 113
114 114
\sourcecode{Elaborado pelo autor (2026)}
115 115
116 -
Aqui, é possível observar alguns elementos, como a extração direta dos
117 -
\textit{enums} mapeados pelo Prisma, citado anteriormente nas linhas 1--5. Em
118 -
seguida, são criados os \textit{schemas} de validação: um valida os componentes
116 +
Aqui, é possível observar alguns elementos. O primeiro é o uso de
117 +
\texttt{z.nativeEnum} para reaproveitar diretamente os \textit{enums}
118 +
declarados no esquema do Prisma (linhas 1--5), em vez de repetir as listas de
119 +
valores no código. Como esses enums são a mesma fonte de verdade que o banco
120 +
impõe, a validação aceita exatamente o conjunto de valores que pode ser
121 +
persistido, e qualquer valor fora dele é rejeitado na borda da API antes de
122 +
chegar à camada de persistência. Esse padrão se repete de forma extensiva nos
123 +
demais esquemas, que reutilizam enumerações de gatilho, tipo de ação de
124 +
cuidado, periodicidade de medicamento e tipo de indicador. Em seguida, são
125 +
criados os \textit{schemas} de validação: um valida os componentes
119 126
de humor de forma aninhada dentro do objeto recebido, e o outro valida os dados
120 127
do próprio registro --- os níveis aceitos de ansiedade, estresse e energia; a
121 128
conversão do campo \texttt{moment} para um objeto de data a partir da entrada
122 129
recebida; a exigência de ao menos um componente de humor selecionado; entre
123 130
outros. Por fim, esses dados são exportados como um \textit{type} do
186 193
O Prisma foi escolhido por permitir a manutenção do esquema do banco de dados
187 194
de forma versionada e sincronizada com o código da aplicação, reduzindo a
188 195
chance de divergência entre a estrutura do banco e as entidades manipuladas
189 196
pelo back-end, além de prover checagem de tipos em tempo de compilação para
190 197
as consultas realizadas.
198 +
199 +
O Código~\ref{lst:prisma-schema} reproduz um trecho representativo desse
200 +
esquema, o modelo de registro de humor. Além dos campos e de seus tipos, o
201 +
trecho evidencia a convenção de nomenclatura adotada: modelos e campos seguem o
202 +
padrão do TypeScript --- \texttt{PascalCase} para tipos e \texttt{camelCase}
203 +
para propriedades ---, enquanto as tabelas e colunas no PostgreSQL usam
204 +
\texttt{snake\_case}, com nomes de tabela no plural. Os atributos
205 +
\texttt{@map} e \texttt{@@map} fazem essa tradução, de modo que o código da
206 +
aplicação manipula \texttt{selectedMood} ou \texttt{anxietyLevel} sem que os
207 +
nomes físicos das colunas vazem para a API. O trecho também mostra a declaração
208 +
explícita das relações e o uso de enumerações para valores fechados. Esses
209 +
\textit{enums} não se limitam ao código: na migração, o Prisma os materializa
210 +
como tipos enumerados nativos do PostgreSQL, de modo que a restrição aos
211 +
valores previstos é imposta também pelo banco. O esquema declara enumerações
212 +
para humores, componentes e intensidade, categorias de gatilho, tipos de ação
213 +
de cuidado e periodicidade de medicamentos, entre outras. A partir desse único
214 +
arquivo, o Prisma gera o cliente tipado consumido pela API e as migrações SQL
215 +
versionadas no repositório, de modo que alterações no modelo passam por revisão
216 +
e são aplicadas de forma reproduzível em qualquer ambiente.
217 +
218 +
\begin{lstlisting}[caption={Trecho do esquema Prisma para o registro de humor}, label={lst:prisma-schema}]
219 +
model Mood {
220 +
id Int @id @default(autoincrement())
221 +
annotation String? @db.Text
222 +
moment DateTime @default(now())
223 +
selectedMood BaseMoodOption @default(GOOD) @map("selected_mood")
224 +
anxietyLevel Int @map("anxiety_level")
225 +
stressLevel Int @map("stress_level")
226 +
energyLevel Int @map("energy_level")
227 +
userId Int @map("user_id")
228 +
user User @relation(fields: [userId], references: [id])
229 +
230 +
moodComponents MoodComponent[]
231 +
triggerLinks TriggerMoodLink[]
232 +
careActions CareAction[]
233 +
234 +
@@map("moods")
235 +
}
236 +
\end{lstlisting}
237 +
238 +
\sourcecode{Elaborado pelo autor (2026)}
191 239
192 240
\subsection{VictoriaLogs e Vector}
193 241
194 242
Observabilidade, no contexto de sistemas distribuídos, designa a capacidade de
195 243
compreender o estado interno de uma aplicação a partir dos sinais que ela emite
390 438
GitHub Actions é uma plataforma de integração e entrega contínuas (CI/CD)
391 439
nativa do GitHub, que permite a definição de fluxos de trabalho automatizados
392 440
--- disparados por eventos como \textit{push} ou \textit{pull request} ---
393 441
descritos de forma declarativa em arquivos YAML \citep{githubactions:2026}.
394 442
395 -
No projeto, o fluxo de trabalho de CI/CD é dividido em três etapas
396 -
sequenciais e dependentes entre si. A primeira etapa (\texttt{test}) sobe um
397 -
serviço efêmero de PostgreSQL e executa a suíte de testes automatizados da
398 -
API a cada \textit{push} ou \textit{pull request} que modifique arquivos do
443 +
No projeto, o fluxo de trabalho de CI/CD é dividido em três etapas sequenciais
444 +
e dependentes entre si, como visto na
445 +
Figura~\ref{fig:screenshot-github-actions}. A primeira etapa (\texttt{test})
446 +
sobe um serviço efêmero de PostgreSQL e executa a suíte de testes automatizados
447 +
da API a cada \textit{push} ou \textit{pull request} que modifique arquivos do
399 448
diretório da API.
400 449
401 450
A segunda etapa (\texttt{build-and-push}), condicionada ao
402 451
sucesso da primeira e restrita a eventos de \textit{push}, constrói a imagem
403 452
Docker de produção da API e a publica no GitHub Container Registry (GHCR),
409 458
\textit{branch} \texttt{master}, conecta-se ao servidor de produção via SSH e
410 459
realiza a atualização do serviço da API por meio da atualização da tag da
411 460
imagem no arquivo \texttt{.env} e da recriação do contêiner correspondente
412 461
via Docker Compose.
413 462
463 +
\begin{figure}[H]
464 +
\centering
465 +
\caption{Github Actions rodando os testes e realizando \textit{deploy}}
466 +
\label{fig:screenshot-github-actions}
467 +
\includegraphics[width=\linewidth]{screenshot-github-actions.png}
468 +
\source{Elaborado pelo autor (2026)}
469 +
\end{figure}
470 +
414 471
Esse processo elimina a necessidade de intervenção manual para a maior
415 472
parte do ciclo de implantação, restringindo a ação humana direta sobre o
416 473
servidor de produção a situações excepcionais, o que reduz a superfície de
417 474
erro humano e aumenta a confiabilidade do processo de entrega de novas
418 475
versões da aplicação.
423 480
descrever, de forma declarativa e idempotente, o estado desejado de um ou
424 481
mais servidores, dispensando a necessidade de um agente instalado na máquina
425 482
gerenciada, uma vez que se comunica com ela via SSH \citep{ansible:2026}.
426 483
427 484
No escopo deste projeto, o papel do Ansible é ortogonal ao do Docker Compose
428 -
e ao do GitHub Actions: enquanto estes automatizam, respectivamente, a
429 -
orquestração dos serviços da aplicação e o ciclo de integração e entrega
430 -
contínuas, o Ansible é responsável exclusivamente pelo provisionamento
431 -
inicial do servidor de produção, executado uma única vez --- ou sempre que um
432 -
novo servidor precisar ser provisionado. Esse provisionamento consiste em:
433 -
(1) criar o usuário dedicado à aplicação; (2) adicionar a chave pública SSH
434 -
utilizada pelo GitHub Actions para se autenticar no servidor durante a etapa
435 -
de implantação; (3) criar os diretórios da aplicação com as permissões
436 -
adequadas; e (4) instalar o Docker no servidor. A cópia do arquivo de
437 -
variáveis de ambiente de produção (\texttt{.env.production}), por conter
438 -
segredos e credenciais sensíveis, é deliberadamente mantida como uma etapa
439 -
manual, não automatizada, e não é versionada no repositório.
485 +
e ao do GitHub Actions: enquanto estes automatizam, respectivamente,
486 +
a orquestração dos serviços da aplicação e o ciclo de integração e entrega
487 +
contínuas, o Ansible é responsável exclusivamente pelo provisionamento inicial
488 +
do servidor de produção, executado uma única vez --- ou sempre que um novo
489 +
servidor precisar ser provisionado. Esse provisionamento consiste em: (1) criar
490 +
o usuário dedicado à aplicação; (2) adicionar a chave pública SSH utilizada
491 +
pelo GitHub Actions para se autenticar no servidor durante a etapa de
492 +
implantação; (3) criar os diretórios da aplicação com as permissões adequadas;
493 +
e (4) instalar o Docker no servidor. A cópia do arquivo de variáveis de
494 +
ambiente de produção (\texttt{.env.production}), por conter segredos
495 +
e credenciais sensíveis, é deliberadamente mantida como uma etapa manual, não
496 +
automatizada, e não é versionada no repositório. O Ansible, trabalha de forma
497 +
modularizada, portanto permite que possamos organizar as tarefas a serem feitas
498 +
em ``perfis'', da qual podem ter uma serie de tarefas para serem executadas em
499 +
sequencia, \textit{templates}, etc. O Código~\ref{lst:ansible-setup} demonstra
500 +
a lista de tarefas iniciais, em qual máquinas devem rodar, através da diretiva
501 +
\textit{hosts}, o método de troca de permissão (através da diretiva
502 +
\textit{become}) e tarefas posteriores.
503 +
504 +
\begin{lstlisting}[caption={Provisionamento utilizando um \textit{playbook} de configuração}, label={lst:ansible-setup}]
505 +
- name: Base host setup
506 +
hosts: vps
507 +
become: yes
508 +
become_method: doas
509 +
510 +
roles:
511 +
- bootstrap
512 +
- docker
513 +
- appuser
514 +
515 +
post_tasks:
516 +
- name: Confirm deploy user can reach Docker
517 +
ansible.builtin.command: docker info
518 +
become_user: "{{ deploy_user }}"
519 +
changed_when: false
520 +
register: docker_check
521 +
522 +
- name: Show Docker status
523 +
ansible.builtin.debug:
524 +
msg: "Docker is reachable by {{ deploy_user }}"
525 +
when: docker_check.rc == 0
526 +
\end{lstlisting}
527 +
528 +
\sourcecode{Elaborado pelo autor (2026)}
529 +
530 +
O Código~\ref{lst:ansible-setup-tasks} mostra o conteúdo de um desses papéis, o
531 +
de Docker. Cada tarefa descreve um estado desejado --- o pacote instalado, o
532 +
diretório existente com determinada permissão, o arquivo de configuração do
533 +
\textit{daemon} com seu conteúdo, o serviço habilitado --- em vez de uma
534 +
sequência de comandos imperativos. É essa forma declarativa que permite ao
535 +
Ansible inspecionar o estado atual e agir apenas sobre o que diverge; a tarefa
536 +
de rotação de \textit{logs}, por exemplo, vale para todos os contêineres, e não
537 +
apenas para aqueles que declaram um bloco de \textit{logging} próprio no arquivo
538 +
de composição.
539 +
540 +
\begin{lstlisting}[caption={Tarefas de provisionamento utilizando um \textit{playbook}}, label={lst:ansible-setup-tasks}]
541 +
- name: Install Docker and Compose plugin
542 +
community.general.apk:
543 +
name:
544 +
- docker
545 +
- docker-cli-compose
546 +
state: present
547 +
548 +
- name: Ensure /etc/docker exists
549 +
ansible.builtin.file:
550 +
path: /etc/docker
551 +
state: directory
552 +
owner: root
553 +
group: root
554 +
mode: "0755"
555 +
556 +
# Bound every container's json-file logs globally, not just the ones that set
557 +
# an explicit logging block in a compose file
558 +
- name: Configure the Docker daemon log rotation
559 +
ansible.builtin.copy:
560 +
dest: /etc/docker/daemon.json
561 +
content: |
562 +
{
563 +
"log-driver": "json-file",
564 +
"log-opts": { "max-size": "10m", "max-file": "3" }
565 +
}
566 +
owner: root
567 +
group: root
568 +
mode: "0644"
569 +
notify: restart docker
570 +
571 +
- name: Enable Docker service
572 +
ansible.builtin.service:
573 +
name: docker
574 +
enabled: yes
575 +
state: started
576 +
577 +
\end{lstlisting}
578 +
579 +
\sourcecode{Elaborado pelo autor (2026)}
580 +
581 +
Automatizar esse provisionamento com o Ansible, em vez de repetir os mesmos
582 +
passos manualmente por SSH, não economiza apenas comandos: troca um
583 +
procedimento implícito, que existiria apenas na memória de quem o executa, por
584 +
um artefato versionado e revisável. Por ser idempotente, o \textit{playbook}
585 +
pode ser reexecutado após uma falha parcial ou sobre um servidor já configurado
586 +
sem duplicar usuários, diretórios ou configurações; como consequência, o
587 +
processo se torna auditável e reproduzível por terceiros.
588 +
Em um projeto de código aberto, é isso que permite que outra pessoa reconstrua
589 +
a infraestrutura do zero --- ou a audite antes de confiar nela --- sem acesso
590 +
prévio ao servidor de produção, além de reduzir a chance de erro humano e de
591 +
divergência entre ambientes. A divisão em papéis (\textit{roles}) reforça essa
592 +
propriedade ao separar responsabilidades --- usuário da aplicação, Docker e
593 +
configuração base ---, de modo que cada parte pode ser alterada e testada
594 +
isoladamente.
440 595
441 596
A separação entre o provisionamento inicial (\textit{Ansible}), a orquestração de
442 597
execução (\textit{Docker Compose}) e a automação do ciclo de
443 598
entrega (\textit{GitHub Actions}) reflete uma decisão arquitetural:
444 599
cada ferramenta atua em uma camada distinta e bem delimitada do processo
451 606
ao documentar e automatizar o processo de provisionamento, o projeto
452 607
favorece um modelo mais alinhado aos princípios de privacidade e de
453 608
autonomia sobre os dados dos usuários, permitindo que instituições ou
454 609
indivíduos preocupados com a confidencialidade de dados sensíveis de
455 610
saúde mental hospedem e controlem sua própria instância da aplicação.
/dev/null → thesis/images/screenshot-github-actions.png
Binary file