本記事は
flaskを使ってRestAPIサーバを作ってみるの「削除」について記述しています。
ここまでの流れはflaskを使ってRestAPIサーバを作ってみるを参照してください。
削除編で説明するコードは feature/chapter2-delete
ブランチにあります。
flask_sv プロジェクトのディレクトリ配下で次のコマンドを実行してください。
$ git checkout feature/chapter2-delete
5. 削除
削除のAPIは、アカウントIDだけわかれば良いのでGETメソッドで特定のURLにアクセスすることにしましょう。
http://サーバーのIP/account/delete/アカウントID
5.1. モデル層・エンティティ
コードの全文はこちらです。
def delete(account_id, operation_account_id):
Session = scoped_session(sessionmaker(bind=engine, autocommit=False))
ses = Session()
account_record = ses.query(Account).with_for_update().get(account_id)
try:
account_record.status=Status.getStatusKey("DELETE")
ses.add(account_record)
ses.commit()
message = ""
res = True
except Exception as e:
message = str(e)
print(f"Account#update error:{message}")
ses.rollback()
res = False
finally:
ses.close()
return (res, message)
コードの解説をします。
4.4. 排他編集で説明したscoped_sessionを使っています。
排他編集同様にaccount_idからのロックを行います。
def delete(account_id, operation_account_id):
Session = scoped_session(sessionmaker(bind=engine, autocommit=False))
ses = Session()
account_record = ses.query(Account).with_for_update().get(account_id)
次に削除ですが実際にレコードを削除しても良いですが、本システムでのレコードは論理削除を行います。
try:
account_record.status=Status.getStatusKey("DELETE")
ses.add(account_record)
ses.commit()
message = ""
res = True
except Exception as e:
message = str(e)
print(f"Account#update error:{message}")
ses.rollback()
res = False
finally:
ses.close()
論理削除をする理由としてはオペレーションミスによる削除から復旧したり、過去のオペレーション履歴を調査することです。また、論理削除ではデータが溜まっていくため、一定期間たったら削除するためのバッチを動かしたり、保守用のテーブルに移動するなどしておかないとDB全体でのパフォーマンス低下、ディスク圧迫を引き起こすので覚えておいてください。
返却は処理結果とエラー時のメッセージのタプルとしています。
return (res, message)
もし、物理削除する場合は次の様に検索結果にdelete()関数を付加します。
ses.query(Account).get(account_id).delete()
5.2. ドメイン層・ビジネスロジック
コードの全文はこちらです。
def delete(account_id, operation_account_id):
"""
/account/deleteで呼び出されたAPIの検索処理
Parameters
----------
account_id : int
削除するアカウントID
operation_account_id : int
Webアプリケーション操作アカウントのID
Returns
-------
JSON形式の処理結果
正常
異常
"""
print(f"AccountApi#delete account_id={account_id}")
message=""
code=""
try:
res = Account.delete(account_id, operation_account_id)
if res[0] == True:
code="I0001"
message="deleted Account Succesfuly."
else:
code="E0001"
message=res[1]
except Exception as e:
code="E0009"
message=f"Deleted failed:{str(e)}"
result_json = {
"body": "",
"status": {
"code" : code,
"message" : message,
"detail" : ""
}
}
ret = result_json
return ret
コードの解説をします。
Account.deleteで処理した結果をアプリケーション層へ返却します。
try:
res = Account.delete(account_id, operation_account_id)
if res[0] == True:
code="I0001"
message="deleted Account Succesfuly."
else:
code="E0001"
message=res[1]
except Exception as e:
code="E0009"
message=f"Deleted failed:{str(e)}"
処理結果をJSONに詰め直し返却します。
result_json = {
"body": "",
"status": {
"code" : code,
"message" : message,
"detail" : ""
}
}
return result_json
5.3. アプリケーション層・フロントへのIF
http://サーバーのIP/account/delete/100
にGETでアクセスされたらAccountApi.deleteを実行して結果をJSON形式で返却します。
@api_bp.route('/account/delete/<id>', methods=['GET'])
def deleteAccount(id):
account_json = AccountApi.delete(id, system_account_id)
return jsonify(account_json)
課題 5.1
5.2. ドメイン層・ビジネスロジックのコードではロックをしてから削除までユーザの手が入れられません。
ユーザーがロックをしたら安心して次のオペレーション(削除)を行え、かつ、他のユーザーにロックされていたら次の処理を待てる様にロックの時に
http://サーバーのIP/account/lock/100
を呼び出し、次に
http://サーバーのIP/account/delete_for_lock/100
を呼び出すことで削除が行われる様に変更してください。
また、ロックをしたセッションを保存するためのコードの作成も必要となります。
5.4. テストコードからの実行
テストコード全文
def test_account_delete():
"""
"""
account = {
'account_name' : "delete_account",
'start_on' : '2021-05-23 00:00:00',
'end_on' : '2030-12-31 00:00:00',
'created_by' : 999,
'created_at' : datetime.datetime.now(),
'updated_by' : 999,
'updated_at' : datetime.datetime.now(),
'status' : Status.getStatusKey("NEW")
}
# create
Account.create(account, 999) == True
search_query = {
"account_name":"delete_account",
"start_on":"2021-05-23 00:00:00",
"end_on":"2030-12-31 00:00:00"
}
result = Account.search(search_query, 999)
assert result[0].account_name == account['account_name']
account_id = result[0].id
# APIから確認
url = f"http://localhost:5000/api/account/delete/{account_id}"
headers = {'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'flask_sv/0.0.1',
'Content-type': 'application/json; charset=utf-8',
}
response = requests.get(url, headers=headers)
# HTTP Statusコードが200であること
assert response.status_code == 200
data = json.loads(response.text)
print(f"test_AccountApi#test_account_delete data={data}")
assert data['body'] == ""
assert data['status']['code'] == "I0001"
assert data['status']['message'] == "deleted Account Succesfuly."
コードの解説をします。
テストAccountを作ります。その元になるパラメータです。
account = {
'account_name' : "delete_account",
'start_on' : '2021-05-23 00:00:00',
'end_on' : '2030-12-31 00:00:00',
'created_by' : 999,
'created_at' : datetime.datetime.now(),
'updated_by' : 999,
'updated_at' : datetime.datetime.now(),
'status' : Status.getStatusKey("NEW")
}
上記のパラメータをAccountのcreateで作成します。
# create
Account.create(account, 999) == True
次に検索をして作成されたAccountのIDを調べます。
search_query = {
"account_name":"delete_account",
"start_on":"2021-05-23 00:00:00",
"end_on":"2030-12-31 00:00:00"
}
result = Account.search(search_query, 999)
assert result[0].account_name == account['account_name']
account_id = result[0].id
テスト対象のAPIを実行します。
# APIから確認
url = f"http://localhost:5000/api/account/delete/{account_id}"
headers = {'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'flask_sv/0.0.1',
'Content-type': 'application/json; charset=utf-8',
}
response = requests.get(url, headers=headers)
返却されたレスポンスの結果を確認します。
# HTTP Statusコードが200であること
assert response.status_code == 200
data = json.loads(response.text)
assert data['body'] == ""
assert data['status']['code'] == "I0001"
assert data['status']['message'] == "deleted Account Succesfuly."
このテストコードではエラー時のテストがされていません。
実際は、全てのパターンを網羅してテストコードを準備するのが望ましいです。
課題 5.2
本テストコードはエラー時のテストができないので、削除しようとしたアカウントIDが存在しない場合のテストコードを作成してください。
課題 5.3
削除しようとしたアカウントIDが既に削除されている場合のテストコードを作成してください。