Apakah dengan menerapkan pola desain yang penting ini berarti kita tidak dapat menelusuri data?

Sebelumnya, saya menulis tentang “Agregat,” sebuah pola desain layanan mikro yang penting namun kurang dihargai. Seringkali ketika saya memperkenalkan pola ini, saya mendengar kekhawatiran umum: jika kita mengadopsi arsitektur berorientasi Agregat, bukankah kita akan menutup pintu untuk mencari data?

Jawabannya adalah, dengan tegas, tidak.

Dalam artikel ini, kami akan merangkum pola Agregat tersebut, menunjukkan mengapa (pada pandangan pertama) pola tersebut mungkin tampak menghalangi penelusuran, lalu menjelaskan mengapa hal tersebut sebenarnya tidak terjadi.

Agregat adalah pola desain yang penting dalam merancang layanan mikro. Saya telah menjelaskan pola ini, manfaatnya, dan cara menerapkannya “di artikel sebelumnya”. Tapi mari kita rangkum di sini.

Secara singkat, Agregat adalah sekelompok entitas terkait yang diperlakukan sebagai satu unit atom. Menurut definisinya, Agregat terdiri dari:

  • Sejumlah entitas terkait
  • Batas yang secara jelas mendefinisikan entitas yang terdapat dalam Agregat (dan juga yang tidak)
  • Entitas root tunggal, yang merupakan satu-satunya entitas dalam Agregat yang dapat diakses langsung dari dunia luar

Salah satu contoh paling jelas dari Agregat mungkin adalah objek User. Meskipun kasus penggunaan tertentu mungkin berbeda, kita dapat dengan mudah membayangkan model objek yang memiliki objek inti User (dengan kolom seperti Nama Depan, Nama Belakang, Tanggal Lahir, Id Nasional, dll). Kami mungkin melampirkan kumpulan informasi kontak, seperti Address, Phone, atau Email ke objek User tersebut.

Objek Pengguna itu sendiri jelas akan ditentukan oleh akar Agregat Pengguna. Tidak ada entitas lain di luar Agregat yang dapat mengakses Agregat Pengguna melalui entitas lain mana pun dalam Agregat.

Ada banyak sekali manfaat mengikuti pola yang saya uraikan di artikel sebelumnya; alasan-alasan tersebut antara lain:

  • Cetak biru yang jelas untuk memisahkan model data monolitik kami
  • Memungkinkan kami untuk membagi basis data kami seiring dengan skala yang kami lakukan
  • Memberikan pesan yang terdefinisi dengan jelas yang dapat dihasilkan oleh layanan kami untuk dikonsumsi oleh layanan lain
  • Memungkinkan percobaan ulang yang aman atas pesan yang tidak berhasil dikonsumsi
  • Caching yang disederhanakan secara dramatis


Agregat dan Layanan Mikro

Bagaimana pengaruh Agregat terhadap desain layanan mikro kami? Terutama, mereka memandu kami dalam merancang Layanan Data kami; yaitu layanan mikro yang berfungsi sebagai pintu gerbang ke data organisasi kami.

Dengan kata lain, saat kami mendesain Agregat, Agregat ini secara alami mendorong desain:

  • skema database yang menyimpan Agregat
  • API layanan mikro yang menyediakan akses langsung ke database tersebut

Melihat contoh Pengguna di atas, kemungkinan besar kami akan mendapatkan pasangan layanan mikro dan basis data yang terlihat seperti berikut:

Perhatikan bahwa kami akan menggambarkan ReST API dalam contoh kami, untuk tujuan ilustrasi. Namun prinsip yang sama berlaku untuk Thrift API, gRPC API, dll. Basis datanya mungkin berupa "RDBMS" seperti MySQL, tetapi bisa juga berupa "penyimpanan data dokumen" seperti MongoDB.

Dalam arsitektur kami, kami akan memiliki layanan lain dengan tingkat lebih tinggi yang akan memanfaatkan layanan data ini. Misalnya, kita mungkin memiliki Layanan Orkestrasi yang menggabungkan berbagai Agregat. Contoh di bawah ini menggambarkan layanan dalam situs web eCommerce yang memberikan gambaran umum pesanan online, melalui langkah-langkah berikut:

  • memperlihatkan API GET yang menggunakan GUID pesanan
  • mengambil Agregat Pesanan yang diwakili oleh GUID tersebut
  • mengekstrak GUID pembeli dan penjual (yang keduanya merupakan Pengguna)
  • mengambil Agregat Pengguna yang mewakili pembeli dan penjual
  • mengemas semua Agregat tersebut ke dalam model Detail Pesanan tingkat tinggi dan mengembalikannya ke pemanggil

Kedengarannya bagus. Namun bagaimana dengan menanyakan Agregat kami?

Salah satu persyaratan impor Agregat, yang harus diulangi, adalah bahwa semua akses ke Agregat harus melalui entitas root. Jadi URL permintaan apa pun harus diawali dengan sesuatu seperti
/aggregate/{aggregate-identifier}
Jadi, jika kami menyediakan API baca ke Layanan Data Pengguna kami, API tersebut akan terlihat seperti ini:

GET  /users/{guid}
GET  /users/{guid}/phones
GET  /users/{guid}/phones/{phoneId}

Artinya kami tidak dapat menyediakan API seperti berikut:

GET /users/phones/{phoneId}

Pembaca yang cerdik mungkin menyadari bahwa kami juga tidak dapat menyediakan API “penelusuran” seperti berikut:

GET /users/[email protected]

Di permukaan, ini tampak seperti sebuah masalah besar.

Bagaimana jika kita doharus dapat mencari pengguna — bukan berdasarkan GUID mereka — tetapi berdasarkan alamat email mereka?

