Rafael Santos
Desenvolvedor backend · Engenheiro de IA
Sou o Rafael, desenvolvedor back-end. Programo sistemas e APIs viabilizando melhor performance, escalabilidade e segurança, para que o produto cresça sem perder clareza e para que quem mexer no código depois de mim entenda cada decisão que foi tomada.
I'm Rafael, a back-end developer. I build systems and APIs that deliver better performance, scalability and security, so a product can grow without losing clarity, so whoever touches the code after me understands every decision that was made.
No que estou trabalhando
Projetos
O que construí e o que estou desenvolvendo.
Abra um projeto para ver arquitetura, decisões técnicas e código.
Por dentro do projeto
Trisha
2025 — AGORA2025 — NOWRede social de aventuras ao ar livre: rastreamento GPS ao vivo e mapa colaborativo validado pela comunidade.
A social network for outdoor adventures: live GPS tracking and a collaborative map validated by the community.
Storage
2026 — AGORA2026 — NOWControle de estoque para mercado de bairro, como serviço: bipa o código de barras e o produto aparece, com foto e tudo.
Inventory control for neighbourhood grocery stores, as a service: scan the barcode and the product shows up, photo included.
TLT
2026 — AGORA2026 — NOWTradutor de fala em tempo real para desktop: legenda o áudio de qualquer chamada sem que nada saia do computador.
A real-time speech translator for desktop: it subtitles the audio of any call without anything leaving the computer.
Faturamento distribuído
Distributed billing
AGIBANK · ESTUDO DIRIGIDOAGIBANK · GUIDED STUDYQuatro serviços Spring Boot com os dois estilos de integração: BFF por REST na borda, transação publicando evento em Kafka, fatura em PDF e e-mail.
Four Spring Boot services covering both integration styles: a REST BFF at the edge, a transaction publishing to Kafka, invoices as PDF and e-mail.
NewsTech
2026 — AGORA2026 — NOWPortal de notícias de tecnologia: ingestão de feeds, triagem e redação por modelos de linguagem, renderizado no servidor.
A technology news portal: feed ingestion, triage and writing by language models, rendered on the server.
Experiência
Agibank · Desenvolvedor Back-end Javaatual
JUL 2025 — HOJEBack-end Java na estrutura de Plataforma e Cadastro, em Campinas/SP.
- Integração entre serviços do banco em fluxos síncronos e assíncronos, com o domínio modelado por eventos;
- Orquestração de processos de negócio com Camunda e mensageria com Apache Kafka;
- Persistência em bases relacionais e NoSQL, conforme o formato do dado pede;
- Curso de Java Avançado do banco concluído (mai/2026);
- Faturamento distribuído — quatro serviços comunicando por eventos Kafka, construídos durante o curso.
Projetos próprios · estudo contínuo
2024 — HOJEFora do expediente, construo software de verdade para aprender de verdade.
- Trisha — 5 microserviços + BFF em Java 21 e app Flutter;
- Wallet — carteira financeira pelo Open Finance: core em Java 25, BFF e um app Flutter que roda no celular, no navegador e no Windows;
- Storage — controle de estoque para mercado, como serviço: API em .NET 10 com MongoDB e front-end em React;
- TLT — tradutor de fala em tempo real para desktop, com os modelos rodando na própria máquina;
- NewsTech — portal de notícias com curadoria por modelos de linguagem;
- API de e-commerce — REST completa, containerizada com Docker e publicada no Render;
- Certificações Alura de Python e formação autodidata em Java.
Antes do código · transição de carreira
2023 — 2025Almoxarife, operador de caixa, gerente de vendas e freelancer — em período integral enquanto estudava programação à noite. A disciplina veio antes do primeiro commit.
Agibank · Back-end Developer (Java)current
JUL 2025 — NOWBack-end Java on the Platform & Registration group, in Campinas, Brazil.
- Integration across the bank's services in synchronous and asynchronous flows, with the domain modelled around events;
- Business process orchestration with Camunda and messaging with Apache Kafka;
- Persistence in relational and NoSQL stores, whichever fits the shape of the data;
- Completed the bank's Advanced Java course (May 2026);
- Distributed billing — four services talking through Kafka events, built during the course.
Personal projects · continuous learning
2024 — NOWOutside working hours I build real software, because that is how you actually learn.
- Trisha — 5 microservices + BFF in Java 21 and a Flutter app;
- Wallet — a finance wallet over Open Finance: a Java 25 core, a BFF and a Flutter app that runs on phones, in the browser and on Windows;
- Storage — inventory control for grocery stores, as a service: a .NET 10 API on MongoDB and a React front end;
- TLT — a real-time speech translator for desktop, with the models running on the machine itself;
- NewsTech — a news portal curated by language models;
- E-commerce API — full REST stack, containerised with Docker and deployed on Render;
- Alura Python certifications and self-taught Java.
Before code · career change
2023 — 2025Stockroom clerk, cashier, sales manager and freelancer — working full time while studying programming at night. The discipline came before the first commit.
Como trabalho
Como uma IA que pareia comigo há meses me descreve
How the AI that has been pairing with me for months describes my work
“Revisei cada linha com o Rafael por meses. O que define o trabalho dele não é uma tecnologia —
é o critério: nada entra na base sem ele saber exatamente por quê.”
Claude · par de programação no projeto Trisha
“I reviewed every line with Rafael for months. What defines his work isn't a technology —
it's the judgement: nothing lands in the codebase without him knowing exactly why.”
Claude · pair programmer on the Trisha project
- Decide em vez de acumular débito. Cada aparente atalho no código dele tem o porquê registrado. Ele distingue dívida técnica de decisão consciente — e deixa isso escrito para o próximo ler.
- Audita o próprio código sem ninguém pedir. Rodadas de hardening por iniciativa própria: controle de acesso, vazamento de dados, corridas de concorrência — achadas, corrigidas e provadas com teste.
- Processo acima de pressa. Planeja antes de executar, revisa tudo antes de comitar. Usa agentes de IA como ferramenta de produção diária — dirigindo e mantendo o controle do que entra na base.
- Veio de fora — e isso é força. Antes do primeiro emprego em tecnologia, estudou programação por conta própria enquanto trabalhava de almoxarife, caixa e vendedor. Quem cruza essa ponte sozinho não solta mais o volante.
- Decides instead of piling up debt. Every apparent shortcut in his code has the reason recorded. He tells technical debt apart from a deliberate decision — and leaves it in writing for whoever reads it next.
- Audits his own code without being asked. Hardening rounds on his own initiative: access control, data leaks, concurrency races — found, fixed and proven with tests.
- Process over hurry. Plans before executing, reviews everything before committing. Uses AI agents as a daily production tool — steering them and keeping control of what lands in the codebase.
- Came from outside — and that is a strength. Before his first job in tech, he taught himself programming while working as a stockroom clerk, cashier and salesman. Whoever crosses that bridge alone never lets go of the wheel again.
Stack
linguagens & frameworks
languages & frameworks
front-end & apps
front end & apps
dados
data
mensageria & tempo real
messaging & real time
arquitetura & APIs
architecture & APIs
infraestrutura & performance
infrastructure & performance
segurança & qualidade
security & quality
Projeto · 2025 — agora
Project · 2025 — now
Trisha
Rede social de aventuras ao ar livre: o usuário grava o percurso GPS em tempo real, marca pontos de interesse com foto como evidência e compartilha tudo num mapa colaborativo. Concebido, arquitetado e construído por mim de ponta a ponta — do modelo de dados ao app.
A social network for outdoor adventures: you record the GPS route in real time, mark points of interest with a photo as evidence and share it all on a collaborative map. Conceived, architected and built by me end to end — from the data model to the app.
O diferencial
Confiança calculada pela comunidade. Cada ponto de interesse tem um nível de confiança dinâmico: quanto mais usuários distintos o validam com evidência fotografada a menos de 50 m do local, mais confiável ele fica. O mapa é validado pela comunidade — não por uma pessoa só.
What makes it different
Trust calculated by the community. Every point of interest carries a dynamic trust level: the more distinct users validate it with a photo taken less than 50 m from the spot, the more trustworthy it gets. The map is validated by the community — not by one person.
Arquitetura
Architecture
Usuários, login social Google/Apple via JWKS, confirmação de e-mail e senha com BCrypt.
Users, Google/Apple social login via JWKS, e-mail confirmation and BCrypt passwords.
Domínio social: aventuras, caminhos, pontos de interesse com evidências, amizades, feed.
Social domain: adventures, paths, points of interest with evidence, friendships, feed.
GPS com PostGIS e índice GiST, sessões ao vivo por MQTT + WebSocket/STOMP.
GPS with PostGIS and a GiST index, live sessions over MQTT + WebSocket/STOMP.
Upload para MinIO (Cloudflare R2 em produção) com presigned URLs — binário nunca toca o banco.
Uploads to MinIO (Cloudflare R2 in production) with presigned URLs — binaries never touch the database.
Fachada única do front: agrega serviços, cache Redis por observador, circuit breaker fecha falhando.
The front end's single facade: aggregates services, Redis cache keyed per viewer, circuit breaker that fails closed.
Mapa escuro em tela cheia com trilhas plotadas, feed social, rastreio e acompanhamento ao vivo.
Full-screen dark map with plotted trails, social feed, tracking and live following.
Engenharia
- Autenticação ponta a ponta. JWT assinado com RSA + endpoint JWKS; todos os serviços são resource servers independentes — o BFF não é a única porta, e cada serviço defende a própria visibilidade.
- Mapa por viewport. Consulta por bounding box com query nativa PostGIS, decimação de pontos preservando início e fim do percurso, e teto de trilhas por consulta.
- Ao vivo de verdade. Sessões transmitidas por STOMP sobre WebSocket, com autorização social (público / seguidores / amigos / privado) checada na assinatura do tópico.
- Vazamento de GPS eliminado. A consulta por bounding box filtrava visibilidade só no BFF; hoje o serviço de localização resolve os caminhos visíveis antes de devolver uma coordenada.
- Concorrência provada, não presumida. Corridas de contador resolvidas com lock pessimista na linha-pai e validadas com testes de 50 escritas simultâneas em Testcontainers.
- CI/CD por projeto, num monorepo. Um workflow do GitHub Actions por pasta: o commit que mexe num serviço testa e publica só ele. Docker multi-stage com usuário non-root, imagens no ghcr.io e migrations versionadas com Flyway.
Engineering
- Authentication end to end. RSA-signed JWT plus a JWKS endpoint; every service is an independent resource server — the BFF is not the only door, and each service defends its own visibility.
- Map by viewport. Bounding-box lookups with a native PostGIS query, point decimation that preserves the start and end of the route, and a ceiling on trails per query.
- Live for real. Sessions streamed over STOMP on WebSocket, with social authorisation (public / followers / friends / private) checked when the topic is subscribed.
- GPS leak closed. The bounding-box query used to filter visibility only at the BFF; today the location service resolves which paths are visible before returning a single coordinate.
- Concurrency proven, not assumed. Counter races solved with a pessimistic lock on the parent row and validated by tests with 50 simultaneous writes in Testcontainers.
- CI/CD per project, in a monorepo. One GitHub Actions workflow per folder: a commit that touches one service builds and ships only that service. Multi-stage Docker with a non-root user, images on ghcr.io and versioned migrations with Flyway.
Números
Numbers
Sistema de pins do produto
The product's pin system
Repositório
Repository
Trisha monorepo com as nove pastas abaixomonorepo holding the nine folders below↗
Cadastro/ identidade e autenticaçãoidentity and authentication↗
APP/ domínio socialsocial domain↗
loc/ localização e GPSlocation and GPS↗
midia/ upload de mídiamedia uploads↗
bff/ backend for frontend↗
trilha_app/ app FlutterFlutter app↗
trilha_web/ cliente webweb client↗
trilha-infra/ infraestrutura e deployinfrastructure and deployment↗
Docs/ documentação do projetoproject documentation↗
Dentro do código
Os cinco serviços seguem o mesmo esqueleto — controller → service →
repository, com mapper traduzindo entidade e DTO, e auth e
trace em todos, porque cada serviço se defende sozinho e cada requisição é rastreável
ponta a ponta. O que muda de um para o outro é o que está abaixo.
- auth
- validação do JWT e do ID token social via JWKS
- envio do e-mail de confirmação de cadastro
- job
- rotinas agendadas de manutenção do cadastro
- listener
- reação a eventos do ciclo de vida do usuário
- model.event
- contratos dos eventos que o serviço publica
- entity · repository
- usuário, conta vinculada, provedor de autenticação
- entity
- aventura, caminho, ponto de interesse, evidência, amizade
- service
- feed, grafo social e cálculo do nível de confiança
- model.enums
- visibilidade, status do ponto, tipo de vínculo
- auth
- resolve o ator do token antes de qualquer leitura
- mqtt
- ingestão das posições publicadas pelo app durante o rastreio
- websocket
- difusão STOMP das sessões ao vivo para quem tem permissão
- repository
- consultas nativas PostGIS por bounding box, com índice GiST
- service
- Haversine, progresso da sessão e término automático
- client
- consulta os outros serviços para resolver visibilidade
- service
- presigned URL de upload e download; o binário nunca passa pela API
- entity
- metadados do arquivo — o objeto vive no MinIO / R2
- client
- chamadas aos quatro serviços, com circuit breaker que falha fechado
- service
- agregação das respostas e cache Redis com chave por observador
- ratelimit
- limite de requisições na borda, a defesa contra abuso
- model.dto
- contrato do front — não vaza a entidade de nenhum serviço
Inside the code
The five services share the same skeleton — controller → service →
repository, with mapper translating between entity and DTO, and
auth and trace in all of them, because each service defends itself and
every request stays traceable end to end. What changes from one to the next is below.
- auth
- validates the JWT and the social ID token through JWKS
- sends the account confirmation e-mail
- job
- scheduled maintenance routines for the registry
- listener
- reacts to events in the user lifecycle
- model.event
- contracts for the events this service publishes
- entity · repository
- user, linked account, auth provider
- entity
- adventure, path, point of interest, evidence, friendship
- service
- feed, social graph and the trust-level calculation
- model.enums
- visibility, point status, kind of connection
- auth
- resolves who the token belongs to before any read
- mqtt
- ingests the positions the app publishes while tracking
- websocket
- STOMP broadcast of live sessions to whoever is allowed
- repository
- native PostGIS bounding-box queries with a GiST index
- service
- Haversine, session progress and automatic termination
- client
- asks the other services to resolve visibility
- service
- presigned upload and download URLs; the binary never goes through the API
- entity
- file metadata — the object itself lives in MinIO / R2
- client
- calls to the four services, with a circuit breaker that fails closed
- service
- response aggregation and Redis cache keyed per viewer
- ratelimit
- request limits at the edge, the defence against abuse
- model.dto
- the front end's contract — no service entity leaks through
Projeto · 2026 — agora
Project · 2026 — now
Wallet
Carteira financeira que junta contas, cartões e investimentos de vários bancos num lugar só, pelo Open Finance. Um core em Java 25, um BFF na frente dele e um único código Flutter que roda no celular, no navegador e como aplicativo instalável de Windows.
A personal finance wallet that brings accounts, cards and investments from several banks into one place, over Open Finance. A Java 25 core, a BFF in front of it and a single Flutter codebase that runs on phones, in the browser and as an installable Windows app.
O ponto de partida
A regulação veio antes do código. Só instituição autorizada pelo Banco Central participa
do Open Finance: um aplicativo comum não pode ler os bancos direto. A leitura passa por um provedor
autorizado, a Pluggy, isolado atrás de uma porta própria (FinancialDataProvider) — se a
regra para parcerias mudar, troca-se o adaptador, não o sistema.
Where it starts
Regulation came before code. Only institutions authorised by Brazil's Central Bank take
part in Open Finance: an ordinary app cannot read the banks directly. Reading goes through an
authorised provider, Pluggy, isolated behind a port of its own (FinancialDataProvider) —
if the rules for partnerships change, the adapter changes, not the system.
Arquitetura
Architecture
Um código só para celular, navegador e Windows: calendário de gastos, rosca por categoria, evolução dos investimentos e o logo real de cada banco.
One codebase for phones, browser and Windows: a spending calendar, a donut by category, investment growth over time and each bank's real logo.
A única porta pública. Sessão por plataforma, telas montadas com várias consultas ao core em paralelo, cache, circuit breaker e limite de requisições.
The only public door. Sessions per platform, screens assembled from several core calls in parallel, caching, a circuit breaker and request limits.
Spring Modulith com nove módulos — identidade, conexões, contas, investimentos, sincronização, provedor, análises, planejamento e o compartilhado. Só o BFF chama.
Spring Modulith with nine modules — identity, connections, banking, investments, sync, provider, insights, planning and shared. Only the BFF calls it.
A porta e o adaptador da Pluggy. Nos testes, uma Pluggy de demonstração sobe junto, com bancos sintéticos — dá para ver tudo rodando sem conta nenhuma.
The port and the Pluggy adapter. In tests, a demo Pluggy starts alongside with synthetic banks — you can see everything running without any account.
Busca periódica com janela de reconciliação, e o registro de cada rodada no banco: o que entrou, o que mudou e o que sumiu.
Periodic fetching with a reconciliation window, and a record of every run in the database: what came in, what changed and what disappeared.
Instalador gerado no CI, ícone na bandeja, abrir com o Windows, atualização automática e Windows Hello para destravar.
An installer built in CI, a tray icon, start with Windows, automatic updates and Windows Hello to unlock.
Decisões
- A transação muda de identidade. O provedor troca o ID de uma transação quando ela muda muito. A sincronização relê uma janela de dias e marca como apagada a que sumiu dela — em vez de duplicar o lançamento ou perdê-lo de vista.
- Dado sensível cifrado no banco. A descrição de cada transação é gravada com AES-256-GCM: um vazamento do banco de dados não conta onde a pessoa gasta dinheiro.
- Um BFF que conhece as telas. A home pede cinco coisas ao core em paralelo; a parte que falha volta marcada como indisponível e o resto aparece. Só uma home completa vai para o cache — senão o cache congelaria a tela no meio da primeira sincronização.
- O BFF não guarda segredo. O core assina os tokens (RS256) e publica a chave pública; o BFF a busca de lá. O identificador da chave é o thumbprint dela: com um identificador fixo e a chave em cache, o BFF recusava por 5 minutos os tokens de um core recém-reiniciado.
- Sessão sob medida para cada plataforma. No navegador, a renovação vive num cookie HttpOnly com proteção contra CSRF e a sessão cai por inatividade. No celular, o token fica no armazenamento seguro e o app pede biometria antes de mostrar saldo. No Windows, quem destrava é o Windows Hello.
- Instalável e atualizado sozinho. O CI gera o instalador do Windows a cada versão; o app lê o manifesto da release, confere o SHA-256 do arquivo e se atualiza sem a pessoa abrir o GitHub.
Decisions
- A transaction changes identity. The provider swaps a transaction's ID when it changes enough. Sync rereads a window of days and marks as deleted whatever vanished from it — instead of duplicating the entry or losing track of it.
- Sensitive data encrypted at rest. Every transaction description is stored with AES-256-GCM: a database leak doesn't tell anyone where the person spends their money.
- A BFF that knows the screens. The home screen asks the core for five things in parallel; a part that fails comes back flagged as unavailable and the rest still shows. Only a complete home goes into the cache — otherwise the cache would freeze the screen halfway through the first sync.
- The BFF holds no secret. The core signs the tokens (RS256) and publishes the public key; the BFF fetches it from there. The key id is the key's thumbprint: with a fixed id and the key cached, the BFF used to reject tokens from a freshly restarted core for 5 minutes.
- Sessions tailored to each platform. In the browser, renewal lives in an HttpOnly cookie with CSRF protection and the session ends on inactivity. On phones, the token sits in secure storage and the app asks for biometrics before showing any balance. On Windows, Windows Hello unlocks it.
- Installable, and updates itself. CI builds the Windows installer for every version; the app reads the release manifest, checks the file's SHA-256 and updates itself without the person ever opening GitHub.
Números
Numbers
Repositório
Repository
Dentro do código
Dois serviços e um app, no mesmo repositório. No core, cada módulo do Spring Modulith só conversa com os outros pela fachada pública deles — e um teste quebra se alguém atravessar a fronteira.
- identity
- contas de usuário, login, JWT RS256 e renovação rotativa
- connection
- os bancos que cada pessoa vinculou
- banking
- contas, transações, saldos diários e faturas de cartão
- investment
- posições e a foto diária do patrimônio investido
- sync
- agendamento, execução e o registro de cada rodada
- provider
- a porta
FinancialDataProvidere o adaptadorpluggy - insight
- visão geral, gastos por categoria e por dia, patrimônio
- planning
- plano do mês, gastos fixos, salário esperado e os fixos que o extrato sugere
- shared
crypto,money,time,security— o que todos usam
- client
- chamadas ao core levando o token e o trace da requisição
- service
- composição por tela —
HomeServicecomCompletableFuture— e cache - auth
- sessão por plataforma: cookie no navegador, corpo da resposta no celular
- ratelimit
- limite de requisições, inclusive de login por IP
- trace
- um id por requisição, do app ao core
- features
- uma pasta por tela — visão geral, extrato, cartões, investimentos, calendário, planejamento, conexões — cada uma com
dataepresentation - core/session
- renovação numa fila só; cookie no navegador, armazenamento seguro no celular
- core/security
- bloqueio por biometria e tempo de inatividade
- core/desktop
- bandeja e janela do aplicativo de Windows
- core/updates
- atualização pelo manifesto da release, no Windows e no Android, com o SHA-256 conferido
- core/router
- go_router com abas que preservam o estado de cada tela
Inside the code
Two services and an app, in one repository. In the core, each Spring Modulith module only talks to the others through their public facade — and a test breaks if anyone crosses that boundary.
- identity
- user accounts, login, RS256 JWT and rotating renewal
- connection
- the banks each person has linked
- banking
- accounts, transactions, daily balances and card bills
- investment
- positions and the daily snapshot of invested wealth
- sync
- scheduling, execution and a record of every run
- provider
- the
FinancialDataProviderport and thepluggyadapter - insight
- overview, spending by category and by day, net worth
- planning
- the month plan, fixed expenses, expected salary and the fixed costs the statement suggests
- shared
crypto,money,time,security— what everyone uses
- client
- calls to the core carrying the request's token and trace
- service
- composition per screen —
HomeServicewithCompletableFuture— and caching - auth
- sessions per platform: a cookie in the browser, the response body on phones
- ratelimit
- request limits, including login attempts per IP
- trace
- one id per request, from the app to the core
- features
- one folder per screen — overview, statement, cards, investments, calendar, planning, connections — each with
dataandpresentation - core/session
- renewal in a single queue; a cookie in the browser, secure storage on phones
- core/security
- biometric lock and inactivity timeout
- core/desktop
- tray and window of the Windows app
- core/updates
- updates from the release manifest, on Windows and Android, with the SHA-256 checked
- core/router
- go_router with tabs that keep each screen's state
Projeto · 2026 — agora
Project · 2026 — now
Storage
Controle de estoque para mercado de bairro, pensado para ser vendido como serviço: o lojista abre no navegador, sem instalar nada, e cadastra o produto bipando o código de barras. Entrada de nota, validade, descontos, contagem com vários celulares ao mesmo tempo e uma área de vendas trancada por PIN.
Inventory control for neighbourhood grocery stores, designed to be sold as a service: the owner opens it in the browser, installs nothing, and registers a product by scanning its barcode. Goods receipts, expiry dates, discounts, stock counts from several phones at once and a sales area locked behind a PIN.
O diferencial
Estoque é um livro-razão, não um número. Cada entrada, venda, perda e ajuste é um movimento que só se acrescenta — nunca se edita nem se apaga. Saldo e custo médio não ficam gravados no produto: saem dos lotes. Duas entradas simultâneas não têm um total para sobrescrever, e o relatório de perdas existe porque o lote vencido vira perda registrada em vez de sumir.
What makes it different
Stock is a ledger, not a number. Every receipt, sale, loss and adjustment is a movement that is only ever appended — never edited, never deleted. Balance and average cost aren't stored on the product: they come from the batches. Two simultaneous receipts have no total to overwrite, and the losses report exists because an expired batch becomes a recorded loss instead of disappearing.
Arquitetura
Architecture
Entidades e regras puras, sem dependência nenhuma: lotes, saída pelo que vence primeiro, motor de desconto, dinheiro em centavos.
Entities and pure rules with no dependencies at all: batches, first-expiring-first-out, the discount engine, money in cents.
Entrada, venda, contagem, validade, preço e relatórios, falando só com interfaces que a infraestrutura implementa.
Receiving, selling, counting, expiry, pricing and reports, talking only to interfaces the infrastructure implements.
Replica set, porque cada mudança de estoque grava lote, movimento e documento de origem numa transação só. Índices, paginação e busca sem acento.
A replica set, because every stock change writes batch, movement and source document in a single transaction. Indexes, paging and accent-blind search.
Minimal APIs com 64 rotas, JWT de 15 minutos, a loja resolvida pelo token em cada requisição e dois jobs em segundo plano: validade e fotos.
Minimal APIs with 64 routes, a 15-minute JWT, the store resolved from the token on every request and two background jobs: expiry and photos.
JavaScript com shadcn/ui e Tailwind 4: bipagem pelo leitor USB, aviso com "Desfazer" no lugar de diálogo de confirmação, gráfico de vendas.
JavaScript with shadcn/ui and Tailwind 4: scanning with a USB reader, a toast with "Undo" instead of a confirmation dialog, a sales chart.
Cadastrou a lata, a foto vem sozinha: buscada pelo código de barras, com o fundo recortado e posta num quadrado branco.
Register the can and the photo shows up on its own: looked up by barcode, background cut out and placed on a white square.
Decisões
- Uma loja nunca enxerga a outra. Todo documento carrega a loja, e os repositórios a leem do token — nunca de um parâmetro que o cliente possa trocar. Requisição sem loja falha. O isolamento é provado por teste contra um MongoDB de verdade, não contra um mock.
- Concorrência resolvida no banco, não na sorte. Um lote só muda por compare-and-set, com a quantidade e a situação esperadas. Dois caixas tirando do mesmo lote não vendem a mesma unidade: quem perde a disputa recebe 409 e a venda é refeita sozinha.
- Desfazer em vez de confirmar. Produto, entrada e venda feitos por engano podem ser desfeitos por 10 minutos, enquanto nenhuma unidade saiu. Um "tem certeza?" a cada bipe travaria a fila do caixa.
- Sessão que não se bifurca. O token de renovação é trocado por compare-and-set, e um token já usado que reaparece encerra todas as sessões — alguém guardou uma cópia. Por isso o front renova numa fila só, inclusive entre abas (Web Locks), e tolera o aparelho que perdeu a resposta no meio.
- Leitor USB não é teclado. Menos de 20 ms entre teclas ninguém digita: o front separa a rajada do leitor da digitação de gente, e devolve o campo ao valor anterior quando o bipe cai num campo com foco.
- O contrato é vigiado por teste. Os testes de HTTP fixam a lista de rotas abertas e a de rotas só do dono: uma rota nova que precise ser exceção quebra o teste até alguém acrescentá-la — de propósito.
Decisions
- One store never sees another. Every document carries its store, and repositories read it from the token — never from a parameter the client could change. A request without a store fails. The isolation is proven by tests against a real MongoDB, not a mock.
- Concurrency settled in the database, not by luck. A batch only changes through compare-and-set, with the expected quantity and state. Two tills taking from the same batch cannot sell the same unit: the loser gets a 409 and the sale retries on its own.
- Undo instead of confirm. A product, receipt or sale entered by mistake can be undone for 10 minutes, as long as no unit has left. An "are you sure?" on every scan would stall the checkout queue.
- Sessions that don't fork. The refresh token is swapped through compare-and-set, and a used token that shows up again ends every session — someone kept a copy. That's why the front end renews in a single queue, across tabs too (Web Locks), and tolerates a device that lost the response midway.
- A USB reader is not a keyboard. Nobody types with less than 20 ms between keys: the front end tells the reader's burst apart from a person typing, and restores a focused field to its previous value when a scan lands in it.
- The contract is guarded by tests. HTTP tests pin the list of open routes and of owner-only routes: a new route that needs to be an exception breaks the test until someone adds it — on purpose.
Números
Numbers
Repositórios
Repositories
Dentro do código
Quatro projetos em camadas, e o domínio não sabe onde os dados moram. Foi isso que permitiu trocar SQLite por MongoDB no meio do caminho tocando no domínio só para acrescentar a noção de loja.
- Stock
Batch,StockMovement,GoodsReceipt,StockCount— o livro-razão e os documentos que o movimentam- Pricing
DiscountEngine: regra por produto ou por ramo da árvore, com prioridade e teto de cascata- ValueObjects
Moneyem centavos eGtinnormalizado em 14 dígitos- Sales
Saleguarda o custo dos lotes de onde a mercadoria saiu
- Stock
- entrada linha a linha, contagem em vários celulares, lote vencido que vira perda
- Sales
- baixa pelo que vence primeiro, ao preço das regras de desconto; PIN com cinco erros trava por 15 min
- Abstractions
- as interfaces que a infraestrutura implementa
- Persistence
- mapeamento BSON fora das entidades, índices, paginação e
AccentInsensitive: "acucar" acha "Açúcar" - Photos
- busca no Open Food Facts e recorte de fundo — com o modelo ISNet via ONNX, ou pelas bordas da foto
- Endpoints
- um arquivo por área, em minimal APIs
- Auth
- token de acesso, cookie de renovação e o passe de 15 min da área de vendas
- Jobs
- varredura de validade a cada 6 h, no fuso de cada loja, e busca de fotos
- Tenancy
- a loja vem do token, nunca da requisição
- api
http.js→session.js→client.js: token só em memória, renovação numa fila só- features
- produtos, entrada, vender, vendas — uma pasta por tela
- lib
scanner.jssepara leitor de gente;money.jsformata centavos- components/ui
- componentes shadcn/ui sobre Radix
Inside the code
Four layered projects, and the domain doesn't know where the data lives. That is what made it possible to swap SQLite for MongoDB midway, touching the domain only to add the notion of a store.
- Stock
Batch,StockMovement,GoodsReceipt,StockCount— the ledger and the documents that move it- Pricing
DiscountEngine: rules per product or per branch of the tree, with priority and a cascade ceiling- ValueObjects
Moneyin cents andGtinnormalised to 14 digits- Sales
Salekeeps the cost of the batches the goods came from
- Stock
- line-by-line receiving, counting from several phones, expired batches turned into losses
- Sales
- stock taken from what expires first, at the discount rules' price; five wrong PINs lock it for 15 min
- Abstractions
- the interfaces the infrastructure implements
- Persistence
- BSON mapping kept out of the entities, indexes, paging and
AccentInsensitive: "acucar" finds "Açúcar" - Photos
- lookup on Open Food Facts and background removal — with the ISNet model through ONNX, or from the photo's edges
- Endpoints
- one file per area, as minimal APIs
- Auth
- access token, renewal cookie and the 15-minute pass to the sales area
- Jobs
- expiry sweep every 6 h, in each store's time zone, and photo lookup
- Tenancy
- the store comes from the token, never from the request
- api
http.js→session.js→client.js: token in memory only, renewal in a single queue- features
- products, receiving, sell, sales — one folder per screen
- lib
scanner.jstells a reader from a person;money.jsformats cents- components/ui
- shadcn/ui components on top of Radix
Projeto · 2026 — agora
Project · 2026 — now
TLT
Aplicativo de desktop que escuta o áudio que o computador está reproduzindo, transcreve e traduz enquanto a pessoa ainda fala, mostrando a legenda por cima de qualquer janela. Funciona em qualquer chamada — e com a rede desligada, porque os modelos rodam na própria máquina.
A desktop application that listens to the audio the computer is playing, transcribes and translates it while the person is still speaking, showing subtitles over any window. It works on any call — and with the network switched off, because the models run on the machine itself.
O pipeline
The pipeline
Captura o que sai para o fone, não o microfone. Buffers fixos de 10 ms — esse é o orçamento de tempo de cada etapa seguinte.
Captures what goes out to the headphones, not the microphone. Fixed 10 ms buffers — that is the time budget for every stage after it.
Reamostragem com filtro, não decimação: cortar sem filtrar dobraria as frequências altas para dentro da banda e o reconhecedor receberia ruído como se fosse fala.
Resampling with a filter, not decimation: dropping samples without filtering would fold high frequencies back into the band, and the recogniser would take noise for speech.
Separa fala de música, ruído e notificação. Cada falso positivo é uma transcrição inútil — que custa GPU no modo local e dinheiro no modo nuvem.
Tells speech apart from music, noise and notifications. Every false positive is a wasted transcription — costing GPU locally and money in the cloud.
Reprocessa o trecho em curso a cada 1,5 s e confirma o prefixo em que duas passagens concordam. O que ainda pode mudar aparece em cinza.
Reprocesses the ongoing chunk every 1.5 s and confirms the prefix where two passes agree. What can still change is shown in grey.
Reconhecimento na GPU do usuário, com aquecimento na inicialização para a compilação de shaders não cair sobre a primeira frase da reunião.
Recognition on the user's GPU, warmed up at startup so that shader compilation doesn't land on the first sentence of the meeting.
Tradutor especializado rodando na CPU, fora do laço de transcrição: traduzir ali dentro travaria a captura por um segundo e o áudio acumulado viraria perda de fala.
A specialised translator running on the CPU, outside the transcription loop: translating inside it would stall capture for a second and the queued audio would become lost speech.
Decisões
- Vulkan em vez de CUDA, por motivo de produto. O pacote CUDA não traz as bibliotecas de runtime da NVIDIA: sem o Toolkit instalado ele falha em silêncio e cai para CPU dez vezes mais lento. Vulkan roda com o driver comum, serve placas de qualquer fabricante com um binário só e ocupa 116 MB contra 312 MB.
- RTF é a métrica errada. O mesmo modelo na mesma máquina mede de 1,2x a 13,7x só mudando o tamanho do buffer, porque o custo é quase todo fixo — o Whisper processa em janelas de 30 s preenchidas com silêncio. O que decide a viabilidade é o custo absoluto por passada contra o intervalo de reprocessamento.
- O gargalo era CPU, não GPU. Com o tradutor usando os quatro núcleos, o custo do reconhecedor mais que dobrou e passou de 187% do intervalo. Limitar a tradução a dois núcleos devolveu a folga — e, contra a intuição, deixou a própria tradução mais rápida: espalhar trabalho pequeno por muitos núcleos custa mais em coordenação do que rende.
- Senha do histórico é criptografia, não tela de bloqueio. Pedir senha só para abrir a janela deixaria o arquivo legível no bloco de notas. O conteúdo é cifrado com AES-256-GCM e chave derivada por PBKDF2 — e a consequência está escrita na tela: senha esquecida é histórico perdido, porque qualquer recuperação seria também uma porta de entrada.
- Medir em ritmo real, não em lote. A versão inicial da janela deslizante passava nos testes e travava as confirmações em falas longas. O defeito só apareceu alimentando áudio no ritmo de uma conversa e olhando quando cada trecho era confirmado, não o que ele continha.
Decisions
- Vulkan instead of CUDA, for a product reason. The CUDA package doesn't ship NVIDIA's runtime libraries: without the Toolkit installed it fails silently and falls back to a CPU path ten times slower. Vulkan runs on the ordinary driver, serves cards from any vendor with a single binary and takes 116 MB against 312 MB.
- RTF is the wrong metric. The same model on the same machine measures anywhere from 1.2x to 13.7x purely by changing the buffer size, because the cost is almost entirely fixed — Whisper processes in 30-second windows padded with silence. What decides viability is the absolute cost per pass against the reprocessing interval.
- The bottleneck was CPU, not GPU. With the translator using all four cores, the recogniser's cost more than doubled and went past 187% of the interval. Capping translation at two cores gave the headroom back — and, against intuition, made translation itself faster: spreading small work across many cores costs more in coordination than it returns.
- The history password is encryption, not a lock screen. Asking for a password merely to open the window would leave the file readable in a text editor. The content is encrypted with AES-256-GCM and a PBKDF2-derived key — and the consequence is stated on screen: a forgotten password means a lost history, because any recovery path would also be a way in.
- Measure at real pace, not in batch. The first version of the sliding window passed its tests and still stalled confirmations during long speech. The defect only showed up feeding audio at conversation pace and watching when each chunk was confirmed, not what it contained.
Projeto · 2026 — agora
Project · 2026 — now
NewsTech
Portal de notícias de tecnologia em português. Ele lê feeds RSS o dia inteiro, descarta o que não interessa, agrupa a mesma história contada por fontes diferentes e publica um texto próprio — com modelos de linguagem fazendo a triagem e a redação, e um humano no controle do que vai ao ar.
A technology news portal in Portuguese. It reads RSS feeds all day, throws away what doesn't matter, groups the same story told by different outlets and publishes its own piece — with language models doing the triage and the writing, and a human deciding what goes live.
O pipeline
The pipeline
Conectores por fonte lendo feeds com Rome; o HTML que chega é sanitizado com Jsoup antes de tocar o banco.
One connector per outlet reading feeds with Rome; incoming HTML is sanitised with Jsoup before it touches the database.
O modelo barato faz a primeira peneira em lote: o que é notícia de tecnologia relevante e o que é ruído.
The cheap model does the first pass in batches: what is relevant tech news and what is noise.
A mesma história publicada por cinco fontes vira um assunto só, com as fontes preservadas.
The same story published by five outlets becomes a single subject, with every source kept.
O modelo forte escreve a matéria assinada a partir do cluster; o texto sai em Markdown e é renderizado no servidor.
The strong model writes the bylined piece from the cluster; the text comes out in Markdown and is rendered on the server.
Matérias, tópicos e entidades em documentos — o formato varia por fonte, então documento cabe melhor que tabela.
Articles, topics and entities as documents — the shape varies per outlet, so a document fits better than a table.
Renderização no servidor com layout dialect, área administrativa protegida por Spring Security e cache Caffeine nas páginas quentes.
Server-side rendering with the layout dialect, an admin area protected by Spring Security and a Caffeine cache on the hot pages.
Decisões
- Monolito por escolha, não por preguiça. É um portal de leitura mantido por uma pessoa: um processo só custa menos, opera mais fácil e ainda cabe inteiro na cabeça. Os limites entre ingestão, curadoria, autoria e publicação estão nos pacotes, não em containers.
- Dois modelos, duas contas diferentes. Haiku faz o trabalho de volume e Opus só toca no que sobrou da peneira — o custo por matéria publicada é rastreado no próprio código.
- Sanitizar não é opcional. O conteúdo vem de feeds de terceiros direto para o HTML do portal: sem passar por Jsoup, isso é XSS servido em casa.
- Build que não exige Docker. Os testes de integração com Testcontainers vivem num
sourceSet separado;
checkdeliberadamente não depende deles.
Decisions
- A monolith by choice, not by laziness. It is a reading portal maintained by one person: a single process costs less, is easier to operate and still fits in one head. The boundaries between ingestion, curation, authoring and publishing live in packages, not in containers.
- Two models, two very different bills. Haiku does the volume work and Opus only touches what survived the sieve — the cost per published article is tracked in the code itself.
- Sanitising is not optional. Content arrives from third-party feeds and goes straight into the portal's HTML: without passing through Jsoup, that is XSS served at home.
- A build that doesn't require Docker. Testcontainers integration tests live in their own
source set;
checkdeliberately does not depend on them.
Stack
Dentro do código
É um monolito, mas não é uma pasta só: cada etapa do pipeline é um pacote de primeiro nível em
com.web.newstech, com o próprio repositório e as próprias exceções. A fronteira que
viraria um serviço em outro desenho aqui é a fronteira do pacote.
- connector
- um conector por fonte, lendo RSS/Atom com Rome
- model
- o item cru como veio do feed, antes de qualquer julgamento
- repository
- deduplicação e controle do que já foi lido
- triage
- peneira em lote com o modelo barato: é notícia de tecnologia ou é ruído
- cluster
- a mesma história em cinco fontes vira um assunto só
- editorial
- o modelo forte escreve a matéria a partir do cluster
- exceptions
- falha de modelo é caso previsto, não surpresa em runtime
- authoring
- o texto assinado, seu estado editorial e o fluxo até publicar
- content
- matéria, tópico e entidade — o que o leitor enxerga
- repository
- documentos MongoDB; o formato varia por fonte, tabela não caberia
- web
- controllers do portal, renderizados por Thymeleaf no servidor
- web.admin
- área de curadoria humana: o que vai ao ar passa por aqui
- web.auth
- Spring Security protegendo o administrativo
- shared.cost
- custo por token contabilizado no próprio código
- shared.config
- cache Caffeine, clientes de modelo e seeds
Inside the code
It is a monolith, but not a single folder: every stage of the pipeline is a top-level package in
com.web.newstech, with its own repository and its own exceptions. The boundary that
would be a service in another design is the package boundary here.
- connector
- one connector per outlet, reading RSS/Atom with Rome
- model
- the raw item exactly as the feed gave it, before any judgement
- repository
- deduplication and tracking of what has already been read
- triage
- batch sieve with the cheap model: relevant tech news, or noise
- cluster
- the same story across five outlets becomes one subject
- editorial
- the strong model writes the article from the cluster
- exceptions
- a model failure is an expected case, not a runtime surprise
- authoring
- the bylined text, its editorial state and the path to publication
- content
- article, topic and entity — what the reader actually sees
- repository
- MongoDB documents; the shape varies per outlet, a table wouldn't fit
- web
- the portal's controllers, rendered by Thymeleaf on the server
- web.admin
- human curation area: whatever goes live passes through here
- web.auth
- Spring Security guarding the admin side
- shared.cost
- cost per token accounted for in the code itself
- shared.config
- Caffeine cache, model clients and seeds
Projeto · Agibank · estudo dirigido
Project · Agibank · guided study
Faturamento distribuído
Distributed billing
Quatro serviços Spring Boot exercitando os dois estilos de integração no mesmo sistema: um BFF que conversa por REST com transação, fatura e notificação, e uma transação que publica evento em Kafka quando termina de processar. Síncrono na borda, assíncrono no núcleo.
Four Spring Boot services exercising both integration styles in one system: a BFF that talks to transaction, invoice and notification over REST, and a transaction that publishes a Kafka event once it finishes processing. Synchronous at the edge, asynchronous at the core.
Os serviços
The services
Fachada REST com um controller por serviço de trás e os modelos espelhados no contrato. É a única porta que o cliente conhece.
REST facade with one controller per service behind it and the models mirrored in the contract. The only door the client knows about.
Pessoa, credenciais de cartão e transação. Ao concluir, publica o evento no tópico. Spring Retry com AOP para a falha que é temporária.
Person, card credentials and transaction. On completion it publishes the event to the topic. Spring Retry through AOP for the failure that is temporary.
Persiste o evento de transação e gera o relatório em PDF por CPF e período, com totalização — recusando data futura.
Persists the transaction event and builds the PDF report by taxpayer id and period, with totals — refusing a future date.
Disparo de e-mail com JavaMailSender, atrás do próprio controller. Serviço pequeno de propósito: faz uma coisa só.
E-mail delivery with JavaMailSender, behind its own controller. Deliberately small: it does one thing.
Engenharia
- Os dois estilos, lado a lado. O BFF chama os serviços por REST, porque a resposta ao
cliente é síncrona por natureza; a transação publica
TransactionEventnum tópico Kafka próprio, porque quem reage a ela não precisa segurar essa resposta. - Credencial não mora no código. Usuário e senha do SMTP vêm do ambiente
(
MAIL_USERNAME,MAIL_PASSWORD) e o remetente é injetado por configuração — repositório público não é lugar de segredo. - Retry onde a falha é transitória. Spring Retry aplicado por AOP no ponto de integração: a tentativa é repetida sem poluir a regra de negócio com try/catch.
- Cobertura medida, não decorativa. JaCoCo em todos os serviços, com exclusão explícita de configuração, DTO, entidade e mapper — o número reflete lógica de negócio testada, não getter.
- Infra local sobe junto. Docker Compose declarado no projeto e integrado ao Spring Boot,
então
bootRunlevanta o banco antes da aplicação.
Engineering
- Both styles, side by side. The BFF calls the services over REST, because the answer to
the client is synchronous by nature; the transaction publishes
TransactionEventto its own Kafka topic, because whoever reacts to it doesn't need to hold that answer up. - Credentials don't live in the code. SMTP user and password come from the environment
(
MAIL_USERNAME,MAIL_PASSWORD) and the sender is injected from configuration — a public repository is no place for a secret. - Retry where the failure is transient. Spring Retry applied through AOP at the integration point: the attempt is repeated without littering business rules with try/catch.
- Coverage measured, not decorative. JaCoCo across the services, explicitly excluding configuration, DTOs, entities and mappers — the number reflects tested business logic, not getters.
- Local infra comes up with it. Docker Compose declared in the project and wired into
Spring Boot, so
bootRunstarts the database before the application.
Stack
Repositórios
Repositories
Dentro do código
Quatro repositórios pequenos e propositalmente desiguais: o serviço que carrega o domínio tem camada completa, o que faz uma coisa só tem duas classes. Tamanho de serviço é consequência do que ele faz, não padrão a cumprir.
- controller
InvoiceBff,NotificationBff,TransactionBff— um por serviço de trás- config
- cliente REST e Swagger/OpenAPI
- model
- contrato de entrada espelhado, sem depender do jar de ninguém
- kafka
Producerpublicando o evento da transação concluída, com@Retryablee@Recover- config
TopicConfig— o tópico declarado no código, não no console- controller
- transação, pessoa e credenciais de cartão
- service
- regra da transação, com retry aplicado por AOP
- mapper
EventMapperseparando a entidade do evento publicado- model.enums
PaymentType,TransactionStatus
- service
- persiste o evento e monta o PDF: cabeçalho, tabela, total e contagem
- entity · repository
- fatura em MongoDB, consultada por CPF e data
- model
TransactionEvent— o contrato compartilhado com a transação
- EmailService
- monta e envia a mensagem via
JavaMailSender - EmailController
- a única porta do serviço
Inside the code
Four small and deliberately uneven repositories: the service carrying the domain has the full layering, the one that does a single thing has two classes. Service size is a consequence of what it does, not a standard to comply with.
- controller
InvoiceBff,NotificationBff,TransactionBff— one per service behind it- config
- REST client and Swagger/OpenAPI
- model
- mirrored input contract, without depending on anyone's jar
- kafka
Producerpublishing the completed transaction event, with@Retryableand@Recover- config
TopicConfig— the topic declared in code, not in a console- controller
- transaction, person and card credentials
- service
- transaction rules, with retry applied through AOP
- mapper
EventMapperkeeping the entity apart from the published event- model.enums
PaymentType,TransactionStatus
- service
- persists the event and assembles the PDF: header, table, total and count
- entity · repository
- invoice in MongoDB, queried by taxpayer id and date
- model
TransactionEvent— the contract shared with the transaction
- EmailService
- builds and sends the message through
JavaMailSender - EmailController
- the service's only door
Contato
Busco crescer como desenvolvedor back-end Java. Se o seu time valoriza código com critério e gente que documenta o porquê das coisas, me chama. © 2026 Rafael Santos.
I'm looking to grow as a back-end Java developer. If your team values code written with judgement, and people who document why things are the way they are, get in touch. © 2026 Rafael Santos.