Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

FIX #5

Merged
merged 5 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed Client
Binary file not shown.
Binary file removed Server
Binary file not shown.
Binary file added docs/Artigo_T2_FPPD.pdf
Binary file not shown.
Binary file added docs/assets/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 50 additions & 17 deletions docs/main.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
18 changes: 10 additions & 8 deletions src/Client/ATM/ATM.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down
11 changes: 8 additions & 3 deletions src/Client/BankBranch/BankBranch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions src/Client/Client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", ".")
Expand All @@ -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
Expand Down
28 changes: 16 additions & 12 deletions src/Client/Menu/Menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
34 changes: 19 additions & 15 deletions src/Server/Bank/Bank.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
}

Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/Server/Bank/bank_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
2 changes: 0 additions & 2 deletions src/Server/Server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading