Skip to content
advanced Phase 87 · Integration Patterns

ERP Integration

ERP integration patterns - SAP, Oracle integration, data synchronization, API design for enterprise systems

45m
0 problems
Topic Progress 0%

ERP Integration Architecture

Integration Architecture Overview

┌─────────────┐     ┌──────────────────┐     ┌─────────────┐
│   Magento   │◄───►│  Integration Hub │◄───►│  SAP/Oracle │
│   2 Store   │     │  (Middleware)    │     │    ERP      │
└─────────────┘     └──────────────────┘     └─────────────┘
       │                    │                       │
       │              ┌─────┴─────┐                │
       │              │  Message  │                │
       └──────────────│  Queue    │────────────────┘
                      └───────────┘

Integration Patterns

namespace Vendor\Integration\Model\Erp;

interface ErpAdapterInterface
{
    /**
     * Sync product data from ERP
     */
    public function syncProduct(array $sku): ProductData;

    /**
     * Sync inventory levels
     */
    public function syncInventory(array $skus): array;

    /**
     * Push order data to ERP
     */
    public function pushOrder(OrderInterface $order): SyncResult;

    /**
     * Pull customer data from ERP
     */
    public function pullCustomers(int $pageSize, int $page): CustomerCollection;
}

SAP Integration Example

namespace Vendor\Integration\Model\Erp\Sap;

class SapAdapter implements ErpAdapterInterface
{
    private SapClient $client;
    private LoggerInterface $logger;

    public function __construct(
        SapClient $client,
        LoggerInterface $logger
    ) {
        $this->client = $client;
        $this->logger = $logger;
    }

    public function syncProduct(array $sku): ProductData
    {
        try {
            $response = $this->client->call('MaterialMaster', [
                'MATNR' => $sku,
                'WERKS' => '1000',
            ]);

            return new ProductData([
                'sku' => $response['MATNR'],
                'name' => $response['MAKTX'],
                'price' => $response['KBETR'],
                'stock' => $response['LABST'],
            ]);
        } catch (SapException $e) {
            $this->logger->error('SAP sync failed', [
                'sku' => $sku,
                'error' => $e->getMessage(),
            ]);
            throw new IntegrationException('ERP sync failed', 0, $e);
        }
    }
}

Data Synchronization

Bidirectional Sync Strategy

namespace Vendor\Integration\Model\Sync;

class BidirectionalSync
{
    private ProductRepositoryInterface $productRepo;
    private ErpAdapterInterface $erpAdapter;
    private SyncStateRepositoryInterface $syncStateRepo;

    public function syncProducts(array $skus): SyncResult
    {
        $result = new SyncResult();

        foreach ($skus as $sku) {
            try {
                // Check last sync time
                $syncState = $this->syncStateRepo->getBySku($sku);
                $lastSync = $syncState?->getLastSyncAt();

                // Pull from ERP
                $erpData = $this->erpAdapter->syncProduct($sku);

                // Compare and merge
                $product = $this->productRepo->get($sku);
                $mergedData = $this->mergeData($product, $erpData, $lastSync);

                // Push changes back if needed
                if ($this->hasLocalChanges($product, $lastSync)) {
                    $this->pushToErp($product);
                }

                // Save locally
                $this->productRepo->save($mergedData);

                // Update sync state
                $syncState->setLastSyncAt(new \DateTime());
                $this->syncStateRepo->save($syncState);

                $result->addSuccess($sku);
            } catch (\Exception $e) {
                $result->addFailure($sku, $e->getMessage());
            }
        }

        return $result;
    }
}

Sync Conflict Resolution

namespace Vendor\Integration\Model\Sync\Conflict;

interface ConflictResolverInterface
{
    /**
     * Resolve conflict between local and remote data
     */
    public function resolve(
        array $localData,
        array $remoteData,
        SyncMetadata $metadata
    ): array;
}

class LastModifiedResolver implements ConflictResolverInterface
{
    public function resolve(
        array $localData,
        array $remoteData,
        SyncMetadata $metadata
    ): array {
        if ($metadata->getRemoteUpdatedAt() > $metadata->getLocalUpdatedAt()) {
            return $remoteData;
        }
        return $localData;
    }
}

API Design for Integrations

REST API for ERP

