Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
クリーンアーキテクチャのリポジトリパターン - Pythonでの実装
Search
shimakaze-git
April 19, 2023
Programming
1
3.7k
クリーンアーキテクチャのリポジトリパターン - Pythonでの実装
クリーンアーキテクチャのリポジトリパターンについて解説する社内勉強会資料。
Pythonを使ってのサンプルコードも作成。
shimakaze-git
April 19, 2023
Tweet
Share
More Decks by shimakaze-git
See All by shimakaze-git
DjangoとFastAPIによる 実践認証技術
shimakaze_soft
0
200
フロントエンドとバックエンドのコミュニケーションをスムーズにするスキーマ駆動開発
shimakaze_soft
2
370
lt20221030.pdf
shimakaze_soft
0
120
Dependabotを使って 運用しているおはなし
shimakaze_soft
0
2k
DjangoCongressJP 2019/2020 & 今年にPyConJP初登壇できたはなし
shimakaze_soft
0
350
GAEによるPythonWEBアプリケーションの高速開発
shimakaze_soft
0
2.9k
FlaskとDjango以外のAPI開発の選択肢
shimakaze_soft
0
410
Python で Dependency Injection(DI) をやるには?
shimakaze_soft
1
2.6k
FalconAPI開発にいいよ!
shimakaze_soft
0
640
Other Decks in Programming
See All in Programming
テストコード文化を0から作り、変化し続けた組織
kazatohiei
2
1.5k
Monixと常駐プログラムの勘どころ / Scalaわいわい勉強会 #4
stoneream
0
280
PHPUnitしか使ってこなかった 一般PHPerがPestに乗り換えた実録
mashirou1234
0
180
Jakarta EE meets AI
ivargrimstad
0
240
情報漏洩させないための設計
kubotak
1
130
HTTP compression in PHP and Symfony apps
dunglas
2
1.7k
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe1999
0
470
From Translations to Multi Dimension Entities
alexanderschranz
2
130
【re:Growth 2024】 Aurora DSQL をちゃんと話します!
maroon1st
0
770
開発者とQAの越境で自動テストが増える開発プロセスを実現する
92thunder
1
180
Effective Signals in Angular 19+: Rules and Helpers @ngbe2024
manfredsteyer
PRO
0
140
useSyncExternalStoreを使いまくる
ssssota
6
1k
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
67
10k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
Site-Speed That Sticks
csswizardry
2
190
Docker and Python
trallard
42
3.1k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
111
49k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
6.9k
Automating Front-end Workflow
addyosmani
1366
200k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Transcript
クリーンアーキテクチャのリポジトリパターン Pythonでの実装 社内勉強会 shimakaze-soft 1
目次 1. クリーンアーキテクチャとは 2. リポジトリパターンの概要 3. インターフェースとは何か? 4. DIP(依存関係逆転の原則)について 5.
Pythonでのリポジトリパターン実装例 6. ビジネスロジックの実装 7. リポジトリパターンの利点 8. 注意点 9. 実践的なリポジトリパターンの例 10. リポジトリパターンの応用 shimakaze-soft 2
ここではドメイン駆動設計やクリーンアーキテクチャの詳細には 触れません shimakaze-soft 3
クリーンアーキテクチャとは クリーンアーキテクチャは、ソフトウェアの設計原則の一つで、ロバート・C・マーチ ン(Uncle Bob)によって提唱された。 ソフトウェアの保守性や拡張性を高めることを目的としている。 クリーンアーキテクチャ(The Clean Architecture翻訳) shimakaze-soft 4
アプリケーションの構造を 以下のように分割 Entity Use Case Interface Adapter Framework & Driver
shimakaze-soft 5
クリーンアーキテクチャとは ソフトウェアの保守性や拡張性を高めることを目的 とした設計原則 構造をエンティティ、ユースケース、インターフェ ースアダプタ、フレームワーク & ドライバに分割 し、各層の責務を明確にする 円状の構造で依存関係が弱くなり、内側の層が外側 の層に依存しないように設計する
高い抽象度のエンティティやユースケースを中心に 設計し、具体的な技術やフレームワークとは独立さ せることで、柔軟性と保守性を向上させる shimakaze-soft 6
クリーンアーキテクチャとは メリット 保守性や拡張性が高い テストが容易 各層の責務が明確になるため、コードの理解が容易になる shimakaze-soft 7
クリーンアーキテクチャとは ドメイン駆動設計との違いは何か? shimakaze-soft 8
クリーンアーキテクチャとは ドメイン駆動設計との違い ドメイン駆動設計(DDD)とクリーンアーキテクチャは、どちらもソフトウェア設計 のアプローチですが、それぞれ異なる視点や目的を持っている。 どちらもアプリケーション開発においてビジネスルールや要件を整理し、保守性や拡 張性を向上させるための設計手法ですが、それぞれ異なる視点や目的を持っている。 ドメイン駆動設計(DDD)はビジネスロジックの設計手法 クリーンアーキテクチャはアプリケーション全体の構造設計手法 両者は相互補完的な関係 shimakaze-soft
9
クリーンアーキテクチャとは ドメイン駆動設計 (DDD) ビジネスロジックやビジネスルールを中心にソフトウェアを設計するアプローチ ビジネスドメインの理解を深め、ドメインモデルを設計して実装を行う ドメインエキスパートとのコミュニケーションを重視し、ユビキタス言語を作成 することで、ドメインエキスパートが理解しやすい形でドメイン知識を共有 ビジネスドメイン - 業務領域
ドメインエキスパート - 業務知識に精通している人 ユビキタス言語 - 開発者とドメインエキスパートとの共通の言語 shimakaze-soft 10
クリーンアーキテクチャとは ドメイン駆動設計 (DDD) 戦略的設計 と 戦術的設計 の2つがある 戦略的設計 - ドメイン全体のモデル化とサブドメイン間の関連を決定する
戦術的設計 - ドメイン内の具体的なモデルを設計して実装するための技術 shimakaze-soft 11
クリーンアーキテクチャとは DDDとの違い DDDはビジネスロジックやビジネスルールを中心に設計し、ドメインエキスパー トとのコミュニケーションを重視して、アプリケーション全体の設計を行う クリーンアーキテクチャは保守性や拡張性を高めることを目的とし、アプリケー ションの構造を分割して依存関係を管理する 両者は相互補完的な関係であり、実際の開発ではDDDとクリーンアーキテクチャを組 み合わせて使うことが多くある。 shimakaze-soft 12
クリーンアーキテクチャとは それぞれの明確な違いは以下の資料が参考になる 質疑応答 ドメインモデルパターンはドメイン騒動設計と同義か? shimakaze-soft 13
クリーンアーキテクチャとは オニオンアーキテクチャとの違い オニオンアーキテクチャとクリーンアーキテクチャは、保守性やテスト容易性を向上 させるためのアーキテクチャパターンですが、いくつかの違いがある The Onion Architecture : part 1
shimakaze-soft 14
オニオンアーキテクチャと の違い ジェフリー・パルモ によって提唱された Domain Model Domain Service Application Service
Infrastructure 内側の層が外側の層に依存しないよ うに設計 shimakaze-soft 15
オニオンアーキテクチャとクリーンアーキテクチャの違い オニオンアーキテクチャはドメインモデルを中心に設計し、ドメインサービスやアプ リケーションサービスがそれに依存する形で構成 クリーンアーキテクチャはエンティティとユースケースを中心に設計し、インターフェ ースアダプタやフレームワーク&ドライバがそれに依存する形で構成 両者ともに、内側が外側には依存しないように適用し、アプリケーションの構造を円 状に設計する点は共通している。 shimakaze-soft 16
リポジトリパターンの概要 ビジネスロジックとデータアクセスの詳細を分離 shimakaze-soft 17
リポジトリパターンの概要 概要 データアクセスロジックを一箇所に集約し、データストレージへのアクセスを抽象化 する設計パターン。 データアクセスの詳細を隠蔽し、ドメインロジックやアプリケーションロジックが永 続化の詳細から独立して実装できるようになる。 RDBに対してデーターを取得、作成と更新の処理を行う REST APIに対してGET/POST/PUT/DELETEなどの shimakaze-soft
18
リポジトリパターンの概要 具体的な実装 リポジトリと呼ばれるクラスやインターフェース(または抽象クラス)を定義して、デ ータアクセスロジックをカプセル化する。 具体的な役割は、 データの取得や保存 、などを行うメソッドを提供すること。 shimakaze-soft 19
リポジトリパターンの概要 アプリケーションロジックからの呼び出す アプリケーションロジックは、データアクセスの詳細を意識することなく、リポジト リを通じてデータストレージとやりとりができる。 簡単に言えば、データアクセスにRDBが使われているのか、NoSQLなのか、テキスト ファイルの読み込みなのか、HTTPリクエストのAPI経由なのを気にしなくて済む。 shimakaze-soft 20
リポジトリパターンの概要 インターフェースとDIP リポジトリパターンは、DIP(依存関係逆転の原則) に基づいて設計されており、ドメ インロジックやアプリケーションロジックは抽象(インターフェースや抽象クラス) に依存する形で実装される。 これにより、リポジトリの具体的な実装を変更することなく、テストや拡張が容易に なります。 shimakaze-soft 21
リポジトリパターンの概要 以下の二つの言葉が出てきた。 インターフェース DIP(依存関係逆転の原則) shimakaze-soft 22
インターフェースとは何か インターフェースとは、クラスが実装すべきメソッドを定義した設計図のようなもの shimakaze-soft 23
インターフェースとは何か - (1) インターフェースを実装したクラスは、インターフェースで定義されたメソッドを必ず 実装しなければならない public interface UserRepository { void
save(User user); User find(int userId); } shimakaze-soft 24
インターフェースとは何か - (2) インターフェースを利用することで、異なるクラス間で共通の振る舞いを定義し、 統一したAPIを提供することができる クラス間の依存関係を低減し、柔軟で保守性の高いコードを実現する public interface UserRepository {
void save(User user); User find(int userId); } UserRepository インターフェースでは、 saveメソッド と findメソッド が定義されてい る。 インターフェースを実装したクラスは、これらのメソッドを必ず実装する必要がある shimakaze-soft 25
Pythonにおけるインターフェースの代替手段: 抽象クラス Pythonではインターフェースが存在しない 代わりに抽象クラスを使用する shimakaze-soft 26
Pythonにおけるインターフェースの代替手段: 抽象クラス - (1) 抽象クラスを継承した具象クラスでは、抽象メソッドを必ず実装しなければならな い。 抽象クラスを用いることで、インターフェースと同様のことができる。 shimakaze-soft 27
Pythonにおけるインターフェースの代替手段: 抽象クラス - (2) abc モジュールの ABC クラスを継承し、 @abstractmethodデコレータ を使って抽象メソ
ッドを定義する。 from abc import ABC, abstractmethod # Pythonでの抽象クラスの例 class UserRepository(ABC): @abstractmethod def save(self, user: User): raise NotImplementedError() @abstractmethod def find(self, user_id: int) -> User: raise NotImplementedError() shimakaze-soft 28
DIP(依存関係逆転の原則)について DIP(Dependency Inversion Principle) とは、ソフトウェア設計において、モジュー ル間の依存関係を逆転させることで、保守性や拡張性を向上させる原則(SOLID原則 の1つ) 上位モジュールは下位モジュールに依存すべきではなく、両者は抽象に依存すべき である 抽象化に依存すべきで、詳細に依存すべきではない
リポジトリパターンでは、DIPを実現するためにインターフェースを使用 Python で Dependency Injection(DI) をやるには? shimakaze-soft 29
DIP(依存関係逆転の原則)について 上位モジュールは下位モジュールに依存すべきではなく、 両者は抽象に依存すべ き である ここでいう下位と上位は以下のような意味になる。 下位 ≒ 標準ライブラリやOS、プロトコル、HW側などの技術的な低水準のレイヤー 上位
≒ 技術的な詳細を実装しないビジネスロジックなどの下位を呼び出す側 shimakaze-soft 30
DIP(依存関係逆転の原則)について 具体的な実装から抽象(インターフェースや抽象クラス)に依存する形になり、 モジュ ールの結合度を低く保つ ことができる。 from abc import ABC, abstractmethod
# UserRepositoryの抽象クラス class UserRepository(ABC): @abstractmethod def get_user(self, user_id): raise NotImplementedError() # GetUserServiceクラス: UserRepositoryに依存 class GetUserService: def __init__(self, user_repository: UserRepository): self.user_repository = user_repository def execute(self, user_id): return self.user_repository.get_user(user_id) shimakaze-soft 31
DIP(依存関係逆転の原則)について 抽象化に依存すべきで、詳細に依存すべきではない ここでいう抽象化と詳細の意味は以下になる。 抽象化 = インターフェースや抽象クラスのこと 詳細 = 実際の処理が書かれているクラス shimakaze-soft
32
DIP(依存関係逆転の原則)について 具体的な実装(詳細)が抽象に従う形で設計されることで、実装の変更や追加が容易 になり、拡張性が向上する。 from abc import ABC, abstractmethod # UserRepositoryの抽象クラス
class UserRepository(ABC): @abstractmethod def get_user(self, user_id: int): raise NotImplementedError() shimakaze-soft 33
DIP(依存関係逆転の原則)について 具体的な実装(詳細)が抽象に従う形で設計されることで、実装の変更や追加が容易 になり、拡張性が向上する。 # UserRepositoryの実装クラス1: SQLiteを使用 class SqliteUserRepository(UserRepository): def get_user(self,
user_id: int): """""" # SQLiteからユーザーを取得する処理 # UserRepositoryの実装クラス2: NoSQLを使用 class NoSQLUserRepository(UserRepository): def get_user(self, user_id: int): """""" # NoSQLからユーザーを取得する処理 shimakaze-soft 34
DIP(依存関係逆転の原則)について DIPを適用することで、以下のような利点が得られる。 柔軟性: 抽象に依存することで、モジュール間の結合度が低くなり、コードの変更 や追加が容易になります。 再利用性: 抽象に依存することで、具体的な実装を変更することなく、他の部分で 再利用が容易になります。 テスタビリティ: 抽象に依存することで、テスト時に具体的な実装をモックやスタ
ブに差し替えることが容易になります。 DIPを適用することで、上記の利点を享受しながらリポジトリパターンの実装を行うこ とができる。 shimakaze-soft 35
Pythonでのリポジトリパターン実装例 リポジトリパターンを実装する際には、抽象クラスを使ってインターフェースを定義す る。 shimakaze-soft 36
Pythonでのリポジトリパターン実装例 抽象クラスを使ってリポジトリのインターフェースを定義する。 このクラスを継承した具象クラスは、 find_by_id と save メソッドが必ず実装されて いることがわかる。 from abc
import ABC, abstractmethod class UserRepository(ABC): @abstractmethod def find_by_id(self, user_id: int): pass @abstractmethod def save(self, user: User): pass shimakaze-soft 37
Pythonでのリポジトリパターン実装例 具体的なリポジトリの実装を行う。 import sqlite3 from typing import Optional # ここでは、データベースとしてSQLiteを使用している。
class SqliteUserRepository(UserRepository): def __init__(self, db_path: str): self.conn = sqlite3.connect(db_path) def find_by_id(self, user_id: int) -> Optional[User]: cursor = self.conn.cursor() cursor.execute("SELECT id, name FROM users WHERE id=?", (user_id,)) result = cursor.fetchone() if result: return User(result[0], result[1]) else: return None def save(self, user: User): cursor = self.conn.cursor() cursor.execute("INSERT OR REPLACE INTO users (id, name) VALUES (?, ?)", (user.id, user.name)) self.conn.commit() shimakaze-soft 38
Pythonでのリポジトリパターン実装例 このように、リポジトリのインターフェース(抽象)と具体的な実装を分けること で、データアクセスの抽象化が実現できる。 また、別のデータストレージへの対応やキャッシュの導入など、リポジトリパターン の応用が容易になる。 shimakaze-soft 39
ビジネスロジックの実装 ビジネスロジックは、アプリケーションが解決すべきビジネス上の問題や要件を実現 するためのコード。 リポジトリパターンを適用することで、ビジネスロジックはデータアクセスの詳細か ら切り離され、独立して実装することができる。 shimakaze-soft 40
ビジネスロジックの実装 以下のコードはユーザーの登録と更新に関するビジネスロジックが実装されている。 class UserService: def __init__(self, user_repository: UserRepository): self.user_repository =
user_repository def register_user(self, user: User): existing_user = self.user_repository.find_by_id(user.id) if existing_user: raise ValueError("ユーザーIDが既に存在しています") self.user_repository.save(user) def update_user(self, user: User): existing_user = self.user_repository.find_by_id(user.id) if not existing_user: raise ValueError("ユーザーが見つかりません") self.user_repository.save(user) shimakaze-soft 41
ビジネスロジックの実装 UserServiceはリポジトリインターフェースを使用してデータアクセスを行っており、 データストレージの具体的な実装に依存していない。 class UserService: def __init__(self, user_repository: UserRepository): self.user_repository
= user_repository def register_user(self, user: User): existing_user = self.user_repository.find_by_id(user.id) if existing_user: raise ValueError("ユーザーIDが既に存在しています") self.user_repository.save(user) def update_user(self, user: User): existing_user = self.user_repository.find_by_id(user.id) if not existing_user: raise ValueError("ユーザーが見つかりません") self.user_repository.save(user) shimakaze-soft 42
ビジネスロジックの実装 先ほど作成した SqliteUserRepository という具象クラスを UserService のインスス タンス生成時に引数として渡す。 UserService はユーザーの作成・取得のためのデータストレージへのアクセス方法の 詳細については知らない。
# user_repository変数の引数には、抽象クラスでありインターフェースのUserRepositoryを使用 user_repository: UserRepository = SqliteUserRepository() service: UserService = UserService(user_repository=user_repository) これにより、ビジネスロジックはデーターアクセスとは独立してテストや保守が行い やすくなる。 shimakaze-soft 43
リポジトリパターンの利点 永続化の詳細を隠蔽 データアクセスの統一 テストの容易性 拡張性 shimakaze-soft 44
リポジトリパターンの利点 - (1) 永続化の詳細を隠蔽 データアクセスやデータストレージの具体的な実装の詳細を隠蔽することができる。 これにより、ドメインロジックやアプリケーションロジックが永続化の詳細から独立 して実装できるようになる。 データアクセスの統一 データアクセスの方法を統一し、コードの整理やメンテナンスが容易になる。 また、データアクセスの変更が必要な場合でも、リポジトリの実装を変更するだけで
済むため、影響範囲を最小限に抑えられる。 shimakaze-soft 45
リポジトリパターンの利点 - (2) テストの容易性 リポジトリインターフェース(または抽象クラス)を使って実装を行うことで、テスト 時にリポジトリのモックやスタブを簡単に差し替えることができる。 これにより、単体テストや結合テストが容易になり、コードの品質を向上させること ができる。 拡張性 データアクセスの実装を交換することが容易になる。
例えば、データストレージの変更やデータアクセスの最適化を行いたい場合、リポジ トリの実装を追加・変更するだけで対応できる。 これにより、アプリケーションの拡張性が向上する。 shimakaze-soft 46
注意点 適切な抽象化 リポジトリの責務 テスト パフォーマンス 技術的な制約 shimakaze-soft 47
注意点 適切な抽象化 リポジトリの抽象化が過剰だと、コードが複雑になりメンテナンスが困難になる可能 性がある。 逆に、抽象化が不十分だと、リポジトリパターンの利点が十分に享受できない。 適切な抽象化を心掛けることが重要。 リポジトリの責務 リポジトリはデータアクセスのみに関心を持つべきであり、ドメインロジックやアプ リケーションロジックを持たないように設計することが重要。 リポジトリが他の責務を持つと、コードの整理や再利用が難しくなる。
shimakaze-soft 48
注意点 テスト リポジトリパターンを適用することで、テスタビリティが向上しますが、実際にテス トコードを書くことも重要になる。 リポジトリのテストを十分に行うことで、データアクセスに関するバグを早期に発見 でき、品質向上に寄与できる。 パフォーマンス リポジトリパターンを適用することで、データアクセスの抽象化が可能ですが、パフ ォーマンスへの影響も考慮することが重要。 例えば、不要なデータ取得や無駄なクエリ発行などがないように、リポジトリの実装
を適切に行うことが求められる。 shimakaze-soft 49
注意点 技術的な制約 使用しているプログラミング言語やフレームワークによっては、リポジトリパターンの 実装方法や適用範囲が制限されることがある。 使用している技術の制約を理解し、適切なリポジトリパターンの適用を行うことが重 要になる。 shimakaze-soft 50
実践的なリポジトリパターンの例 実践的なリポジトリパターンの例として、キャッシュ機能を追加したリポジトリを考 えます。 shimakaze-soft 51
実践的なリポジトリパターンの例 - (1) キャッシュ機能を持つリポジトリのインターフェースを定義する。 from abc import ABC, abstractmethod from
typing import Optional class CachingUserRepository(ABC): @abstractmethod def find_by_id(self, user_id: int) -> Optional[User]: pass @abstractmethod def save(self, user: User): pass @abstractmethod def clear_cache(self): pass shimakaze-soft 52
実践的なリポジトリパターンの例 - (2) キャッシュ機能を持つリポジトリの具象クラスの実装を行う。 class InMemoryCachingUserRepository(CachingUserRepository): def __init__(self, user_repository: UserRepository):
self.user_repository = user_repository self.cache = {} def find_by_id(self, user_id: int) -> Optional[User]: if user_id in self.cache: return self.cache[user_id] else: user = self.user_repository.find_by_id(user_id) if user: self.cache[user_id] = user return user def save(self, user: User): self.user_repository.save(user) self.cache[user.id] = user def clear_cache(self): self.cache.clear() shimakaze-soft 53
実践的なリポジトリパターンの例 - (3) リポジトリパターンを適用することで、データアクセスの抽象化が実現でき、キ ャッシュ機能の追加や切り替えが容易になる。 他のデータストレージへの対応や、異なるキャッシュ戦略の導入など、様々な実 践的なリポジトリパターンの例が考えられる。 リポジトリパターンを活用することで、データアクセスの柔軟性や拡張性が向上 し、アプリケーションの品質向上に寄与する。 shimakaze-soft
54
リポジトリパターンの応用 異なるデータストレージへの対応 キャッシュの導入 検索機能の追加 トランザクションの管理 shimakaze-soft 55
リポジトリパターンの応用 - (1) 異なるデータストレージへの対応 データストレージの種類を容易に切り替えることができる。 例えば、データベースやファイル、APIなど、異なるデータストレージへのアクセスを 同じインターフェース(または抽象クラス)で扱うことができる。 キャッシュの導入 リポジトリパターンを使うことで、キャッシュの導入が容易になる。 リポジトリの実装を変更することで、データの取得や保存の際にキャッシュを利用す
ることができ、パフォーマンスの向上が期待できる。 shimakaze-soft 56
リポジトリパターンの応用 - (2) 検索機能の追加 リポジトリパターンを用いることで、検索機能を簡単に追加することができる。 リポジトリに検索条件を受け取るメソッドを追加することで、データストレージから 条件に合ったデータを取得できる。 トランザクションの管理 リポジトリパターンを使うことで、データアクセスのトランザクション管理が容易に なる。
リポジトリにトランザクション開始やコミット、ロールバックのメソッドを追加する ことで、データストレージのトランザクションを一元管理できる。 shimakaze-soft 57
まとめ クリーンアーキテクチャは、アプリケーション全体の構造を柔軟にする設計手法 リポジトリパターンは、データアクセスの詳細を隠蔽し、ビジネスロジックとデ ータストレージを分離 データソースが変更されても、ビジネスロジックに影響を与えない DIPを実現するためにインターフェース(Pythonでは抽象クラス)を使用 shimakaze-soft 58