{"id":65,"date":"2025-01-24T10:56:35","date_gmt":"2025-01-24T10:56:35","guid":{"rendered":"https:\/\/magendoo.ro\/insights\/?p=65"},"modified":"2025-01-26T17:42:14","modified_gmt":"2025-01-26T17:42:14","slug":"product-import-strategies-in-magento-2-part-4-rest-api-and-microservice-integration","status":"publish","type":"post","link":"https:\/\/magendoo.ro\/insights\/product-import-strategies-in-magento-2-part-4-rest-api-and-microservice-integration\/","title":{"rendered":"Product Import Strategies in Magento 2 &#8211; Part 4: REST API and Microservice Integration"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>In this final installment of our series, we&#8217;ll explore implementing product imports using Magento 2&#8217;s REST API with a microservice architecture. This approach provides real-time feedback, granular control, and the ability to handle complex product relationships.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">System Architecture<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"668\" src=\"https:\/\/magendoo.ro\/insights\/wp-content\/uploads\/2025\/01\/rest-api-microservices-1024x668.png\" alt=\"\" class=\"wp-image-66\" srcset=\"https:\/\/magendoo.ro\/insights\/wp-content\/uploads\/2025\/01\/rest-api-microservices-1024x668.png 1024w, https:\/\/magendoo.ro\/insights\/wp-content\/uploads\/2025\/01\/rest-api-microservices-300x196.png 300w, https:\/\/magendoo.ro\/insights\/wp-content\/uploads\/2025\/01\/rest-api-microservices-768x501.png 768w, https:\/\/magendoo.ro\/insights\/wp-content\/uploads\/2025\/01\/rest-api-microservices-1536x1002.png 1536w, https:\/\/magendoo.ro\/insights\/wp-content\/uploads\/2025\/01\/rest-api-microservices-2048x1336.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Implementation<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Microservice Core<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Product Import Service (Node.js\/TypeScript)\nimport axios, { AxiosInstance } from 'axios';\nimport { Queue } from '.\/queue';\nimport { ImageProcessor } from '.\/image-processor';\n\ninterface MagentoConfig {\n    baseUrl: string;\n    token: string;\n    batchSize: number;\n}\n\nclass ProductImportService {\n    private readonly client: AxiosInstance;\n    private readonly queue: Queue;\n    private readonly imageProcessor: ImageProcessor;\n\n    constructor(config: MagentoConfig) {\n        this.client = axios.create({\n            baseURL: config.baseUrl,\n            headers: {\n                'Authorization': `Bearer ${config.token}`,\n                'Content-Type': 'application\/json'\n            }\n        });\n        this.queue = new Queue(config.batchSize);\n        this.imageProcessor = new ImageProcessor();\n    }\n\n    async importProducts(products: Product&#91;]): Promise&lt;ImportResult&gt; {\n        try {\n            \/\/ Queue products in batches\n            await this.queue.addBatch(products);\n\n            \/\/ Process queue\n            while (!this.queue.isEmpty()) {\n                const batch = await this.queue.getNext();\n                await this.processBatch(batch);\n            }\n\n            return this.generateReport();\n        } catch (error) {\n            this.handleError(error);\n        }\n    }\n\n    private async processBatch(products: Product&#91;]): Promise&lt;void&gt; {\n        for (const product of products) {\n            try {\n                \/\/ Process images first\n                const processedImages = await this.imageProcessor.process(product.images);\n\n                \/\/ Prepare product data with processed image URLs\n                const productData = this.prepareProductData(product, processedImages);\n\n                \/\/ Check if product exists\n                const exists = await this.checkProductExists(product.sku);\n\n                if (exists) {\n                    await this.updateProduct(product.sku, productData);\n                } else {\n                    await this.createProduct(productData);\n                }\n            } catch (error) {\n                this.logError(product.sku, error);\n            }\n        }\n    }\n\n    private async createProduct(productData: any): Promise&lt;void&gt; {\n        await this.client.post('\/rest\/V1\/products', {\n            product: productData\n        });\n    }\n\n    private async updateProduct(sku: string, productData: any): Promise&lt;void&gt; {\n        await this.client.put(`\/rest\/V1\/products\/${sku}`, {\n            product: productData\n        });\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. Product Data Transformer<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>class ProductTransformer {\n    transformForMagento(rawProduct: RawProduct): MagentoProduct {\n        return {\n            sku: rawProduct.sku,\n            name: rawProduct.name,\n            price: rawProduct.price,\n            status: 1,\n            visibility: 4,\n            type_id: 'simple',\n            attribute_set_id: 4,\n            extension_attributes: this.getExtensionAttributes(rawProduct),\n            custom_attributes: this.getCustomAttributes(rawProduct)\n        };\n    }\n\n    private getExtensionAttributes(product: RawProduct): any {\n        return {\n            website_ids: &#91;1],\n            category_links: this.getCategoryLinks(product.categories),\n            stock_item: {\n                qty: product.quantity,\n                is_in_stock: product.quantity &gt; 0\n            }\n        };\n    }\n\n    private getCustomAttributes(product: RawProduct): any&#91;] {\n        const attributes = &#91;];\n\n        \/\/ Handle each custom attribute\n        if (product.description) {\n            attributes.push({\n                attribute_code: 'description',\n                value: product.description\n            });\n        }\n\n        \/\/ Add more custom attributes as needed\n        return attributes;\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. Image Processing Service<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>class ImageProcessor {\n    private readonly allowedTypes = &#91;'jpg', 'jpeg', 'png', 'webp'];\n    private readonly maxSize = 2000; \/\/ pixels\n\n    async process(images: ProductImage&#91;]): Promise&lt;ProcessedImage&#91;]&gt; {\n        const processed: ProcessedImage&#91;] = &#91;];\n\n        for (const image of images) {\n            try {\n                \/\/ Download image\n                const buffer = await this.downloadImage(image.url);\n\n                \/\/ Validate image\n                await this.validateImage(buffer);\n\n                \/\/ Process image\n                const optimized = await this.optimize(buffer);\n\n                \/\/ Upload to storage\n                const url = await this.upload(optimized, image.label);\n\n                processed.push({\n                    original: image.url,\n                    processed: url,\n                    label: image.label,\n                    position: image.position,\n                    types: image.types || &#91;'image']\n                });\n            } catch (error) {\n                this.logImageError(image.url, error);\n            }\n        }\n\n        return processed;\n    }\n\n    private async optimize(buffer: Buffer): Promise&lt;Buffer&gt; {\n        \/\/ Implement image optimization logic\n        \/\/ Example: using sharp for image processing\n        return sharp(buffer)\n            .resize(this.maxSize, this.maxSize, {\n                fit: 'inside',\n                withoutEnlargement: true\n            })\n            .webp({ quality: 85 })\n            .toBuffer();\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4. Queue Management<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>class ImportQueue {\n    private readonly batchSize: number;\n    private queue: Product&#91;]&#91;];\n    private processing: boolean;\n\n    constructor(batchSize: number) {\n        this.batchSize = batchSize;\n        this.queue = &#91;];\n        this.processing = false;\n    }\n\n    async addBatch(products: Product&#91;]): Promise&lt;void&gt; {\n        const batches = this.createBatches(products);\n        this.queue.push(...batches);\n    }\n\n    private createBatches(products: Product&#91;]): Product&#91;]&#91;] {\n        const batches: Product&#91;]&#91;] = &#91;];\n\n        for (let i = 0; i &lt; products.length; i += this.batchSize) {\n            batches.push(products.slice(i, i + this.batchSize));\n        }\n\n        return batches;\n    }\n\n    async processQueue(handler: (batch: Product&#91;]) =&gt; Promise&lt;void&gt;): Promise&lt;void&gt; {\n        if (this.processing) {\n            return;\n        }\n\n        this.processing = true;\n\n        try {\n            while (this.queue.length &gt; 0) {\n                const batch = this.queue.shift();\n                if (batch) {\n                    await handler(batch);\n                }\n            }\n        } finally {\n            this.processing = false;\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">API Integration Patterns<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Error Handling and Retry Logic<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>class RetryManager {\n    private readonly maxRetries: number;\n    private readonly backoffMs: number;\n\n    constructor(maxRetries: number = 3, backoffMs: number = 1000) {\n        this.maxRetries = maxRetries;\n        this.backoffMs = backoffMs;\n    }\n\n    async retry&lt;T&gt;(operation: () =&gt; Promise&lt;T&gt;): Promise&lt;T&gt; {\n        let lastError: Error;\n\n        for (let attempt = 1; attempt &lt;= this.maxRetries; attempt++) {\n            try {\n                return await operation();\n            } catch (error) {\n                lastError = error;\n\n                if (!this.isRetryable(error) || attempt === this.maxRetries) {\n                    throw error;\n                }\n\n                await this.delay(attempt);\n            }\n        }\n\n        throw lastError;\n    }\n\n    private isRetryable(error: any): boolean {\n        \/\/ Define retry conditions\n        return error.response?.status &gt;= 500 || \n               error.code === 'ECONNRESET' ||\n               error.code === 'ETIMEDOUT';\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. Bulk Operations<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>class BulkOperationManager {\n    private readonly client: AxiosInstance;\n\n    async executeBulkOperation(operations: Operation&#91;]): Promise&lt;BulkResult&gt; {\n        const bulkRequest = {\n            operations: operations.map(op =&gt; ({\n                entity: op.entity,\n                action: op.action,\n                data: op.data\n            }))\n        };\n\n        const response = await this.client.post('\/rest\/async\/bulk\/V1\/products', bulkRequest);\n        return this.trackBulkOperation(response.data.bulk_uuid);\n    }\n\n    private async trackBulkOperation(uuid: string): Promise&lt;BulkResult&gt; {\n        while (true) {\n            const status = await this.getBulkStatus(uuid);\n\n            if (status.is_complete) {\n                return this.processBulkResult(status);\n            }\n\n            await new Promise(resolve =&gt; setTimeout(resolve, 5000));\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Monitoring and Logging<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>class ImportMonitor {\n    private readonly metrics: MetricsClient;\n    private readonly logger: Logger;\n\n    recordMetrics(result: ImportResult): void {\n        this.metrics.gauge('import.products.total', result.total);\n        this.metrics.gauge('import.products.success', result.success);\n        this.metrics.gauge('import.products.failed', result.failed);\n        this.metrics.timing('import.duration', result.duration);\n    }\n\n    async monitorHealth(): Promise&lt;void&gt; {\n        const metrics = await this.gatherMetrics();\n\n        if (metrics.errorRate &gt; this.threshold) {\n            await this.alertTeam({\n                type: 'error_rate',\n                metrics: metrics,\n                timestamp: new Date()\n            });\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Best Practices<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. API Usage<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Implement rate limiting<\/li>\n\n\n\n<li>Use bulk operations when possible<\/li>\n\n\n\n<li>Cache API responses<\/li>\n\n\n\n<li>Handle token expiration<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Error Handling<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Implement circuit breakers<\/li>\n\n\n\n<li>Use exponential backoff<\/li>\n\n\n\n<li>Log detailed error information<\/li>\n\n\n\n<li>Handle partial success scenarios<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Performance<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Process images asynchronously<\/li>\n\n\n\n<li>Use connection pooling<\/li>\n\n\n\n<li>Implement request batching<\/li>\n\n\n\n<li>Cache frequently accessed data<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Advantages<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Real-time Processing<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Immediate feedback<\/li>\n\n\n\n<li>Error handling<\/li>\n\n\n\n<li>Progress tracking<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Scalability<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Independent scaling<\/li>\n\n\n\n<li>Resource optimization<\/li>\n\n\n\n<li>Load distribution<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Flexibility<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Custom business logic<\/li>\n\n\n\n<li>Multiple data sources<\/li>\n\n\n\n<li>Easy integration<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Limitations<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Complexity<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>API maintenance<\/li>\n\n\n\n<li>Token management<\/li>\n\n\n\n<li>Version compatibility<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Performance<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>API rate limits<\/li>\n\n\n\n<li>Network latency<\/li>\n\n\n\n<li>Resource constraints<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">When to Use This Approach<\/h2>\n\n\n\n<p>This approach is ideal when:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Real-time processing is required<\/li>\n\n\n\n<li>Complex validation rules exist<\/li>\n\n\n\n<li>Integration with multiple systems is needed<\/li>\n\n\n\n<li>Custom business logic is required<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>The REST API with microservice approach offers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Real-time processing capabilities<\/li>\n\n\n\n<li>Granular control over the import process<\/li>\n\n\n\n<li>Flexibility in handling complex scenarios<\/li>\n\n\n\n<li>Scalability for large operations<\/li>\n<\/ul>\n\n\n\n<p>Choose this approach when you need tight integration with Magento 2 and want to maintain control over the entire import process.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>This concludes our series on Product Import Strategies in Magento 2. Each approach has its strengths, and the choice depends on your specific requirements and constraints.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">magento2 #api #microservices #ecommerce #nodejs #typescript #programming<\/h1>\n","protected":false},"excerpt":{"rendered":"<p>Introduction In this final installment of our series, we&#8217;ll explore implementing product imports using Magento 2&#8217;s REST API with a microservice architecture. This approach provides real-time feedback, granular control, and the ability to handle complex product relationships.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-container-style":"default","site-container-layout":"default","site-sidebar-layout":"default","disable-article-header":"default","disable-site-header":"default","disable-site-footer":"default","disable-content-area-spacing":"default","footnotes":""},"categories":[1,9],"tags":[],"class_list":["post-65","post","type-post","status-publish","format-standard","hentry","category-general","category-magento-2"],"_links":{"self":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts\/65","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/comments?post=65"}],"version-history":[{"count":3,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts\/65\/revisions"}],"predecessor-version":[{"id":72,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts\/65\/revisions\/72"}],"wp:attachment":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/media?parent=65"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/categories?post=65"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/tags?post=65"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}