<!-- app/code/Vendor/Integration/etc/webapi.xml -->
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

    <!-- Product sync endpoint -->
    <route url="/V1/integration/erp/product/:sku" method="GET">
        <service class="Vendor\Integration\Api\ErpProductInterface" method="getBySku"/>
        <resources>
            <resource ref="Vendor_Integration::erp_product"/>
        </resources>
    </route>

    <!-- Bulk inventory sync -->
    <route url="/V1/integration/erp/inventory" method="POST">
        <service class="Vendor\Integration\Api\ErpInventoryInterface" method="bulkSync"/>
        <resources>
            <resource ref="Vendor_Integration::erp_inventory"/>
        </resources>
    </route>

    <!-- Order push -->
    <route url="/V1/integration/erp/order/:incrementId" method="PUT">
        <service class="Vendor\Integration\Api\ErpOrderInterface" method="pushOrder"/>
        <resources>
            <resource ref="Vendor_Integration::erp_order"/>
        </resources>
    </route>
</routes>

API Rate Limiting

namespace Vendor\Integration\Model\Api\RateLimit;

class ErpRateLimiter
{
    private int $maxRequestsPerMinute = 60;
    private int $maxBulkSize = 100;

    public function validate(array $items): void
    {
        if (count($items) > $this->maxBulkSize) {
            throw new LocalizedException(
                __('Bulk size exceeds limit of %1 items', $this->maxBulkSize)
            );
        }
    }
}

Error Handling and Resilience

Circuit Breaker Pattern

namespace Vendor\Integration\Model\Resilience;

class CircuitBreaker
{
    private int $failureThreshold = 5;
    private int $recoveryTimeout = 60;
    private int $failureCount = 0;
    private string $state = 'closed';
    private ?\DateTime $lastFailure = null;

    public function call(callable $operation, callable $fallback)
    {
        if ($this->state === 'open') {
            if ($this->shouldRetry()) {
                $this->state = 'half-open';
            } else {
                return $fallback();
            }
        }

        try {
            $result = $operation();
            $this->onSuccess();
            return $result;
        } catch (\Exception $e) {
            $this->onFailure();
            return $fallback();
        }
    }

    private function onSuccess(): void
    {
        $this->failureCount = 0;
        $this->state = 'closed';
    }

    private function onFailure(): void
    {
        $this->failureCount++;
        $this->lastFailure = new \DateTime();
        if ($this->failureCount >= $this->failureThreshold) {
            $this->state = 'open';
        }
    }
}

Retry with Exponential Backoff

namespace Vendor\Integration\Model\Resilience;

class RetryHandler
{
    private int $maxRetries = 3;
    private int $baseDelay = 1000;

    public function executeWithRetry(callable $operation): mixed
    {
        $attempt = 0;
        while ($attempt < $this->maxRetries) {
            try {
                return $operation();
            } catch (TransientException $e) {
                $attempt++;
                $delay = $this->baseDelay * pow(2, $attempt);
                usleep($delay * 1000);
            }
        }
        throw new IntegrationException('Max retries exceeded');
    }
}

Quiz

1. What pattern protects against ERP downtime?

Question 1 options

2. What is bidirectional sync?

Question 2 options

3. How should ERP API responses be handled?

Question 3 options

Flashcards

Question

What is the Circuit Breaker pattern?

Answer

Prevents cascading failures by stopping calls to a failing service

Question

What is bidirectional sync?

Answer

Data flows in both directions between Magento and ERP

Question

What is exponential backoff?

Answer

Increasing delays between retry attempts

Question

What is an ERP adapter?

Answer

A class that translates between Magento and ERP data formats

Revision Notes

Key Takeaways

  • 1. ERP integration uses adapter pattern for different systems (SAP, Oracle)
  • 2. Bidirectional sync requires conflict resolution strategies
  • 3. Circuit breaker and retry patterns improve resilience
  • 4. API rate limiting prevents ERP overload
  • 5. Bulk sync operations reduce API calls

Interview Tips

  • Explain how you would handle ERP downtime gracefully
  • Discuss conflict resolution in bidirectional sync
  • Describe the circuit breaker state machine
  • Talk about data mapping between Magento and ERP formats

Cheat Sheet

ERP Integration:
  Adapter Pattern → SAP/Oracle/Custom
  Bidirectional Sync → Push + Pull
  Circuit Breaker → closed → open → half-open
  Exponential Backoff → 1s, 2s, 4s, 8s...

Conflict Resolution:
  Last-Modified → remote wins if newer
  Local-First → local always wins
  Manual → queue for review

Resilience:
  Retry → transient errors
  Circuit Breaker → persistent failures
  Bulk Operations → reduce API calls