diff --git a/Client b/Client deleted file mode 100755 index 18ce951..0000000 Binary files a/Client and /dev/null differ diff --git a/Server b/Server deleted file mode 100755 index ac79398..0000000 Binary files a/Server and /dev/null differ diff --git a/docs/Artigo_T2_FPPD.pdf b/docs/Artigo_T2_FPPD.pdf new file mode 100644 index 0000000..ea81ec8 Binary files /dev/null and b/docs/Artigo_T2_FPPD.pdf differ diff --git a/docs/assets/image.png b/docs/assets/image.png new file mode 100644 index 0000000..714e598 Binary files /dev/null and b/docs/assets/image.png differ diff --git a/docs/main.tex b/docs/main.tex index c1ab75e..02f1bd2 100644 --- a/docs/main.tex +++ b/docs/main.tex @@ -30,36 +30,69 @@ \section{Introdução} +Este relatório explora a implementação de um sistema bancário em Go, com uma arquitetura cliente-servidor baseada em chamadas de procedimento remoto (Remote Procedure Call, ou RPC). O sistema foi modularizado em três pacotes principais: ATM (Caixa Automático), que permite aos clientes realizarem transações como saques, depósitos e consultas de saldo; BankManager (Administração), que centraliza o gerenciamento de contas e operações bancárias no servidor; e BankBranch (Agência), que atua como intermediário entre o cliente e o servidor. Essa estrutura modular facilita a manutenção e ampliação do sistema, promovendo flexibilidade e escalabilidade na arquitetura. Neste relatório, cada módulo será abordado detalhadamente, com foco nas funcionalidades de cada camada e nos mecanismos de controle de concorrência e idempotência, essenciais para a segurança e eficiência das operações bancárias. + \section{Arquitetura do Sistema} +O sistema foi projetado com o modelo cliente-servidor utilizando RPC para possibilitar que clientes remotos possam realizar operações bancárias como saques, depósitos e consultas de saldo. Cada operação é uma função no servidor (BankManager), que é acionada remotamente pelo cliente via RPC. O módulo ATM, atuando como cliente, envia as solicitações ao módulo BankBranch (Agência), responsável por gerenciar as comunicações e transmitir as operações ao servidor. Esse modelo modular, além de otimizar a organização do código, facilita a manutenção e escalabilidade do sistema, uma vez que cada componente pode ser aprimorado de forma independente. + +Ao utilizar RPC, o sistema permite que operações sejam solicitadas de qualquer lugar com conexão ao servidor, mantendo uma resposta rápida e sem a necessidade de configuração avançada pelo cliente. O uso de RPC proporciona uma experiência mais ágil para o usuário, dado que o cliente envia comandos diretos para o servidor, minimizando latências e simplificando a comunicação entre os componentes. + \subsection{Processo Administração} +O processo Administração, implementado na camada BankManager, é responsável pela gestão das contas bancárias e pela execução de operações críticas, como a abertura e fechamento de contas, saques e depósitos. Este processo é fundamental para assegurar a integridade dos dados do sistema e garantir que as operações sejam executadas de forma segura em um ambiente concorrente. + \subsection{Processo Agência} +O processo Agência, encapsulado no pacote BankBranch, fornece uma interface para a interação do cliente com as funcionalidades do banco. Ele permite que os usuários abram novas contas, realizem saques, depósitos e consultem saldos. As chamadas feitas pelo cliente ao servidor são mediadas por funções RPC que invocam operações específicas do servidor. Cada chamada é tratada de forma assíncrona, garantindo que a interface do usuário permaneça responsiva enquanto as operações estão em andamento. + \subsection{Processo Caixa Automático} -\section{Implementação e Controle de Concorrência} +O processo Caixa Automático, encapsulado no pacote ATM, oferece aos clientes uma interface direta para realizar transações bancárias, como saques, depósitos e consultas de saldo. Este sistema permite que os usuários interajam com suas contas de maneira conveniente e eficiente, sem a necessidade de atendimento presencial. -\end{multicols} +\section{Controle de Concorrência} -\newpage +O controle de concorrência foi implementado para garantir a segurança das operações de contas quando múltiplos clientes acessam simultaneamente o sistema. O BankManager utiliza mutexes para proteger as operações críticas, como criação de contas e movimentação de saldo, assegurando que as transações ocorrem de forma atômica e consistente. -\section{Resultados e Testes} -Nesta seção, são apresentados os resultados dos testes realizados e os screenshots de execução dos processos. +\subsection{Implementação} -%\begin{figure}[h!] -% \centering -% \includegraphics[width=0.8\textwidth]{screenshot.png} -% \caption{screenshot} -%\end{figure} +Em Go, mutexes foram utilizados para bloquear temporariamente a estrutura de dados enquanto uma operação estava em andamento. Cada operação que envolvia uma modificação no saldo ou criação de contas era protegida por uma exclusão mútua (mutex lock), garantindo que apenas uma operação de escrita ocorresse por vez. Essa implementação evita problemas como a criação de contas duplicadas ou a inconsistência nos saldos devido a condições de corrida. -\newpage +\subsection{Testes de Concorrência} + +Para testar o controle de concorrência, múltiplas instâncias de clientes foram simuladas acessando e alterando simultaneamente o saldo de uma conta específica. Os testes verificaram que nenhuma condição de corrida ocorria, assegurando que as operações críticas eram concluídas de forma segura e coerente. Além disso, testes adicionais foram realizados com operações simultâneas em contas diferentes para avaliar o desempenho e garantir que o sistema mantinha a integridade dos dados em ambientes com alto volume de requisições. + +\section{Controle de Idempotência} + +Para garantir que operações idênticas produzissem os mesmos resultados, mesmo em casos de falhas de rede ou duplicação de solicitações, foi implementado o controle de idempotência no BankManager. Este controle assegura que operações repetidas, como saques ou encerramento de contas, não causem efeitos indesejados no sistema. -\section{Código Fonte} -\lstset{language=go, breaklines=true, basicstyle=\ttfamily\footnotesize} -\begin{lstlisting} -// Codigo fonte aqui se necessario (nao deve ser pq vamos entregar codigo junto) -\end{lstlisting} +\subsection{Implementação} +Cada operação RPC possui um identificador único associado à solicitação inicial. Quando uma requisição é recebida pelo servidor, ele verifica se já processou aquele identificador, retornando o resultado anterior se necessário. Esse método foi aplicado em operações de saque e encerramento de conta, garantindo que uma mesma solicitação não afete o sistema repetidas vezes, prevenindo, por exemplo, saques múltiplos do mesmo valor ou fechamento duplicado de uma conta. + +\subsection{Testes de Idempotência} + +O controle de idempotência foi testado simulando falhas e repetição de operações. Por exemplo, em um teste de saque duplicado, duas requisições idênticas foram enviadas para sacar o mesmo valor de uma conta. O sistema reconheceu a segunda solicitação como repetida e retornou o mesmo resultado da primeira operação, sem duplicar o saque. Esse tipo de teste foi aplicado em diversas operações para garantir que as ações idempotentes funcionavam conforme esperado, e que o sistema mantinha sua integridade mesmo sob falhas. + +\section{Conclusão} + +O desenvolvimento de um sistema bancário robusto e seguro, baseado na arquitetura cliente/servidor utilizando RPC/RMI, é um desafio significativo que envolve a implementação de diversas camadas de funcionalidade e segurança. Neste trabalho, abordamos os principais componentes do sistema, incluindo os processos de Administração, Agência e Caixa Automático, cada um desempenhando um papel crucial na operação do banco. + +O processo Administração, representado pela camada BankManager, garante a integridade das contas bancárias e a realização segura de transações financeiras. A adoção de mecanismos de controle de concorrência, como mutexes e locks, permite que múltiplas operações ocorram simultaneamente sem comprometer a segurança dos dados. Além disso, a implementação de idempotência nas operações críticas assegura que ações repetidas não resultem em erros ou inconsistências. + +Este trabalho demonstra a importância de uma arquitetura bem projetada e a implementação de princípios fundamentais como controle de concorrência e idempotência na construção de sistemas bancários modernos. A integração eficaz desses elementos não apenas melhora a eficiência operacional, mas também fortalece a confiança dos usuários na plataforma, proporcionando uma experiência segura e confiável. + +\end{multicols} + +\newpage + +\section{Resultados e Testes} +Nesta seção, são apresentados os resultados dos testes realizados e os screenshots de execução dos processos. -\end{document} +\begin{figure}[h!] + \centering + \includegraphics[width=1\linewidth]{assets/image.png} + \caption{bank test output} + \label{fig:enter-label} +\end{figure} +\end{document} \ No newline at end of file diff --git a/src/Client/ATM/ATM.go b/src/Client/ATM/ATM.go index a9563dc..59bf35d 100644 --- a/src/Client/ATM/ATM.go +++ b/src/Client/ATM/ATM.go @@ -6,19 +6,21 @@ import ( ) type AccountAccessRequest struct { - ID int - Password string + AccountID int + Password string + RequstID int64 } type FundsOperationRequest struct { - ID int - Password string - Quantity float64 + AccountID int + Password string + Quantity float64 + RequestID int64 } func Withdraw(id int, password string, quantity float64, requestID int64) func(*rpc.Client) error { return func(client *rpc.Client) error { - request := FundsOperationRequest{id, password, quantity} + request := FundsOperationRequest{id, password, quantity, requestID} var response bool @@ -33,7 +35,7 @@ func Withdraw(id int, password string, quantity float64, requestID int64) func(* func Deposit(id int, password string, quantity float64, requestID int64) func(*rpc.Client) error { return func(client *rpc.Client) error { - request := FundsOperationRequest{id, password, quantity} + request := FundsOperationRequest{id, password, quantity, requestID} var response bool @@ -48,7 +50,7 @@ func Deposit(id int, password string, quantity float64, requestID int64) func(*r func CheckBalance(id int, password string, requestID int64) func(*rpc.Client) error { return func(client *rpc.Client) error { - request := AccountAccessRequest{id, password} + request := AccountAccessRequest{id, password, requestID} var response float64 diff --git a/src/Client/BankBranch/BankBranch.go b/src/Client/BankBranch/BankBranch.go index 989b345..481e4c9 100644 --- a/src/Client/BankBranch/BankBranch.go +++ b/src/Client/BankBranch/BankBranch.go @@ -12,13 +12,13 @@ type OpenAccountRequest struct { } type AccountAccessRequest struct { - ID int + AccountID int Password string RequestID int64 } type FundsOperationRequest struct { - ID int + AccountID int Password string Quantity float64 RequestID int64 @@ -86,7 +86,12 @@ func Deposit(id int, password string, quantity float64, requestID int64) func(*r func CheckBalance(id int, password string, requestID int64) func(*rpc.Client) error { return func(client *rpc.Client) error { - request := AccountAccessRequest{id, password, requestID} + request := AccountAccessRequest{ + AccountID: id, + Password: password, + RequestID: requestID, + } + fmt.Printf("Debug: Enviando Request - AccountID=%d, Password=%s, RequestID=%d\n", request.AccountID, request.Password, request.RequestID) var response float64 diff --git a/src/Client/Client.go b/src/Client/Client.go index d754fd7..75f72cc 100644 --- a/src/Client/Client.go +++ b/src/Client/Client.go @@ -4,9 +4,12 @@ import ( "Coinnect-FPPD/src/Client/Menu" Pygmalion "Coinnect-FPPD/src/deps" "fmt" + "sync" "net/rpc" ) +var mutex sync.Mutex + func main() { // Carrega configurações Pygmalion.InitConfigReader("settings.yml", ".") @@ -19,8 +22,10 @@ func main() { callback := Menu.ObtainClientOperation(requestID) if callback != nil { // Executa a chamada ao servidor + mutex.Lock() SendOperation(address, port, callback) requestID++ + mutex.Unlock() continue } break diff --git a/src/Client/Menu/Menu.go b/src/Client/Menu/Menu.go index d5f748e..2b6cfd6 100644 --- a/src/Client/Menu/Menu.go +++ b/src/Client/Menu/Menu.go @@ -155,21 +155,25 @@ func getNamePasswordInput() (string, string) { } func getIDPasswordInput() (int, string) { - fmt.Print("Digite o ID da conta: ") - var stringId string - var password string + for { + fmt.Print("Digite o ID da conta: ") + var stringId string + var password string - _, err := fmt.Scanln(&stringId) - if err != nil { - fmt.Println("Erro ao ler o ID da conta. Certifique-se de inserir um número válido.") - } + _, err := fmt.Scanln(&stringId) + if err != nil { + fmt.Println("Erro ao ler o ID da conta. Certifique-se de inserir um número válido.") + } - fmt.Print("Digite a senha: ") - fmt.Scanln(&password) + fmt.Print("Digite a senha: ") + fmt.Scanln(&password) - id, _ := strconv.Atoi(stringId) - fmt.Printf("ClientID: %d ClientPasswort:%s\n", id, password) - return id, password + id, _ := strconv.Atoi(stringId) + if id != 0 { + return id, password + } + fmt.Println("Erro: ID da conta não pode ser zero.") + } } func getIDPasswordAmountInput(operation string) (int, string, float64) { diff --git a/src/Server/Bank/Bank.go b/src/Server/Bank/Bank.go index 80ef5b6..257bce4 100644 --- a/src/Server/Bank/Bank.go +++ b/src/Server/Bank/Bank.go @@ -50,7 +50,7 @@ func (b *Bank) Initialize() { defer b.mutex.Unlock() b.accounts = make(map[int]*account) - b.nextID = 1 + b.nextID = 0 b.processedRequests = make(map[int64]interface{}) // Conta hardcoded para teste @@ -72,7 +72,7 @@ func (b *Bank) OpenAccount(request OpenAccountRequest, result *int) error { b.mutex.Lock() defer b.mutex.Unlock() - fmt.Printf("BankManager.OpenAccount : Opening a new account : AccountID=%d : Name=%s\n", b.nextID, request.Name) + fmt.Printf("BankManager.OpenAccount [RequestID=%d] : Opening a new account : AccountID=%d : Name=%s\n", request.RequestID, b.nextID, request.Name) b.accounts[b.nextID] = &account{ Name: request.Name, Password: request.Password, @@ -82,7 +82,7 @@ func (b *Bank) OpenAccount(request OpenAccountRequest, result *int) error { *result = b.nextID b.logRequestID(request.RequestID, *result) b.nextID++ - fmt.Printf("BankManager.OpenAccount : Opened a new account successfully : Next Usable ID=%d\n", b.nextID) + fmt.Printf("BankManager.OpenAccount [RequestID=%d] : Opened a new account successfully : Next Usable ID=%d\n", request.RequestID, b.nextID) return nil } @@ -98,16 +98,16 @@ func (b *Bank) CloseAccount(request AccountAccessRequest, result *bool) error { b.mutex.Lock() defer b.mutex.Unlock() - fmt.Printf("BankManager.CloseAccount : Closing account : AccountID=%d : Balance=%.2f : ClientName=%s\n", request.AccountID, account.Balance, account.Name) + fmt.Printf("BankManager.CloseAccount [RequestID=%d] : Closing account : AccountID=%d : Balance=%.2f : ClientName=%s\n", request.RequestID, request.AccountID, account.Balance, account.Name) delete(b.accounts, request.AccountID) *result = true b.logRequestID(request.RequestID, *result) return nil } *result = false - fmt.Printf("BankManager.CloseAccount : Failed to authenticate account : AccountID=%d : AccountPassword=%s\n", request.AccountID, request.Password) + fmt.Printf("BankManager.CloseAccount [RequestID=%d] : Failed to authenticate account : AccountID=%d : AccountPassword=%s\n", request.RequestID, request.AccountID, request.Password) b.logRequestID(request.RequestID, *result) - return fmt.Errorf("BankManager.CloseAccount : Failed to authenticate account : AccountID=%d", request.AccountID) + return fmt.Errorf("BankManager.CloseAccount [RequestID=%d] : Failed to authenticate account : AccountID=%d", request.RequestID, request.AccountID) } // Withdraw realiza um saque de uma conta especificada, caso haja saldo suficiente. @@ -127,16 +127,18 @@ func (b *Bank) Withdraw(request FundsOperationRequest, result *bool) error { account.Balance -= request.Quantity *result = true b.logRequestID(request.RequestID, *result) - fmt.Printf("BankManager.Withdraw : Withdrawing funds : AccountID=%d : Balance=%.2f : Quantity=%.2f\n", request.AccountID, account.Balance, request.Quantity) + fmt.Printf("BankManager.Withdraw [RequestID=%d] : Withdrawing funds : AccountID=%d : Balance=%.2f : Quantity=%.2f\n", request.RequestID, request.AccountID, account.Balance, request.Quantity) return nil } *result = false b.logRequestID(request.RequestID, *result) - return fmt.Errorf("BankManager.Withdraw : Insufficient funds for account : AccountID=%d : Quantity=%.2f", request.AccountID, request.Quantity) + fmt.Printf("BankManager.Withdraw [RequestID=%d] : Insufficient funds for account : AccountID=%d : Quantity=%.2f\n", request.RequestID, request.AccountID, request.Quantity) + return fmt.Errorf("BankManager.Withdraw [RequestID=%d] : Insufficient funds for account : AccountID=%d", request.RequestID, request.AccountID) } *result = false b.logRequestID(request.RequestID, *result) - return fmt.Errorf("BankManager.Withdraw : Failed to authenticate account : AccountID=%d", request.AccountID) + fmt.Printf("BankManager.Withdraw [RequestID=%d] : Failed to authenticate account : AccountID=%d\n", request.RequestID, request.AccountID) + return fmt.Errorf("BankManager.Withdraw [RequestID=%d] : Failed to authenticate account : AccountID=%d", request.RequestID, request.AccountID) } // Deposit adiciona um valor ao saldo de uma conta especificada. @@ -155,17 +157,19 @@ func (b *Bank) Deposit(request FundsOperationRequest, result *bool) error { account.Balance += request.Quantity *result = true b.logRequestID(request.RequestID, *result) - fmt.Printf("BankManager.Deposit : Depositing on account : AccountID=%d : Balance=%.2f : Quantity=%.2f\n", request.AccountID, account.Balance, request.Quantity) + fmt.Printf("BankManager.Deposit [RequestID=%d] : Depositing on account : AccountID=%d : Balance=%.2f : Quantity=%.2f\n", request.RequestID, request.AccountID, account.Balance, request.Quantity) return nil } *result = false b.logRequestID(request.RequestID, *result) - fmt.Printf("BankManager.Deposit : Failed to authenticate account : AccountID=%d", request.AccountID) - return fmt.Errorf("BankManager.Deposit : Failed to authenticate account : AccountID=%d", request.AccountID) + fmt.Printf("BankManager.Deposit [RequestID=%d] : Failed to authenticate account : AccountID=%d\n", request.RequestID, request.AccountID) + return fmt.Errorf("BankManager.Deposit [RequestID=%d] : Failed to authenticate account : AccountID=%d", request.RequestID, request.AccountID) } // PeekBalance consulta o saldo de uma conta, se a senha estiver correta. func (b *Bank) PeekBalance(request AccountAccessRequest, result *float64) error { + fmt.Printf("Debug: Request recebido - AccountID=%d, Password=%s, RequestID=%d\n", request.AccountID, request.Password, request.RequestID) + if previousResult, exists := b.checkRequestID(request.RequestID); exists { *result = previousResult.(float64) return nil @@ -179,13 +183,13 @@ func (b *Bank) PeekBalance(request AccountAccessRequest, result *float64) error *result = account.Balance b.logRequestID(request.RequestID, *result) - fmt.Printf("BankManager.PeekBalance : Peeking balance : AccountID=%d : Balance=%.2f\n", request.AccountID, account.Balance) + fmt.Printf("BankManager.PeekBalance [RequestID=%d] : Peeking balance : AccountID=%d : Balance=%.2f\n", request.RequestID, request.AccountID, account.Balance) return nil } *result = -1 b.logRequestID(request.RequestID, *result) - fmt.Printf("BankManager.PeekBalance : Failed to authenticate account : AccountID=%d : AccountPassword=%s", request.AccountID, request.Password) - return fmt.Errorf("BankManager.PeekBalance : Failed to authenticate account : AccountID=%d", request.AccountID) + fmt.Printf("BankManager.PeekBalance [RequestID=%d] : Failed to authenticate account : AccountID=%d : AccountPassword=%s\n", request.RequestID, request.AccountID, request.Password) + return fmt.Errorf("BankManager.PeekBalance [RequestID=%d] : Failed to authenticate account : AccountID=%d", request.RequestID, request.AccountID) } func (b *Bank) getAuthenticatedAccount(AccountID int, accountPassword string) (*account, bool) { diff --git a/src/Server/Bank/bank_test.go b/src/Server/Bank/bank_test.go index 8e346d8..afda802 100644 --- a/src/Server/Bank/bank_test.go +++ b/src/Server/Bank/bank_test.go @@ -17,8 +17,8 @@ func TestOpenAccount(t *testing.T) { t.Fatalf("Expected no error, got %v", err) } - if accountID != 2 { // Verifica se a próxima conta é criada com ID correto - t.Errorf("Expected account ID to be 2, got %d", accountID) + if accountID != 1 { // Verifica se a próxima conta é criada com ID correto + t.Errorf("Expected account ID to be 1, got %d", accountID) } } diff --git a/src/Server/Server.go b/src/Server/Server.go index e3f3623..98255a6 100644 --- a/src/Server/Server.go +++ b/src/Server/Server.go @@ -27,8 +27,6 @@ func Run(port int) { bank := new(BankManager.Bank) bank.Initialize() - // ainda não temos objetos thread-safe - // e fugimos da idempotência. Um passo de cada vez 💪 rpc.Register(bank) listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil {