Skip to content

jtonynet/go-payments-api

Repository files navigation

Go Logo Gin Logo PostgreSql Logo Redis Logo gRPC Logo Docker Logo Ubuntu Viper DotEnv Logo GitHub Logo VsCode Logo Swagger Logo Miro Logo MermaidJS Logo GithubActions Logo Gatling Logo Prometheus Logo Grafana Logo Grafana Loki Logo

Badge Status Github Project Badge GitHubActions Go Version

🕸️ Redes

linkedin dev.to gmail



📁 O Projeto

⤴️ Índice

Go Payments API

  1. ⤴️ Índice
  2. 📖 Sobre
  3. 💻 Rodando o Projeto
  4. 📰 Documentação da API
  5. Testes Unitários & Integração
  6. 🚚 Testes Carga & Performance
  7. 🕵️ Observabilidade WIP
  8. 🧑‍🔧 Validação Manual
  9. 📊 Diagramas
  10. 🅻 Questão Aberta L4
  11. 🧠 ADR - Architecture Decision Records
  12. 🔢 Versões
  13. 🧰 Ferramentas
  14. 👏 Boas Práticas
  15. 🤖 Uso de IA
  16. 🏁 Conclusão


📖 Sobre

Resumo:

Desafio de Payment Authorizer de benefícios em Hexagonal Architecture com 100ms SLA por request e controle de concorrência com baixa possibilidade de colisão. Construído com Gin e Gorm, protocolo gRPC entre o serviço REST HTTP ("aberto" ao mundo, o ponto de entrada) e o serviço Processor ("fechado" ao mundo, o processador de pagamentos) por segurança.

Principais Tecnologias e abordagens:

  • Hexagonal Architecture
  • TDD, DDD, SOLID, ADRs
  • REST HTTP e gRPC entre Microsservices
  • Dockerized Solução com uso de containers
  • PostgreSQL modelado inspirado em Event Sourcing garantindo Consistency
  • Concurrent Programming
  • Redis para Pessimistic Memory Lock
  • Redis Keyspace Notification como Pub/Sub para notificar Unlocks
    (Robust Queues foram desconsideradas devido Additional Latency)
  • CI com GitHub Actions
  • Diagram as code com Mermaid.js e Miro
  • Performance/Load Test Dockerized com Gatling
  • Observability (WIP)
    • Prometheus e Grafana para RED Metrics
    • Grafana Loki e Promtail para Logs

Após concluir os requisitos obrigatórios (L1, L2, L3) no prazo, retomei o desafio, focando melhorias do diagrama Miro criado em conjunto a equipe proponente. Implementei o requisito L4 como aprimoramento técnico sugerido no diagrama e na ADR 0003: gRPC e Redis Keyspace Notification reduzindo Latência e evitando Concorrência.


*Para acompanhar a evolução do projeto com seus respectivos diagramas, acesse o seguinte documento

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

Texto Original:

Acompanhe as tarefas pelo Kanban

Este repositório foi criado com a intenção de propor uma possível solução para o seguinte desafio:


👨‍💻 Desafio Técnico:

Além de avaliar a correção da sua solução, temos interesse em ver como você modela o domínio, organiza seu código e implementa seus testes.

Linguagem e bibliotecas:

Na *********, usamos Scala e Kotlin no nosso dia a dia (e demonstrar experiência em alguma delas é um grande diferencial). No entanto, você pode implementar sua solução utilizando sua linguagem favorita, dando preferência ao paradigma de programação funcional.

Como entregar a solução?

Entregue a sua solução preferencialmente criando um repositório git (Github, Gitlab, etc).

É muito importante escrever um arquivo README com as instruções para execução do projeto.

Agora, vamos guiá-lo através de alguns conceitos básicos.




Transaction

Uma versão simplificada de um transaction payload de cartão de crédito é o seguinte:

{
	"account": "123",
	"totalAmount": 100.00,
	"mcc": "5811",
	"merchant": "PADARIA DO ZE               SAO PAULO BR"
}

Atributos

  • id - Um identificador único para esta transação.

  • accountId - Um identificador para a conta.

  • amount - O valor a ser debitado de um saldo.

  • merchant - O nome do estabelecimento.

  • mcc - Um código numérico de 4 dígitos que classifica os estabelecimentos comerciais de acordo com o tipo de produto vendido ou serviço prestado.

    O MCC contém a classificação do estabelecimento. Baseado no seu valor, deve-se decidir qual o saldo será utilizado (na totalidade do valor da transação). Por simplicidade, vamos usar a seguinte regra:

    • Se o mcc for "5411" ou "5412", deve-se utilizar o saldo de FOOD.
    • Se o mcc for "5811" ou "5812", deve-se utilizar o saldo de MEAL.
    • Para quaisquer outros valores do mcc, deve-se utilizar o saldo de CASH.



