Marketplace náutico
Aluguel de jet-ski do anúncio ao repasse: três aplicações sobre um backend só, com o caminho do dinheiro tratado como a parte que não admite ensaio.
O GoJet aproxima quem tem um jet-ski parado de quem quer o dia na água, e absorve a parte chata: reserva, pagamento, caução, identidade conferida e avaliação dos dois lados. São três aplicações, web, iPhone e Android, sobre um backend Java único, dono dos dados e dos contratos. Antes de chamar de pronto, executamos a jornada inteira contra a API real: 22 de 22 etapas, pagamento e estorno incluídos.
- 22 de 22Etapas da jornada de negócio executadas contra a API real
- 792Testes automáticos verdes no backend, incluindo os de arquitetura
- 225 de 225Testes do aplicativo na medição da auditoria, em execução serial
- 14Domínios de negócio no backend, de identidade a seguros
- 14 minutosDo push ao backend no ar: integração contínua mais troca de imagem
Um jet-ski parado de um lado, um acordo no escuro do outro
Quem tem um jet-ski sabe que ele passa mais tempo na garagem do que na água. Quem quer alugar por um fim de semana quase sempre cai num grupo de WhatsApp, combina o valor em dinheiro e torce para dar certo. Não há contrato, não há caução, ninguém confere quem está do outro lado, e a agenda vive na cabeça do dono, até o dia em que duas pessoas reservam o mesmo sábado.
O que falta nesse arranjo não é aplicativo, é confiança. E confiança, num marketplace de duas pontas, não é discurso: é ordem de execução, restrição de banco de dados e verificação de posse. Foi assim que tratamos o problema desde o primeiro dia: o desconforto fica com a plataforma, não com as duas pessoas que só queriam passar o domingo na água.
O produto foi desenhado a partir de três pessoas: quem aluga, quem anuncia e o time que cuida dos bastidores, moderação, conferência de identidade, seguro e sinais de fraude. As duas primeiras se encontram no momento da reserva; a terceira nunca aparece na tela do cliente, e é justamente por isso que precisa existir.
A reserva só nasce quando o dinheiro entra
O caminho do dinheiro não admite ensaio. Numa mesma reserva há divisão de valor entre plataforma e locador, uma caução bloqueada no cartão antes da retirada e um estorno quando a reserva cai. Errar aqui não gera log feio, gera cobrança dobrada no cartão de alguém, e isso não se desfaz com um deploy.
Escolhemos Stripe Connect com cobrança destinada e taxa de aplicação: a mesma transação já sai repartida entre a plataforma e o locador, em vez de entrar inteira e ser devolvida depois. A caução é uma autorização fora de sessão sobre o cartão salvo como cliente do provedor, e o cancelamento reverte a transferência original em vez de emitir um pagamento novo no sentido contrário.
A confirmação da reserva não vem do clique do usuário: vem do aviso do provedor de pagamento, verificado por assinatura antes de qualquer processamento e idempotente por chave: um reenvio do mesmo evento não confirma duas vezes. A jornada de pagar, receber o aviso, ver a reserva confirmada e depois estornar foi executada de ponta a ponta em produção, ainda em modo de teste, em 2 de julho de 2026.
Um detalhe pequeno que só aparece sob concorrência: a caução ganhou bloqueio otimista de versão, e o registro é gravado e descarregado no banco antes da chamada ao gateway, numa migração dedicada. Assim, quando dois pedidos disputam a mesma caução, o perdedor da corrida recebe um erro de conflito sem que um centavo tenha se movido. A ordem das operações é a garantia; a mensagem de erro é só a consequência.
A regra que o banco se recusa a quebrar
Duas pessoas nunca reservam o mesmo período do mesmo jet-ski. Essa regra existe no serviço que checa conflito de datas, como seria de esperar, mas existe também dentro do PostgreSQL, como restrição de exclusão sobre um intervalo de datas com os dois extremos inclusivos, apoiada na extensão btree_gist. Um caminho de código que esquecesse a checagem simplesmente não conseguiria gravar a linha.
Essa escolha cobrou um preço, e vale contar qual: o H2 que roda a suíte rápida de testes não entende essa sintaxe. A migração ficou então num diretório irmão, versionada por fabricante de banco: irmão, e não subdiretório, porque uma pasta dentro do caminho padrão entraria duas vezes na varredura recursiva do Flyway e o banco recusaria subir acusando versão duplicada.
O backend é hexagonal, organizado por módulo de negócio: são 14 domínios, de identidade e frota a pagamentos, seguros e fraude. As fronteiras entre eles não dependem de disciplina: são verificadas por um teste de arquitetura que quebra o build de propósito quando um módulo alcança o interior do outro. O schema evolui só por migração numerada e imutável; a próxima a ser escrita é a de número 62, e uma já aplicada nunca é renumerada, porque o Flyway recusaria o boot por divergência de checksum.
Uma conta, dois papéis, e a autorização que decide pelo conjunto
A mesma pessoa aluga num fim de semana e anuncia no outro. O perfil de locador é ligado quando ela quiser, depois de uma verificação, sem segunda conta e sem segundo login. É simples de prometer na tela e traiçoeiro de sustentar por baixo.
Aprendemos isso com um bug nosso. A autorização decidia pelo papel ativo, aquele que a interface usa só para saber em que modo a pessoa está navegando; o resultado é que um usuário com os dois papéis, em modo locador, levava 403 em toda tentativa de reservar. A invariante virou: quem decide acesso é o conjunto de papéis da sessão, por um predicado único, nos três pontos de verificação: o middleware de rotas, o guardião de página e o repasse ao backend. O papel ativo ficou restrito a pouso e redirecionamento.
Instrução escrita em documento não é barreira técnica, então a regra ganhou guarda mecânica: uma regra de lint quebra a verificação se o cookie de papel for lido fora dos arquivos que têm direito de tocá-lo, e um teste unitário fixa o comportamento do predicado. Na mesma linha, a verificação de identidade tem default manual e à prova de falha, sem provedor real configurado, quem pede para virar locador fica pendente até um administrador aprovar. O default anterior aprovava sozinho; trocamos, e fixamos a variável explicitamente no ambiente de produção.
Três aplicações, um contrato
O backend é Java 21 com Spring Boot sobre PostgreSQL 16, e é o dono dos dados e dos contratos. O site é Next.js atuando como BFF: o navegador nunca fala com o backend direto, a sessão vive num cookie httpOnly, convertido no servidor para um cabeçalho de autorização, e o token jamais chega ao armazenamento local do navegador. O aplicativo, em Expo e React Native, conversa direto com a API por token, sem intermediário.
Três clientes sobre um contrato só têm um jeito conhecido de sair do compasso: alguém muda o contrato e avisa depois. Por isso toda mudança de contrato exige uma decisão registrada por escrito no repositório do backend, antes do código. É burocrático de propósito, e é o que mantém a regra de negócio com uma fonte só.
Alguns cuidados só aparecem quando o produto está no ar. Toda chamada do BFF ao backend carrega um tempo limite de 8 segundos que vira um erro explícito de indisponibilidade, sem ele, a função fica pendurada até o limite da plataforma e o usuário recebe um genérico que não ajuda ninguém. E código de erro ambíguo não entra no dicionário global: o backend responde não autorizado tanto para senha errada quanto para sessão expirada, e traduzir isso de uma vez faria a frase de login inválido vazar para cerca de 20 pontos de chamada que nada têm a ver com login.
Auditamos o próprio produto antes de vender
Antes de chamar de pronto, submetemos o ecossistema a uma auditoria de jornadas contra uma stack real, não a uma leitura de código. Uma execução completa percorreu 22 de 22 etapas pela API: o locador cria do zero e publica, o locatário reserva, paga, dá caução, conclui e avalia, e um segundo ciclo cancela com estorno. No backend a suíte fecha em 792 testes verdes, incluindo os de arquitetura; no aplicativo foram 225 de 225 na medição da auditoria.
A parte que mais nos ensinou foi a suíte visual gravada: oito vídeos e um passeio contínuo de 41 passos em que o locatário aluga exatamente o jet-ski que o locador acabou de publicar. Uma dessas jornadas termina vermelha de propósito: os problemas são registrados com asserção suave, então o vídeo grava o ciclo inteiro e o relatório acusa o defeito no fim. Suíte de auditoria que esconde defeito para ficar verde não serve para auditar nada.
A auditoria achou coisa de verdade, e o mais grave estava no caminho do dinheiro: um endpoint de checkout aceitava o identificador do pedido sem conferir posse e ainda repassava a chave de idempotência enviada pelo cliente: abertura para um locatário pagar o pedido de outro e para uma janela de cobrança dupla. Corrigimos com verificação de posse, resposta 404 uniforme para não deixar enumerar identificadores e chave de deduplicação derivada no servidor. Também aprendemos que sondar a saúde do processo não é sondar o dado: o backend responde saudável em cerca de 21 segundos, antes de a carga de exemplo terminar, e o roteiro passou a esperar a busca devolver resultado.
Onde está hoje, e o que falta para abrir as portas
O GoJet está em piloto de produção, não em maquete. O backend roda numa VPS com Docker Compose e Caddy, com certificado próprio; o site está na Vercel; e a régua entre um push e o código no ar, no backend, é de 14 minutos: cerca de dez de integração contínua e mais quatro até o supervisor de contêiner trocar a imagem, medidos em 31 de julho de 2026. O provedor de pagamento já está publicado em produção, ainda com chaves de teste.
O que falta é, em boa parte, comercial e de papelada: ligar as chaves reais, fechar o perfil da plataforma no provedor e reconduzir os locadores pelo cadastro dele, publicar o aplicativo nas lojas e passar os termos por revisão jurídica. Existe um runbook escrito para essa virada, porque a hora de descobrir o passo esquecido não é com dinheiro real no caminho.
E há lacunas que preferimos declarar a maquiar. O locador que só usa o aplicativo ainda não conclui sozinho a ativação do recebimento: o cadastro hospedado do provedor não está exposto lá, e o caminho hoje passa pelo site. Uma reserva criada e concluída no mesmo dia também continua fora de alcance, porque a regra que impede data de início no passado e a que exige o fim da locação para concluir se anulam. As duas estão na fila, com correção conhecida.
Um marketplace inteiro, do primeiro clique ao repasse na conta do locador, rodando em produção com o caminho do dinheiro tratado como caminho do dinheiro: verificado etapa por etapa contra a API real, e com as lacunas que restam escritas em vez de escondidas.