Este projeto foi desenvolvido como parte da disciplina MI - Concorrência e Conectividade, do curso de Engenharia de Computação da Universidade Estadual de Feira de Santana (UEFS).
Este relatório apresenta a solução desenvolvida para implementar um sistema de transações bancárias semelhante ao Pix, em um país que não possui um banco central. A principal demanda do governo era a criação de um sistema que permitisse a realização de pagamentos, depósitos e transferências entre contas de diferentes bancos, sem utilizar recursos centralizados para controlar as transações.
Com o objetivo de atender aos requisitos solicitados, foi estabelecido um consórcio bancário. A solução proposta foi baseada em um sistema distribuído que garante a comunicação eficiente e segura entre os bancos, permitindo que os clientes realizem transações atômicas sobre suas contas, sejam elas de pessoa física (particulares ou conjuntas) ou jurídica.
Os desafios principais incluíam a prevenção de movimentações de dinheiro além do saldo disponível e a eliminação do risco de duplo gasto, onde o mesmo dinheiro poderia ser transferido para mais de uma conta simultaneamente.
O código apresentado utiliza um método de "two phase commit" (compromisso em duas fases) em conjunto com bloqueios (locks) para garantir a consistência das operações bancárias distribuídas.
O "two phase commit" é um protocolo utilizado para garantir a atomicidade das transações distribuídas, ou seja, garantir que todas as operações relacionadas a uma transação sejam executadas com sucesso ou que nenhuma delas seja executada.
-
Função preparar_transferencia() e preparar_conta_externa(): Essas funções são responsáveis por preparar as contas envolvidas na transação. Isso inclui verificar se as contas existem, aplicar bloqueios (lock.acquire()) para garantir exclusividade durante a modificação da conta e realizar qualquer outra preparação necessária, como verificar saldos suficientes para transferências.
-
Bloqueios (Locks): Os bloqueios são fundamentais para garantir que apenas uma thread ou processo por vez possa modificar uma determinada conta. Isso previne problemas de consistência que poderiam ocorrer se múltiplos processos tentassem modificar a mesma conta simultaneamente. Após a preparação, os bloqueios são liberados (lock.release()) para permitir que outras operações possam ser executadas.
Após a fase de preparação, o sistema avança para a fase de confirmação das transações preparadas:
-
Função confirmar_transferencia() e confirmacao_conta_externa(): Estas funções são responsáveis por confirmar as operações preparadas na fase anterior. Isso inclui efetuar transferências reais entre contas dentro do banco ou enviar solicitações de transferência para bancos externos, dependendo do destino da transação.
-
Commit ou Rollback: Durante esta fase, é crucial garantir que todas as operações preparadas sejam efetivadas (commit) apenas se todas forem executadas com sucesso. Caso contrário, se ocorrer algum erro durante a confirmação, é necessário desfazer (rollback) todas as operações preparadas para manter a integridade dos dados.
Se ocorrer qualquer falha durante a preparação ou confirmação das transações, é necessário desfazer todas as operações preparadas para evitar estados inconsistentes no sistema:
- Função desfazer_transferencia() e desfazer_conta_externa(): Essas funções são acionadas em caso de erro durante a preparação ou confirmação das transações. Elas revertem quaisquer modificações feitas durante a preparação, garantindo que nenhuma mudança inconsistente seja persistida no sistema.
POST
/cadastro_pessoa_fisica- Recebe dados JSON (nome, CPF, senha) e cadastra uma pessoa física.
- Verifica se todos os dados obrigatórios foram fornecidos e se o cliente já está cadastrado. Em caso de sucesso, retorna uma mensagem de confirmação e detalhes da conta criada.
POST
/cadastro_pessoa_juridica- Recebe dados JSON (nome, CNPJ, senha) e cadastra uma pessoa jurídica.
- Verifica se todos os dados obrigatórios foram fornecidos e se o cliente já está cadastrado. Em caso de sucesso, retorna uma mensagem de confirmação e detalhes da conta criada.
POST
/cadastro_conta_conjunta- Recebe dados JSON (identificador1, identificador2, senha) e cadastra uma conta conjunta.
- Verifica se os identificadores e a senha foram fornecidos e se os clientes existem. Em caso de sucesso, retorna uma mensagem de confirmação e detalhes da conta criada.
POST
/login- Recebe dados JSON (identificador, senha) e autentica o cliente.
- Verifica se o identificador e a senha foram fornecidos.
- Em caso de sucesso, retorna uma mensagem de login bem-sucedido e detalhes do cliente.
POST
/logout- Desloga o cliente atualmente logado.
- Retorna uma mensagem de confirmação de logout.
GET
/contas_cliente/- Recebe um identificador (CPF/CNPJ) e retorna todas as contas associadas a esse cliente em todos os bancos.
- Verifica se o identificador foi fornecido e se o cliente existe.
- Em caso de sucesso, retorna uma lista de todas as contas do cliente.
GET
/get_contas/- Recebe um identificador (CPF/CNPJ) e retorna todas as contas associadas a esse cliente no banco atual.
- Verifica se o identificador foi fornecido e se o cliente existe.
- Em caso de sucesso, retorna uma lista de todas as contas do cliente no banco atual.
GET
/get_conta/<nome_banco>/<numero_conta>- Recebe o nome do banco e o número da conta e retorna os detalhes dessa conta.
- Verifica se o nome do banco e o número da conta foram fornecidos.
- Em caso de sucesso, retorna os detalhes da conta.
GET
/get_identificador- Retorna o identificador (CPF/CNPJ) do cliente atualmente logado.
- Verifica se há um cliente logado.
- Em caso de sucesso, retorna o identificador do cliente logado.
GET
/get_nome_banco- Retorna o nome do banco atual.
- Endpoint: /deposito
- Método HTTP:
POST
- Funcionalidade:
- Recebe dados em formato JSON contendo o número da conta, nome do banco e valor a ser depositado. Valida os dados recebidos e executa o depósito na conta correspondente:
- Se o banco for o mesmo em que o cliente está logado (banco.nome), o depósito é realizado diretamente na conta interna usando métodos do objeto banco. Caso contrário, delega a operação para o método deposito_outro_banco do objeto banco, que lida com depósitos em bancos externos.
- Endpoint: /saque
- Método HTTP:
POST
- Funcionalidade:
- Recebe dados em formato JSON contendo o número da conta e o valor a ser sacado.
- Valida os dados e executa o saque na conta correspondente usando métodos do objeto banco.
- Endpoint: /preparar_transferencia
- Método HTTP:
POST
- Funcionalidade:
- Recebe dados em formato JSON contendo o número da conta, nome do banco, tipo de transferência e valor a ser transferido.
- Valida os dados e prepara a transferência: Se o banco for o mesmo em que o cliente está logado (banco.nome), prepara a transferência usando métodos do objeto banco. Caso contrário, delega a operação para o método preparar_conta_externa do objeto banco, que prepara a conta externa para a transferência.
- Endpoint: /confirmar_transferencia
- Método HTTP:
POST
- Funcionalidade: Recebe dados em formato JSON contendo o número da conta, nome do banco, tipo de transferência e valor a ser transferido.
- Valida os dados e confirma a transferência: Se o banco for o mesmo em que o cliente está logado (banco.nome), confirma a transferência usando métodos do objeto banco. Caso contrário, delega a operação para o método confirmacao_conta_externa do objeto banco, que confirma a transferência na conta externa.
- Endpoint: /desfazer_transferencia
- Método HTTP:
POST
- Funcionalidade: Recebe dados em formato JSON contendo o número da conta, nome do banco, tipo de transferência e valor a ser desfeito.
- Valida os dados e desfaz as alterações: Se o banco for o mesmo em que o cliente está logado (banco.nome), desfaz as alterações usando métodos do objeto banco. Caso contrário, delega a operação para o método desfazer_conta_externa do objeto banco, que desfaz as alterações na conta externa.
- Endpoint: /transferir
- Método HTTP:
POST
- Funcionalidade: Recebe dados em formato JSON contendo o nome do banco de destino, número da conta de destino, valor a ser transferido e uma lista de transferências adicionais.
- Executa a transferência em duas fases: Preparação: Prepara todas as contas envolvidas na transferência. Confirmação: Confirma as operações preparadas. Usa métodos do objeto banco para realizar essas operações de preparação e confirmação.
- Endpoint: /
- Método HTTP:
GET
- Funcionalidade: Renderiza o template login.html, que geralmente contém formulários para login.
- Endpoint: /home
- Método HTTP:
GET
- Funcionalidade: Tenta obter informações das contas do cliente logado e o nome do banco. Realiza chamadas HTTP para endpoints específicos do banco usando requests.get. Se obter sucesso nas chamadas, renderiza o template home.html passando o nome do banco e informações das contas.
- Endpoint: /cadastro
- Método HTTP:
GET
- Funcionalidade: Renderiza o template cadastro.html, utilizado para cadastro de novos clientes.
- Endpoint: /deposito_page
- Método HTTP:
GET
- Funcionalidade: Renderiza o template deposito.html, que provavelmente contém formulários para realizar depósitos em contas.
- Endpoint: /saque_page
- Método HTTP:
GET
- Funcionalidade: Renderiza o template saque.html, onde são disponibilizados formulários para realizar saques de contas bancárias.
- Endpoint: /transferencia_page
- Método HTTP:
GET
- Funcionalidade: Tenta obter informações das contas do cliente logado e o nome do banco. Realiza chamadas HTTP para endpoints específicos do banco usando requests.get. Se obter sucesso nas chamadas, renderiza o template transferencia.html passando o nome do banco e informações das contas.
Para executar sem precisar contruir uma imagem, já existe uma imagem feita que está hospedada no site https://www.docker.com/
Siga pos seguintes passos para executar o broker:
docker pull joaogabrielaraujo/bank
docker run --network=host -it -e NUMERO_BANCO=<número do banco> -e IP_BANCO1=<ip do banco 1> -e IP_BANCO2=<ip do banco 2> -e IP_BANCO3=<ip do banco 3> joaogabrielaraujo/bank
Tomando exemplo de números de ip's fictícios: Computador 1 = 250.0.0.1
, Computador 2 =250.0.0.2
, Computador 3 = 250.0.0.3
- comando do computador 1:
docker run --network=host - it - NUMERO_BANCO=1 -e IP_BANCO2=250.0.0.2 -e IP_BANCO3=250.0.0.3 joaogabrielaraujo/bank
- comando do computador 2:
docker run --network=host - it - NUMERO_BANCO=2 -e IP_BANCO1=250.0.0.1 -e IP_BANCO3=250.0.0.3 joaogabrielaraujo/bank
- comando do computador 3:
docker run --network=host - it - NUMERO_BANCO=3 -e IP_BANCO1=250.0.0.1 -e IP_BANCO2=250.0.0.2 joaogabrielaraujo/bank
A implementação bem-sucedida deste sistema distribuído para transações bancárias possibilita a realização de pagamentos, depósitos e transferências entre contas de diferentes bancos sem a necessidade de um banco central. A solução desenvolvida atendeu plenamente aos requisitos estabelecidos pelo governo, proporcionando uma plataforma robusta e segura para os clientes realizarem transações atômicas de forma eficiente.
Ao adotar uma abordagem distribuída, foi possível garantir a integridade das transações, prevenindo movimentações de dinheiro além do saldo disponível e eliminando o risco de duplo gasto. A comunicação entre os bancos foi estabelecida de maneira eficaz, assegurando que as transações fossem processadas de forma correta.