Desafios (o que você deve fazer)

Cada um dos desafios a seguir são etapas na criação de um autorizador completo. Seu autorizador deve ser um servidor HTTP que processe a transaction payload JSON usando as regras a seguir.

As possíveis respostas são:

  • { "code": "00" } se a transação é aprovada
  • { "code": "51" } se a transação é rejeitada, porque não tem saldo suficiente
  • { "code": "07" } se acontecer qualquer outro problema que impeça a transação de ser processada

O HTTP Status Code é sempre 200


  1. L1. Autorizador simples

    • O autorizador simples deve funcionar da seguinte forma:
      • Recebe a transação
      • Usa apenas a MCC para mapear a transação para uma categoria de benefícios
      • Aprova ou rejeita a transação
      • Caso a transação seja aprovada, o saldo da categoria mapeada deverá ser diminuído em totalAmount.
  2. L2. Autorizador com fallback

    • Para despesas não relacionadas a benefícios, criamos outra categoria, chamada CASH. O autorizador com fallback deve funcionar como o autorizador simples, com a seguinte diferença:
      • Se a MCC não puder ser mapeado para uma categoria de benefícios ou se o saldo da categoria fornecida não for suficiente para pagar a transação inteira, verifica o saldo de CASH e, se for suficiente, debita esse saldo.
  3. L3.Dependente do comerciante

    • As vezes, os MCCs estão incorretos e uma transação deve ser processada levando em consideração também os dados do comerciante. Crie um mecanismo para substituir MCCs com base no nome do comerciante. O nome do comerciante tem maior precedência sobre as MCCs.
    • Exemplos:
      • UBER TRIP SAO PAULO BR
      • UBER EATS SAO PAULO BR
      • PAG*JoseDaSilva RIO DE JANEI BR
      • PICPAY*BILHETEUNICO GOIANIA BR
  4. L4. Questão aberta

    • A seguir está uma questão aberta sobre um recurso importante de um autorizador completo (que você não precisa implementar, apenas discuta da maneira que achar adequada, como texto, diagramas, etc.).
      • Transações simultâneas: dado que o mesmo cartão de crédito pode ser utilizado em diferentes serviços online, existe uma pequena mas existente probabilidade de ocorrerem duas transações ao mesmo tempo. O que você faria para garantir que apenas uma transação por conta fosse processada em um determinado momento? Esteja ciente do fato de que todas as solicitações de transação são síncronas e devem ser processadas rapidamente (menos de 100 ms), ou a transação atingirá o timeout.



Para este teste, tente ao máximo implementar um sistema de autorização de transações considerando todos os desafios apresentados (L1 a L4) e conceitos básicos.



. . . . . . . . . . . . . . . . . . . . . . . . . . . .


O desafio sugere Scala, Kotlin e o paradigma funcional, mas aceita outras linguagens e paradigmas. Realizei em Golang, com arquitetura Hexagonal, por maior experiência e familiaridade, além de ser mencionada na job description como parte do stack utilizado. Essa combinação atende bem aos requisitos do desafio.

Contudo, sou aberto a expandir minhas habilidades e disposto a aprender novas tecnologias e paradigmas conforme necessário.


⤴️ de volta ao índice



💻 Rodando o Projeto

🌐 Ambiente

Docker e Docker Compose são necessários para rodar a aplicação de forma containerizada, e é fortemente recomendado utilizá-los para rodar o banco de dados localmente.

Crie uma copia do arquivo ./payments-api/.env.SAMPLE e renomeie para ./payments-api/.env.


. . . . . . . . . . . . . . . . . . . . . . . . . . . .

🐋 Containerizado

Recomendado

Após a .env renomeada, rode os comandos docker compose (de acordo com sua versão do docker compose) no diretório raiz do projeto

# Construir a imagem
docker compose build

# Rodar o PostgreSQL e o Redis de Desenvolvimento
docker compose up postgres redis -d

Caso seja a primeira execução do projeto, rode os comandos de migrations e seeds para dar carga inicial no banco de dados de desenvolvimento.

# Rodar Migrations de Desenvolvimento
docker compose up migrate

# Carga inicial no banco
docker compose exec postgres psql -U api_user -d payments_db -f /seeds/load_test_charge.up.sql

Para então subir os serviços REST e Processor

# Rodar as APIs (Sugiro em terminais distintos para acompanhar debug logs)
docker compose up transaction-processor
docker compose up transaction-rest

A API está pronta e a rota da Documentação da API (Swagger) estará disponível, assim como os Testes poderão ser executados.


. . . . . . . . . . . . . . . . . . . . . . . . . . . .

🏠 Local

Apenas se necessário