Atau jika kita memvariasikan contoh Pesanan kita dari atas; bagaimana jika Layanan Orkestrasi kami malah menerima GUID Pengguna, dan kemudian perlu menelusuri Pesanan untuk menemukan pesanan dengan GUID pembeli atau penjual yang cocok?

Secara lebih luas, bagaimana jika kita perlu menemukan Agregat apa saja dengan sesuatu selain selain GUID-nya? Ini bukanlah suatu kebutuhan yang luar biasa atau permintaan yang tidak masuk akal. Namun apakah arsitektur berorientasi agregat menghalangi hal ini?

Solusi: Terapkan layanan pengindeksan terpisah

Solusi terhadap masalah ini tampaknya sederhana. Kami akan menerapkan layanan terpisah, yang dioptimalkan untuk mengindeks istilah pencarian. Semua “pencarian” — yaitu, semua permintaan “GET” yang menanyakan apa pun selain contoh agregat kami yang ditentukan oleh ID-nya — akan dilakukan terhadap Layanan Pengindeksan ini.

Layanan Data kami akan tetap menjadi sumber asal, atau sistem pencatatan, untuk Agregat. Semua penulisan ke Agregat kami (penyisipan, pembaruan, penghapusan, dll) akan dilakukan melalui layanan ini. Sebaliknya, Layanan Pengindeksan tidak akan pernah diperbarui secara langsung. Sebaliknya, penulisan ini akan diberitahukan — biasanya melalui mekanisme asinkron seperti Kafka — dan pada akhirnya tetap konsisten.

Mari kita lihat kembali dilema temukan-pesanan-oleh-penggunayang telah kami sampaikan sebelumnya. Layanan orkestrasi kami harus dapat mencari Pesanan dengan GUID pembeli atau GUID penjual cocok dengan GUID Pengguna yang diberikan.

Untuk mendukung hal ini, kami akan memperkenalkan Layanan Pengindeksan Pesanan. Layanan ini akan diberitahukan oleh Layanan Data Pesanan mengenai setiap penambahan (atau pembaruan, jika kami mengizinkan pembaruan pada pesanan yang sudah ada). Ini kemudian akan mempertahankan indeks GUID pembeli dan GUID penjual, yang dipetakan kembali ke Pesanan terkait. Layanan Orkestrasi, kemudian, akan menanyakan Layanan Pengindeksan Pesanan.

Apa yang akan dikembalikan oleh Layanan Pengindeksan Pesanan, dengan asumsi Layanan tersebut menemukan Pesanan yang cocok? Kami memiliki beberapa pilihan.

  1. Kembalikan GUID Pesanan yang cocok. Layanan Pengindeksan kami mungkin tidak mengembalikan objek Pesanan itu sendiri, namun hanya GUID Pesanan yang cocok. Penelepon (dalam hal ini, Layanan Orkestrasi Detail Pesanan) akan bertanggung jawab untuk memanggil Layanan Data Pesanan untuk mengambil rincian pesanan yang cocok.
    Manfaat utama dari pendekatan ini adalah bahwa layanan orkestrasi akan selalu mengambil representasi Ordo saat ini. Ingatlah bahwa berdasarkan desain, layanan pengindeksan pada akhirnya akan konsisten, sehingga detail yang dikembalikan mungkin tidak mutakhir.
    Manfaat tambahannya adalah jumlah data yang dikirim antara Layanan Pengindeksan dan Orkestrasi Layanan berkurang drastis.
    Kelemahannya jelas: panggilan tambahan yang harus dilakukan oleh layanan orkestrasi. Tergantung pada jumlah Pesanan yang cocok, hal ini dapat mengakibatkan sejumlah besar panggilan ke layanan Detail Pesanan.
  2. Mengembalikan representasi kaya dari Pesanan yang cocok. Layanan Pengindeksan kami mungkin mengembalikan seluruh objek Pesanan.
    Pro dan kontra tentu saja merupakan kebalikan dari pendekatan sebelumnya. Di sini, lebih banyak data akan dikembalikan oleh Layanan Pengindeksan, namun Layanan Orkestrasi tidak perlu melakukan panggilan tambahan ke Layanan Data. Namun, data yang dikembalikan oleh Layanan Orkestrasi mungkin sudah kedaluwarsa.

Pendekatan mana yang disukai bergantung pada desain spesifik kami. Misalnya, jika data kita bersifat tambahan saja — yaitu, tidak diperbarui setelah dibuat — maka kita tidak perlu khawatir tentang data yang tidak sinkron. Dalam hal ini, kami mungkin memilih opsi 2.

Selain itu, jika hal ini terjadi, maka kami mungkin memutuskan untuk mengembangkan desain kami lebih jauh lagi, dengan mengharuskan semua operasi baca melalui Layanan Pengindeksan kami. Dalam hal ini, kami memiliki pemisahan yang lebih jelas antara Layanan Data dan Layanan Pengindeksan.

Ringkasan

Sebelumnya, kami menyajikan representasi visual Agregat dan layanan mikro yang digambarkan di sebelah kiri, di bawah. Ketika pencarian diperlukan, model mental kita harus disesuaikan dengan representasi di sebelah kanan.

Tentu saja itu gambaran umum. Detail kami yang sebenarnya bisa berbeda. Misalnya, kami dapat membuat Layanan Pengindeksan dan Pencarian terpisah. Atau kita mungkin menggunakan mekanisme yang berbeda dari Kafka untuk menjaga sistem tetap sinkron.

Namun gagasan keseluruhannya tetap sama. Untuk mendukung arsitektur berorientasi Agregat serta kemampuan penelusuran, kita harus menggabungkan Layanan Data dengan Layanan Pengindeksan pendamping untuk mendukung penelusuran.

Dapatkan Akses ke Expert View — Berlangganan DDI Intel