Depois de anos a preencher formulários e à espera, conseguiste finalmente a tua licença bancária. Isto significa que tens agora oficialmente o direito de abrir o teu próprio banco, viva!
A tua primeira prioridade é pôr os sistemas informáticos a funcionar. Depois de um dia de trabalho árduo, já consegues abrir e fechar contas, e ainda tratar de levantamentos e depósitos.
Como não te apetecia escrever testes, convidas uns amigos para ajudar a testar o sistema. No entanto, ao fim de apenas cinco minutos, um dos teus amigos afirma ter perdido dinheiro! Embora tenhas a certeza de que o teu code está livre de bugs, começas a percorrer os registos para investigar.
Ah, pois, exatamente como suspeitavas, a culpa é do teu amigo! Partilhou as credenciais de teste com outro amigo e, juntos, conspiraram para fazer depósitos e levantamentos na mesma conta em paralelo. Quem faria uma coisa dessas?
Enquanto argumentas que é fisicamente impossível que alguém aceda à sua conta em paralelo, o teu amigo avisa-te, com um ar presunçoso, de que as regras bancárias exigem que dês apoio a isso. Ou seja: sem suporte para operações bancárias em paralelo, não há sinal de lançamento. Suspiras e fazes uma nota mental para tratar disto amanhã. Isto vai adiar a tua data de lançamento em pelo menos mais um dia, mas enfim...
A tua tarefa é implementar contas bancárias que suportem a abertura e o fecho, levantamentos e depósitos de dinheiro.
Como as contas bancárias podem ser acedidas de muitas formas diferentes (internet, telemóveis, cobranças automáticas), o teu software bancário tem de permitir que se aceda às contas em segurança a partir de múltiplas threads/processos (a terminologia depende da tua linguagem de programação) em paralelo. Por exemplo, pode haver muitos depósitos e levantamentos a acontecer em paralelo; tens de garantir que não existem condições de corrida entre o momento em que lês o saldo da conta e o momento em que defines o novo saldo.
Deve ser possível fechar uma conta; as operações sobre uma conta fechada têm de falhar.
Em código Roc, normalmente concentramo-nos na lógica de negócio, enquanto a plataforma trata da concorrência e do estado partilhado.
Por isso, neste exercício só precisas de implementar as operações de conta em BankAccount.roc, sem te preocupares com a concorrência.
As contas começam fechadas, reabrir uma conta repõe o saldo, e os depósitos e levantamentos têm de ser positivos.
O ficheiro bank-account-demo.roc incluído mostra como a tua implementação pode ser usada num pequeno serviço web.
Podes lê-lo no editor, mas é independente dos testes e não faz parte da tua solução.
Para executar a demonstração, tens de instalar o Roc na tua máquina, instalar a CLI do Exercism e depois executar este comando num terminal:
exercism download --exercise=bank-account --track=roc
De seguida, implementa BankAccount.roc e testa-o com o seguinte comando (muda o local de transferência indicado pela CLI se for diferente do caminho abaixo):
cd ~/Exercism/roc/bank-account
roc test bank-account-test.roc
Quando todos os testes passarem, podes iniciar o servidor da demonstração com o seguinte comando:
roc bank-account-demo.roc
O servidor usa a plataforma basic-webserver e o suporte SQLite integrado (o Roc transfere a plataforma na primeira execução).
Não é preciso um servidor de base de dados separado.
O servidor escuta em http://127.0.0.1:8000 e cria bank-account-demo.db no teu diretório atual (define DB_PATH para usares um ficheiro de base de dados diferente).
Pára o servidor com Ctrl-C.
Os dados sobrevivem a reinícios.
Noutro terminal, podes chamar o serviço web com curl (no Windows, usa sempre curl.exe para evitares o alias curl do PowerShell).
Por exemplo, eis como abrir a conta n.º 123, depositar 50, levantar 10 e verificar o saldo:
curl -X POST http://127.0.0.1:8000/accounts/123/open
curl -X POST http://127.0.0.1:8000/accounts/123/deposit/50
curl -X POST http://127.0.0.1:8000/accounts/123/withdraw/10
curl http://127.0.0.1:8000/accounts/123/balance
O saldo é 40.
Os IDs das contas e os montantes têm de ser números inteiros sem sinal.
As atualizações devolvem OK em caso de sucesso ou um erro da tua implementação de conta em caso de falha.
Para fechar a conta n.º 123:
curl -X POST http://127.0.0.1:8000/accounts/123/close
Para experimentares pedidos concorrentes no Linux/macOS, usa um ID de conta novo e envia 100 depósitos a partir de oito clientes:
curl -X POST http://127.0.0.1:8000/accounts/456/open
seq 100 | xargs -P 8 -I '{}' curl --fail --silent --show-error -X POST http://127.0.0.1:8000/accounts/456/deposit/1
curl http://127.0.0.1:8000/accounts/456/balance
No Windows, usa o PowerShell 7+ para o exemplo em paralelo:
curl.exe -X POST http://127.0.0.1:8000/accounts/456/open
1..100 | ForEach-Object -Parallel { curl.exe --fail --silent --show-error -X POST http://127.0.0.1:8000/accounts/456/deposit/1 } -ThrottleLimit 8
curl.exe http://127.0.0.1:8000/accounts/456/balance
Todos os depósitos devem ser bem-sucedidos e o saldo deve ser 100.
Cada pedido lê a conta, chama as tuas funções Roc e guarda o resultado dentro de uma transação Immediate do SQLite.
As operações falhadas são revertidas sem alterar a conta.
O SQLite permite apenas um escritor de cada vez, o que impede que pedidos concorrentes sobreponham as atualizações uns dos outros (mesmo entre contas diferentes).
Nota: o exercício usa U64, mas a demonstração guarda os saldos como inteiros do SQLite e rejeita atualizações que excedam o valor máximo de I64 (9223372036854775807).
Para um exemplo de como executar cálculos independentes em paralelo, experimenta o exercício parallel-letter-frequency.
Usa match para distinguires Closed de Open(balance).
Uma operação devolve uma conta atualizada em vez de modificar a conta que lhe é passada.
Usa Ok(updated_account) para uma atualização bem-sucedida e Err(...) para uma operação rejeitada.
Inscreve-te no Exercism para aprenderes e dominares Roc com 135 exercícios, e mentoria humana real, tudo grátis.