トラック
/
Roc
Roc
/
演習
/
銀行口座
銀行口座

銀行口座

初級

はじめに

長年書類を書き続けて待ち続けた末に、ついに銀行の免許を取得しました。これで正式に、自分の銀行を開く資格を得たことになります。やった!

まず取りかかるのは、ITシステムを動かすことです。1日がんばったおかげで、もう口座の開設と閉鎖、そして引き出しと預け入れまでできるようになりました。

テストを書くのは面倒だったので、友達に声をかけてシステムのテストを手伝ってもらいます。ところが、始めてからわずか5分で、友達の一人がお金がなくなったと言い出しました! 自分のコードにバグはないと確信していますが、念のためログを調べて原因を探ることにします。

ああ、やっぱり。疑ったとおり、悪いのは友達のほうでした! その友達はテスト用の認証情報を別の友達と共有していて、二人で結託して同じ口座から_並行して_預け入れと引き出しを繰り返していたのです。いったい誰がそんなことをするでしょうか?

誰かが同時に自分の口座にアクセスするのは物理的に_不可能_だと主張しますが、友達は得意げに、銀行のルールではそれをサポートすることが_必須_だと告げます。つまり、並行処理をサポートしなければ、サービス開始のゴーサインは出ないということです。ため息をつきながら、明日取りかかろうと心にメモします。これでサービス開始は_少なくとも_あと1日は延びますが、まあ仕方ありません…

説明

この課題では、口座の開設・閉鎖、出金、入金に対応する銀行口座を実装します。

銀行口座には、インターネット、携帯電話、自動引き落としなど、さまざまな方法でアクセスできます。そのため、作成する銀行ソフトウェアは、複数のスレッドやプロセス(呼び方はプログラミング言語によって異なります)から、口座に安全に並行アクセスできるようにしなければなりません。 たとえば、多数の入金と出金が並行して行われることがあります。残高を読み取ってから新しい残高を設定するまでの間に、競合状態が起きないようにする必要があります。

口座は閉鎖できる必要があります。閉鎖された口座に対する操作は、必ず失敗しなければなりません。

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では、PowerShellのcurlエイリアスを避けるため、常にcurl.exeを使ってください)。たとえば、口座#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は同時に1つの書き込みしか許さないため、並行するリクエストが互いの更新を上書きすることはありません(異なる口座間でも同様です)。

注意:この演習ではU64を使いますが、デモでは残高をSQLiteの整数として保存するため、I64の最大値(9223372036854775807)を超える更新は拒否されます。

独立した計算を並行して実行する例については、Parallel Letter Frequencyの演習を試してみてください。

ヒント

ヒントを表示

matchを使ってClosedとOpen(balance)を区別します。操作は、渡された口座を書き換えるのではなく、更新後の口座を返します。更新が成功したときはOk(updated_account)、操作が拒否されたときはErr(...)を使います。

GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Roc Exercism

銀行口座を始める準備はできましたか?

Exercismに登録すれば、135個の演習、そして本物の人間によるメンタリングとともに、Rocを学んでマスターできます。すべて無料です。