Com o Golang 1.23 instalado e após ter renomeado a copia de .env.SAMPLE para .env, serão necessárias outras alterações para que a aplicação funcione corretamente no seu localhost.

No arquivo .env, substitua os valores das variáveis de ambiente que contêm comentários no formato local: valueA | containerized: valueB pelos valores sugeridos na opção local (exceto o DATABASE_PORT).

DATABASE_HOST=localhost         ### local: localhost | conteinerized: test-postgres

PUBSUB_HOST=localhost           ### local: localhost | conteinerized: redis
IN_MEMORY_LOCK_HOST=localhost   ### local: localhost | conteinerized: redis
IN_MEMORY_CACHE_HOST=localhost  ### local: localhost | conteinerized: redis

GRPC_SERVER_HOST=localhost      ### local: localhost | conteinerized: transaction-processor
GRPC_CLIENT_HOST=localhost      ### local: localhost | conteinerized: transaction-processor

Após editar o arquivo, suba apenas o banco e o redis de dados com o comando:

# Rodar o PostgreSQL e o Redis de Desenvolvimento
docker compose up postgres redis -d

Caso seja a primeira execução do projeto, rode os comandos de migrations e seeds para dar carga inicial no banco de dados de desenvolvimento.

# Rodar Migrations de Desenvolvimento
docker compose up migrate

# Carga inicial no banco
docker compose exec postgres psql -U api_user -d payments_db -f /seeds/load_test_charge.up.sql

ou se conecte a database/redis válidos no arquivo .env, então no diretório payments-api execute os comandos:

# Instala Dependências
go mod download

# Rodar as APIs (Sugiro em terminais distintos para acompanhar debug logs)
go run cmd/processor/main.go
go run cmd/rest/main.go

A API está pronta e a rota da Documentação da API (Swagger) estará disponível, assim como os Testes poderão ser executados.


⤴️ de volta ao índice



📰 Documentação da API

Swagger Swagger

Com a aplicação em execução, a rota de documentação Swagger fica disponível em http://localhost:8080/swagger/index.html

A interface do Swagger pode executar Validação Manual a partir de requests no endpoint POST: /payment


⤴️ de volta ao índice



✅ Testes Unitários & Integração

🐋 Containerizado

Recomendado

Para Executar os Testes usando container, é necessário que já esteja Rodando o Projeto Containerizado.

As configurações para executar os testes de repositório e integração (dependentes de infraestrutura) de maneira containerizada estão no arquivo ./payments-api/.env.TEST. Não é necessário alterá-lo ou renomeá-lo, pois a API o usará automaticamente se a variável de ambiente ENV estiver definida como test.


. . . . . . . . . . . . . . . . . . . . . . . . . . . .

🏠 Local

Apenas se necessário

Para Executar os Testes com a API fora do container, de maneira local, é necessário editar seu /.env.TEST.

No arquivo /.env.TEST, substitua os valores das variáveis de ambiente que contêm comentários no formato local: valueA | containerized: valueB pelos valores sugeridos na opção local.

DATABASE_HOST=localhost         ### local: localhost | conteinerized: test-postgres
DATABASE_PORT=5433              ### local: 5433      | conteinerized: 5432

PUBSUB_HOST=localhost           ### local: localhost | conteinerized: redis
IN_MEMORY_LOCK_HOST=localhost   ### local: localhost | conteinerized: redis
IN_MEMORY_CACHE_HOST=localhost  ### local: localhost | conteinerized: redis

GRPC_SERVER_HOST=localhost      ### local: localhost | conteinerized: transaction-processor
GRPC_CLIENT_HOST=localhost      ### local: localhost | conteinerized: transaction-processor

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

⚙️ Executando Testes

Rodando o Projeto payment-api em seu ambiente local ou containerizado, levante o banco de testes com

# Rodar o PostgreSQL de Testes
docker compose up test-postgres -d

Comando para executar o teste containerizado com a API levantada (Recomendado)

# Executa Testes no Docker com ENV test (PostgreSQL de Testes na Integração)
docker compose exec -e ENV=test transaction-rest go test -v -count=1 ./internal/adapter/repository/gormRepos ./internal/adapter/repository/redisRepos ./internal/core/service ./internal/adapter/http/router

Comando para executar o teste local em payments-api (Apenas se necessário)

# Executa Testes Localmente com ENV test (PostgreSQL de Testes na Integração)
ENV=test go test -v -count=1  ./internal/adapter/repository/gormRepos ./internal/adapter/repository/redisRepos ./internal/core/service ./internal/adapter/http/router
Saída esperada do comando

Cada vez que o comando for executado, as tabelas e índices da base de dados testada serão truncados e recriados no banco de dados do ambiente selecionado garantindo uma execução segura e limpa.

