再起動後も貸出窓口の状態を保持する

JavaBeginner
オンラインで実践に進む

はじめに

貸出窓口は、アプリケーションを閉じた後も、誰がどの工具を持っているか覚えておく必要があります。この独立した環境には、動作する貸出ルールと、既存の貸出や整備中の工具を含む新しい窓口ファイルが用意されています。未完成の保存コードは、現在、読み込み時に保存済み状態を失い、変更を書き込みません。

UTF-8 のファイル記録をオブジェクトに対応付け、有効な変更だけを保存します。前のラボのオブジェクトルールを理解していることが前提ですが、そちらの環境のファイルは不要です。これは単一プロセスの学習用アプリケーションであり、データベースでも停電への保証でもありません。読解と練習の時間は、作成者による見積もりで 45 分です。

ディスクに実際に保存されている状態を読み込む

このステップでは、通常のファイル記録を 1 件読んでから、保存された状態と借用者の関係を Java オブジェクトに戻す処理を修復します。

Terminal タブを開きます。cd はこの独立したラボのフォルダーを選択します。入力を間違えた未実行のシェル行は、Ctrl+U で消せます。ファイルパスのエラーが起きたら、ここに戻り、正しいコマンドを再試行してください。

cd /home/labex/project/tool-library

アプリケーションには動作するオブジェクトルールがありますが、保存処理は未完成です。まず、小さな練習ファイルで、既存の貸出を危険にさらさず 5 フィールドの形式を確認します。mkdir -p は必要に応じて練習用ディレクトリを作ります。printf は指定した形式で出力し、\t はタブ、\n は記録の終わりを表します。> はその出力を指定したファイルに書き込み、以前の練習用コピーがあれば置き換えます。単一引用符は、シェルが形式を変更するのを防ぎます。cp でメンバー一覧をそのファイルの隣にコピーします。アプリケーションが貸出窓口の入力ファイルの隣で members.tsv を探すためです。これらのコマンドは、実際の貸出窓口のデータを変更しません。

mkdir -p work
printf 'S01\tSpare drill\tAVAILABLE\t-\tPractice only\n' > work/sample.tsv
cp members.tsv work/members.tsv

現在のソースをコンパイルします。-encoding UTF-8 はテキストのエンコーディング、-d out はコンパイル出力先、*.java はこのフォルダーの Java ファイルを指定します。エラーなくコンパイルが成功した場合だけ先へ進んでください。

javac -encoding UTF-8 -d out *.java

-cp out は Java にクラスの場所を伝え、-Dfile.encoding=UTF-8 は実行時のテキストエンコーディングを指定します。練習用の入力は work/sample.tsv で、--memory はこの実演での保存を防ぎます。

java -Dfile.encoding=UTF-8 -cp out Desk work/sample.tsv --memory

プログラム内で次の行を入力し、その都度 Enter を押します。

list
borrow S01 M01
list
quit

S01 は借用者が - の AVAILABLE で始まり、M01 への貸出が受理されます。練習用の行が、壊れたコードの想定する状態をもともと持つため、この通常のケースは未完成の読み込み処理でも動きます。既存の貸出が保持される証明にはなりません。練習での変更は、このプロセスを閉じると消えます。

実際の貸出窓口と登録メンバーを読みます。cat はファイルを編集せず表示します。

cat desk.tsv
cat members.tsv

ID、名前、列挙型の状態、借用者、メモという 5 つのタブ区切りフィールドがあります。この制御された形式では、フィールド内の空白と UTF-8 テキストは使えますが、タブや改行は使えません。T24 はすでに M03 が借りています。T25 は M02 が借りており、Cafe workshop というメモがあります。T23 は整備中です。これらの事実を読み込み後も保持する必要があります。desk.tsv、members.tsv、reference フォルダーを直接編集しないでください。

用意された保存コードを読みます。

cat Store.java

Store は永続化の境界です。load では記録がオブジェクトになり、save ではオブジェクトが記録になります。Files.readAllLines は UTF-8 を明示して読み込みます。fields(line) は形式を確認し、f[0] から f[4] のフィールドを返します。new Tool(...) の呼び出しを探してください。現在はすべての行で、保存された値の代わりに Tool.State.AVAILABLE と "-" を使っています。T24 の借用者と T23 の整備状態がどうなるか予想しましょう。ループ、リスト、解析と検証のヘルパーはすでに用意されているので残します。内部の HashSet 実装を書き直す必要はありません。

編集前に、ソースだけのバックアップを作ります。

cp Store.java work/Store-before-load.java.txt
nano Store.java

最初のラボと同様、Ctrl+W で検索し、Enter で検索を確定し、矢印キーで移動し、Backspace で文字を修正します。Tool tool = new Tool を検索して、このコンストラクタ呼び出しを探します。状態と借用者の引数を編集し、次の形にします。

            Tool tool = new Tool(f[0], f[1], Tool.State.valueOf(f[2]), f[3], f[4]);

Tool.State.valueOf(f[2]) は保存された列挙型の名前を列挙値に変換し、f[3] は保存された借用者を復元します。ID、名前、メモの引数、周囲のループ、すべてのヘルパーを残してください。新しい貸出可能な工具を作り出すのではなく、既存の情報を対応付けています。Ctrl+O、Enter で保存し、Ctrl+X で終了します。未保存の間違いを破棄するには Ctrl+X、N を使います。コンパイルエラーはファイルを開き直してこの呼び出しを検索し、修正してください。意図的にこの編集を最初からやり直す場合、cp work/Store-before-load.java.txt Store.java でこのソースファイルだけを戻してから、対応付けをやり直します。編集を取り消すために setup を再実行してはいけません。

