| 1 | \chapter{Tecnologias}
|
| 2 |
|
| 3 | Este capítulo apresenta as principais tecnologias empregadas no desenvolvimento
|
| 4 | do aplicativo, organizadas de acordo com a camada da arquitetura em que atuam:
|
| 5 | \textit{front-end}, \textit{back-end} e infraestrutura/persistência de dados.
|
| 6 |
|
| 7 | \section{Front-end}
|
| 8 |
|
| 9 | O termo \textit{frontend} refere-se à camada de um sistema responsável pela
|
| 10 | interação direta com o usuário: a interface visual, a lógica de apresentação e
|
| 11 | o processamento que ocorre no dispositivo do próprio usuário, seja um navegador
|
| 12 | ou, como no caso do Nexo, um aplicativo móvel, conforme
|
| 13 | ~\citet{sommerville:2010}. É o frontend que renderiza telas, captura entradas
|
| 14 | do usuário (como toques e formulários) e comunica-se com o \textit{backend}
|
| 15 | através de requisições de rede, geralmente utilizando APIs. No caso do Nexo,
|
| 16 | essa camada é implementada com \textit{React Native} e Expo, permitindo que a
|
| 17 | mesma base de código seja compilada tanto para Android utilizando bibliotecas
|
| 18 | em \textit{TypeScript}.
|
| 19 |
|
| 20 | \subsection{React Native}
|
| 21 |
|
| 22 | React Native é um \textit{framework} de código aberto, mantido pela Meta, que
|
| 23 | permite o desenvolvimento de aplicativos móveis multiplataforma a partir de
|
| 24 | uma única base de código escrita em JavaScript/TypeScript, utilizando
|
| 25 | componentes que são traduzidos para elementos nativos tanto no iOS quanto no
|
| 26 | Android \citep{reactnative:2026}.
|
| 27 |
|
| 28 | A escolha do React Native se justifica pelo escopo reduzido da equipe de
|
| 29 | desenvolvimento (um único desenvolvedor) e facilidade de conhecimento e
|
| 30 | manutenção, sem trazer, para este projeto, benefícios que justifiquem esse
|
| 31 | custo adicional. Além disso, o ecossistema maduro do React Native oferece
|
| 32 | ampla disponibilidade de bibliotecas para funcionalidades já previstas no
|
| 33 | aplicativo, como notificações locais e gráficos.
|
| 34 |
|
| 35 | \subsection{Zustand}
|
| 36 |
|
| 37 | Zustand é uma biblioteca leve de gerenciamento de estado para aplicações
|
| 38 | React e React Native, que dispensa a estrutura verbosa de \textit{providers}
|
| 39 | e \textit{reducers} tipicamente associada a bibliotecas como Redux
|
| 40 | \citep{zustand:2026}.
|
| 41 |
|
| 42 | Ela foi escolhida para gerenciar o estado efêmero da aplicação (por exemplo,
|
| 43 | dados de formulários em preenchimento ou estados de navegação temporários)
|
| 44 | por sua API minimalista e baixo \textit{overhead} de configuração, adequada a um
|
| 45 | projeto de escopo individual em que a complexidade adicional de soluções
|
| 46 | mais robustas de gerenciamento de estado não se justifica.
|
| 47 |
|
| 48 | \section{Back-end}
|
| 49 |
|
| 50 | Já o termo \textit{backend} refere-se à camada de um sistema que processa a
|
| 51 | lógica de negócio, gerencia o acesso e a persistência de dados, e expõe essa
|
| 52 | funcionalidade a clientes (como o frontend) através de uma interface bem
|
| 53 | definida, tipicamente uma API HTTP \cite{Fowler_2013}. O \textit{backend} é
|
| 54 | responsável por validar dados recebidos, aplicar regras de negócio, autenticar
|
| 55 | usuários, e coordenar o acesso ao banco de dados e a outros serviços
|
| 56 | auxiliares. No caso do Nexo, essa camada é implementada em Node.js com o
|
| 57 | framework \textit{Express}, seguindo uma arquitetura em camadas (\textit{Route}
|
| 58 | $\rightarrow$ \textit{Controller} $\rightarrow$ \textit{Service} $\rightarrow$
|
| 59 | Prisma) que separa as responsabilidades de validação, lógica de negócio e
|
| 60 | acesso a dados.
|
| 61 |
|
| 62 | \subsection{Express.js}
|
| 63 |
|
| 64 | Express.js é um \textit{framework} minimalista para aplicações web em
|
| 65 | Node.js, amplamente utilizado para a construção de APIs \textit{REST},
|
| 66 | oferecendo uma camada fina de abstração sobre o roteamento de requisições HTTP e
|
| 67 | \textit{middlewares} \citep{express:2026}.
|
| 68 |
|
| 69 | Sua adoção se deve à simplicidade e à flexibilidade não opinativa do
|
| 70 | \textit{framework}, que permite estruturar a API de acordo com as
|
| 71 | necessidades específicas do projeto, sem impor convenções rígidas de
|
| 72 | arquitetura, além de sua ampla documentação e maturidade no ecossistema
|
| 73 | Node.js.
|
| 74 |
|
| 75 | \subsection{Zod}
|
| 76 |
|
| 77 | Zod é uma biblioteca de validação e definição de esquemas de dados para
|
| 78 | TypeScript, que permite declarar formatos esperados de entrada (como corpos
|
| 79 | de requisição) e inferir automaticamente os tipos estáticos correspondentes
|
| 80 | \citep{zod:2026}.
|
| 81 |
|
| 82 | Zod foi escolhido para validar os dados recebidos pela API antes de seu
|
| 83 | processamento, garantindo que registros de humor, sono e demais entradas do
|
| 84 | usuário estejam em conformidade com o formato esperado. A inferência
|
| 85 | automática de tipos a partir dos esquemas evita a duplicação de definições de
|
| 86 | tipo entre a camada de validação e o restante do código TypeScript do
|
| 87 | back-end.
|
| 88 |
|
| 89 | \begin{lstlisting}[language=typescript, caption={Validação e Inferência usando Zod}, label={lst:zod-inference}]
|
| 90 | import {
|
| 91 | BaseMoodOption,
|
| 92 | MoodComponentOption,
|
| 93 | IntensityLevel
|
| 94 | } from '@prisma/client';
|
| 95 |
|
| 96 | export const MoodComponentSchema = z.object({
|
| 97 | component: z.nativeEnum(MoodComponentOption),
|
| 98 | intensity: z.nativeEnum(IntensityLevel)
|
| 99 | });
|
| 100 |
|
| 101 | export const CreateMoodSchema = z.object({
|
| 102 | annotation: z.string().optional(),
|
| 103 | moment: z.coerce.date(),
|
| 104 | selectedMood: z.nativeEnum(BaseMoodOption),
|
| 105 | anxietyLevel: z.number().int().min(0).max(10),
|
| 106 | stressLevel: z.number().int().min(0).max(10),
|
| 107 | energyLevel: z.number().int().min(0).max(10),
|
| 108 | moodComponents: z.array(MoodComponentSchema).min(1)
|
| 109 | });
|
| 110 |
|
| 111 | export type CreateMoodInput = z.infer<typeof CreateMoodSchema>;
|
| 112 | \end{lstlisting}
|
| 113 |
|
| 114 | \sourcecode{Elaborado pelo autor (2026)}
|
| 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
|
| 119 | de humor de forma aninhada dentro do objeto recebido, e o outro valida os dados
|
| 120 | do próprio registro --- os níveis aceitos de ansiedade, estresse e energia; a
|
| 121 | conversão do campo \texttt{moment} para um objeto de data a partir da entrada
|
| 122 | recebida; a exigência de ao menos um componente de humor selecionado; entre
|
| 123 | outros. Por fim, esses dados são exportados como um \textit{type} do
|
| 124 | TypeScript, usado pelo compilador para a verificação estática de tipos no
|
| 125 | restante do back-end.
|
| 126 |
|
| 127 | \subsection{Valkey e BullMQ}
|
| 128 |
|
| 129 | Valkey é um banco de dados em memória, do tipo chave-valor, criado como
|
| 130 | \textit{fork} de código aberto do Redis, mantido sob a governança da Linux
|
| 131 | Foundation \citep{valkey:2026}. BullMQ é uma biblioteca para gerenciamento de
|
| 132 | filas de tarefas em Node.js, construída sobre bancos de dados compatíveis com
|
| 133 | o protocolo Redis \citep{bullmq:2026}.
|
| 134 |
|
| 135 | A combinação das duas ferramentas foi adotada para o processamento paralelo
|
| 136 | e assíncrono de tarefas que não precisam bloquear a resposta imediata da API,
|
| 137 | como o disparo de notificações programadas e o processamento de indicadores
|
| 138 | a partir do histórico de registros do usuário. A escolha do Valkey em
|
| 139 | específico, e não do Redis diretamente, se deve à mudança de licenciamento do
|
| 140 | Redis em 2024, que passou a restringir certos usos comerciais; o Valkey
|
| 141 | mantém compatibilidade total de protocolo, permanecendo sob licença
|
| 142 | permissiva de código aberto.
|
| 143 |
|
| 144 | \section{Infraestrutura e Persistência de Dados}
|
| 145 |
|
| 146 | A camada de \textit{infraestrutura e persistência} de um sistema abrange os
|
| 147 | componentes responsáveis por armazenar e gerenciar os dados de forma durável,
|
| 148 | bem como os serviços auxiliares que sustentam o funcionamento da aplicação em
|
| 149 | produção \cite{Fowler_2013}. O elemento central dessa camada costuma ser o
|
| 150 | sistema de gerenciamento de banco de dados (SGBD), responsável por garantir a
|
| 151 | integridade, consistência e durabilidade dos dados armazenados. No caso do
|
| 152 | Nexo, essa função é cumprida pelo PostgreSQL, um SGBD relacional, acessado
|
| 153 | através do Prisma, um \textit{Object-Relational Mapper} (ORM) que traduz
|
| 154 | operações do banco de dados em chamadas de código TypeScript, eliminando a
|
| 155 | necessidade de escrever consultas SQL manualmente na maior parte dos casos.
|
| 156 |
|
| 157 | Complementarmente, o Nexo utiliza o Valkey como armazenamento em memória de
|
| 158 | alta velocidade, empregado tanto para cache quanto como \textit{broker} de
|
| 159 | mensagens para o sistema de filas, cujo suas filas são gerenciadas com a
|
| 160 | biblioteca BullMQ para lidar com os \textit{background jobs} enviados. Os
|
| 161 | mesmos são responsáveis por processar os indicadores semanais e diários
|
| 162 | descritos nos capítulos anteriores. Além disso, o Nexo mantém uma pilha de
|
| 163 | observabilidade dedicada à coleta e à consulta dos \textit{logs} de seus
|
| 164 | contêineres, descrita a seguir, que complementa a persistência dos dados de
|
| 165 | domínio.
|
| 166 |
|
| 167 | \subsection{PostgreSQL}
|
| 168 |
|
| 169 | PostgreSQL é um sistema gerenciador de banco de dados relacional, de código
|
| 170 | aberto, reconhecido por sua conformidade com o padrão SQL, robustez e suporte
|
| 171 | a tipos de dados avançados \citep{postgresql:2026}.
|
| 172 |
|
| 173 | Sua escolha se justifica pela natureza estruturada e relacional dos dados do
|
| 174 | domínio da aplicação (registros de humor associados a usuários, horários,
|
| 175 | gatilhos e categorias), que se beneficia das garantias de integridade
|
| 176 | referencial e das transações ACID oferecidas por um banco relacional
|
| 177 | maduro, em detrimento de soluções não relacionais.
|
| 178 |
|
| 179 | \subsection{Prisma}
|
| 180 |
|
| 181 | Prisma é um \textit{Object-Relational Mapper} (ORM) para o ecossistema
|
| 182 | Node.js/TypeScript, que gera automaticamente um cliente de acesso ao banco de
|
| 183 | dados tipado a partir de um esquema declarativo, além de oferecer um sistema
|
| 184 | de migrações versionadas \citep{prisma:2026}.
|
| 185 |
|
| 186 | O Prisma foi escolhido por permitir a manutenção do esquema do banco de dados
|
| 187 | de forma versionada e sincronizada com o código da aplicação, reduzindo a
|
| 188 | chance de divergência entre a estrutura do banco e as entidades manipuladas
|
| 189 | pelo back-end, além de prover checagem de tipos em tempo de compilação para
|
| 190 | as consultas realizadas.
|
| 191 |
|
| 192 | \subsection{VictoriaLogs e Vector}
|
| 193 |
|
| 194 | Observabilidade, no contexto de sistemas distribuídos, designa a capacidade de
|
| 195 | compreender o estado interno de uma aplicação a partir dos sinais que ela emite
|
| 196 | --- em especial \textit{logs}, métricas e rastros ---, o que permite investigar
|
| 197 | problemas sem conhecer previamente sua causa \citep{opentelemetry:2026}. Entre
|
| 198 | esses sinais, os \textit{logs} são o mais direto: registros datados de eventos
|
| 199 | emitidos pelos serviços. No Nexo, eles cobrem tanto as requisições HTTP
|
| 200 | recebidas pela API quanto as tarefas processadas pelos \textit{workers} de
|
| 201 | segundo plano. A pilha descrita a seguir é dedicada a coletá-los, armazená-los
|
| 202 | e torná-los consultáveis.
|
| 203 |
|
| 204 | VictoriaLogs é um sistema de armazenamento e consulta de \textit{logs}
|
| 205 | desenvolvido pela VictoriaMetrics, projetado para operar como um único binário,
|
| 206 | sem dependências externas, e com consumo reduzido de memória e de disco
|
| 207 | \citep{victorialogs:2026}. Vector é um agente de coleta e roteamento de
|
| 208 | observabilidade, também distribuído como um único binário, capaz de ler
|
| 209 | \textit{logs} de diversas fontes --- arquivos, soquetes ou a API do Docker ---,
|
| 210 | transformá-los por meio de uma linguagem própria de remapeamento e enviá-los a
|
| 211 | diferentes destinos \citep{vector:2026}.
|
| 212 |
|
| 213 | A motivação para adotar essa combinação, em vez de uma pilha bastante difundida
|
| 214 | como Elasticsearch, Logstash e Kibana (ELK) \citep{elastic:2026}, é
|
| 215 | proporcional ao porte do projeto: o ELK exige múltiplos serviços, e o
|
| 216 | Elasticsearch é executado sobre a máquina virtual Java; segundo a documentação
|
| 217 | do VictoriaLogs, soluções como ele demandam consideravelmente mais memória e
|
| 218 | disco que o VictoriaLogs \citep{victorialogs:2026}. Em um servidor único que
|
| 219 | também hospeda a API, o PostgreSQL e o Valkey, esse custo seria
|
| 220 | desproporcional. VictoriaLogs e Vector cumprem o mesmo papel com dois
|
| 221 | contêineres menores/mais leves.
|
| 222 |
|
| 223 | No Nexo, o fluxo de \textit{logs} começa na própria API, que utiliza a
|
| 224 | biblioteca Pino para emitir um objeto JSON por linha, em vez de texto livre
|
| 225 | \citep{pino:2026}. Como o \textit{logger} só aplica formatação legível quando a
|
| 226 | saída é um terminal interativo, no contêiner --- onde a saída é um
|
| 227 | \textit{pipe} --- os registros permanecem em JSON de linha única. O
|
| 228 | \textit{middleware} \texttt{pino-http} associa a cada requisição um
|
| 229 | identificador (\texttt{X-Request-Id}), convenção difundida para correlacionar
|
| 230 | os registros de uma mesma requisição \citep{heroku:2026}, reaproveitando o
|
| 231 | cabeçalho homônimo quando enviado pelo cliente ou gerando um UUID.
|
| 232 |
|
| 233 | O Vector lê essas linhas por meio da API do Docker, cujo \textit{socket} é
|
| 234 | montado no contêiner como volume somente leitura, e aplica uma transformação de
|
| 235 | remapeamento que tenta interpretar cada mensagem como JSON; quando a
|
| 236 | interpretação é bem-sucedida, os campos do objeto são elevados ao nível
|
| 237 | superior do evento, o que torna consultáveis atributos como o nível do
|
| 238 | \textit{log}, a mensagem e o identificador do usuário. O resultado é enviado em
|
| 239 | lote ao \textit{endpoint} de ingestão compatível com a API do Elasticsearch do
|
| 240 | VictoriaLogs, conforme o Código~\ref{lst:vector-pipeline}.
|
| 241 |
|
| 242 | \begin{lstlisting}[caption={Configuração do \textit{pipeline} de \textit{logs} no \texttt{vector.yaml}}, label={lst:vector-pipeline}]
|
| 243 | sources:
|
| 244 | docker:
|
| 245 | type: docker_logs
|
| 246 |
|
| 247 | transforms:
|
| 248 | parse_json_message:
|
| 249 | type: remap
|
| 250 | inputs: [docker]
|
| 251 | source: |
|
| 252 | parsed, err = parse_json(.message)
|
| 253 | if err == null && is_object(parsed) {
|
| 254 | . = merge(., parsed) ?? .
|
| 255 | }
|
| 256 |
|
| 257 | sinks:
|
| 258 | victorialogs:
|
| 259 | type: elasticsearch
|
| 260 | inputs: [parse_json_message]
|
| 261 | endpoints: ["http://victorialogs:9428/logs/insert/elasticsearch/"]
|
| 262 | mode: bulk
|
| 263 | api_version: v8
|
| 264 | compression: gzip
|
| 265 | request:
|
| 266 | headers:
|
| 267 | VL-Stream-Fields: label.com.docker.compose.service
|
| 268 | VL-Time-Field: timestamp
|
| 269 | VL-Msg-Field: msg,message,_msg
|
| 270 | \end{lstlisting}
|
| 271 |
|
| 272 | \sourcecode{Elaborado pelo autor (2026)}
|
| 273 |
|
| 274 | O contêiner do VictoriaLogs é configurado com um período de retenção de 14
|
| 275 | dias e um teto de 2\,GiB de uso de disco; ambos são limites deliberados para
|
| 276 | que o armazenamento de \textit{logs} não comprometa o disco da máquina de
|
| 277 | produção, descartando os registros mais antigos quando qualquer um deles é
|
| 278 | atingido. O serviço escuta apenas na interface de \textit{loopback}
|
| 279 | (\texttt{127.0.0.1:9428}), e não em todas as interfaces, de modo que a
|
| 280 | interface de consulta não é exposta diretamente à internet. O acesso externo
|
| 281 | se dá pelo Nginx, que publica o caminho \texttt{/logs/} sob autenticação
|
| 282 | básica HTTP, com credenciais definidas no \textit{vault} criptografado do
|
| 283 | Ansible; a diretiva \texttt{-http.pathPrefix=/logs} no VictoriaLogs garante
|
| 284 | que os caminhos gerados pela interface correspondam ao prefixo publicado
|
| 285 | pelo \textit{proxy}, conforme ilustrado na
|
| 286 | Figura~\ref{fig:screenshot-victorialogs}.
|
| 287 |
|
| 288 | \begin{figure}[H]
|
| 289 | \centering
|
| 290 | \caption{Interface de consulta do VictoriaLogs com os \textit{logs} da API}
|
| 291 | \label{fig:screenshot-victorialogs}
|
| 292 | \includegraphics[width=\linewidth]{screenshot-victorialogs.png}
|
| 293 | \source{Elaborado pelo autor (2026)}
|
| 294 | \end{figure}
|
| 295 |
|
| 296 | Na captura, a consulta está restrita ao fluxo de \textit{logs} da API, por
|
| 297 | meio do filtro \texttt{label.com.docker.compose.service="api"}. À esquerda,
|
| 298 | o painel de \textit{stream fields} lista os fluxos disponíveis e o volume de
|
| 299 | registros de cada um; na parte superior, um histograma mostra a distribuição
|
| 300 | desses registros em intervalos de 30 minutos ao longo do período
|
| 301 | selecionado; e, na parte inferior, a tabela exibe as entradas
|
| 302 | correspondentes, com data, nível e mensagem, incluindo desde a sequência de
|
| 303 | inicialização da API e de seus \textit{workers} até o envio de notificações.
|
| 304 | Consultas como essa permitem, por exemplo, correlacionar as linhas de uma
|
| 305 | mesma requisição a partir do seu \texttt{X-Request-Id}.
|
| 306 |
|
| 307 | Como o Vector não substitui o \textit{driver} de \textit{logs} do Docker, o
|
| 308 | \textit{driver} \texttt{json-file} permanece ativo, porém limitado por
|
| 309 | rotação (10\,MB por arquivo, 3 arquivos por contêiner), de modo que
|
| 310 | \texttt{docker compose logs} continue funcionando sem que o disco do servidor
|
| 311 | cresça indefinidamente. As versões das imagens, a retenção e os limites de
|
| 312 | disco são declarados como variáveis no inventário do Ansible e renderizados
|
| 313 | no arquivo de ambiente, e o arquivo de configuração do Vector é copiado para
|
| 314 | o diretório da aplicação pela \textit{role} \texttt{app}, o que mantém a
|
| 315 | pilha de observabilidade reproduzível pelo mesmo processo de provisionamento
|
| 316 | descrito na Seção~\ref{sec:deploy-and-ci}.
|
| 317 |
|
| 318 | \section{Implantação e Desenvolvimento Contínuo} \label{sec:deploy-and-ci}
|
| 319 |
|
| 320 | Já os termos \textit{implantação} (\textit{deployment}) e
|
| 321 | \textit{automatização} referem-se ao conjunto de práticas e ferramentas
|
| 322 | utilizadas para transformar o código-fonte de uma aplicação em um sistema
|
| 323 | efetivamente disponível e em execução, de forma repetível e com o mínimo de
|
| 324 | intervenção manual \cite{Humble_Farley_2011}. No caso do Nexo, essa camada é
|
| 325 | sustentada por três ferramentas complementares. O Docker é utilizado para
|
| 326 | empacotar a aplicação e suas dependências em contêineres isolados e portáveis,
|
| 327 | garantindo que o ambiente de execução seja idêntico entre desenvolvimento e
|
| 328 | produção; o Docker Compose orquestra a execução conjunta de múltiplos
|
| 329 | contêineres (aplicação, banco de dados, Valkey), definindo suas configurações e
|
| 330 | interdependências em um único arquivo declarativo. O GitHub Actions é
|
| 331 | responsável pela integração e entrega contínuas (CI/CD), automatizando
|
| 332 | etapas como execução de testes e construção de imagens a cada alteração no
|
| 333 | código. Por fim, o Ansible é utilizado para o provisionamento inicial da
|
| 334 | infraestrutura, automatizando a configuração do servidor onde a aplicação é
|
| 335 | hospedada.
|
| 336 |
|
| 337 | \subsection{Docker e Docker Compose}
|
| 338 |
|
| 339 | Docker é uma plataforma de conteinerização que permite empacotar uma
|
| 340 | aplicação e suas dependências em uma unidade isolada e portátil, capaz de ser
|
| 341 | executada de forma consistente em diferentes ambientes \citep{docker:2026}. O
|
| 342 | Docker Compose é uma ferramenta complementar que permite definir e orquestrar
|
| 343 | múltiplos contêineres --- e as relações entre eles --- por meio de um único
|
| 344 | arquivo de configuração declarativo \citep{dockercompose:2026}.
|
| 345 |
|
| 346 | No projeto, o Docker Compose é utilizado tanto no ambiente de desenvolvimento
|
| 347 | quanto no ambiente de produção para orquestrar os serviços de banco de dados
|
| 348 | (PostgreSQL), armazenamento em memória (Valkey) e da própria API, garantindo
|
| 349 | que as versões e configurações desses serviços sejam idênticas entre as
|
| 350 | máquinas de desenvolvimento e o servidor de produção.
|
| 351 |
|
| 352 | O uso de \textit{healthchecks} em conjunto com a diretiva
|
| 353 | \texttt{depends\_on} e a condição \texttt{service\_healthy} assegura
|
| 354 | que a API só seja iniciada após o banco de dados e o Valkey estarem de
|
| 355 | fato prontos para aceitar conexões, evitando falhas de inicialização
|
| 356 | por condição de corrida (\textit{race condition}), como ilustrado no
|
| 357 | Código~\ref{lst:healthcheck-compose}.
|
| 358 |
|
| 359 | \begin{lstlisting}[caption={Configuração do \textit{healthcheck} do Valkey e de dependência condicional da API no \texttt{docker-compose.yml}}, label={lst:healthcheck-compose}]
|
| 360 | valkey:
|
| 361 | image: valkey/valkey:9.0-alpine
|
| 362 | healthcheck:
|
| 363 | test: ["CMD", "valkey-cli", "ping"]
|
| 364 | interval: 10s
|
| 365 | timeout: 5s
|
| 366 | retries: 5
|
| 367 | start_period: 5s
|
| 368 |
|
| 369 | api:
|
| 370 | build: ./api
|
| 371 | depends_on:
|
| 372 | valkey:
|
| 373 | condition: service_healthy
|
| 374 | db:
|
| 375 | condition: service_healthy
|
| 376 | \end{lstlisting}
|
| 377 |
|
| 378 | \sourcecode{Elaborado pelo autor (2026)}
|
| 379 |
|
| 380 | A adoção do Docker se justifica, no contexto de uma aplicação de saúde
|
| 381 | mental de código aberto, pelo objetivo de garantir a reprodutibilidade do
|
| 382 | ambiente de execução: qualquer pessoa que clone o repositório --- seja para
|
| 383 | contribuir com o projeto, auditar seu funcionamento ou realizar sua própria
|
| 384 | implantação --- consegue reproduzir o ambiente completo sem depender de
|
| 385 | configurações manuais específicas do sistema operacional hospedeiro, o que
|
| 386 | reforça o caráter \textit{self-service} do projeto.
|
| 387 |
|
| 388 | \subsection{GitHub Actions}
|
| 389 |
|
| 390 | GitHub Actions é uma plataforma de integração e entrega contínuas (CI/CD)
|
| 391 | nativa do GitHub, que permite a definição de fluxos de trabalho automatizados
|
| 392 | --- disparados por eventos como \textit{push} ou \textit{pull request} ---
|
| 393 | descritos de forma declarativa em arquivos YAML \citep{githubactions:2026}.
|
| 394 |
|
| 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
|
| 399 | diretório da API.
|
| 400 |
|
| 401 | A segunda etapa (\texttt{build-and-push}), condicionada ao
|
| 402 | sucesso da primeira e restrita a eventos de \textit{push}, constrói a imagem
|
| 403 | Docker de produção da API e a publica no GitHub Container Registry (GHCR),
|
| 404 | com marcação automática pelo SHA do \textit{commit} e, condicionalmente, pela
|
| 405 | tag \texttt{latest} quando o \textit{push} ocorre na \textit{branch}
|
| 406 | \texttt{master}.
|
| 407 |
|
| 408 | Por último, a terceira etapa (\texttt{deploy-production}), restrita à
|
| 409 | \textit{branch} \texttt{master}, conecta-se ao servidor de produção via SSH e
|
| 410 | realiza a atualização do serviço da API por meio da atualização da tag da
|
| 411 | imagem no arquivo \texttt{.env} e da recriação do contêiner correspondente
|
| 412 | via Docker Compose.
|
| 413 |
|
| 414 | Esse processo elimina a necessidade de intervenção manual para a maior
|
| 415 | parte do ciclo de implantação, restringindo a ação humana direta sobre o
|
| 416 | servidor de produção a situações excepcionais, o que reduz a superfície de
|
| 417 | erro humano e aumenta a confiabilidade do processo de entrega de novas
|
| 418 | versões da aplicação.
|
| 419 |
|
| 420 | \subsection{Ansible}
|
| 421 |
|
| 422 | Ansible é uma ferramenta de automação de infraestrutura que permite
|
| 423 | descrever, de forma declarativa e idempotente, o estado desejado de um ou
|
| 424 | mais servidores, dispensando a necessidade de um agente instalado na máquina
|
| 425 | gerenciada, uma vez que se comunica com ela via SSH \citep{ansible:2026}.
|
| 426 |
|
| 427 | 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.
|
| 440 |
|
| 441 | A separação entre o provisionamento inicial (\textit{Ansible}), a orquestração de
|
| 442 | execução (\textit{Docker Compose}) e a automação do ciclo de
|
| 443 | entrega (\textit{GitHub Actions}) reflete uma decisão arquitetural:
|
| 444 | cada ferramenta atua em uma camada distinta e bem delimitada do processo
|
| 445 | de implantação, o que favorece a reprodutibilidade de todo o processo,
|
| 446 | do zero, por qualquer pessoa que deseje hospedar sua própria instância da aplicação.
|
| 447 |
|
| 448 | Essa preocupação é particularmente relevante no contexto de um
|
| 449 | aplicativo de saúde mental de código aberto: ao reduzir a dependência
|
| 450 | de serviços proprietários de implantação (\textit{walled gardens}) e
|
| 451 | ao documentar e automatizar o processo de provisionamento, o projeto
|
| 452 | favorece um modelo mais alinhado aos princípios de privacidade e de
|
| 453 | autonomia sobre os dados dos usuários, permitindo que instituições ou
|
| 454 | indivíduos preocupados com a confidencialidade de dados sensíveis de
|
| 455 | saúde mental hospedem e controlem sua própria instância da aplicação.
|