Heim > Artikel > Backend-Entwicklung > Best Practices für Alembic und SQLAlchemy
In diesem Artikel gehe ich kurz auf einige Best Practices ein, die dabei helfen, Projekte organisiert zu halten, die Datenbankwartung zu vereinfachen und häufige Fallstricke zu vermeiden bei der Arbeit mit Alembic und SQLAlchemy. Diese Techniken haben mich mehr als einmal vor Ärger bewahrt. Folgendes werden wir behandeln:
SQLAlchemy ermöglicht Ihnen die Einrichtung einer Namenskonvention, die beim Generieren von Migrationen automatisch auf alle Tabellen und Einschränkungen angewendet wird. Dies erspart Ihnen die manuelle Benennung von Indizes, Fremdschlüsseln und anderen Einschränkungen, wodurch die Datenbankstruktur vorhersehbar und konsistent wird.
Um dies in einem neuen Projekt einzurichten, fügen Sie der Basisklasse eine Konvention hinzu, damit Alembic automatisch das gewünschte Benennungsformat verwendet. Hier ist ein Beispiel für eine Konvention, die in den meisten Fällen gut funktioniert:
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-Migrationsdateinamen beginnen normalerweise mit einem Revisions-Tag, wodurch die Reihenfolge der Migrationen im Verzeichnis zufällig erscheinen kann. Manchmal ist es nützlich, sie chronologisch zu sortieren.
Alembic ermöglicht das Anpassen der Vorlage für den Migrationsdateinamen in der Datei alembic.ini mit der Einstellung file_template. Hier sind zwei praktische Namensformate, um Migrationen organisiert zu halten:
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
Durch die Verwendung von Datums- oder Unix-Zeitstempeln in Dateinamen bleiben Migrationen organisiert und die Navigation wird einfacher. Ich bevorzuge die Verwendung von Unix-Zeitstempeln, und ein Beispiel wird im nächsten Abschnitt bereitgestellt.
Für diejenigen, die in einem Team arbeiten, ist das Kommentieren von Attributen eine gute Vorgehensweise. Erwägen Sie bei SQLAlchemy-Modellen das direkte Hinzufügen von Kommentaren zu Spalten und Tabellen, anstatt sich auf Dokumentzeichenfolgen zu verlassen. Auf diese Weise sind Kommentare sowohl im Code als auch in der Datenbank verfügbar, was es DBAs oder Analysten erleichtert, Tabellen- und Feldzwecke zu verstehen.
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' )
Es ist auch hilfreich, Kommentare zu Migrationen hinzuzufügen, damit sie im Dateisystem leichter gefunden werden können. Ein Kommentar kann mit -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)
Modelle werden häufig für Datenmanipulationen verwendet, beispielsweise zum Übertragen von Daten von einer Tabelle in eine andere oder zum Ändern von Spaltenwerten. Allerdings kann die Verwendung von ORM-Modellen bei Migrationen zu Problemen führen, wenn sich das Modell nach der Erstellung der Migration ändert. In solchen Fällen wird eine Migration auf Basis des alten Modells bei der Ausführung scheitern, da das Datenbankschema möglicherweise nicht mehr mit dem aktuellen Modell übereinstimmt.
Migrationen sollten statisch und unabhängig vom aktuellen Zustand der Modelle erfolgen, um eine korrekte Ausführung unabhängig von Codeänderungen sicherzustellen. Im Folgenden finden Sie zwei Möglichkeiten, die Verwendung von Modellen für Datenmanipulationen zu vermeiden.
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
Der Stairway-Test beinhaltet das schrittweise Testen von Upgrade-/Downgrade-Migrationen, um sicherzustellen, dass die gesamte Migrationskette korrekt funktioniert. Dadurch wird sichergestellt, dass bei jeder Migration erfolgreich eine neue Datenbank von Grund auf erstellt und ein Downgrade ohne Probleme durchgeführt werden kann. Das Hinzufügen dieses Tests zu CI ist für Teams von unschätzbarem Wert und spart Zeit und Frustration.
Die Integration des Tests in Ihr Projekt ist einfach und schnell möglich. Ein Codebeispiel finden Sie in diesem Repository. Es enthält auch andere wertvolle Migrationstests, die hilfreich sein können.
Ein separater Dienst zur Durchführung von Migrationen. Dies ist nur eine Möglichkeit, Migrationen durchzuführen. Bei der Entwicklung vor Ort oder in entwicklungsähnlichen Umgebungen passt diese Methode gut. Ich möchte Sie an die Funktion „Conditional depend_on“ erinnern, die hier relevant ist. Wir nehmen das Anwendungsimage mit Alembic auf und führen es in einem separaten Container aus. Wir fügen eine Abhängigkeit von der Datenbank mit der Bedingung hinzu, dass Migrationen nur beginnen, wenn die Datenbank bereit ist, Anfragen zu verarbeiten (service_healthy). Darüber hinaus kann für die Anwendung eine Bedingung depend_on (service_completed_successfully) hinzugefügt werden, um sicherzustellen, dass sie erst gestartet wird, nachdem die Migrationen erfolgreich abgeschlossen wurden.
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' )
Die depend_on-Bedingung stellt sicher, dass Migrationen erst ausgeführt werden, wenn die Datenbank vollständig bereit ist, und dass die Anwendung nach Abschluss der Migrationen gestartet wird.
Obwohl dies ein offensichtlicher Punkt sein mag, ist es wichtig, ihn nicht zu übersehen. Die Verwendung von Mixins ist eine praktische Möglichkeit, Codeduplizierung zu vermeiden. Mixins sind Klassen, die häufig verwendete Felder und Methoden enthalten, die in beliebige Modelle integriert werden können, wo sie benötigt werden. Beispielsweise benötigen wir häufig die Felder „created_at“ und „update_at“, um die Erstellungs- und Aktualisierungszeiten von Datensätzen zu verfolgen. Es kann auch nützlich sein, eine auf UUID basierende ID zu verwenden, um Primärschlüssel zu standardisieren. All dies kann in Mixins gekapselt werden.
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)
Durch das Hinzufügen dieser Mixins können wir bei Bedarf UUID-IDs und Zeitstempel in jedes Modell einbinden:
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
Der Umgang mit Migrationen kann eine Herausforderung sein, aber die Befolgung dieser einfachen Vorgehensweisen trägt dazu bei, dass Projekte gut organisiert und überschaubar bleiben. Namenskonventionen, Datumssortierung, Kommentare und Tests haben mich vor dem Chaos bewahrt und dazu beigetragen, Fehler zu vermeiden. Ich hoffe, dieser Artikel erweist sich als hilfreich – teilen Sie uns gerne Ihre eigenen Migrationstipps in den Kommentaren mit!
Das obige ist der detaillierte Inhalt vonBest Practices für Alembic und SQLAlchemy. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!