Gustavo Bicalho
Maurício Verardo
Construindo a
NuConta
Agenda
● NuConta
● Microsserviços no Nubank
● Transferindo dinheiro entre NuContas
○ Event-sourcing: Modularidade e Escalabilidade
○ Consistência em sistemas distribuídos
● O feed de movimentações
○ Backend for Frontends com GraphQL
NuConta
NuConta
NuConta
NuConta
NuConta
NuConta
NuConta
NuConta
https://nubank.design/
Microsserviços
Microsserviços
Fatura
Saldo
Antecipação
Antecipações
Saldos
Faturas
Saldos
Antecipações
Faturas
Microsserviços
Fatura
Saldo
Antecipação
Load
Balancer
Load
Balancer
Load
Balancer
Microsserviços
Serviço A Serviço B
HTTP
Microsserviços
Serviço A
Tópico do
Kafka
Serviço B
Transferindo dinheiro entre NuContas
SOUTHEAST BRAZIL REGION FROM SPACE
Transferindo dinheiro entre NuContas
Pedido de envio
Envio
Recebimentos
Recebimento
Envios
Liquidação
Saldos
Depósito
Transferindo dinheiro entre NuContas
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Transferindo dinheiro entre NuContas
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Liquidação efetuada
Transferindo dinheiro entre NuContas
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Transferindo dinheiro entre NuContas
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Transferindo dinheiro entre NuContas
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Depósito
Características interessantes desse fluxo
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Depósito
Event Sourcing
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Depósito
E se o cliente quiser enviar transferências para outra instituição financeira?
Pedido de envio
Envios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Depósito
E se o cliente quiser enviar transferências para outra instituição financeira?
Dinheiro enviado
para outra
instituição
Integração
com o
banco
central
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Depósito
E se o cliente receber transferências de outra instituição financeira?
Recebimentos
Saldos
Liquidação
Recebimento
Dinheiro
Recebido
Depósito
E se o cliente receber transferências de outra instituição financeira?
Dinheiro recebido
do Banco Central
Integração
com o
Banco
Central
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Depósito
E se o cliente quiser pagar a fatura do cartão de crédito?
Pedido de Pagamento
Pagamento de
Fatura
Saldos
Pedido
Pagamento
Solicitado
Liquidação
Pagamento
Liquidação efetuada
Pagamento de fatura
Depósito
E se o cliente quiser pagar a fatura do cartão de crédito?
Cartão de
crédito
Saldos
Liquidação
Depósito
Event Sourcing
Saldo hoje
Saldo no ano que vem
Saldo no mês passado
Pedido de envio
RecebimentosEnvios
Saldos
Pedido
Envio Solicitado
Liquidação
Envio
Liquidação efetuada
Dinheiro enviado para
NuConta
Recebimento
Dinheiro
Recebido
Depósito
Event Sourcing
Consistência em sistemas distribuídos
O que pode dar errado?
Pedido de envio
Envios
Liquidação
Saldos
Envio
Solicitado
Envio
Liquidação para envio
Recebimentos
Recebimento
Dinheiro enviado para
NuConta
Dinheiro
Recebido
Depósito
Pedido
O que pode dar errado?
Pedido de envio
Envios
Saldos
Envio
Solicitado
Envio
Liquidação para envio
Recebimentos
Recebimento
Dinheiro enviado para
NuConta
Dinheiro
Recebido
Depósito
Liquidação
Pedido
Primeiro requisito: Processamento at-least-once
Todo evento publicado é recebido e processado
completamente pelos consumidores pelo menos uma vez
Primeiro requisito: Processamento at-least-once
MSG
OK!
:)
MSG
MSG
MSG
Primeiro requisito: Processamento at-least-once
● Mensagens são persistidas e replicadas no Kafka cluster, podendo ser
consumidas a qualquer momento
● Próxima mensagem da fila será entregue de novo até que consumidor
confirme que completou seu processamento
● Consumidor só deve confirmar o processamento depois que completar todos
os efeitos colaterais
Segundo requisito: Idempotência
● Uma operação é idempotente se aplicá-la várias vezes é equivalente a aplicá-la
uma vez
● Exemplos:
○ Multiplicação por 0: 7*0 = 7*0*0 = 7*0*0*0 … = 0
○ DELETE FROM users WHERE users.id = 186
Segundo requisito: Idempotência
● Cada evento em nossa arquitetura tem um UUID aleatório
● Cada evento derivado usa o UUID do evento anterior (origem) como chave
única (chave de idempotência)
● Serviços garantem consistência interna: banco de dados local valida a chave
única
● Resultado: Criar um evento a partir de outro é uma operação idempotente
Liquidacao
ID: 78
Origem: PedidoDeEnvio:32
PedidoDeEnvio
ID: 32
Segundo requisito: Idempotência
Liquidacao
ID: 78
Origem: PedidoDeEnvio:32
PedidoDeEnvio
ID: 32
Envio
ID: 44
Origem: Liquidacao:78
Recebimento
ID: 156
Origem: Envio:44
Deposito
ID: 377
Origem: Recebimento:156
E quando um serviço cair?
Envios
Saldos
Pedido de envio
Envio
Solicitado
Liquidação
Pedido
E quando um serviço cair?
Envios
Saldos
Pedido de envio
Liquidação
Envio
Solicitado
Pedido
E se rolar um bug ou mensagem inválida?
Envio
Saldos
Deadletters
xkcd.com (CC BY-NC 2.5)
Deadletters:
Tópico onde guardamos mensagens cujo
processamento falhou (inclui metadados como
stack-traces, timestamp, etc)
Pedido de envio
Liquidação
Envio
Solicitado
Pedido
E se rolar um bug ou mensagem inválida?
Envio
Saldos
Deadletters
xkcd.com (CC BY-NC 2.5)
Republicar
/dev/null
Descartar
Deadletters:
Tópico onde guardamos mensagens cujo
processamento falhou (inclui metadados como
stack-traces, timestamp, etc)
Pedido de envio
Liquidação
Envio
Solicitado
Pedido
Feed de movimentações
Dados distribuídos = muitos requests
Envios
Pedido de envio
Envio
Recebimentos Recebimento
Saldos
Depósito
Liquidação
Evento de envio no feed
● Valor
● Data
● Status: pendente | falha | sucesso
Modelo do backend != visão do cliente
Liquidação Envio
404 | 404
404
Envios
Pedido de envio
Envio
Saldos
Liquidação
● Lançar uma nova versão de um aplicativo numa app store pode demorar dias
● Muitos usuários continuam com versões antigas por muito tempo
● Bugfixes e otimizações demoram para chegar
● Frontend acoplado impede evoluções no backend
Ciclos de atualização lentos
Backend For Frontend
BFF
Envios
Pedido de envio
Envio
Recebimentos Recebimento
Saldos
Depósito
Liquidação
"GraphQL is a query language designed to build client
applications by providing an intuitive and flexible syntax
and system for describing their data requirements and
interactions."
(GraphQL spec: http://facebook.github.io/graphql/)
Schema é o modelo disponível para o frontend
schema {
query: Query
}
type Query {
saldo(accountId: ID!): Float
feed(accountId: ID!):
[Envio | Recebimento]
}
type Recebimento {
data: Date
valor: Float
}
enum StatusEnvio {
PENDENTE, FALHA, SUCESSO
}
type Envio {
data: Date
valor: Float
status: StatusEnvio
}
Envios
Pedido de envio
Envio
Recebimentos Recebimento
Saldos
Depósito
Liquidação
Query define os campos que o client precisa
schema {
query: Query
}
type Query {
saldo(accountId: ID!): Float
feed(accountId: ID!):
[Envio | Recebimento]
}
type Recebimento {
data: Date
valor: Float
}
enum StatusEnvio {
PENDENTE, FALHA, SUCESSO
}
type Envio {
data: Date
valor: Float
status: StatusEnvio
}
query feedScreen($accountId: ID!) {
feed(accountId: $accountId) {
... on Envio {
data
valor
status
}
... on Recebimento {
data
valor
}
}
}
POST /api/query
Query define os campos que o client precisa
schema {
query: Query
}
type Query {
saldo(accountId: ID!): Float
feed(accountId: ID!): [Envio | Recebimento]
saldoDetalhado(accountId: ID!): SaldoDetalhado
}
query($accountId: ID!) {
feed(accountId: $accountId) { … }
saldoDetalhado(accountId: $accountId) { … }
}
Client v2
query($accountId: ID!) {
feed(accountId: $accountId) { … }
saldo(accountId: $accountId)
}
Client v1
Recapitulando
● Event-sourcing
● Comunicação assíncrona via Kafka
● Backend for Frontend com GraphQL
Gustavo Bicalho
Maurício Verardo
Obrigado!
https://sou.nu/vagasnu

Construindo a NuConta

  • 1.
  • 2.
    Agenda ● NuConta ● Microsserviçosno Nubank ● Transferindo dinheiro entre NuContas ○ Event-sourcing: Modularidade e Escalabilidade ○ Consistência em sistemas distribuídos ● O feed de movimentações ○ Backend for Frontends com GraphQL
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
    Transferindo dinheiro entreNuContas SOUTHEAST BRAZIL REGION FROM SPACE
  • 17.
    Transferindo dinheiro entreNuContas Pedido de envio Envio Recebimentos Recebimento Envios Liquidação Saldos Depósito
  • 18.
    Transferindo dinheiro entreNuContas Pedido de envio RecebimentosEnvios Saldos Pedido Envio Solicitado
  • 19.
    Transferindo dinheiro entreNuContas Pedido de envio RecebimentosEnvios Saldos Pedido Envio Solicitado Liquidação Liquidação efetuada
  • 20.
    Transferindo dinheiro entreNuContas Pedido de envio RecebimentosEnvios Saldos Pedido Envio Solicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta
  • 21.
    Transferindo dinheiro entreNuContas Pedido de envio RecebimentosEnvios Saldos Pedido Envio Solicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido
  • 22.
    Transferindo dinheiro entreNuContas Pedido de envio RecebimentosEnvios Saldos Pedido Envio Solicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido Depósito
  • 23.
    Características interessantes dessefluxo Pedido de envio RecebimentosEnvios Saldos Pedido Envio Solicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido Depósito
  • 24.
  • 25.
    Pedido de envio RecebimentosEnvios Saldos Pedido EnvioSolicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido Depósito E se o cliente quiser enviar transferências para outra instituição financeira?
  • 26.
    Pedido de envio Envios Saldos Pedido EnvioSolicitado Liquidação Envio Liquidação efetuada Depósito E se o cliente quiser enviar transferências para outra instituição financeira? Dinheiro enviado para outra instituição Integração com o banco central
  • 27.
    Pedido de envio RecebimentosEnvios Saldos Pedido EnvioSolicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido Depósito E se o cliente receber transferências de outra instituição financeira?
  • 28.
    Recebimentos Saldos Liquidação Recebimento Dinheiro Recebido Depósito E se ocliente receber transferências de outra instituição financeira? Dinheiro recebido do Banco Central Integração com o Banco Central
  • 29.
    Pedido de envio RecebimentosEnvios Saldos Pedido EnvioSolicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido Depósito E se o cliente quiser pagar a fatura do cartão de crédito?
  • 30.
    Pedido de Pagamento Pagamentode Fatura Saldos Pedido Pagamento Solicitado Liquidação Pagamento Liquidação efetuada Pagamento de fatura Depósito E se o cliente quiser pagar a fatura do cartão de crédito? Cartão de crédito
  • 31.
  • 32.
    Pedido de envio RecebimentosEnvios Saldos Pedido EnvioSolicitado Liquidação Envio Liquidação efetuada Dinheiro enviado para NuConta Recebimento Dinheiro Recebido Depósito Event Sourcing
  • 33.
  • 34.
    O que podedar errado? Pedido de envio Envios Liquidação Saldos Envio Solicitado Envio Liquidação para envio Recebimentos Recebimento Dinheiro enviado para NuConta Dinheiro Recebido Depósito Pedido
  • 35.
    O que podedar errado? Pedido de envio Envios Saldos Envio Solicitado Envio Liquidação para envio Recebimentos Recebimento Dinheiro enviado para NuConta Dinheiro Recebido Depósito Liquidação Pedido
  • 36.
    Primeiro requisito: Processamentoat-least-once Todo evento publicado é recebido e processado completamente pelos consumidores pelo menos uma vez
  • 37.
    Primeiro requisito: Processamentoat-least-once MSG OK! :) MSG MSG MSG
  • 38.
    Primeiro requisito: Processamentoat-least-once ● Mensagens são persistidas e replicadas no Kafka cluster, podendo ser consumidas a qualquer momento ● Próxima mensagem da fila será entregue de novo até que consumidor confirme que completou seu processamento ● Consumidor só deve confirmar o processamento depois que completar todos os efeitos colaterais
  • 39.
    Segundo requisito: Idempotência ●Uma operação é idempotente se aplicá-la várias vezes é equivalente a aplicá-la uma vez ● Exemplos: ○ Multiplicação por 0: 7*0 = 7*0*0 = 7*0*0*0 … = 0 ○ DELETE FROM users WHERE users.id = 186
  • 40.
    Segundo requisito: Idempotência ●Cada evento em nossa arquitetura tem um UUID aleatório ● Cada evento derivado usa o UUID do evento anterior (origem) como chave única (chave de idempotência) ● Serviços garantem consistência interna: banco de dados local valida a chave única ● Resultado: Criar um evento a partir de outro é uma operação idempotente Liquidacao ID: 78 Origem: PedidoDeEnvio:32 PedidoDeEnvio ID: 32
  • 41.
    Segundo requisito: Idempotência Liquidacao ID:78 Origem: PedidoDeEnvio:32 PedidoDeEnvio ID: 32 Envio ID: 44 Origem: Liquidacao:78 Recebimento ID: 156 Origem: Envio:44 Deposito ID: 377 Origem: Recebimento:156
  • 42.
    E quando umserviço cair? Envios Saldos Pedido de envio Envio Solicitado Liquidação Pedido
  • 43.
    E quando umserviço cair? Envios Saldos Pedido de envio Liquidação Envio Solicitado Pedido
  • 44.
    E se rolarum bug ou mensagem inválida? Envio Saldos Deadletters xkcd.com (CC BY-NC 2.5) Deadletters: Tópico onde guardamos mensagens cujo processamento falhou (inclui metadados como stack-traces, timestamp, etc) Pedido de envio Liquidação Envio Solicitado Pedido
  • 45.
    E se rolarum bug ou mensagem inválida? Envio Saldos Deadletters xkcd.com (CC BY-NC 2.5) Republicar /dev/null Descartar Deadletters: Tópico onde guardamos mensagens cujo processamento falhou (inclui metadados como stack-traces, timestamp, etc) Pedido de envio Liquidação Envio Solicitado Pedido
  • 46.
  • 48.
    Dados distribuídos =muitos requests Envios Pedido de envio Envio Recebimentos Recebimento Saldos Depósito Liquidação
  • 49.
    Evento de enviono feed ● Valor ● Data ● Status: pendente | falha | sucesso Modelo do backend != visão do cliente Liquidação Envio 404 | 404 404 Envios Pedido de envio Envio Saldos Liquidação
  • 50.
    ● Lançar umanova versão de um aplicativo numa app store pode demorar dias ● Muitos usuários continuam com versões antigas por muito tempo ● Bugfixes e otimizações demoram para chegar ● Frontend acoplado impede evoluções no backend Ciclos de atualização lentos
  • 51.
    Backend For Frontend BFF Envios Pedidode envio Envio Recebimentos Recebimento Saldos Depósito Liquidação
  • 52.
    "GraphQL is aquery language designed to build client applications by providing an intuitive and flexible syntax and system for describing their data requirements and interactions." (GraphQL spec: http://facebook.github.io/graphql/)
  • 53.
    Schema é omodelo disponível para o frontend schema { query: Query } type Query { saldo(accountId: ID!): Float feed(accountId: ID!): [Envio | Recebimento] } type Recebimento { data: Date valor: Float } enum StatusEnvio { PENDENTE, FALHA, SUCESSO } type Envio { data: Date valor: Float status: StatusEnvio } Envios Pedido de envio Envio Recebimentos Recebimento Saldos Depósito Liquidação
  • 54.
    Query define oscampos que o client precisa schema { query: Query } type Query { saldo(accountId: ID!): Float feed(accountId: ID!): [Envio | Recebimento] } type Recebimento { data: Date valor: Float } enum StatusEnvio { PENDENTE, FALHA, SUCESSO } type Envio { data: Date valor: Float status: StatusEnvio } query feedScreen($accountId: ID!) { feed(accountId: $accountId) { ... on Envio { data valor status } ... on Recebimento { data valor } } } POST /api/query
  • 55.
    Query define oscampos que o client precisa schema { query: Query } type Query { saldo(accountId: ID!): Float feed(accountId: ID!): [Envio | Recebimento] saldoDetalhado(accountId: ID!): SaldoDetalhado } query($accountId: ID!) { feed(accountId: $accountId) { … } saldoDetalhado(accountId: $accountId) { … } } Client v2 query($accountId: ID!) { feed(accountId: $accountId) { … } saldo(accountId: $accountId) } Client v1
  • 56.
    Recapitulando ● Event-sourcing ● Comunicaçãoassíncrona via Kafka ● Backend for Frontend com GraphQL
  • 57.