Transformacja kodu
AI automatyzuje konwersję składni i wzorców między technologiami
Migracje technologiczne należą do najtrudniejszych zadań w rozwoju oprogramowania. Ta lekcja demonstruje jak możliwości AI Cursor przekształcają złożone migracje—od aktualizacji frameworków po kompletne zmiany platform—w możliwe do zarządzania, systematyczne procesy.
Tradycyjne migracje wymagają głębokiej znajomości zarówno technologii źródłowej jak i docelowej, często prowadząc do miesięcy ostrożnego planowania i wykonania. Pomoc AI przyspiesza ten proces poprzez automatyzację transformacji kodu, identyfikację wzorców i zapewnienie, że nic nie zostanie pominięte.
Transformacja kodu
AI automatyzuje konwersję składni i wzorców między technologiami
Mapowanie zależności
AI identyfikuje równoważne biblioteki i sugeruje zamienniki
Pokrycie testowe
AI zapewnia zachowanie funkcjonalności przez kompleksowe testowanie
Migracja stopniowa
AI pomaga planować i wykonywać stopniowe strategie migracji
Analiza projektu
// Poproś AI o analizę istniejącego projektu React"Przeanalizuj tę aplikację React i stwórz plan migracji do Next.js 14:- Zidentyfikuj wzorce routingu do konwersji na App Router- Znajdź wzorce pobierania danych do konwersji na Server Components- Wymień zarządzanie stanem wymagające dostosowania- Zidentyfikuj wywołania API do konwersji na Server Actions- Sprawdź niekompatybilne zależności"
Strategia migracji
// AI tworzy mapę drogową migracji"Stwórz plan migracji etapowej:Etap 1: Skonfiguruj Next.js obok ReactEtap 2: Migruj routing i layoutyEtap 3: Konwertuj komponenty na Server ComponentsEtap 4: Migruj zarządzanie stanemEtap 5: Optymalizuj i usuń stary kod"
// AI generuje listę kontrolną migracjiinterface MigrationPlan { phases: { name: string; tasks: MigrationTask[]; estimatedDays: number; risks: string[]; }[];
rollbackStrategy: string; testingStrategy: string;}
Migracja komponentów
// AI konwertuje komponenty React na Next.js"Konwertuj ten komponent React na Next.js Server Component:- Przenieś logikę client-side do komponentów 'use client'- Konwertuj pobieranie danych na async server component- Zaktualizuj importy dla funkcji specyficznych dla Next.js- Optymalizuj dla server-side rendering"
// Przed: Komponent Reactimport React, { useState, useEffect } from 'react';import { useParams } from 'react-router-dom';
export function ProductPage() { const { id } = useParams(); const [product, setProduct] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { fetch(`/api/products/${id}`) .then(res => res.json()) .then(data => { setProduct(data); setLoading(false); }); }, [id]);
if (loading) return <div>Ładowanie...</div>;
return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> <AddToCartButton productId={product.id} /> </div> );}
// Po: Next.js Server Component (wygenerowany przez AI)import { notFound } from 'next/navigation';import { AddToCartButton } from './AddToCartButton';
async function getProduct(id: string) { const res = await fetch(`${process.env.API_URL}/products/${id}`, { next: { revalidate: 3600 } // Cache na 1 godzinę });
if (!res.ok) return null; return res.json();}
export default async function ProductPage({ params}: { params: { id: string }}) { const product = await getProduct(params.id);
if (!product) { notFound(); }
return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> <AddToCartButton productId={product.id} /> </div> );}
// Client Component (AddToCartButton.tsx)'use client';
import { useState } from 'react';
export function AddToCartButton({ productId }: { productId: string }) { const [isAdding, setIsAdding] = useState(false);
const handleAddToCart = async () => { setIsAdding(true); // Logika dodania do koszyka setIsAdding(false); };
return ( <button onClick={handleAddToCart} disabled={isAdding} > {isAdding ? 'Dodawanie...' : 'Dodaj do koszyka'} </button> );}
<!-- AI konwertuje Vue SFC na Nuxt -->"Konwertuj komponent Vue 3 na Nuxt 3:- Zaktualizuj na auto-importy Nuxt- Konwertuj na composition API jeśli potrzeba- Użyj composables Nuxt- Obsługuj async data z useAsyncData"
<!-- Przed: Komponent Vue --><template> <div v-if="!loading"> <h1>{{ user.name }}</h1> <UserPosts :user-id="user.id" /> </div> <div v-else>Ładowanie...</div></template>
<script>import { ref, onMounted } from 'vue'import axios from 'axios'import UserPosts from './UserPosts.vue'
export default { components: { UserPosts }, props: ['userId'], setup(props) { const user = ref(null) const loading = ref(true)
onMounted(async () => { try { const { data } = await axios.get(`/api/users/${props.userId}`) user.value = data } finally { loading.value = false } })
return { user, loading } }}</script>
<!-- Po: Komponent Nuxt (wygenerowany przez AI) --><template> <div> <h1>{{ user.name }}</h1> <UserPosts :user-id="user.id" /> </div></template>
<script setup lang="ts">interface User { id: string name: string}
const props = defineProps<{ userId: string}>()
const { data: user } = await useAsyncData<User>( `user-${props.userId}`, () => $fetch(`/api/users/${props.userId}`))
if (!user.value) { throw createError({ statusCode: 404, statusMessage: 'Użytkownik nie znaleziony' })}</script>
// AI konwertuje Vuex na Pinia"Migruj store Vuex na Pinia dla Nuxt 3:- Konwertuj moduły na stores- Zaktualizuj gettery na computed- Konwertuj akcje na metody- Obsługuj persystencję"
// Przed: Store Vuexexport const userModule = { namespaced: true, state: () => ({ currentUser: null, users: [], loading: false }), getters: { isAuthenticated: state => !!state.currentUser, getUserById: state => id => state.users.find(user => user.id === id) }, mutations: { SET_CURRENT_USER(state, user) { state.currentUser = user }, SET_USERS(state, users) { state.users = users }, SET_LOADING(state, loading) { state.loading = loading } }, actions: { async login({ commit }, credentials) { commit('SET_LOADING', true) try { const user = await authApi.login(credentials) commit('SET_CURRENT_USER', user) return user } finally { commit('SET_LOADING', false) } }, async fetchUsers({ commit }) { const users = await userApi.getAll() commit('SET_USERS', users) } }}
// Po: Store Pinia (wygenerowany przez AI)export const useUserStore = defineStore('user', () => { // Stan const currentUser = ref<User | null>(null) const users = ref<User[]>([]) const loading = ref(false)
// Gettery const isAuthenticated = computed(() => !!currentUser.value) const getUserById = computed(() => (id: string) => users.value.find(user => user.id === id) )
// Akcje async function login(credentials: LoginCredentials) { loading.value = true try { const user = await $fetch('/api/auth/login', { method: 'POST', body: credentials }) currentUser.value = user
// Odśwież przy nawigacji client-side await navigateTo('/dashboard') return user } finally { loading.value = false } }
async function fetchUsers() { users.value = await $fetch('/api/users') }
// Persystencja stanu auth const nuxtApp = useNuxtApp() if (process.client) { nuxtApp.hook('app:mounted', () => { const stored = localStorage.getItem('auth_token') if (stored) { // Przywróć sesję } }) }
return { // Stan currentUser: readonly(currentUser), users: readonly(users), loading: readonly(loading),
// Gettery isAuthenticated, getUserById,
// Akcje login, fetchUsers }})
// AI pomaga w migracji Express do Fastify"Migruj aplikację Express na Fastify:- Konwertuj middleware na pluginy Fastify- Zaktualizuj handlery tras- Migruj walidację na schematy Fastify- Konwertuj obsługę błędów- Zaktualizuj testy"
// Przed: Aplikacja Expressimport express from 'express';import bodyParser from 'body-parser';import cors from 'cors';import { authenticate } from './middleware/auth';import { validate } from './middleware/validate';import { userSchema } from './schemas/user';
const app = express();
app.use(cors());app.use(bodyParser.json());
app.post('/users', authenticate, validate(userSchema), async (req, res, next) => { try { const user = await createUser(req.body); res.status(201).json(user); } catch (error) { next(error); } });
app.use((err, req, res, next) => { res.status(err.status || 500).json({ error: err.message });});
// Po: Aplikacja Fastify (wygenerowana przez AI)import Fastify from 'fastify';import cors from '@fastify/cors';import { Type } from '@sinclair/typebox';
const fastify = Fastify({ logger: true, ajv: { customOptions: { removeAdditional: 'all', coerceTypes: true } }});
// Rejestracja pluginówawait fastify.register(cors);await fastify.register(authenticate);
// Definicja schematuconst createUserSchema = { body: Type.Object({ email: Type.String({ format: 'email' }), password: Type.String({ minLength: 8 }), name: Type.String() }), response: { 201: Type.Object({ id: Type.String(), email: Type.String(), name: Type.String(), createdAt: Type.String({ format: 'date-time' }) }) }};
// Handler trasyfastify.post('/users', { schema: createUserSchema, preHandler: fastify.auth([fastify.verifyJWT])}, async (request, reply) => { const user = await createUser(request.body); return reply.code(201).send(user);});
// Obsługa błędówfastify.setErrorHandler((error, request, reply) => { request.log.error(error);
const statusCode = error.statusCode || 500; const response = { error: error.message, statusCode };
reply.status(statusCode).send(response);});
// Plugin autentykacjiasync function authenticate(fastify, options) { fastify.decorate('verifyJWT', async (request, reply) => { try { const token = request.headers.authorization?.replace('Bearer ', ''); if (!token) throw new Error('Brakujący token');
const decoded = fastify.jwt.verify(token); request.user = decoded; } catch (err) { reply.code(401).send({ error: 'Nieautoryzowany' }); } });}
// AI tworzy pipeline migracji bazy danych"Stwórz migrację bazy danych z MongoDB do PostgreSQL:- Mapowanie i normalizacja schematu- Skrypty transformacji danych- Sync stopniowy podczas przejścia- Walidacja i reconciliacja- Możliwość rollback"
export class DatabaseMigrator { private source: MongoClient; private target: PostgresClient; private validator: DataValidator;
async analyzeSchemaDifferences() { // AI analizuje kolekcje MongoDB const collections = await this.source.listCollections(); const schemaMappings: SchemaMapping[] = [];
for (const collection of collections) { const sample = await this.source .collection(collection) .find() .limit(1000) .toArray();
// Wnioskuj schemat z dokumentów const mongoSchema = this.inferSchema(sample);
// Generuj schemat PostgreSQL const pgSchema = this.generatePostgresSchema( collection, mongoSchema );
schemaMappings.push({ source: collection, target: pgSchema, transformations: this.identifyTransformations(mongoSchema, pgSchema) }); }
return schemaMappings; }
async migrateCollection(mapping: SchemaMapping) { const batchSize = 1000; let offset = 0; let hasMore = true;
// Stwórz tabelę docelową await this.createPostgresTable(mapping.target);
while (hasMore) { // Pobierz partię z MongoDB const documents = await this.source .collection(mapping.source) .find() .skip(offset) .limit(batchSize) .toArray();
if (documents.length === 0) { hasMore = false; continue; }
// Transformuj dokumenty const rows = await this.transformDocuments( documents, mapping.transformations );
// Wstaw do PostgreSQL await this.batchInsert(mapping.target.table, rows);
// Waliduj zmigrowane dane await this.validator.validateBatch( mapping.source, mapping.target.table, offset, batchSize );
offset += batchSize;
// Loguj postęp this.logger.info(`Zmigrowano ${offset} dokumentów z ${mapping.source}`); } }
private generatePostgresSchema( collectionName: string, mongoSchema: any ): PostgresSchema { // AI generuje znormalizowany schemat const tables: TableDefinition[] = [];
// Tabela główna const mainTable: TableDefinition = { name: collectionName, columns: [] };
// Analizuj pola for (const [field, info] of Object.entries(mongoSchema)) { if (info.type === 'array' && info.itemType === 'object') { // Stwórz oddzielną tabelę dla tablicy obiektów tables.push(this.createChildTable(collectionName, field, info));
// Dodaj referencję foreign key mainTable.columns.push({ name: `${field}_id`, type: 'UUID', references: `${collectionName}_${field}.id` }); } else if (info.type === 'object') { // Spłaszcz zagnieżdżone obiekty const flattened = this.flattenObject(field, info); mainTable.columns.push(...flattened); } else { // Mapowanie bezpośrednie mainTable.columns.push({ name: field, type: this.mapMongoTypeToPostgres(info.type), nullable: info.nullable }); } }
tables.unshift(mainTable);
return { tables, indexes: this.generateIndexes(mongoSchema), constraints: this.generateConstraints(mongoSchema) }; }}
// AI konwertuje JavaScript na TypeScript"Konwertuj bazę kodu JavaScript na TypeScript:- Dodaj definicje typów- Konwertuj na strict mode- Generuj interfejsy z użycia- Obsługuj typy any progresywnie- Zaktualizuj konfigurację build"
// Strategia migracji AIclass TypeScriptMigrator { async migrateFile(filePath: string) { const content = await fs.readFile(filePath, 'utf-8');
// Parsuj AST JavaScript const ast = parse(content);
// Wnioskuj typy z użycia const typeInfo = await this.inferTypes(ast);
// Generuj TypeScript const tsContent = this.generateTypeScript(ast, typeInfo);
// Dodaj importy typów const withImports = this.addTypeImports(tsContent, typeInfo);
// Zapisz plik TypeScript const tsPath = filePath.replace('.js', '.ts'); await fs.writeFile(tsPath, withImports);
// Waliduj kompilację await this.validateTypeScript(tsPath); }
private inferTypes(ast: AST): TypeInfo { // AI analizuje wzorce kodu const functions = this.extractFunctions(ast); const typeInfo: TypeInfo = {};
for (const func of functions) { // Analizuj użycie parametrów const params = func.params.map(param => { const usage = this.findParameterUsage(param, func.body); return { name: param.name, type: this.inferTypeFromUsage(usage) }; });
// Analizuj typ zwracany const returnType = this.inferReturnType(func);
typeInfo[func.name] = { params, returnType }; }
return typeInfo; }}
// Przykład konwersji// Przed: JavaScriptfunction processUser(user, options) { if (!user.id) { throw new Error('User ID wymagane'); }
const result = { id: user.id, name: user.name || 'Nieznany', email: user.email, isActive: options?.active !== false, metadata: {} };
if (options?.includeMetadata) { result.metadata = { createdAt: user.createdAt, lastLogin: user.lastLogin }; }
return result;}
// Po: TypeScript (wygenerowany przez AI)interface User { id: string; name?: string; email: string; createdAt?: Date; lastLogin?: Date;}
interface ProcessUserOptions { active?: boolean; includeMetadata?: boolean;}
interface ProcessedUser { id: string; name: string; email: string; isActive: boolean; metadata: { createdAt?: Date; lastLogin?: Date; };}
function processUser( user: User, options?: ProcessUserOptions): ProcessedUser { if (!user.id) { throw new Error('User ID wymagane'); }
const result: ProcessedUser = { id: user.id, name: user.name || 'Nieznany', email: user.email, isActive: options?.active !== false, metadata: {} };
if (options?.includeMetadata) { result.metadata = { createdAt: user.createdAt, lastLogin: user.lastLogin }; }
return result;}
Ekstrakcja serwisów
Identyfikuj i wyodrębnij bounded context
API Gateway
Stwórz zunifikowany interfejs API
Separacja danych
Bezpiecznie podziel współdzielone bazy danych
Migracja stopniowa
Implementacja wzorca strangler fig
// AI pomaga dekompozować monolit"Przeanalizuj monolit i zasugeruj architekturę mikrousług:- Zidentyfikuj bounded context- Mapuj zależności- Zasugeruj granice serwisów- Zaplanuj separację bazy danych- Stwórz mapę drogową migracji"
export class MicroservicesExtractor { async analyzeMonolith(codebasePath: string) { // Analizuj strukturę kodu const modules = await this.identifyModules(codebasePath); const dependencies = await this.analyzeDependencies(modules);
// Identyfikuj kandydatów na serwisy const services = this.identifyBoundedContexts(modules, dependencies);
// Generuj definicje serwisów return services.map(service => ({ name: service.name, responsibilities: service.modules, dependencies: this.getServiceDependencies(service, services), api: this.generateServiceAPI(service), database: this.planDatabaseSeparation(service), migrationPhase: this.calculateMigrationPhase(service, dependencies) })); }
async extractService( serviceDef: ServiceDefinition, monolithPath: string ) { // Stwórz strukturę serwisu const servicePath = `./services/${serviceDef.name}`; await this.createServiceScaffold(servicePath);
// Wyodrębnij moduły kodu for (const module of serviceDef.responsibilities) { await this.extractModule( `${monolithPath}/${module}`, `${servicePath}/src/${module}` ); }
// Generuj API serwisu await this.generateAPI(servicePath, serviceDef.api);
// Stwórz klient API dla monolitu await this.generateClient( monolithPath, serviceDef.name, serviceDef.api );
// Implementuj wzorzec strangler await this.implementStranglerProxy( monolithPath, serviceDef ); }
private async implementStranglerProxy( monolithPath: string, service: ServiceDefinition ) { // AI generuje kod proxy const proxyCode = ` export class ${service.name}Proxy { private useNewService = process.env.USE_${service.name.toUpperCase()}_SERVICE === 'true'; private client = new ${service.name}Client(); private legacy = new Legacy${service.name}();
async ${service.api.methods.map(method => ` ${method.name}(${method.params}) { if (this.useNewService) { // Wywołaj nowy mikrousług return this.client.${method.name}(${method.params}); } else { // Wywołaj stary kod return this.legacy.${method.name}(${method.params}); } } `).join('\n')}
// Helper stopniowej migracji async migrateTraffic(percentage: number) { this.useNewService = Math.random() * 100 < percentage; } }`;
await fs.writeFile( `${monolithPath}/proxies/${service.name}Proxy.ts`, proxyCode ); }}
// AI tworzy kompleksową strategię testowania"Stwórz testowanie równoległe dla migracji:- Uruchom testy przeciwko staremu i nowemu systemowi- Porównaj wyniki automatycznie- Śledź metryki parytetu- Identyfikuj rozbieżności- Porównanie wydajności"
export class MigrationTester { async runParityTests( oldSystem: System, newSystem: System, testSuite: TestSuite ) { const results = { total: 0, passed: 0, failed: 0, discrepancies: [] };
for (const test of testSuite.tests) { results.total++;
// Uruchom przeciwko obu systemom const [oldResult, newResult] = await Promise.all([ this.runTest(oldSystem, test), this.runTest(newSystem, test) ]);
// Porównaj wyniki const comparison = this.compareResults(oldResult, newResult);
if (comparison.identical) { results.passed++; } else { results.failed++; results.discrepancies.push({ test: test.name, differences: comparison.differences, oldResult: oldResult, newResult: newResult }); }
// Loguj postęp this.logger.info(`Test ${test.name}: ${comparison.identical ? 'PASS' : 'FAIL'}`); }
// Generuj raport await this.generateParityReport(results);
return results; }
async performanceComparison( oldSystem: System, newSystem: System ) { const metrics = { responseTime: {}, throughput: {}, resourceUsage: {} };
// Scenariusze testów obciążeniowych const scenarios = await this.loadScenarios();
for (const scenario of scenarios) { // Uruchom test wydajności na starym systemie const oldMetrics = await this.runLoadTest(oldSystem, scenario);
// Uruchom test wydajności na nowym systemie const newMetrics = await this.runLoadTest(newSystem, scenario);
// Porównaj metryki metrics.responseTime[scenario.name] = { old: oldMetrics.avgResponseTime, new: newMetrics.avgResponseTime, improvement: ((oldMetrics.avgResponseTime - newMetrics.avgResponseTime) / oldMetrics.avgResponseTime) * 100 };
metrics.throughput[scenario.name] = { old: oldMetrics.requestsPerSecond, new: newMetrics.requestsPerSecond, improvement: ((newMetrics.requestsPerSecond - oldMetrics.requestsPerSecond) / oldMetrics.requestsPerSecond) * 100 }; }
return metrics; }}
// AI implementuje mechanizmy rollback"Stwórz strategię rollback dla migracji:- Skrypty rollback bazy danych- Przełączanie wersji aplikacji- Sprawdzanie spójności danych- Zarządzanie ruchem- Plan komunikacji"
export class RollbackManager { async prepareRollback(migration: Migration) { // Generuj skrypty rollback const rollbackPlan = { database: await this.generateDatabaseRollback(migration), application: await this.generateAppRollback(migration), configuration: await this.generateConfigRollback(migration), validation: await this.generateValidation(migration) };
// Testuj rollback w staging await this.testRollback(rollbackPlan);
return rollbackPlan; }
async executeRollback(plan: RollbackPlan, reason: string) { this.logger.error(`Wykonuję rollback: ${reason}`);
// Zatrzymaj nowy ruch await this.enableMaintenanceMode();
try { // Rollback aplikacji await this.rollbackApplication(plan.application);
// Rollback bazy danych jeśli potrzeba if (plan.database.hasSchemaChanges) { await this.rollbackDatabase(plan.database); }
// Przywróć konfigurację await this.rollbackConfiguration(plan.configuration);
// Waliduj stan systemu const validation = await this.validateSystem(plan.validation);
if (!validation.success) { throw new Error('Walidacja rollback nie powiodła się'); }
// Wznów ruch await this.disableMaintenanceMode();
// Powiadom interesariuszy await this.notifyRollback(reason, validation);
} catch (error) { // Procedury awaryjne await this.executeEmergencyProcedures(error); throw error; } }}
Podejście stopniowe
Migruj w małych, testowalnych krokach
Równoległe uruchamianie
Uruchamiaj stary i nowy system równolegle podczas przejścia
Kompleksowe testowanie
Testuj funkcjonalność, wydajność i przypadki brzegowe
Gotowość rollback
Zawsze miej przetestowany plan rollback
Projektowanie architektury
Zaprojektuj architekturę docelową przed migracją
Optymalizacja wydajności
Optymalizuj podczas procesu migracji
Szkolenie zespołu
Upewnij się, że zespół jest gotowy na nową technologię