この記事では、Alembic と SQLAlchemy を使用する際にプロジェクトを整理し、データベースのメンテナンスを簡素化し、よくある落とし穴を防ぐのに役立ついくつかのベスト プラクティスを簡単に説明します。これらのテクニックにより、私は何度もトラブルから救われました。ここで取り上げる内容は次のとおりです:
SQLAlchemy を使用すると、移行の生成時にすべてのテーブルと制約に自動的に適用される命名規則を設定できます。これにより、インデックス、外部キー、その他の制約に手動で名前を付ける必要がなくなり、データベース構造が予測可能で一貫性のあるものになります。
これを新しいプロジェクトで設定するには、Alembic が希望の命名形式を自動的に使用するように、基本クラスに規則を追加します。ほとんどの場合にうまく機能する規則の例を次に示します。
from sqlalchemy import MetaData from sqlalchemy.orm import DeclarativeBase convention = { 'all_column_names': lambda constraint, table: '_'.join( [column.name for column in constraint.columns.values()] ), 'ix': 'ix__%(table_name)s__%(all_column_names)s', 'uq': 'uq__%(table_name)s__%(all_column_names)s', 'ck': 'ck__%(table_name)s__%(constraint_name)s', 'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s', 'pk': 'pk__%(table_name)s', } class BaseModel(DeclarativeBase): metadata = MetaData(naming_convention=convention)
Alembic 移行ファイル名は通常、リビジョン タグで始まるため、ディレクトリ内の移行順序がランダムに見える可能性があります。場合によっては、時系列に並べ替えておくと便利です。
Alembic では、file_template 設定を使用して、alembic.ini ファイル内の移行ファイル名テンプレートをカスタマイズできます。移行を整理しておくための 2 つの便利な命名形式を次に示します。
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
ファイル名に日付または Unix タイムスタンプを使用すると、移行が整理され、ナビゲーションが容易になります。私は Unix タイムスタンプを使用することを好みます。次のセクションで例を示します。
チームで作業している人にとって、属性にコメントすることは良い習慣です。 SQLAlchemy モデルでは、docstring に依存するのではなく、列やテーブルにコメントを直接追加することを検討してください。こうすることで、コードとデータベースの両方でコメントを利用できるようになり、DBA やアナリストがテーブルとフィールドの目的を理解しやすくなります。
class Event(BaseModel): __table_args__ = {'comment': 'System (service) event'} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, comment='Event ID - PK', ) service_id: Mapped[int] = mapped_column( sa.Integer, sa.ForeignKey( f'{IntegrationServiceModel.__tablename__}.id', ondelete='CASCADE', ), nullable=False, comment='FK to integration service that owns the event', ) name: Mapped[str] = mapped_column( sa.String(256), nullable=False, comment='Event name' )
ファイル システム内で見つけやすくするために、移行にコメントを追加することも役立ちます。コメントは -m
from sqlalchemy import MetaData from sqlalchemy.orm import DeclarativeBase convention = { 'all_column_names': lambda constraint, table: '_'.join( [column.name for column in constraint.columns.values()] ), 'ix': 'ix__%(table_name)s__%(all_column_names)s', 'uq': 'uq__%(table_name)s__%(all_column_names)s', 'ck': 'ck__%(table_name)s__%(constraint_name)s', 'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s', 'pk': 'pk__%(table_name)s', } class BaseModel(DeclarativeBase): metadata = MetaData(naming_convention=convention)
モデルは、あるテーブルから別のテーブルにデータを転送したり、列の値を変更したりするなどのデータ操作によく使用されます。ただし、移行で ORM モデルを使用すると、移行の作成後にモデルが変更された場合に問題が発生する可能性があります。このような場合、データベース スキーマが現在のモデルと一致しなくなる可能性があるため、古いモデルに基づく移行は実行時に中断されます。
コードの変更に関係なく正しく実行できるように、移行は静的であり、モデルの現在の状態から独立している必要があります。以下は、データ操作にモデルの使用を避ける 2 つの方法です。
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
階段テストでは、アップグレード/ダウングレード移行を段階的にテストして、移行チェーン全体が正しく機能することを確認します。これにより、各移行で新しいデータベースを最初から作成し、問題なくダウングレードできることが保証されます。このテストを CI に追加することは、時間とフラストレーションを節約し、チームにとって非常に貴重です。
テストをプロジェクトに統合することは、簡単かつ迅速に行うことができます。コード例はこのリポジトリにあります。また、役立つ可能性のある他の貴重な移行テストも含まれています。
移行を実行するための別のサービス。これは移行を実行する方法の 1 つにすぎません。ローカルで開発する場合、または開発に似た環境で開発する場合、この方法は適しています。ここで関連する条件付き depend_on 機能について思い出していただきたいと思います。 Alembic でアプリケーション イメージを取得し、別のコンテナで実行します。データベースがリクエストを処理する準備ができた場合にのみ移行を開始するという条件 (service_healthy) を備えたデータベースへの依存関係を追加します。さらに、アプリケーションに条件付き depend_on (service_completed_ successly) を追加して、移行が正常に完了した後にのみアプリケーションが開始されるようにすることができます。
class Event(BaseModel): __table_args__ = {'comment': 'System (service) event'} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, comment='Event ID - PK', ) service_id: Mapped[int] = mapped_column( sa.Integer, sa.ForeignKey( f'{IntegrationServiceModel.__tablename__}.id', ondelete='CASCADE', ), nullable=False, comment='FK to integration service that owns the event', ) name: Mapped[str] = mapped_column( sa.String(256), nullable=False, comment='Event name' )
depends_on 条件により、データベースの準備が完全に整った後にのみ移行が実行され、移行の完了後にアプリケーションが開始されることが保証されます。
これは明らかな点かもしれませんが、見落とさないことが重要です。ミックスインを使用すると、コードの重複を避ける便利な方法です。ミックスインは、頻繁に使用されるフィールドとメソッドを含むクラスであり、必要に応じてモデルに統合できます。たとえば、レコードの作成時刻と更新時刻を追跡するには、created_at フィールドと updated_at フィールドが必要になることがよくあります。 UUID に基づく ID を使用して主キーを標準化することも役立つ場合があります。これらはすべてミックスインにカプセル化できます。
from sqlalchemy import MetaData from sqlalchemy.orm import DeclarativeBase convention = { 'all_column_names': lambda constraint, table: '_'.join( [column.name for column in constraint.columns.values()] ), 'ix': 'ix__%(table_name)s__%(all_column_names)s', 'uq': 'uq__%(table_name)s__%(all_column_names)s', 'ck': 'ck__%(table_name)s__%(constraint_name)s', 'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s', 'pk': 'pk__%(table_name)s', } class BaseModel(DeclarativeBase): metadata = MetaData(naming_convention=convention)
これらのミックスインを追加すると、必要に応じてモデルに UUID ID とタイムスタンプを含めることができます。
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
移行の処理は難しい場合がありますが、これらの簡単な実践に従うことで、プロジェクトを適切に組織化して管理しやすくすることができます。命名規則、日付の並べ替え、コメント、テストにより、混乱を避け、間違いを防ぐことができました。この記事がお役に立てば幸いです。コメント欄で独自の移行ヒントを自由に共有してください。
以上がAlembic と SQLAlchemy のベスト プラクティスの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。