Os testes também são executados como parte da rotina minima de CI do GitHub Actions, garantindo que versões estáveis sejam mescladas na branch principal. O badge CI no cabeçalho do arquivo readme é uma ajuda visual para verificar rapidamente a integridade do desenvolvimento.

Saída esperada do workload na fase test do github
*Essa abordagem pode evoluir para uma rotina adequada de `CD`

⤴️ de volta ao índice



🚚 Testes Carga & Performance

Apenas Containerizado.

Atualmente o Gatling na versão 3.9.5 (desatualizada), realiza os testes de carga. Para executá-los, é necessário estar Rodando o Projeto Containerizado. Em outro terminal, no diretório raiz do projeto, execute os seguintes comandos

# Rodar o Gatling
docker compose up gatling -d

# Executa o teste de carga 
docker exec -ti gatling /entrypoint run-test 
Saída esperada do comando no Terminal de Execução do Gatling

Saída esperada nos terminais de transaction-rest e transaction-processor

Saída esperada no client Redis de cache-inmemory (db0) e lock-inmemory (db1)

Saída esperada no site Gatling em seu localhost


O teste executa 7500k transações em 5 minutos (ou 25 TPS, Transações Por Segundo. Valor pico médio de tráfego de esperado pelo proponente do desafio, validado por benchmarks reais). O objetivo é validar o timeoutSLA de 100ms em uma máquina local. Essa configuração pode ser encontrada nas seguintes linhas do arquivo PaymentSimulation.scala:

  private val tps = 25
  private val window = 5.minutes
  private val activeUsers = (window.toSeconds * tps).toInt
  • Picos de TPS sugeridos:
    • 50, 100 : Cenários de carga regular em ambientes controlados (máquina local).
    • Acima de 100: Considerados como stress test quando realizados em máquina local, devido à concorrência por recursos.
    • 200, 250, ...: Cenários de carga avançada para validação de homologação/produção.
    • Em ambientes de homologação/produção, espera-se que o sistema suporte valores superiores a 250 TPS (a validar).
    • Em ambientes de homologação/produção, a carga ofensora deve ser distribuída entre diferentes pods.

O comando abaixo remove o histórico dos testes de carga.

# Limpa os dados do teste de carga 
docker exec -ti gatling /entrypoint clean-test 

  • Considerações
    • Os resultados variam conforme os processos e a configuração da máquina. Recomenda-se executar com poucas aplicações rodando e monitorar via htop.
    • Para fins de comparação, os testes mais antigos permanecem no diretório tests/gatling/results/history/.
    • Os resultados são apenas referência para o desenvolvimento local
    • Quando possível, valide em ambientes próximos à produção, eles tendem a ter perfomances superiores a máquina de desenvolvimento local.

  • Métricas Relevantes
    • Timeout: tempo médio, mínimo e máximo de cada request.
    • Erros: Use logs de debug para mapear serviços e identificar gargalos. (Utilize as ferramentas de Observabilidade)

  • Pré-produção e Stress Tests
    • Nos ambientes de pre-prod e stg, se possível, use amostras maiores de dados próximos aos reais (TPS, usuários médios e picos históricos). Realize também stress tests, comprimindo cargas (e.g., simular 30 minutos de tráfego em 10). Esses testes ajudam a identificar falhas e garantem a escalabilidade progressiva.

⤴️ de volta ao índice



🕵️ Observabilidade (Work In Progress)

Apenas Containerizado. Validado no SO Ubunto 22


Ambiente & Stack:

Altere em seu arquivo .env as configurações de métricas do database para true e o log_output para loki.

DATABASE_METRICS_ENABLED=true
...
LOG_OPT_OUTPUT=loki                   ### text | json | loki

Agora, Rodando o Projeto payment-api em seu ambiente containerizado com seu .env configurado, suba as imagens necessárias com o comando abaixo e reinicie o transaction-processor e o transaction-rest.

# Rodar o pushgateway, redis-exporter, prometheus e grafana
docker compose up pushgateway redis-exporter prometheus loki promtail grafana -d

Saída esperada no site Prometheus em seu localhost

Rodando a consulta gin_gonic_request_duration_bucket{path="/payment"}


Agora, acesse e configure o Grafana em seu localhost (usr/pwd: admin/admin)


. . . . . . . . . . . . . . . . . . . . . . . . . . . .

Configurando o Grafana:

A primeira vez que executarmos o Grafana, entramos com usr/pwd padrão de admin/admin. Ele solicita a alteração da senha, para facilitar o desenvolvimento local, alteramos para admin/12345.

Criando conexão Datasource com Prometheus

No menu lateral procure por `Connections > Add New Connection` digite Prometheus no campo de Search, selecione-o, clique em `Add New Datasource` e configure-o com a URL: http://prometheus:9090 e clique no botão `Save & test` no final da página


Criando conexão Datasource com Loki

