Upgrade to Pro — share decks privately, control downloads, hide ads and more …

クリーンアーキテクチャのリポジトリパターン - Pythonでの実装

クリーンアーキテクチャのリポジトリパターン - Pythonでの実装

クリーンアーキテクチャのリポジトリパターンについて解説する社内勉強会資料。
Pythonを使ってのサンプルコードも作成。

shimakaze-git

April 19, 2023
Tweet

More Decks by shimakaze-git

Other Decks in Programming

Transcript

  1. 目次 1. クリーンアーキテクチャとは 2. リポジトリパターンの概要 3. インターフェースとは何か? 4. DIP(依存関係逆転の原則)について 5.

    Pythonでのリポジトリパターン実装例 6. ビジネスロジックの実装 7. リポジトリパターンの利点 8. 注意点 9. 実践的なリポジトリパターンの例 10. リポジトリパターンの応用 shimakaze-soft 2
  2. インターフェースとは何か - (2) インターフェースを利用することで、異なるクラス間で共通の振る舞いを定義し、 統一したAPIを提供することができる クラス間の依存関係を低減し、柔軟で保守性の高いコードを実現する public interface UserRepository {

    void save(User user); User find(int userId); } UserRepository インターフェースでは、 saveメソッド と findメソッド が定義されてい る。 インターフェースを実装したクラスは、これらのメソッドを必ず実装する必要がある shimakaze-soft 25
  3. 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
  4. 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
  5. 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
  6. 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
  7. ビジネスロジックの実装 以下のコードはユーザーの登録と更新に関するビジネスロジックが実装されている。 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
  8. ビジネスロジックの実装 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
  9. ビジネスロジックの実装 先ほど作成した SqliteUserRepository という具象クラスを UserService のインスス タンス生成時に引数として渡す。 UserService はユーザーの作成・取得のためのデータストレージへのアクセス方法の 詳細については知らない。

    # user_repository変数の引数には、抽象クラスでありインターフェースのUserRepositoryを使用 user_repository: UserRepository = SqliteUserRepository() service: UserService = UserService(user_repository=user_repository) これにより、ビジネスロジックはデーターアクセスとは独立してテストや保守が行い やすくなる。 shimakaze-soft 43
  10. 実践的なリポジトリパターンの例 - (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
  11. 実践的なリポジトリパターンの例 - (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