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?
2. What is bidirectional sync?
3. How should ERP API responses be handled?
Flashcards
Question
What is the Circuit Breaker pattern?
Click to reveal answer
Answer
Prevents cascading failures by stopping calls to a failing service
Question
What is bidirectional sync?
Click to reveal answer
Answer
Data flows in both directions between Magento and ERP
Question
What is exponential backoff?
Click to reveal answer
Answer
Increasing delays between retry attempts
Question
What is an ERP adapter?
Click to reveal answer
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