Guía de Migración¶
Esta guía te ayuda a migrar a Dynamite desde otros ORMs o actualizar entre versiones.
Tabla de Contenidos¶
- Migrar desde Otros ORMs
- Guia de Actualizacion de Version
- Migracion de Esquema
- Migracion de Datos
- Pruebas de Migraciones
- Despliegue en Produccion
Migrar desde Otros ORMs¶
Desde Sequelize (SQL)¶
Sequelize está diseñado para bases de datos relacionales. Aquí te mostramos cómo adaptarte al paradigma NoSQL de DynamoDB.
Modelo Sequelize:
// 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
});
// Consulta
const users = await User.findAll({
where: {
email: { [Op.like]: '%@example.com' }
},
include: [Order]
});
Equivalente en Dynamite:
// 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>; // Usar UUID en lugar de auto-increment
@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>;
}
// Consulta - enfoque diferente para NoSQL
const users = await User.where({ email: "user@example.com" }, {
include: { orders: true }
});
// Para coincidencia de patrones, obtener y filtrar
const all_users = await User.where({});
const filtered = all_users.filter(u => u.email.endsWith("@example.com"));
Diferencias Clave:
| Concepto | Sequelize (SQL) | Dynamite (DynamoDB) |
|---|---|---|
| Clave Primaria | Entero auto-increment | UUID o clave compuesta |
| Consultas | Cláusulas WHERE complejas | Condiciones de clave + filtros |
| Joins | Soporte nativo de JOIN | Carga manual de relaciones |
| Transacciones | Transacciones ACID | Transacciones limitadas (25 items) |
| Esquema | Esquema rígido | Esquema flexible |
Estrategia de Migración:
// 1. Exportar datos desde SQL
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}`, // Transformar formato de ID
email: user.email,
name: user.name,
created_at: user.created_at.toISOString()
}));
// Guardar en archivo
await fs.writeFile(
'users_export.json',
JSON.stringify(export_data, null, 2)
);
}
// 2. Importar a DynamoDB
async function ImportToDynamoDB(): Promise<void> {
const data = JSON.parse(
await fs.readFile('users_export.json', 'utf-8')
);
// Importar en lotes
for (const item of data) {
await User.create(item);
}
console.log(`Importados ${data.length} usuarios`);
}
Desde TypeORM¶
TypeORM soporta múltiples bases de datos incluyendo DynamoDB (soporte básico).
Entidad TypeORM:
// 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;
}
// Consulta
const users = await userRepository.find({
where: { name: Like('%John%') },
relations: ['orders']
});
Equivalente en Dynamite:
// 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>;
}
// Consulta
const users = await User.where({});
const filtered = users.filter(u => u.name.includes("John"));
// Cargar relaciones
const users_with_orders = await User.where({}, {
include: { orders: true }
});
Pasos de Migración:
// 1. Crear función de mapeo
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. Migrar con streaming
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(`Migrada página ${page}`);
}
}
Desde Mongoose (MongoDB)¶
MongoDB y DynamoDB son ambos NoSQL, pero tienen patrones de consulta diferentes.
Esquema Mongoose:
// 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);
// Consulta
const users = await User.find({
tags: { $in: ['premium', 'verified'] }
}).sort({ created_at: -1 });
Equivalente en Dynamite:
// 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>;
}
// Consulta - obtener y filtrar para consultas de tags
const all_users = await User.where({}, { order: "DESC" });
const premium_users = all_users.filter(u =>
u.tags?.some(tag => ["premium", "verified"].includes(tag))
);
Script de Migración:
// Migrar de MongoDB a DynamoDB
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()
});
}
}
Guía de Actualización de Versión¶
Actualizando a v1.0¶
Cambios Clave:
- Modelos basados en Clases
// Antes: Objetos planos
const user = { id: "123", name: "John" };
// Después: Clase extendiendo Table
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
}
const user = await User.create({ name: "John" });
- Configuración basada en Decoradores
// Toda la configuración via decoradores
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
@NotNull()
@Validate((v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v as string) || "Email inválido")
declare email: string;
@CreatedAt()
declare created_at: CreationOptional<string>;
@UpdatedAt()
declare updated_at: CreationOptional<string>;
}
- Inicialización del Cliente
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();
Migración de Esquema¶
Agregar Nuevos Atributos¶
Agregar atributos a items existentes requiere planificación cuidadosa.
// Esquema antiguo
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
}
// Esquema nuevo
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
declare email?: string; // Nuevo atributo opcional
@Default(() => new Date().toISOString())
declare created_at: CreationOptional<string>; // Nuevo con default
}
// Script de migración
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(`Migrados ${users.length} usuarios`);
}
Renombrar Atributos¶
DynamoDB no soporta renombrar atributos. Crea un nuevo atributo y copia los datos.
// Migración: name → full_name
async function RenameAttribute(): Promise<void> {
const users = await User.where({});
for (const user of users) {
// Copiar al nuevo atributo
(user as any).full_name = user.name;
await user.save();
}
console.log(`Procesados ${users.length} usuarios`);
}
// Actualizar definición de entidad después de la migración
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare full_name: string; // Renombrado desde 'name'
}
Migración de Datos¶
Transformar Datos Durante la Migración¶
Aplica transformaciones mientras migras.
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(`Migrados ${migrated} items`);
}
}
console.log(`Migración completa: ${migrated} items`);
}
// Uso
await MigrateWithTransform(
old_users,
User,
(old_user) => ({
id: old_user.id,
email: old_user.email.toLowerCase(), // Transformar: normalizar email
name: old_user.first_name + ' ' + old_user.last_name, // Transformar: combinar nombres
created_at: new Date(old_user.created_at).toISOString() // Transformar: fecha a ISO
})
);
Pruebas de Migraciones¶
Framework de Pruebas¶
Crea un framework de pruebas para migraciones.
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: `Usuario ${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('Migración fallida:', 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();
}
}
// Uso
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('Migración:', result.success ? 'PASS' : 'FAIL');
console.log('Verificación:', verified ? 'PASS' : 'FAIL');
await tester.Teardown();
Despliegue en Producción¶
Lista de Verificación Pre-Despliegue¶
- [ ] Probar migración con datos similares a producción
- [ ] Preparar plan de rollback
- [ ] Configurar alertas de monitoreo
- [ ] Programar durante ventana de bajo tráfico
- [ ] Comunicar con el equipo
- [ ] Respaldar datos críticos
- [ ] Probar en ambiente de staging
Estrategia de Rollback¶
Siempre ten un plan de rollback.
class MigrationRollback {
private backup_data: any[] = [];
async CreateBackup(): Promise<void> {
console.log('Creando respaldo...');
this.backup_data = await User.where({});
console.log(`Respaldo completo: ${this.backup_data.length} items`);
}
async Rollback(): Promise<void> {
console.log('Revirtiendo...');
for (const item of this.backup_data) {
await User.create(item);
}
console.log('Rollback completo');
}
}
// Uso
const rollback = new MigrationRollback();
await rollback.CreateBackup();
try {
await RunMigration();
} catch (error) {
console.error('Migración fallida, revirtiendo...');
await rollback.Rollback();
}
Para más información: - Guía de Decoradores - Referencia de API - Documentación de AWS DynamoDB