轨道
/
Roc
Roc
/
练习
/
银行账户
银行账户

银行账户

简单

简介

填了多年的表格、等了多年之后,你终于拿到了银行牌照。这意味着你正式有资格开一家自己的银行了,太好了!

你的首要任务是让 IT 系统跑起来。经过一天的辛苦工作,你已经能开户、销户,还能处理取款和存款了。

你懒得写测试,于是请了几个朋友来帮忙测试系统。可是才过了五分钟,就有个朋友说自己丢钱了!你虽然确信自己的代码没有 bug,还是开始翻日志,想查个究竟。

啊哈,果然如你所料,问题出在你朋友身上!他把自己的测试凭据分享给了另一个朋友,两人合谋对同一个账户_并行_地存取款。谁会干这种事?

你争辩说,一个人物理上_不可能_并行访问自己的账户,朋友却得意地告诉你,银行规则_要求_你必须支持这种操作。所以,不支持并行银行操作,就拿不到上线许可。你叹了口气,默默记下明天要处理这件事。这会让你的上线日期_至少_再推迟一天,不过嘛……

说明

你的任务是实现银行账户,支持开立、关闭、取款和存款。

由于银行账户可以通过多种方式访问(互联网、手机、自动扣款),你的银行软件必须允许从多个线程/进程(具体术语取决于你的编程语言)并行、安全地访问账户。 例如,可能有许多存款和取款同时发生,你需要确保在读取账户余额和设置新余额之间不存在竞态条件。

账户应当可以关闭;针对已关闭账户的操作必须失败。

Roc 与宿主的职责

在 Roc 代码中,我们通常只关注业务逻辑,而由平台负责并发和共享状态。因此,在本练习中你只需要在BankAccount.roc中实现账户操作,不必操心并发。账户初始状态为关闭,重新开启会重置余额,存款和取款金额都必须是正数。

可选的服务器演示

附带的bank-account-demo.roc展示了如何把你的实现用在一个小型 Web 服务里。你可以在编辑器中阅读它,但它与测试无关,也不属于你要提交的解答。

要运行演示程序,你需要先在机器上安装 Roc、安装 Exercism CLI,然后在终端中运行这条命令:

exercism download --exercise=bank-account --track=roc

接下来,实现BankAccount.roc,并用下面的命令测试它(如果 CLI 打印的下载位置与下面的路径不同,请相应替换):

cd ~/Exercism/roc/bank-account
roc test bank-account-test.roc

等所有测试都通过后,就可以用下面的命令启动演示服务器:

roc bank-account-demo.roc

服务器使用 basic-webserver 平台及其内置的 SQLite 支持(Roc 会在第一次运行时下载该平台)。不需要单独的数据库服务器。服务器监听http://127.0.0.1:8000,并在当前目录下创建bank-account-demo.db(把DB_PATH设为别的值即可使用不同的数据库文件)。按 Ctrl-C 停止服务器。数据在重启后仍然存在。

在另一个终端里,你可以用curl调用这个 Web 服务(在 Windows 上请一律使用curl.exe,以避开 PowerShell 的curl别名)。例如,下面演示如何开启 123 号账户、存入 50、取出 10,并查询其余额:

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

余额是40。账户 ID 和金额必须是无符号整数。更新成功时返回OK,失败时返回你的账户实现给出的错误。要关闭 123 号账户:

curl -X POST http://127.0.0.1:8000/accounts/123/close

想在 Linux/macOS 上试试并发请求,可以用一个新的账户 ID,从 8 个客户端发送 100 笔存款:

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

在 Windows 上,请使用 PowerShell 7+来运行这个并行示例:

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

这些存款应该全部成功,余额应该是100。

演示程序如何处理并发

每个请求都会读取账户、调用你的 Roc 函数,并在一个 SQLite Immediate事务中保存结果。失败的操作会回滚,不会改动账户。SQLite 一次只允许一个写入者,因此并发请求不会互相覆盖更新(即使涉及不同的账户)。

注意:本练习使用U64,但演示程序把余额存为 SQLite 整数,并会拒绝超过I64最大值(9223372036854775807)的更新。

想看并行运行相互独立的计算的例子,可以试试并行字母频率练习。

提示

显示提示

用match区分Closed和Open(balance)。一个操作会返回更新后的账户,而不是修改传给它的那个账户。用Ok(updated_account)表示更新成功,用Err(...)表示操作被拒绝。

通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Roc Exercism

准备好开始 银行账户 了吗?

注册 Exercism,借助 135 个练习 和真人导师指导,学习并掌握 Roc,全部免费。