Adapter menerjemahkan satu interface ke interface lain yang diharapkan client. Seperti colokan listrik Eropa → adapter → colokan Indonesia: alatnya sama, cara nyambungnya berbeda.
Problem yang diselesaikan:
Kode kamu dirancang bekerja dengan interface tertentu, tapi class dari library/legacy tidak match. Tiga opsi:
- Refactor kode kamu (mahal kalau dipakai banyak tempat)
- Refactor library/legacy (sering tidak bisa — pihak ketiga, atau risk breaking change)
- Bikin adapter — wrapper tipis yang menjembatani dua interface
Implementasi minimal — wrap axios jadi terlihat seperti fetch:
// Interface yang kode kita harapkan (fetch-style)
async function getUser(http, id) {
const res = await http(`/api/users/${id}`);
if (!res.ok) throw new Error(res.status);
return res.json();
}
// Library axios punya interface berbeda
// axios.get(url) → { data, status, ... }
// tidak ada .ok, .json(), dll
// Adapter: wrap axios agar match interface fetch
function axiosAsFetch(axios) {
return async (url, options = {}) => {
const response = await axios.request({
url,
method: options.method || "GET",
headers: options.headers,
data: options.body,
});
return {
ok: response.status < 400,
status: response.status,
json: async () => response.data,
text: async () => JSON.stringify(response.data),
};
};
}
// Sekarang getUser bisa dipakai dengan axios maupun fetch asli
const http = axiosAsFetch(axios);
const user = await getUser(http, 42);
Kode domain (getUser) tidak perlu tahu axios ada. Adapter yang jembatani.
Object adapter vs class adapter:
- Object adapter (composition) — seperti contoh di atas. Adapter menyimpan reference ke adaptee dan meneruskan call. Ini yang dominan di JS karena tidak butuh multiple inheritance.
- Class adapter (inheritance) — adapter extends adaptee DAN implement target interface. Perlu multiple inheritance, tidak ada di JS. Di TypeScript bisa pakai mixin, tapi jarang.
Default di JS: selalu object adapter.
Dunia nyata di ekosistem JS:
- fetch polyfill —
isomorphic-fetch,node-fetch— bikin Node.js punya API miripwindow.fetch. Secara fungsional adapter: XMLHttpRequest / http module adaptee → fetch interface. - Database client adapters — Knex.js dialect, Sequelize connector. Tiap driver (pg, mysql2, sqlite3) punya API berbeda, Knex adapter bikin semua terlihat seragam.
- Payment gateway SDK shim — API Stripe ≠ API Midtrans ≠ API Xendit. Bikin adapter
PaymentAdapterdengan methodcharge(amount, token)— implement tiap gateway dengan class berbeda. - Legacy API wrapping — tim lain expose REST endpoint dengan format aneh (misal SOAP-over-JSON). Bikin adapter yang translate ke domain model bersih agar sisanya tidak tahu detail format legacy.
Adapter vs Facade vs Decorator — tipis perbedaannya:
- Adapter — translate interface A → interface B. Target berbeda dari source.
- Facade — sederhanakan interface kompleks jadi ringkas (target berbeda, biasanya banyak → satu).
- Decorator — tambah behavior tanpa ubah interface. Target sama dengan source.
Kalau niatnya "aku punya X tapi butuh Y" → Adapter. Kalau "aku mau sembunyikan kompleksitas" → Facade.
Kapan pakai:
- Integrasi library pihak ketiga yang interface-nya tidak match
- Migrasi bertahap: interface lama dibungkus agar pelan-pelan kode bisa pakai interface baru
- Testing: bikin fake adapter untuk dependency eksternal
Kapan JANGAN pakai:
- Kalau kamu yang control kedua sisi — lebih baik refactor langsung daripada tambah layer terjemahan
- Interface sudah match atau hampir match — kode tambahan tidak worth it
- Adaptee punya method yang kompleks tidak mappable 1:1 — adapter jadi tempat logic bocor
Trade-off:
Adapter hebat untuk interop, tapi tiap adapter adalah layer tambahan yang harus di-maintain. Kalau dependensi berubah API, adapter yang pertama rusak. Pakai dengan sadar.