ここで throws IOException は、ファイルアクセスが失敗し得ることを宣言します。入力がない場合や形式が不正な場合、コマンドループは Cannot continue を表示して停止します。これはファイルの問題であり、貸出の拒否ではありません。示されたパスやソースの対応付けを修正してください。エラーを消すために元の記録を空にしたり書き直したりしてはいけません。

保存したソースをコンパイルして、実際の貸出窓口を起動します。実演と異なり、--memory を省きます。次のステップで保存を完成させると、実際の貸出窓口は有効な変更を永続化します。構文エラーにはファイル名と行が示されます。古いクラスを実行せず、nano で修正してから、起動前にコンパイルを成功させてください。

javac -encoding UTF-8 -d out *.java
java -Dfile.encoding=UTF-8 -cp out Desk desk.tsv

貸出窓口内で次の行を入力します。

list
members
quit

一覧は T24 → M03、T25 → M02、T23 の MAINTENANCE 状態、すべてのメモを保持していなければなりません。members は登録 ID を表示します。開く操作と一覧表示でファイルを書き直してはいけません。quit でシェルに戻ります。次のステップで実際の保存を完成させます。成功メッセージだけでは永続化の証明にはなりません。

有効な変更を保存して窓口を開き直す

このステップでは、新しい貸出 1 件と有効な返却 1 件を、本当にプロセスが終了した後も保持できるようにします。

同じ Terminal タブのシェルから始めます。保存では、変更していない貸出や整備の記録も含め、現在のすべてのオブジェクトをシリアライズします。row(tool) は 5 つのフィールドをタブで連結します。ループはすべての行をリストに追加し、Files.write はそれらを UTF-8 で書き込みます。コマンドループが save を呼ぶのは、ドメインメソッドが正常に戻った後だけです。拒否された操作は、同じ内容を書き直す場合も含め、データファイルに書き込んではいけません。

このルールと永続化の境界を結び付けるため、コマンドの枠組みを読みます。

cat Desk.java

found.borrow(c[2])、found.giveBack(c[2]) と、その後の Store.save(path, tools) を探します。ドメインの例外が送出されると、保存の呼び出しを飛ばして通常の拒否になります。この用意された順序は保ってください。拒否処理に書き込みを追加すると、契約に違反します。

修正済みの読み込み処理を保持する、ソースだけのバックアップを作ります。

cp Store.java work/Store-before-save.java.txt
nano Store.java

Ctrl+W を押して TODO: write all rows を検索し、Enter を押します。すぐ上のループを読んでください。変更していない貸出も含め、各工具のシリアライズ済みの行を集めています。矢印キーでそのコメントの末尾に移動し、Backspace でコメントの文字を消します。同じ字下げ位置に書き込み処理を入力します。ループの後、メソッドを閉じる波括弧の前で、TODO コメントだけを次の実際の書き込みに置き換えてください。

        Files.write(path, rows, StandardCharsets.UTF_8);

path は選択された貸出窓口ファイルで、rows は最後に変更したオブジェクトだけではなく、シリアライズした完全な一覧です。文字セットを明示することで、各フィールドのテキストエンコーディングを保ちます。修正済みの load メソッド、行のループ、すべてのヘルパーを残します。Ctrl+O、Enter、Ctrl+X で保存して終了します。未保存のエラーは Ctrl+X、N で破棄できます。保存済みのソースの間違いは、開き直して修正します。cp work/Store-before-save.java.txt Store.java は、読み込み処理を保ったままソースだけを戻す任意の復旧手段です。その後、書き込み処理をやり直してください。貸出窓口ファイルのリセットや setup の再実行はしないでください。

保存したソースを再コンパイルします。

javac -encoding UTF-8 -d out *.java
java -Dfile.encoding=UTF-8 -cp out Desk desk.tsv

貸出窓口内で以下を入力します。

borrow T21 M01
borrow T21 M02
return T24 M01
borrow T23 M01
borrow TYPO M01
return T24 M03
list
quit

T21 は M01 に貸し出されます。重複した貸出、間違った返却者、整備中の工具の貸出、入力を間違えた ID は、意図どおり拒否されます。M03 による T24 の返却だけが成功します。T22 は貸出可能、T23 は整備中のままで、T25 は元のメモを保持したまま M02 に貸し出された状態です。不明な ID が拒否されてもコマンドループは使用できます。再起動やリセットではなく、ID を修正してください。

今度は、同じファイルを使って新しい Java プロセスを起動します。

java -Dfile.encoding=UTF-8 -cp out Desk desk.tsv
list
quit

T21 は引き続き M01 によって BORROWED で、T24 は借用者が - の AVAILABLE のままである必要があります。そうならない場合は、現在の Store.java をコンパイルし、--memory を付けずに起動したか確認してください。保存された貸出を装うために最終記録を編集してはいけません。受理された操作がすでに保存されているなら、今は拒否されるという理由だけで繰り返さないでください。list が現在の事実を表示します。

シェルに戻り、実際に保存された結果を読みます。

cat desk.tsv

記録の関係は、新しいプロセスの一覧と一致する必要があります。すべての ID、名前、メンバー、整備状態、対象外の貸出、メモを保持してください。変わるのは T21 の状態・借用者と、T24 の状態・借用者だけです。この小さな書き込み処理は、保存成功時にファイルを置き換えます。同時アクセス、クラッシュからの復旧、アトミックなトランザクションは提供しません。書き込みに失敗したら、先に進む前に停止してエラーを調べてください。

まとめ

UTF-8 の記録を Java オブジェクトに対応付け、有効な状態遷移後に現在の全記録を保存し、新しいプロセスで貸出窓口を開き直しました。既存の貸出、整備、メモは保持され、拒否された操作はストレージに書き込みませんでした。