web-dev-qa-db-ja.com

Pythonでのリポジトリパターンの実装?

主に好奇心から、永続ロジックをドメインロジックから分離するリポジトリパターンのPythonフレームワークまたは例を探しています。

「リポジトリパターン」という名前は投稿に表示されます " ドメインと永続性ロジックをキュレーターで解き明かす "(ルビー)、アイデアは「ドメイン駆動設計」の セクション から来ています本と マーティンファウラー 。モデルクラスには永続ロジックが含まれていませんが、アプリは、インスタンスがモデルインスタンスのメモリ内コレクションのように機能するリポジトリサブクラスを宣言します。各リポジトリは、SQL(さまざまなスキーマ規則)、Riakまたはその他のnoSQL、メモリ(キャッシュ用)など、さまざまな方法でモデルを永続化します。フレームワークの規則は、リポジトリサブクラスが通常最小限のコードを必要とすることを意味します。SQLRepositoryの「WidgetRepository」サブクラスを宣言するだけで、モデルウィジェットを「ウィジェット」という名前のDBテーブルに永続化し、列をウィジェット属性に一致させるコレクションが提供されます。

他のパターンとの違い:

アクティブレコードパターン:たとえば、Django ORM。アプリケーションは、ドメインロジックといくつかのメタデータを使用してモデルクラスのみを定義します永続性のために。ORMはモデルクラスに永続性ロジックを追加します。これにより、ドメインと永続性が1つのクラスに混在します(投稿によると望ましくありません)。

@marcinのおかげで、Active Recordが多様なバックエンドと.save(using = "other_database")関数をサポートしている場合、リポジトリパターンのマルチバックエンドの利点が得られることがわかりました。

したがって、ある意味で、リポジトリパターンは、永続性ロジックが別のクラスに移動されたアクティブレコードと同じです。

データマッパーパターン:たとえば、SQLAlchemyのクラシックマッピング。アプリは、データベーステーブルの追加クラス、およびモデルからテーブルへのデータマッパーを定義します。したがって、モデルインスタンスは複数の方法でテーブルにマッピングできます。レガシースキーマをサポートします。 SQLAlchemyが非SQLストレージへのマッパーを提供するとは思わないでください。

24
Graham

James Dennis ' DictShieldプロジェクト をよく見てみてください。

「DictShieldはデータベースにとらわれないモデリングシステムです。データを簡単にモデリング、検証、再形成する方法を提供します。すべて特定のデータベースを必要としません。」

3
Jordan Dimov

私の頭から:

2つのドメイン例UserAnimal、基本ストレージクラスStore、および2つの特殊なストレージクラスUserStoreAnimalStoreを定義します。コンテキストマネージャーを使用すると、db接続が閉じます(簡単にするために、この例ではsqliteを使用します)。

import sqlite3

def get_connection():
    return sqlite3.connect('test.sqlite')

class StoreException(Exception):
    def __init__(self, message, *errors):
        Exception.__init__(self, message)
        self.errors = errors


# domains

class User():
    def __init__(self, name):
        self.name = name


class Animal():
    def __init__(self, name):
        self.name = name


# base store class
class Store():
    def __init__(self):
        try:
            self.conn = get_connection()
        except Exception as e:
            raise StoreException(*e.args, **e.kwargs)
        self._complete = False

    def __enter__(self):
        return self

    def __exit__(self, type_, value, traceback):
        # can test for type and handle different situations
        self.close()

    def complete(self):
        self._complete = True

    def close(self):
        if self.conn:
            try:
                if self._complete:
                    self.conn.commit()
                else:
                    self.conn.rollback()
            except Exception as e:
                raise StoreException(*e.args)
            finally:
                try:
                    self.conn.close()
                except Exception as e:
                    raise StoreException(*e.args)


# store for User obects
class UserStore(Store):

    def add_user(self, user):
        try:
            c = self.conn.cursor()
            # this needs an appropriate table
            c.execute('INSERT INTO user (name) VALUES(?)', (user.name,))
        except Exception as e:
            raise StoreException('error storing user')


# store for Animal obects
class AnimalStore(Store):

    def add_animal(self, animal):
        try:
            c = self.conn.cursor()
            # this needs an appropriate table
            c.execute('INSERT INTO animal (name) VALUES(?)', (animal.name,))
        except Exception as e:
            raise StoreException('error storing animal')

# do something
try:
    with UserStore() as user_store:
        user_store.add_user(User('John'))
        user_store.complete()

    with AnimalStore() as animal_store:
        animal_store.add_animal(Animal('Dog'))
        animal_store.add_animal(Animal('Pig'))
        animal_store.add_animal(Animal('Cat'))
        animal_store.add_animal(Animal('Wolf'))
        animal_store.complete()
except StoreException as e:
    # exception handling here
    print(e)
17
laurasia