Migrationsanleitung¶
Diese Anleitung hilft Ihnen bei der Migration zu Dynamite von anderen ORMs oder beim Upgrade zwischen Versionen.
Inhaltsverzeichnis¶
- Migration von anderen ORMs
- Versions-Upgrade-Anleitung
- Schema-Migration
- Daten-Migration
- Migrationen testen
- Produktions-Deployment
Migration von anderen ORMs¶
Von Sequelize (SQL)¶
Sequelize ist für relationale Datenbanken konzipiert. Hier erfahren Sie, wie Sie sich an das NoSQL-Paradigma von DynamoDB anpassen können.
Sequelize-Modell:
// Sequelize (PostgreSQL/MySQL)
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
name: DataTypes.STRING,
created_at: DataTypes.DATE
});
const Order = sequelize.define('Order', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
total: DataTypes.DECIMAL,
status: DataTypes.STRING
});
// Abfrage
const users = await User.findAll({
where: {
email: { [Op.like]: '%@example.com' }
},
include: [Order]
});
Dynamite-Äquivalent:
// Dynamite (DynamoDB)
import {
Table, PrimaryKey, Default, NotNull, CreatedAt,
HasMany, BelongsTo, CreationOptional, NonAttribute
} from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>; // UUID statt Auto-Increment verwenden
@NotNull()
declare email: string;
declare name: string;
@CreatedAt()
declare created_at: CreationOptional<string>;
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<Order[]>;
}
class Order extends Table<Order> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
@NotNull()
declare user_id: string;
declare total: number;
declare status: string;
@BelongsTo(() => User, "user_id")
declare user: NonAttribute<User | null>;
}
// Abfrage - anderer Ansatz für NoSQL
const users = await User.where({ email: "user@example.com" }, {
include: { orders: true }
});
// Für Pattern-Matching: abrufen und filtern
const all_users = await User.where({});
const filtered = all_users.filter(u => u.email.endsWith("@example.com"));
Wichtige Unterschiede:
| Konzept | Sequelize (SQL) | Dynamite (DynamoDB) |
|---|---|---|
| Primärschlüssel | Auto-Increment Integer | UUID oder zusammengesetzter Schlüssel |
| Abfragen | Komplexe WHERE-Klauseln | Schlüsselbedingungen + Filter |
| Joins | Native JOIN-Unterstützung | Manuelle Beziehungsladung |
| Transaktionen | ACID-Transaktionen | Begrenzte Transaktionen (25 Items) |
| Schema | Starres Schema | Flexibles Schema |
Migrationsstrategie:
// 1. Daten aus SQL exportieren
import { Sequelize } from 'sequelize';
async function ExportFromSQL(): Promise<void> {
const sequelize = new Sequelize('postgresql://...');
const users = await sequelize.models.User.findAll();
const export_data = users.map(user => ({
id: `user-${user.id}`, // ID-Format transformieren
email: user.email,
name: user.name,
created_at: user.created_at.toISOString()
}));
// In Datei speichern
await fs.writeFile(
'users_export.json',
JSON.stringify(export_data, null, 2)
);
}
// 2. In DynamoDB importieren
async function ImportToDynamoDB(): Promise<void> {
const data = JSON.parse(
await fs.readFile('users_export.json', 'utf-8')
);
// In Batches importieren
for (const item of data) {
await User.create(item);
}
console.log(`${data.length} Benutzer importiert`);
}
Von TypeORM¶
TypeORM unterstützt mehrere Datenbanken einschließlich DynamoDB (grundlegende Unterstützung).
TypeORM-Entity:
// TypeORM
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm";
@Entity()
class User {
@PrimaryGeneratedColumn()
id!: number;
@Column({ unique: true })
email!: string;
@Column()
name!: string;
@OneToMany(() => Order, order => order.user)
orders!: Order[];
@CreateDateColumn()
created_at!: Date;
@UpdateDateColumn()
updated_at!: Date;
}
@Entity()
class Order {
@PrimaryGeneratedColumn()
id!: number;
@ManyToOne(() => User, user => user.orders)
user!: User;
@Column('decimal')
total!: number;
}
// Abfrage
const users = await userRepository.find({
where: { name: Like('%John%') },
relations: ['orders']
});
Dynamite-Äquivalent:
// Dynamite
import {
Table, PrimaryKey, Default, NotNull, CreatedAt, UpdatedAt,
HasMany, BelongsTo, CreationOptional, NonAttribute
} from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
@NotNull()
declare email: string;
declare name: string;
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<Order[]>;
@CreatedAt()
declare created_at: CreationOptional<string>;
@UpdatedAt()
declare updated_at: CreationOptional<string>;
}
class Order extends Table<Order> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
@NotNull()
declare user_id: string;
declare total: number;
@BelongsTo(() => User, "user_id")
declare user: NonAttribute<User | null>;
}
// Abfrage
const users = await User.where({});
const filtered = users.filter(u => u.name.includes("John"));
// Beziehungen laden
const users_with_orders = await User.where({}, {
include: { orders: true }
});
Migrationsschritte:
// 1. Mapping-Funktion erstellen
function MapTypeORMToDynamite(typeorm_user: any): any {
return {
id: `user-${typeorm_user.id}`,
email: typeorm_user.email,
name: typeorm_user.name,
created_at: typeorm_user.created_at.toISOString(),
updated_at: typeorm_user.updated_at.toISOString()
};
}
// 2. Mit Streaming migrieren
async function MigrateFromTypeORM(): Promise<void> {
const typeorm_repo = connection.getRepository(TypeORMUser);
let page = 0;
const page_size = 100;
while (true) {
const users = await typeorm_repo.find({
skip: page * page_size,
take: page_size
});
if (users.length === 0) break;
const dynamite_users = users.map(MapTypeORMToDynamite);
for (const user of dynamite_users) {
await User.create(user);
}
page++;
console.log(`Seite ${page} migriert`);
}
}
Von Mongoose (MongoDB)¶
MongoDB und DynamoDB sind beide NoSQL, haben aber unterschiedliche Abfragemuster.
Mongoose-Schema:
// Mongoose (MongoDB)
const UserSchema = new mongoose.Schema({
email: { type: String, unique: true, required: true },
name: String,
profile: {
bio: String,
avatar_url: String
},
tags: [String],
created_at: { type: Date, default: Date.now }
});
const User = mongoose.model('User', UserSchema);
// Abfrage
const users = await User.find({
tags: { $in: ['premium', 'verified'] }
}).sort({ created_at: -1 });
Dynamite-Äquivalent:
// Dynamite
import {
Table, PrimaryKey, Default, NotNull, CreatedAt,
CreationOptional
} from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>; // MongoDB _id → DynamoDB id
@NotNull()
declare email: string;
declare name: string;
declare profile: {
bio: string;
avatar_url: string;
};
declare tags: string[];
@CreatedAt()
declare created_at: CreationOptional<string>;
}
// Abfrage - Abrufen und Filtern für Tag-Abfragen
const all_users = await User.where({}, { order: "DESC" });
const premium_users = all_users.filter(u =>
u.tags?.some(tag => ["premium", "verified"].includes(tag))
);
Migrationsskript:
// Von MongoDB zu DynamoDB migrieren
async function MigrateFromMongoDB(): Promise<void> {
const mongo_users = await mongoose.models.User.find().lean();
for (const user of mongo_users) {
await User.create({
id: user._id.toString(),
email: user.email,
name: user.name,
profile: user.profile,
tags: user.tags,
created_at: user.created_at.toISOString()
});
}
}
Versions-Upgrade-Anleitung¶
Upgrade auf v1.0¶
Wichtige Änderungen:
- Klassenbasierte Modelle
// Vorher: Einfache Objekte
const user = { id: "123", name: "John" };
// Nachher: Klasse erweitert Table
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
}
const user = await User.create({ name: "John" });
- Decorator-basierte Konfiguration
// Alle Konfiguration über Decorators
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
@NotNull()
@Validate((v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v as string) || "Ungültige E-Mail")
declare email: string;
@CreatedAt()
declare created_at: CreationOptional<string>;
@UpdatedAt()
declare updated_at: CreationOptional<string>;
}
- Client-Initialisierung
import { Dynamite } from "@arcaelas/dynamite";
const client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User, Order]
});
client.connect();
await client.sync();
Schema-Migration¶
Neue Attribute hinzufügen¶
Das Hinzufügen von Attributen zu bestehenden Items erfordert sorgfältige Planung.
// Altes Schema
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
}
// Neues Schema
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
declare email?: string; // Neues optionales Attribut
@Default(() => new Date().toISOString())
declare created_at: CreationOptional<string>; // Neu mit Default
}
// Migrationsskript
async function AddAttributes(): Promise<void> {
const users = await User.where({});
for (const user of users) {
if (!user.created_at) {
user.created_at = new Date().toISOString();
await user.save();
}
}
console.log(`${users.length} Benutzer migriert`);
}
Attribute umbenennen¶
DynamoDB unterstützt das Umbenennen von Attributen nicht. Erstellen Sie ein neues Attribut und kopieren Sie die Daten.
// Migration: name → full_name
async function RenameAttribute(): Promise<void> {
const users = await User.where({});
for (const user of users) {
// In neues Attribut kopieren
(user as any).full_name = user.name;
await user.save();
}
console.log(`${users.length} Benutzer verarbeitet`);
}
// Entity-Definition nach der Migration aktualisieren
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare full_name: string; // Umbenannt von 'name'
}
Daten-Migration¶
Daten während der Migration transformieren¶
Wenden Sie Transformationen während der Migration an.
interface TransformFunction<TInput, TOutput> {
(input: TInput): TOutput;
}
async function MigrateWithTransform<TInput, TOutput>(
source_data: TInput[],
target_model: typeof Table,
transform: TransformFunction<TInput, TOutput>
): Promise<void> {
let migrated = 0;
for (const item of source_data) {
const transformed = transform(item);
await target_model.create(transformed as any);
migrated++;
if (migrated % 100 === 0) {
console.log(`${migrated} Items migriert`);
}
}
console.log(`Migration abgeschlossen: ${migrated} Items`);
}
// Verwendung
await MigrateWithTransform(
old_users,
User,
(old_user) => ({
id: old_user.id,
email: old_user.email.toLowerCase(), // Transformieren: E-Mail normalisieren
name: old_user.first_name + ' ' + old_user.last_name, // Transformieren: Namen kombinieren
created_at: new Date(old_user.created_at).toISOString() // Transformieren: Datum zu ISO
})
);
Migrationen testen¶
Test-Framework¶
Erstellen Sie ein Test-Framework für Migrationen.
import { Dynamite } from "@arcaelas/dynamite";
class MigrationTester {
private client: Dynamite;
constructor() {
this.client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User]
});
}
async Setup(): Promise<void> {
this.client.connect();
await this.client.sync();
await this.SeedData();
}
private async SeedData(): Promise<void> {
for (let i = 0; i < 100; i++) {
await User.create({
name: `Benutzer ${i}`,
email: `user${i}@example.com`
});
}
}
async RunMigration(
migration_fn: () => Promise<void>
): Promise<{ success: boolean; duration_ms: number }> {
const start = Date.now();
try {
await migration_fn();
return { success: true, duration_ms: Date.now() - start };
} catch (error) {
console.error('Migration fehlgeschlagen:', error);
return { success: false, duration_ms: Date.now() - start };
}
}
async Verify(
assertion: () => Promise<boolean>
): Promise<boolean> {
return assertion();
}
async Teardown(): Promise<void> {
this.client.disconnect();
}
}
// Verwendung
const tester = new MigrationTester();
await tester.Setup();
const result = await tester.RunMigration(async () => {
await AddEmailAttribute();
});
const verified = await tester.Verify(async () => {
const users = await User.where({});
return users.every(u => u.email !== undefined);
});
console.log('Migration:', result.success ? 'PASS' : 'FAIL');
console.log('Verifizierung:', verified ? 'PASS' : 'FAIL');
await tester.Teardown();
Produktions-Deployment¶
Pre-Deployment-Checkliste¶
- [ ] Migration mit produktionsähnlichen Daten testen
- [ ] Rollback-Plan vorbereiten
- [ ] Überwachungsalarme einrichten
- [ ] Während Niedrigverkehrs-Zeitfenster planen
- [ ] Mit dem Team kommunizieren
- [ ] Kritische Daten sichern
- [ ] In Staging-Umgebung testen
Rollback-Strategie¶
Haben Sie immer einen Rollback-Plan.
class MigrationRollback {
private backup_data: any[] = [];
async CreateBackup(): Promise<void> {
console.log('Backup wird erstellt...');
this.backup_data = await User.where({});
console.log(`Backup abgeschlossen: ${this.backup_data.length} Items`);
}
async Rollback(): Promise<void> {
console.log('Rollback wird durchgeführt...');
for (const item of this.backup_data) {
await User.create(item);
}
console.log('Rollback abgeschlossen');
}
}
// Verwendung
const rollback = new MigrationRollback();
await rollback.CreateBackup();
try {
await RunMigration();
} catch (error) {
console.error('Migration fehlgeschlagen, Rollback wird durchgeführt...');
await rollback.Rollback();
}
Weitere Informationen: - Decorator-Leitfaden - API-Referenz - AWS DynamoDB-Dokumentation