Volte para `Connections > Add New Connection` e dessa vez digite Loki no campo de Search, selecione-o, clique em `Add New Datasource` e configure-o com a URL: http://loki:3100 e clique no botão `Save & test` no final da página


Importando o Dashboard RED / USE (wip)

Agora você pode usar o menu `Dashboards > New > Import` para importar o arquivo dash-payments-api.json que está localizado no diretório: ./scripts/grafana-dashboards. Acesse o diretório em seu computador, clique e arraste o arquivo para o campo correto especificado pela tela Upload Dashboard JSON File, selecione o Prometheus previamente configurado como data source e proceda o import


Vincule o Dashboard às conexões previamente criadas e acesse-o

Importando o Dashboard REDIS

Repita os passos de Importando o Dashboard RED / USE (wip) de dashboard anteriores para o arquivo dash-redis.json atentando-se para o fato de que o `Redis` não consome dados do `Loki`


*Imagem retirada durante teste de carga. **Dashboard baixado da GrafanaLabs

Consultando Logs no Grafana Loki

Agora é possível utilizar o `Grafana` para filtrar seus logs de forma mais eficiente. Acesse o menu `Home > Connections > Data sources`, localize a lista de `sources` e, na linha correspondente ao `Loki`, clique no botão `Explore`. Isso abrirá a seção de consultas de logs, permitindo uma análise detalhada dos dados.

Essa funcionalidade é especialmente poderosa quando utilizada em conjunto com os Testes de Carga & Performance, proporcionando insights valiosos para otimização e resolução de problemas ainda em desenvolvimento como visto abaixo em um exemplo de Erro 07 por timeOut.

Tela de consulta de logs no Grafana Loki

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

Resultado Esperado:

Quando adequadamente importados, os Dashboards REDIS e RED / USE (wip) estarão disponíveis e responderão às solicitações que você pode simular pela Documentação da API ou pelos Testes Carga & Performance (fortemente recomendado rodar em conjunto com a Observabilidade) bem como filtors de logs através do Grafana Loki.

*Imagem retirada durante teste de carga

⤴️ de volta ao índice



🧑‍🔧 Validação Manual

O banco de desenvolvimento local, quando adequadamente instalado, possui uma carga inicial de dados que pode ser utilizada para Validação Manual.

Registros e Saldos no banco para teste manual

accounts

Account: AcountID:
123e4567-e89b-12d3-a456-426614174000 1

Query Consulta Balances (saldo mais recente de transactions por categories , não é a mesma da repository) por account:

SELECT 
	a.id as account_id, 
	a.uid as account_uid, 
	t.id as transaction_id, 
	t.uid as transaction_uid, 
	t.amount as amount, 
	c.id as category_id, 
	c.name as category_name, 
	c.priority as priority,
	STRING_AGG(mc.mcc, ',') AS codes
FROM 
	accounts as a 
JOIN 
	account_categories as ac ON ac.account_id = a.id 
JOIN 
	categories as c ON c.id = ac.category_id 
JOIN 
	transactions as t ON t.account_id = a.id  
	AND t.category_id = c.id 
	AND t.id = (
		SELECT MAX(t2.id) 
		FROM transactions t2 
		WHERE t2.account_id = a.id AND t2.category_id = c.id
	)
LEFT JOIN 
	mccs as mc ON mc.category_id = c.id 
WHERE 
	a.uid = '123e4567-e89b-12d3-a456-426614174000' 
GROUP BY a.id, a.uid, t.id, t.uid, t.amount, c.id, c.name, c.priority;

L1. L2. Resultado esperado:

account_id account_uid transaction_id transaction_uid amount category_id category_name priority codes
1 123e4567-e89b-12d3-a456-426614174000 1 [NULL] 1205.11 1 FOOD 1 5411,5412
1 123e4567-e89b-12d3-a456-426614174000 2 [NULL] 1110.22 2 MEAL 2 5811,5812
1 123e4567-e89b-12d3-a456-426614174000 3 [NULL] 1115.33 3 MOBILITY 3 6411
1 123e4567-e89b-12d3-a456-426614174000 4 [NULL] 1215.33 5 CASH 5

*Com acesso ao banco a partir dos dados de .env, para validar. Bem como a Documentação da API (Swagger) pode ser utilizado para proceder as requests.


L3. merchants com mapeamentos MCC incorretos

Merchant MCCs Mapeado para Categoria
UBER EATS SAO PAULO BR 5412 FOOD
PAG*JoseDaSilva RIO DE JANEI BR 5812 MEAL
...

*Utilize o campo name real da tabela merchant, o github pode formatar de maneira incorreta esse dado no markdown.


⤴️ de volta ao índice



📊 Diagramas do Sistema

*Diagramas Mermaid podem apresentar problemas de visualização em aplicativos mobile

