| 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.
|
| 163 |
|
| 164 | \subsection{PostgreSQL}
|
| 165 |
|
| 166 | PostgreSQL é um sistema gerenciador de banco de dados relacional, de código
|
| 167 | aberto, reconhecido por sua conformidade com o padrão SQL, robustez e suporte
|
| 168 | a tipos de dados avançados \citep{postgresql:2026}.
|
| 169 |
|
| 170 | Sua escolha se justifica pela natureza estruturada e relacional dos dados do
|
| 171 | domínio da aplicação (registros de humor associados a usuários, horários,
|
| 172 | gatilhos e categorias), que se beneficia das garantias de integridade
|
| 173 | referencial e das transações ACID oferecidas por um banco relacional
|
| 174 | maduro, em detrimento de soluções não relacionais.
|
| 175 |
|
| 176 | \subsection{Prisma}
|
| 177 |
|
| 178 | Prisma é um \textit{Object-Relational Mapper} (ORM) para o ecossistema
|
| 179 | Node.js/TypeScript, que gera automaticamente um cliente de acesso ao banco de
|
| 180 | dados tipado a partir de um esquema declarativo, além de oferecer um sistema
|
| 181 | de migrações versionadas \citep{prisma:2026}.
|
| 182 |
|
| 183 | O Prisma foi escolhido por permitir a manutenção do esquema do banco de dados
|
| 184 | de forma versionada e sincronizada com o código da aplicação, reduzindo a
|
| 185 | chance de divergência entre a estrutura do banco e as entidades manipuladas
|
| 186 | pelo back-end, além de prover checagem de tipos em tempo de compilação para
|
| 187 | as consultas realizadas.
|
| 188 |
|
| 189 | \section{Implantação e Desenvolvimento Contínuo} \label{sec:deploy-and-ci}
|
| 190 |
|
| 191 | Já os termos \textit{implantação} (\textit{deployment}) e
|
| 192 | \textit{automatização} referem-se ao conjunto de práticas e ferramentas
|
| 193 | utilizadas para transformar o código-fonte de uma aplicação em um sistema
|
| 194 | efetivamente disponível e em execução, de forma repetível e com o mínimo de
|
| 195 | intervenção manual \cite{Humble_Farley_2011}. No caso do Nexo, essa camada é
|
| 196 | sustentada por três ferramentas complementares. O Docker é utilizado para
|
| 197 | empacotar a aplicação e suas dependências em contêineres isolados e portáveis,
|
| 198 | garantindo que o ambiente de execução seja idêntico entre desenvolvimento e
|
| 199 | produção; o Docker Compose orquestra a execução conjunta de múltiplos
|
| 200 | contêineres (aplicação, banco de dados, Valkey), definindo suas configurações e
|
| 201 | interdependências em um único arquivo declarativo. O GitHub Actions é
|
| 202 | responsável pela integração e entrega contínuas (CI/CD), automatizando
|
| 203 | etapas como execução de testes e construção de imagens a cada alteração no
|
| 204 | código. Por fim, o Ansible é utilizado para o provisionamento inicial da
|
| 205 | infraestrutura, automatizando a configuração do servidor onde a aplicação é
|
| 206 | hospedada.
|
| 207 |
|
| 208 | \subsection{Docker e Docker Compose}
|
| 209 |
|
| 210 | Docker é uma plataforma de conteinerização que permite empacotar uma
|
| 211 | aplicação e suas dependências em uma unidade isolada e portátil, capaz de ser
|
| 212 | executada de forma consistente em diferentes ambientes \citep{docker:2026}. O
|
| 213 | Docker Compose é uma ferramenta complementar que permite definir e orquestrar
|
| 214 | múltiplos contêineres --- e as relações entre eles --- por meio de um único
|
| 215 | arquivo de configuração declarativo \citep{dockercompose:2026}.
|
| 216 |
|
| 217 | No projeto, o Docker Compose é utilizado tanto no ambiente de desenvolvimento
|
| 218 | quanto no ambiente de produção para orquestrar os serviços de banco de dados
|
| 219 | (PostgreSQL), armazenamento em memória (Valkey) e da própria API, garantindo
|
| 220 | que as versões e configurações desses serviços sejam idênticas entre as
|
| 221 | máquinas de desenvolvimento e o servidor de produção.
|
| 222 |
|
| 223 | O uso de \textit{healthchecks} em conjunto com a diretiva
|
| 224 | \texttt{depends\_on} e a condição \texttt{service\_healthy} assegura
|
| 225 | que a API só seja iniciada após o banco de dados e o Valkey estarem de
|
| 226 | fato prontos para aceitar conexões, evitando falhas de inicialização
|
| 227 | por condição de corrida (\textit{race condition}), como ilustrado no
|
| 228 | Código~\ref{lst:healthcheck-compose}.
|
| 229 |
|
| 230 | \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}]
|
| 231 | valkey:
|
| 232 | image: valkey/valkey:9.0-alpine
|
| 233 | healthcheck:
|
| 234 | test: ["CMD", "valkey-cli", "ping"]
|
| 235 | interval: 10s
|
| 236 | timeout: 5s
|
| 237 | retries: 5
|
| 238 | start_period: 5s
|
| 239 |
|
| 240 | api:
|
| 241 | build: ./api
|
| 242 | depends_on:
|
| 243 | valkey:
|
| 244 | condition: service_healthy
|
| 245 | db:
|
| 246 | condition: service_healthy
|
| 247 | \end{lstlisting}
|
| 248 |
|
| 249 | \sourcecode{Elaborado pelo autor (2026)}
|
| 250 |
|
| 251 | A adoção do Docker se justifica, no contexto de uma aplicação de saúde
|
| 252 | mental de código aberto, pelo objetivo de garantir a reprodutibilidade do
|
| 253 | ambiente de execução: qualquer pessoa que clone o repositório --- seja para
|
| 254 | contribuir com o projeto, auditar seu funcionamento ou realizar sua própria
|
| 255 | implantação --- consegue reproduzir o ambiente completo sem depender de
|
| 256 | configurações manuais específicas do sistema operacional hospedeiro, o que
|
| 257 | reforça o caráter \textit{self-service} do projeto.
|
| 258 |
|
| 259 | \subsection{GitHub Actions}
|
| 260 |
|
| 261 | GitHub Actions é uma plataforma de integração e entrega contínuas (CI/CD)
|
| 262 | nativa do GitHub, que permite a definição de fluxos de trabalho automatizados
|
| 263 | --- disparados por eventos como \textit{push} ou \textit{pull request} ---
|
| 264 | descritos de forma declarativa em arquivos YAML \citep{githubactions:2026}.
|
| 265 |
|
| 266 | No projeto, o fluxo de trabalho de CI/CD é dividido em três etapas
|
| 267 | sequenciais e dependentes entre si. A primeira etapa (\texttt{test}) sobe um
|
| 268 | serviço efêmero de PostgreSQL e executa a suíte de testes automatizados da
|
| 269 | API a cada \textit{push} ou \textit{pull request} que modifique arquivos do
|
| 270 | diretório da API.
|
| 271 |
|
| 272 | A segunda etapa (\texttt{build-and-push}), condicionada ao
|
| 273 | sucesso da primeira e restrita a eventos de \textit{push}, constrói a imagem
|
| 274 | Docker de produção da API e a publica no GitHub Container Registry (GHCR),
|
| 275 | com marcação automática pelo SHA do \textit{commit} e, condicionalmente, pela
|
| 276 | tag \texttt{latest} quando o \textit{push} ocorre na \textit{branch}
|
| 277 | \texttt{master}.
|
| 278 |
|
| 279 | Por último, a terceira etapa (\texttt{deploy-production}), restrita à
|
| 280 | \textit{branch} \texttt{master}, conecta-se ao servidor de produção via SSH e
|
| 281 | realiza a atualização do serviço da API por meio da atualização da tag da
|
| 282 | imagem no arquivo \texttt{.env} e da recriação do contêiner correspondente
|
| 283 | via Docker Compose.
|
| 284 |
|
| 285 | Esse processo elimina a necessidade de intervenção manual para a maior
|
| 286 | parte do ciclo de implantação, restringindo a ação humana direta sobre o
|
| 287 | servidor de produção a situações excepcionais, o que reduz a superfície de
|
| 288 | erro humano e aumenta a confiabilidade do processo de entrega de novas
|
| 289 | versões da aplicação.
|
| 290 |
|
| 291 | \subsection{Ansible}
|
| 292 |
|
| 293 | Ansible é uma ferramenta de automação de infraestrutura que permite
|
| 294 | descrever, de forma declarativa e idempotente, o estado desejado de um ou
|
| 295 | mais servidores, dispensando a necessidade de um agente instalado na máquina
|
| 296 | gerenciada, uma vez que se comunica com ela via SSH \citep{ansible:2026}.
|
| 297 |
|
| 298 | No escopo deste projeto, o papel do Ansible é ortogonal ao do Docker Compose
|
| 299 | e ao do GitHub Actions: enquanto estes automatizam, respectivamente, a
|
| 300 | orquestração dos serviços da aplicação e o ciclo de integração e entrega
|
| 301 | contínuas, o Ansible é responsável exclusivamente pelo provisionamento
|
| 302 | inicial do servidor de produção, executado uma única vez --- ou sempre que um
|
| 303 | novo servidor precisar ser provisionado. Esse provisionamento consiste em:
|
| 304 | (1) criar o usuário dedicado à aplicação; (2) adicionar a chave pública SSH
|
| 305 | utilizada pelo GitHub Actions para se autenticar no servidor durante a etapa
|
| 306 | de implantação; (3) criar os diretórios da aplicação com as permissões
|
| 307 | adequadas; e (4) instalar o Docker no servidor. A cópia do arquivo de
|
| 308 | variáveis de ambiente de produção (\texttt{.env.production}), por conter
|
| 309 | segredos e credenciais sensíveis, é deliberadamente mantida como uma etapa
|
| 310 | manual, não automatizada, e não é versionada no repositório.
|
| 311 |
|
| 312 | A separação entre o provisionamento inicial (\textit{Ansible}), a orquestração de
|
| 313 | execução (\textit{Docker Compose}) e a automação do ciclo de
|
| 314 | entrega (\textit{GitHub Actions}) reflete uma decisão arquitetural:
|
| 315 | cada ferramenta atua em uma camada distinta e bem delimitada do processo
|
| 316 | de implantação, o que favorece a reprodutibilidade de todo o processo,
|
| 317 | do zero, por qualquer pessoa que deseje hospedar sua própria instância da aplicação.
|
| 318 |
|
| 319 | Essa preocupação é particularmente relevante no contexto de um
|
| 320 | aplicativo de saúde mental de código aberto: ao reduzir a dependência
|
| 321 | de serviços proprietários de implantação (\textit{walled gardens}) e
|
| 322 | ao documentar e automatizar o processo de provisionamento, o projeto
|
| 323 | favorece um modelo mais alinhado aos princípios de privacidade e de
|
| 324 | autonomia sobre os dados dos usuários, permitindo que instituições ou
|
| 325 | indivíduos preocupados com a confidencialidade de dados sensíveis de
|
| 326 | saúde mental hospedem e controlem sua própria instância da aplicação.
|