Tidak ada ERP yang berdiri sendiri. Setiap bisnis memiliki ekosistem sistem yang harus bertukar data dengan ERP: payment gateway untuk penagihan AR, penyedia email untuk notifikasi otomatis, portal pajak pemerintah untuk pengiriman e-faktur, API perbankan untuk pemrosesan pembayaran, API logistik untuk pelacakan pengiriman. Mendapatkan integrasi yang salah menyebabkan korupsi data, transaksi duplikat, dan acara yang terlewat — masalah yang mahal untuk didiagnosis dan bahkan lebih mahal untuk diperbaiki dalam sistem produksi.
Untuk ERP kustom yang melayani UKM Indonesia, tiga pola mencakup sebagian besar kebutuhan integrasi. Integrasi API langsung: ERP memanggil API eksternal secara sinkron dan menunggu respons. Cocok untuk: kueri real-time, tindakan di mana pengguna mengharapkan respons segera. Integrasi event-driven: ERP menerbitkan acara dan satu atau lebih sistem hilir mengonsumsinya. Integrasi berbasis antrian: ERP menempatkan pekerjaan dalam antrian, pekerja memprosesnya dan menangani percobaan ulang pada kegagalan.
Integrasi API langsung adalah pola yang paling umum dan paling rapuh. Jika API eksternal mati, permintaan ERP Anda gagal. Desain integrasi yang andal memecahkan masalah ini dengan tiga teknik: kunci idempotency (setiap permintaan API menyertakan ID unik yang mencegah pemrosesan duplikat), circuit breakers (hentikan percobaan ulang API yang gagal setelah N kegagalan berturut-turut), dan async fallback (konversi panggilan sinkron ke pekerjaan antrian async).
Untuk ERP yang dibangun di NestJS, BullMQ (antrian pekerjaan yang didukung Redis) adalah pilihan standar untuk pemrosesan integrasi async. Polanya: ketika ERP perlu memanggil API eksternal (kirim email, hasilkan PDF, kirimkan e-faktur), alih-alih memanggil API secara langsung, ERP membuat pekerjaan dalam antrian BullMQ dan segera kembali ke pengguna. Proses pekerja mengambil pekerjaan, mengeksekusi panggilan API, menangani kegagalan dengan logika percobaan ulang yang dapat dikonfigurasi.
ERP Integration Architecture — Queue-Based Async
┌──────────────────────────────────────────────────────────────┐
│ ERP Application (NestJS) │
│ │
│ User Action → Service → BullMQ Queue → Response to User │
│ ↓ │
│ ┌──────────┐ │
│ │ Redis │ (job storage) │
│ └──────┬───┘ │
└────────────────────────────────┼─────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ Worker Processes │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐│
│ │ Email Worker │ │ PDF Worker │ │ e-Faktur Worker ││
│ │ (SendGrid) │ │ (Puppeteer) │ │ (DJP API) ││
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘│
│ │ │ │ │
│ ┌──────▼──────────────────▼─────────────────────▼─────────┐│
│ │ Circuit Breaker + Retry Logic ││
│ │ Attempt 1 → wait 1s → Attempt 2 → wait 4s → Attempt 3 ││
│ │ On 3 failures: route to Dead Letter Queue (DLQ) ││
│ └──────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────┘
EXTERNAL SYSTEMS:
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌────────┐
│ Midtrans │ │ SendGrid │ │ DJP e-Faktur│ │ BPJS │
│ (payments) │ │ (email) │ │ (tax portal)│ │(social)│
└─────────────┘ └─────────────┘ └──────────────┘ └────────┘
Dead Letter Queue review: check daily.
DLQ items = integration broken or data quality issue.Dari pengalaman saya mengimplementasikan ERP di Commsult: selalu implementasikan dead letter queue (DLQ) untuk pekerjaan integrasi yang gagal. Pekerjaan yang menghabiskan percobaan ulang mereka harus masuk ke DLQ dengan konteks penuh — payload pekerjaan, pesan kesalahan dari setiap percobaan, dan timestamp. Tinjau DLQ setiap hari. Pola kegagalan dalam DLQ sering mengungkapkan perubahan kontrak API, kedaluwarsa autentikasi, atau masalah kualitas data.
API integrasi ERP Anda mendefinisikan cara sistem eksternal dapat membaca dan menulis data ke ERP. Rancang dengan prinsip-prinsip ini: versioning (awali semua endpoint API dengan /api/v1/), idempotency (endpoint POST menerima kunci idempotency yang disediakan klien), pagination (tidak pernah mengembalikan daftar tak terbatas), dan autentikasi (integrasi mesin-ke-mesin menggunakan kunci API atau JWT akun layanan).
// NestJS: BullMQ Integration Service with DLQ
import { InjectQueue } from '@nestjs/bullmq';
import { Queue, Worker, QueueEvents } from 'bullmq';
// Integration queue definitions
const QUEUES = {
EMAIL: 'email-queue',
PDF: 'pdf-queue',
EFAKTUR: 'efaktur-queue',
WEBHOOK: 'webhook-queue',
} as const;
// Service: Add jobs to queue (async — returns immediately to user)
@Injectable()
export class IntegrationService {
constructor(
@InjectQueue(QUEUES.EMAIL) private emailQueue: Queue,
@InjectQueue(QUEUES.EFAKTUR) private efakturQueue: Queue,
) {}
async sendInvoiceEmail(invoiceId: string, recipientEmail: string) {
await this.emailQueue.add(
'send-invoice-email',
{ invoiceId, recipientEmail },
{
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
removeOnComplete: { age: 86400 }, // keep 24hrs for debugging
removeOnFail: false, // DLQ — keep failed jobs forever
}
);
// Returns immediately. User gets "Email queued" response.
}
async submitEFaktur(invoiceId: string) {
await this.efakturQueue.add(
'submit-efaktur',
{ invoiceId },
{
attempts: 5,
backoff: { type: 'exponential', delay: 2000 },
// e-Faktur DJP API is slow — allow more retries
timeout: 30_000,
}
);
}
}
// Worker: Processes jobs (handles retries, logs failures)
@Processor(QUEUES.EMAIL)
export class EmailWorker extends WorkerHost {
async process(job: Job<{ invoiceId: string; recipientEmail: string }>) {
const { invoiceId, recipientEmail } = job.data;
const invoice = await this.invoiceService.findOne(invoiceId);
const pdfUrl = await this.pdfService.generateInvoicePdf(invoiceId);
await this.emailProvider.send({
to: recipientEmail,
subject: `Invoice ${invoice.number} from Commsult Indonesia`,
html: this.templates.invoiceEmail(invoice),
attachments: [{ url: pdfUrl, filename: `invoice-${invoice.number}.pdf` }],
});
// Log to audit trail on success
await this.auditService.log({
action: 'EMAIL_SENT',
resource: 'invoice',
resourceId: invoiceId,
metadata: { recipient: recipientEmail, jobId: job.id },
});
}
}
// Webhook validation — ALWAYS verify signatures
@Post('/webhooks/midtrans')
async handleMidtransWebhook(
@Headers('x-callback-token') token: string,
@Body() payload: MidtransWebhookPayload,
) {
// Verify signature before processing
const expectedToken = createHash('sha512')
.update(`${payload.order_id}${payload.status_code}${payload.gross_amount}${MIDTRANS_SERVER_KEY}`)
.digest('hex');
if (token !== expectedToken) {
this.logger.warn(`Invalid Midtrans webhook signature: ${payload.order_id}`);
throw new UnauthorizedException('Invalid webhook signature');
}
// Safe to process
await this.paymentService.processCallback(payload);
}Implementasi ERP Indonesia biasanya memerlukan integrasi yang bukan bagian dari perpustakaan konektor bawaan platform standar mana pun. E-Faktur (DJP): sistem faktur elektronik otoritas pajak memerlukan pengiriman data faktur dalam format CSV tertentu. BPJS Kesehatan dan BPJS Ketenagakerjaan: pelaporan bulanan kontribusi asuransi kesehatan dan ketenagakerjaan karyawan memerlukan pengiriman file dalam format tertentu ke portal pemerintah. Midtrans atau Xendit: integrasi payment gateway untuk penagihan AR.
Webhook — callback HTTP dari sistem eksternal ke ERP Anda — adalah pola integrasi umum untuk konfirmasi pembayaran, pembaruan status pengiriman, dan hasil persetujuan. Tetapi webhook juga merupakan risiko keamanan: sistem mana pun yang mengetahui URL webhook Anda dapat mengirimkan payload arbitrer kepada Anda. Selalu validasi tanda tangan webhook. Payment gateway (Midtrans, Xendit) menyertakan tanda tangan di header webhook yang dihitung dari payload dan kunci rahasia Anda. Verifikasi tanda tangan ini sebelum memproses webhook.
Kegagalan integrasi tidak terlihat kecuali Anda secara aktif memantaunya. Bangun dashboard kesehatan integrasi yang menampilkan: kedalaman antrian pekerjaan, tingkat kegagalan pekerjaan berdasarkan jenis integrasi, jumlah item dead letter queue, tingkat kesalahan dan latensi API eksternal, dan tingkat keberhasilan pengiriman webhook.
Setiap integrasi harus didokumentasikan dalam kontrak integrasi: nama dan versi sistem eksternal, pola integrasi, data yang dipertukarkan, metode autentikasi, perilaku penanganan kesalahan, throughput dan latensi yang diharapkan, dan pemilik yang bertanggung jawab memelihara integrasi. Ketika penyedia API eksternal mengubah API mereka, kontrak integrasi memberi tahu Anda persis kode mana yang perlu diperbarui.