📈 ER

erDiagram

    accounts_categories {
        int id PK
        int account_id FK
        int category_id FK
        timestamp created_at
        timestamp updated_at
        timestamp deleted_at
    }


    merchants {
        int id PK
        UUID uid
        string name
        int mcc_id FK
        timestamp created_at
        timestamp updated_at
        timestamp deleted_at
    }

    transactions {
        int id PK
        UUID uid
        int account_id FK
        int category_id FK
        numeric amount
        string mcc
        string merchant_name
        numeric total_amount
        timestamp created_at
        timestamp updated_at
        timestamp deleted_at
    }


   mccs {
        int id PK
        string mcc
        int category_id FK
        datetime created_at
        datetime updated_at
        timestamp deleted_at
    }


    categories {
        int id PK
        UUID uid
        string name
        int priority
        datetime created_at
        datetime updated_at
        timestamp deleted_at
    }
    
    accounts {
        int id PK
        UUID uid
        string name
        datetime created_at
        datetime updated_at
        timestamp deleted_at
    }

    transactions_latest {
        int account_id PK
        int category_id PK
        int transactions_latest_id
        numeric amount
    }

    categories ||--o{ transactions : has
    categories ||--o{ mccs : has
    categories ||--o{ accounts_categories : defines
    mccs ||--o{ merchants : has


    accounts_categories }o--|| accounts : has
    transactions }o--|| accounts : has

Loading

📝 Descrição

accounts Tabela principal, conectada a transactions e accounts_categories, armazenando informações sobre as contas.
accounts_categories Vincula contas a categorias associadas.
categories Armazena categorias (FOOD, MEAL, CASH...), com priority para definir a ordem de utilização.
mccs Contém MCCs (códigos de quatro dígitos) associados às categorias.
merchants Ajusta MCCs com base no nome do comerciante. transactions Registra o histórico de transações realizadas, incluindo categoria, comerciante e valores.
transactions_latest: Tabela auxiliar para reduzir o tempo de consulta às transações recentes das contas. Atualizada através da trigger trg_update_latest_transaction.



. . . . . . . . . . . . . . . . . . . . . . . . . . . .

📈 Fluxo

Autorização de Pagamento

flowchart TD
    A([▶️<br/>Recebe Transação JSON]) --> B[Mapeia Categoria pelo nome do comerciante]
    B --> C[Buscar Saldos da Conta]
    C --> D{Saldo é suficiente <br/> na Categoria?}
    
    D -- Sim --> E[Debita Saldo da Categoria]
    D -- Não --> F{Saldo suficiente na <br/> Categoria e CASH?}
    
    F -- Sim --> G[Debita Categoria e CASH]
    F -- Não --> H{Saldo suficiente em CASH?}
    
    H -- Sim --> I[Debita Saldo de CASH]
    H -- Não --> J[Rejeita Transação com Código 51]
    
    E --> K[Registrar Transação Aprovada]
    G --> K[Registrar Transação Aprovada]
    I --> K[Registrar Transação Aprovada]
    
    K --> P{Ocorreu Erro no Processo da Transação?}
    P -- Sim --> Q[❌<br/><b>Rejeitada</b><br/> Retorna Código <b>07</b> por Falha Genérica</b>]
        P -- Não --> M[✅<br/><b>Aprovada</b><br/> Retorna Código <b>00</b>]
    
    J --> N[❌<br/><b>Rejeitada</b><br/> Retorna Código <b>51</b> por Saldo Insuficiente</b>]

    N --> O([⏹️<br/>Fim do Processo])
    M --> O
    Q --> O

    style M fill:#009933,stroke:#000
    style N fill:#cc0000,stroke:#000
    style Q fill:#cc0000,stroke:#000
Loading

*Diagrama apresenta uma interpretação do sistema

📝 Descrição
  1. Recebe Transação JSON: O sistema recebe o payload de transação.

  2. Mapeia MCC pelo Merchant Name: Busca um relacionamento entre o merchant e uma categoria adequada. Caso categoria Não exista segue o fluxo para debitar de CASH

  3. Buscar Saldos da Conta: A conta e os saldos (FOOD, MEAL, CASH) são buscados no banco de dados

  4. Saldo é suficiente na Categoria?: Verifica se o saldo disponível na categoria mapeada (com base no MCC) é suficiente.

    • Se sim, debita o saldo da categoria correspondente.
    • Se não, verifica o saldo de CASH.
  5. Saldo suficiente em CASH?: Se a categoria principal não tiver saldo suficiente, o sistema verifica o saldo de CASH.

    • Se sim, debita parcial ou totalmente o saldo de CASH.
    • Se não, rejeita a transação com o código "51" (fundos insuficientes).
  6. Registrar Transação Aprovada: A transação aprovada é registrada no banco de dados.

  7. Retorna Código "00": Se a transação foi aprovada, retorna o código "00" (aprovada).

  8. Retorna Código "51": Se a transação foi rejeitada por falta de fundos, retorna o código "51".

*Esse fluxo representa o processo de aprovação, fallback e rejeição da transação com base nos saldos e MCC.


⤴️ de volta ao índice



🅻 Questão Aberta L4

🔒 Locks Distribuídos com Redis e Keyspace Notification

Com Locks Distribuídos e Bloqueio Pessimista, o processamento por account é síncrono, mas operações distintas seguem simultâneas. O Redis gerencia locks para coordenar o acesso eficiente a recursos e o Redis Keyspace Notifications, provê unlocks através de Pub/Sub. Consulte a ADR 0003: gRPC e Redis Keyspace Notification reduzindo Latência e evitando Concorrência para maiores detalhes.

flowchart TD
    A([▶️<br/>Recebe Transação JSON]) --> B[Inicia Processamento de Transação]
    B --> C{Account da Transação está Bloqueado no <b>Lock Distribuído</b>?}
    
    C -- Não --> D[🔐<br/><b>Bloqueia</b><br/>Account da Transação no Lock Distribuído]
    D  --> E[[Processa Autorização de Pagamento]]

    C -- Sim --> M[✉️⬅️<br/><b>Subscreve</b><br/>Redis Keyspace Notification<br/><br/> ]
    M --> R[⏸️<br/><b>Aguarda</b><br> receber Mensagem de desbloqueio da Account do Redis Keyspace Notification]
    R --> N{Recebi Mensagem de desbloqueio em tempo útil? <br/> <b><i>t<i> < 100 ms</b>}
    N -- Sim --> D
    N -- Não --> O[❌<br/><b>Rejeitada</b><br/> Retorna Código <b>07</b> por Falha Genérica</b>]

    E --> F{Ocorreu Erro no Processo da Transação?}
    F -- Não --> G{Saldo é Suficiente?}
    F -- Sim --> K[❌<br/><b>Rejeitada</b><br/> Retorna Código <b>07</b> por Falha Genérica</b>]
    K --> J
    
    G -- Sim --> H[Atualiza Saldo e Registra Transação Aprovada]
    H --> I[✅<br/><b>Aprovada</b><br/> Retorna Código <b>00</b>]
    I --> J[🔓<br/><b>Desbloqueia</b><br/> Account da Transação no <br> Lock Distribuído]

    G -- Não --> L[❌<br/><b>Rejeitada</b><br/> Retorna Código <b>51</b> por Saldo Insuficiente</b>]
    L --> J

    J --> P[✉️➡️<br/><b>Publica</b><br/> mensagem de desbloqueio Redis Keyspace Notification]
    P --> Q([⏹️<br/>Fim do Processo])
    O --> Q

    style D fill:#78771b,stroke:#000
    style I fill:#009933,stroke:#000

    style L fill:#cc0000,stroke:#000
    style K fill:#cc0000,stroke:#000
    style O fill:#cc0000,stroke:#000

    style M fill:#007bff,stroke:#000
    style R fill:#007bff,stroke:#000

    style J fill:#78771b,stroke:#000
    style P fill:#007bff,stroke:#000
Loading

*A etapa Processa Autorização de Pagamento é uma sub-rotina do fluxo de Autorização, simplificada para sentido isolado. Detalhes do débito de saldos estão no fluxograma vinculado.

O diagrama de fluxo acima foi produzido após uma sessão de Miro Board conduzida pelos proponentes do desafio. O diagrama Miro da proposta de arquitetura, resultado dessa sessão, pode ser visto Aqui
A partir desse diagrama, construí uma segunda versão com poucas modificações, acrescentando detalhes e contexto para os que não estiveram presentes nessa sessão. Esse diagrama gerou o ADR 0003: gRPC e Redis Keyspace Notification reduzindo Latência e evitando Concorrência, visando nortear a implementação do requisito L4 neste projeto, com finalidade estritamente de treinamento.

Via de regra, o que foi discutido naquela reunião deve ser implementado.


⤴️ de volta ao índice



🧠 ADR - Architecture Decision Records


⤴️ de volta ao índice



🔢 Versões

As tags de versões estão sendo criadas manualmente a medida que o projeto avança com melhorias notáveis. Cada funcionalidade é desenvolvida em uma branch a parte (Branch Based, feature branch) quando finalizadas é gerada tag e mergeadas em master.

Para obter mais informações, consulte o Histórico de Versões.


⤴️ de volta ao índice



🧰 Ferramentas


⤴️ de volta ao índice



👏 Boas Práticas


⤴️ de volta ao índice



🤖 Uso de IA

A figura do cabeçalho nesta página foi criada com a ajuda de inteligência artificial e um mínimo de retoques e construção no Gimp Gimp Logo

Os seguintes prompts foram usados para criação no Bing IA:

Gopher caixa de mercado "gopher azul, simbolo da linguagem golang com um bone laranja, trabalhando como caixa de supermercado com algumas maquinhas de cartão de credito e cartões em cima da mesa, estilo cartoon, historia em quadrinhos, fundo branco chapado para facilitar remoção"(sic)

IA também é utilizada em minhas pesquisas e estudos como ferramenta de apoio; no entanto, artes e desenvolvimento são, acima de tudo, atividades criativas humanas. Valorize as pessoas!

Contrate artistas para projetos comerciais ou mais elaborados e aprenda a ser engenhoso!


⤴️ de volta ao índice



🏁 Conclusão

  • Adotei o modelo hexagonal por sua flexibilidade com ports e adapters, permitindo suporte a HTTP, gRPC e fácil extensão para Mensagens ou pub/sub para atender ao requisito L4, sem impacto no core e com responsabilidades bem separadas.

  • Para o L4, filas foram descartadas pelo proponente no Miro Board devido à latência. Detalhes no ADR 0003: gRPC e Redis Keyspace Notification e no Kanban.

  • Refatoração das tabelas centralizou transactions e atualização de saldos por categories, garantindo imutabilidade e mitigando inconsistências. Detalhes no ADR 0004: Banco Relacional Modelado Inspirado em Eventos.

  • Testes de performance com Gatling foram criados para garantir implantações seguras. Detalhes no ADR 0005: Estratégia de Testes de Carga e Performance com Cliente Sintético.

  • Adicionada Observabilidade Métricas RED usando Prometheus e Grafana. Essas ferramentas também são úteis no desenvolvimento, quando usadas em conjunto aos testes de Performance e Carga citadas anteriormente. Detalhes no ADR 0006: Observabilidade com Prometheus e Grafana.

  • Os números de métricas entre os testes do Gatling e do Grafana (WIP) estão descolados (não de maneira muito significativa). É necessário maior investigação para entender os motivos. Também é esperado que, ao se adotar o Cliente Sintético K6, pertencente ao ecossistema Grafana, esse descolamento deixe de ocorrer.

  • Teste de stress abaixo foi realizado em um Notebook ROG Strix G16 - 13ª Geração após melhorias no banco e consultas para Performance. Os resultados podem variar dependendo das configurações e processos abertos na máquina de desenvolvimento. Detalhes no ADR 0007: Tabela Auxiliar para Melhoria de Performance.

  • O requisito L4 foi aprimorado em sua Concorrência com o Refactor do adapter de PubSub Redis e do repository de memoryLock. A instância da aplicação sobrescreve no PubSub uma única vez e distribui as mensagens de desbloqueio apenas para as requests bloqueadas, através de channels com bufferSize de 1, armazenados em um syncMap com chave accountUID. Esse map, por sua vez, armazena os channels de subscriptions em um outro syncMap cujas chaves são transactionUID, tornando cada requisição única e eliminando conexões custosas com o Redis. Com isso, espera-se validar a hipótese de que múltiplas instâncias são capazes de gerenciar os recursos de forma mais eficiente, resultando no aumento do TPS atendido.

// Exemplo ilustrativo de estrutura sync.Map usada no sistema para gerenciar concorrência
subscriptions := sync.Map{}

// Criando um sync.Map para "account1"
account1Transactions := &sync.Map{}
account1Transactions.Store("transaction1", make(chan string, 1))
account1Transactions.Store("transaction2", make(chan string, 1))
subscriptions.Store("account1", account1Transactions)

// Criando um sync.Map para "account2"
account2Transactions := &sync.Map{}
account2Transactions.Store("transaction3", make(chan string, 1))
subscriptions.Store("account2", account2Transactions)
  • Aprimoramentos na Observabilidade com painéis de monitoramento Grafana do Redis PubSub e Cache fazem-se necessários, bem como melhorias na criação e no gerenciamento de Logs (Grafana Loki) e Traces baseados em OpenTelemetry com Jaeger. Esses aprimoramentos devem ser considerados no futuro próximo.

  • Aprimorar o Disaster Recovery e implementar Gracefull Shutdown

  • Testes adicionais devem ser criados (Concorrência e múltiplos cenários de erros nas rotas e serviços).

Este desafio me permite consolidar conhecimentos e identificar pontos cegos para aprimoramento. Continuarei trabalhando para evoluir o projeto e expandir minhas habilidades.


⤴️ de volta ao índice



"Lifelong Learning & Prosper"
Mr. Spock, maybe 🖖🏾🚀

About

💳 💸 Possível solução Hexagonal para o desafio de API de Payments

Resources

Stars

Watchers

Forks

Packages

No packages published