プログラミングの基礎思考
ソフトウェア界隈はオブジェクト指向だとかMVVMとかフレームワーク各種と、高度なお話が色々ありついていけないのですが、そういう物以前の基礎、概念の分け方について書いていきます。
グループ分けしてフォルダ・ファイル分けをし、namespace や static method を使って見やすい、追いやすいコードを作ろうという話をします。
純粋処理と依存処理
処理には 純粋処理 と 依存処理 の2グループがあります
純粋処理とは次のようにリソースや業務に依存しない処理を指します
左辺値、右辺値を加算して答えを求めるプログラムの例です。
a = 1
b = 2
c = a + b
print(c) # 厳密にはIO処理は副作用であって純粋ではないが…
依存処理とは何らかのドメイン・プラットフォームに依存した処理を指します
前述の例を
- 左辺値はアプリケーションの環境ルールから取得
- 右辺値をMySQLから取得
a = AppRule.LeftValueRepository.get_left_value() # 環境ルールから
b = MySQL.RIghtValueRepository.get_right_value() # MySQLから取得してくる
c = a + b
print(c)
「環境ルール」はアカウントとかトークンとかそのアプリ特有のなんらかの概念に置き換えて考えてください。
この程度なら1ファイルに収めて問題無いとは思うのですが
これを 純粋処理 と 依存処理 として整理して実装し直します。
依存処理には業務依存とリソース依存がある
グループに namespace(フォルダ名)を決める
図のように
- 純粋処理 → workflow
- 業務依存処理 → service_domain
- リソース依存処理 platform_domain
として疑似コードを作成します。(※疑似コードなので動作はしません)
# ./service_domain/app_rule/repository.py
class AppRuleRepository:
@staticmethod
def get_left_value() -> int:
return int(os.environment["LEFT_VALUE"])
# ./platform_domain/mysql/repository.py
class MySQL_RightValueRepository:
@staticmethod
def get_right_value() -> int:
# 省略
row = cursor.fetchone()
return row[0]
# ./workflow/plus_service/model.py
class PlusServiceInputParam:
left_value: int
right_value: int
class PlusServiceResult:
answer: int
# ./service/plus_service.py
def plus_service_input() -> PlusServiceInputParam:
return PlusServiceInputParam(
left_value=AppRuleRepository.get_left_value(),
right_value=MySQL_RightValueRepository.get_right_value()
)
def plus_service_workflow(param PlusServiceInputParam) -> PlusServiceResult:
answer = param.left_value + param.right_value
return PlusServiceResult(answer)
def plus_service_output(answer PlusServiceResult):
AnswerRepository.save(answer.answer)
def plus_service(param PlusServiceInputParam) -> PlusServiceResult:
input_param = plus_service_input()
result = plus_service_workflow(input_param)
plus_service_output(result)
return result
純粋処理と依存処理の分離ができました。
サンプルがただの足し算だと説得力は無いですが、リソース混在の時にはこういう分離をして作りましょうという事を伝えたかったです。
この例の場合にロジック修正は plus_service_workflow だけに注目して修正すれば良いので、見通しが良いと思います。
実装時の設計まとめ
次のように考えます
- input 依存処理の実行(入力パラメータを純粋データとして揃える)
- 例:Cognito と DynamoDB からユーザー情報を集めて、必要な情報だけ一つの構造体にまとめる
- workflow 純粋処理の実行(データに対する処理を行う)
- 出力に必要なパラメータだけ次の処理に構造化して渡す
- output 依存処理の実行(出力先があれば出力する)
- 処理結果を展開しMySQLやDynamoDBに保存する
ポイントは
- workflow で依存処理を実行しない事
- 依存処理は input, output に寄せる
workflow は例外だったりメール送信だったりフラグ構築だったりで、リソースに依存した処理を呼ぶことはあると思います、なので絶対に依存処理を呼んではいけないというわけではないです。
あくまでもそういう方向性で作りましょうという事です。
フォルダ名について
service_domain, platform_domain workflow としましたが、 domain, service, platform とかでもいいと思います。
この辺はセンスというか、バックエンドかフロントエンドか、Winアプリかとか、作るものや環境文化に合わせて適切に考える必要があると思います。
以上です。