Bagikan melalui


Tantangan dan solusi untuk manajemen data terdistribusi

Petunjuk / Saran

Konten ini adalah kutipan dari eBook, Arsitektur Layanan Mikro .NET untuk Aplikasi .NET Kontainer, tersedia di .NET Docs atau sebagai PDF gratis yang dapat diunduh yang dapat dibaca secara offline.

Arsitektur Layanan Mikro .NET untuk Aplikasi .NET Dalam Kontainer: gambar kecil sampul eBook.

Tantangan #1: Cara menentukan batas setiap layanan mikro

Menentukan batas layanan mikro mungkin merupakan tantangan pertama yang dihadapi siapa pun. Setiap layanan mikro harus menjadi bagian dari aplikasi Anda dan setiap layanan mikro harus otonom dengan semua manfaat dan tantangan yang disampaikannya. Tapi bagaimana Anda mengidentifikasi batas-batas tersebut?

Pertama, Anda perlu fokus pada model domain logis aplikasi dan data terkait. Cobalah untuk mengidentifikasi pulau data yang dipisahkan dan konteks yang berbeda dalam aplikasi yang sama. Setiap konteks dapat memiliki bahasa bisnis yang berbeda (istilah bisnis yang berbeda). Konteks harus ditentukan dan dikelola secara independen. Istilah dan entitas yang digunakan dalam konteks yang berbeda mungkin terdengar mirip, tetapi Anda mungkin menemukan bahwa dalam konteks tertentu, konsep bisnis dengan entitas yang digunakan untuk tujuan yang berbeda dalam konteks lain, dan bahkan mungkin memiliki nama yang berbeda. Misalnya, pengguna dapat disebut sebagai pengguna dalam konteks identitas atau keanggotaan, sebagai pelanggan dalam konteks CRM, sebagai pembeli dalam konteks pemesanan, dan sebagainya.

Cara Anda mengidentifikasi batas antara beberapa konteks aplikasi dengan domain yang berbeda untuk setiap konteks adalah bagaimana Anda dapat mengidentifikasi batas untuk setiap layanan mikro bisnis dan model dan data domain terkait. Anda selalu mencoba untuk meminimalkan kopling antara layanan mikro tersebut. Panduan ini menjelaskan lebih detail tentang identifikasi ini dan desain model domain di bagian Mengidentifikasi batas model domain untuk setiap layanan mikro nanti.

Tantangan #2: Cara membuat kueri yang mengambil data dari beberapa layanan mikro

Tantangan kedua adalah cara menerapkan kueri yang mengambil data dari beberapa layanan mikro, sambil menghindari komunikasi berlebihan ke layanan mikro dari aplikasi klien yang terletak jauh. Contohnya bisa berupa satu layar dari aplikasi seluler yang perlu menampilkan informasi pengguna yang dimiliki oleh layanan mikro seperti basket, katalog, dan identitas pengguna. Contoh lain adalah laporan kompleks yang melibatkan banyak tabel yang terletak di beberapa layanan mikro. Solusi yang tepat tergantung pada kompleksitas kueri. Tetapi dalam hal apa pun, Anda memerlukan cara untuk menggabungkan informasi jika Anda ingin meningkatkan efisiensi dalam komunikasi sistem Anda. Solusi paling populer adalah sebagai berikut.

API Gateway. Untuk agregasi data sederhana dari beberapa layanan mikro yang memiliki database yang berbeda, pendekatan yang direkomendasikan adalah layanan mikro agregasi yang disebut sebagai API Gateway. Namun, Anda perlu berhati-hati dalam menerapkan pola ini, karena dapat menjadi titik tersedak dalam sistem Anda, dan dapat melanggar prinsip otonomi layanan mikro. Untuk mengurangi kemungkinan ini, Anda dapat memiliki beberapa Gateway API yang diperhalus, yang masing-masing berfokus pada "bagian" vertikal atau area bisnis dari sistem. Pola API Gateway dijelaskan secara lebih rinci di bagian API Gateway nanti.

Federasi GraphQL Salah satu opsi untuk mempertimbangkan apakah layanan mikro Anda sudah menggunakan GraphQL adalah Federasi GraphQL. Federasi memungkinkan Anda menentukan "subgraf" dari layanan lain dan menyusunnya menjadi "supergraf" agregat yang bertindak sebagai skema mandiri.

CQRS dengan tabel kueri/baca. Solusi lain untuk menggabungkan data dari beberapa layanan mikro adalah pola Tampilan Materialisasi. Dalam pendekatan ini, Anda memproduksi terlebih dahulu (siapkan data denormalisasi sebelum kueri benar-benar dilakukan), tabel yang hanya bisa dibaca dengan data yang dimiliki oleh beberapa layanan mikro. Tabel memiliki format yang sesuai dengan kebutuhan aplikasi klien.

Pertimbangkan sesuatu seperti layar untuk aplikasi seluler. Jika Anda memiliki database tunggal, Anda mungkin mengumpulkan data untuk layar tersebut menggunakan kueri SQL yang melakukan gabungan kompleks yang melibatkan beberapa tabel. Namun, ketika Anda memiliki beberapa database, dan setiap database dimiliki oleh layanan mikro yang berbeda, Anda tidak dapat mengkueri database tersebut dan membuat gabungan SQL. Pertanyaan kompleks Anda menjadi sebuah tantangan. Anda dapat mengatasi persyaratan menggunakan pendekatan CQRS—Anda membuat tabel denormalisasi dalam database berbeda yang digunakan hanya untuk kueri. Tabel dapat dirancang khusus untuk data yang Anda butuhkan untuk kueri kompleks, dengan hubungan satu-ke-satu antara bidang yang diperlukan oleh layar aplikasi Anda dan kolom dalam tabel kueri. Ini juga dapat berfungsi untuk tujuan pelaporan.

Pendekatan ini tidak hanya memecahkan masalah asli (cara mengkueri dan bergabung di seluruh layanan mikro), tetapi juga meningkatkan performa jauh jika dibandingkan dengan gabungan yang kompleks, karena Anda sudah memiliki data yang dibutuhkan aplikasi dalam tabel kueri. Tentu saja, menggunakan Pemisahan Tanggung Jawab Perintah dan Kueri (CQRS) dengan tabel kueri/baca berarti pekerjaan pengembangan tambahan, dan Anda harus merangkul konsistensi akhir. Meskipun demikian, persyaratan tentang performa dan skalabilitas tinggi dalam skenario kolaboratif (atau skenario kompetitif, tergantung pada sudut pandang) adalah tempat Anda harus menerapkan CQRS dengan beberapa database.

"Data dingin" dalam database pusat. Untuk laporan dan kueri kompleks yang mungkin tidak memerlukan data real-time, pendekatan umumnya adalah mengekspor "data panas" Anda (data transaksi dari layanan mikro) sebagai "data dingin" ke dalam database besar yang hanya digunakan untuk pelaporan. Sistem database pusat itu bisa menjadi sistem berbasis Big Data, seperti Hadoop; gudang data seperti yang didasarkan pada Gudang Data Azure SQL; atau bahkan database SQL tunggal yang digunakan hanya untuk laporan (jika ukuran tidak akan menjadi masalah).

Perlu diingat bahwa database terpusat ini hanya akan digunakan untuk kueri dan laporan yang tidak memerlukan data real-time. Pembaruan dan transaksi asli, sebagai sumber kebenaran Anda, harus berada di data layanan mikro Anda. Cara Anda menyinkronkan data akan baik dengan menggunakan komunikasi berbasis peristiwa (tercakup di bagian berikutnya) atau dengan menggunakan alat impor/ekspor infrastruktur database lainnya. Jika Anda menggunakan komunikasi berbasis peristiwa, proses integrasi tersebut akan mirip dengan cara Anda menyebarluaskan data seperti yang dijelaskan sebelumnya untuk tabel kueri CQRS.

Namun, jika desain aplikasi Anda melibatkan penggabungan informasi secara terus-menerus dari beberapa layanan mikro untuk menjalankan kueri yang kompleks, hal ini mungkin menandakan desain yang buruk -a layanan mikro harus diisolasi semaksimal mungkin dari layanan mikro lainnya. (Ini mengecualikan laporan/analitik yang selalu harus menggunakan database pusat yang menggunakan data dingin.) Mengalami masalah ini secara sering bisa menjadi alasan untuk menggabungkan layanan mikro. Anda perlu menyeimbangkan otonomi evolusi dan penyebaran setiap layanan mikro dengan dependensi, kohesi, dan agregasi data yang kuat.

Tantangan #3: Cara mencapai konsistensi di beberapa layanan mikro

Seperti yang dinyatakan sebelumnya, data yang dimiliki oleh setiap layanan mikro bersifat pribadi untuk layanan mikro tersebut dan hanya dapat diakses menggunakan API layanan mikronya. Oleh karena itu, tantangan yang dihadirkan adalah bagaimana menerapkan proses bisnis end-to-end sambil menjaga konsistensi di beberapa layanan mikro.

Untuk menganalisis masalah ini, mari kita lihat contoh dari aplikasi referensi eShopOnContainers. Layanan mikro Katalog mempertahankan informasi tentang semua produk, termasuk harga produk. Layanan mikro Basket mengelola data temporal tentang barang-barang produk yang ditambahkan pengguna ke keranjang belanja mereka, yang mencakup harga barang pada saat mereka ditambahkan ke keranjang. Ketika harga produk diperbarui dalam katalog, harga itu juga harus diperbarui di keranjang aktif yang menyimpan produk yang sama, ditambah sistem mungkin harus memperingatkan pengguna yang mengatakan bahwa harga item tertentu telah berubah sejak mereka menambahkannya ke keranjang mereka.

Dalam versi monolitik hipotetis aplikasi ini, ketika harga berubah dalam tabel produk, subsistem katalog hanya dapat menggunakan transaksi ACID untuk memperbarui harga saat ini dalam tabel Basket.

Namun, dalam aplikasi berbasis layanan mikro, tabel Produk dan Keranjang dimiliki oleh layanan mikro masing-masing. Layanan mikro tidak boleh menyertakan tabel/penyimpanan yang dimiliki oleh layanan mikro lain dalam transaksinya sendiri, bahkan tidak dalam kueri langsung, seperti yang ditunjukkan pada Gambar 4-9.

Diagram memperlihatkan bahwa data database layanan mikro tidak dapat dibagikan.

Gambar 4-9. Layanan mikro tidak dapat langsung mengakses tabel di layanan mikro lain

Mikroservis Katalog tidak boleh memperbarui tabel Basket secara langsung, karena tabel Basket dimiliki oleh mikroservis Basket. Untuk membuat pembaruan pada layanan mikro Basket, layanan mikro Katalog harus mungkin menggunakan konsistensi akhir yang didasarkan pada komunikasi asinkron seperti event integrasi (komunikasi berbasis pesan dan peristiwa). Ini adalah bagaimana aplikasi referensi eShopOnContainers melakukan jenis konsistensi ini di seluruh layanan mikro.

Seperti yang dinyatakan oleh teori CAP, Anda perlu memilih antara ketersediaan dan konsistensi kuat ACID. Sebagian besar skenario berbasis layanan mikro menuntut ketersediaan dan skalabilitas tinggi dibandingkan dengan konsistensi yang kuat. Aplikasi misi penting harus tetap aktif dan berjalan, dan pengembang dapat mengatasi konsistensi yang kuat dengan menggunakan teknik untuk bekerja dengan konsistensi yang lemah atau akhirnya. Ini adalah pendekatan yang diambil oleh sebagian besar arsitektur berbasis layanan mikro.

Selain itu, transaksi penerapan gaya ACID atau dua fase tidak hanya bertentangan dengan prinsip layanan mikro; sebagian besar database NoSQL (seperti Azure Cosmos DB, MongoDB, dll.) tidak mendukung transaksi penerapan dua fase, biasanya dalam skenario database terdistribusi. Namun, menjaga konsistensi data di seluruh layanan dan database sangat penting. Tantangan ini juga terkait dengan pertanyaan tentang cara menyebarluaskan perubahan di beberapa layanan mikro ketika data tertentu perlu redundan—misalnya, ketika Anda perlu memiliki nama atau deskripsi produk di layanan mikro Katalog dan layanan mikro Keranjang.

Solusi yang baik untuk masalah ini adalah menggunakan konsistensi akhir antara layanan mikro yang diartikulasikan melalui komunikasi berbasis peristiwa dan sistem penerbitan dan langganan. Topik-topik ini tercakup dalam bagian Komunikasi berbasis peristiwa asinkron nanti dalam panduan ini.

Tantangan #4: Cara merancang komunikasi di seluruh batas layanan mikro

Berkomunikasi di seluruh batas layanan mikro adalah tantangan nyata. Dalam konteks ini, komunikasi tidak mengacu pada protokol apa yang harus Anda gunakan (HTTP dan REST, AMQP, olahpesan, dan sebagainya). Sebaliknya, ini membahas gaya komunikasi apa yang harus Anda gunakan, dan terutama seberapa erat layanan mikro Anda seharusnya digabungkan. Tergantung pada tingkat kopling, ketika kegagalan terjadi, dampak kegagalan tersebut pada sistem Anda akan bervariasi secara signifikan.

Dalam sistem terdistribusi seperti aplikasi berbasis layanan mikro, dengan begitu banyak artefak bergerak di sekitar dan dengan layanan terdistribusi di banyak server atau host, komponen pada akhirnya akan gagal. Kegagalan parsial dan pemadaman yang lebih besar akan terjadi, jadi Anda perlu merancang layanan mikro Anda dan komunikasi di seluruh mereka mengingat risiko umum dalam jenis sistem terdistribusi ini.

Pendekatan populer adalah menerapkan layanan mikro berbasis HTTP (REST), karena kesederhanaannya. Pendekatan berbasis HTTP dapat diterima dengan sempurna; masalah di sini terkait dengan cara Anda menggunakannya. Jika Anda menggunakan permintaan dan respons HTTP hanya untuk berinteraksi dengan layanan mikro Anda dari aplikasi klien atau dari API Gateway, itu baik-baik saja. Tetapi jika Anda membuat rantai panjang panggilan HTTP sinkron di seluruh layanan mikro, berkomunikasi di seluruh batas mereka seolah-olah layanan mikro adalah objek dalam aplikasi monolitik, aplikasi Anda pada akhirnya akan mengalami masalah.

Misalnya, bayangkan aplikasi klien Anda melakukan panggilan API HTTP ke layanan mikro individual seperti layanan mikro Pemesanan. Jika layanan mikro Pemesanan pada gilirannya memanggil layanan mikro tambahan menggunakan HTTP dalam siklus permintaan/respons yang sama, Anda membuat rantai panggilan HTTP. Ini mungkin terdengar masuk akal pada awalnya. Namun, ada poin penting yang perlu dipertimbangkan saat turun di jalur ini:

  • Pemblokiran dan kinerja rendah. Karena sifat HTTP yang sinkron, permintaan asli tidak mendapatkan respons hingga semua panggilan HTTP internal selesai. Bayangkan jika jumlah panggilan ini meningkat secara signifikan dan pada saat yang sama salah satu panggilan HTTP perantara ke layanan mikro diblokir. Hasilnya adalah bahwa performa terpengaruh, dan skalabilitas keseluruhan akan terpengaruh secara eksponensial saat permintaan HTTP tambahan meningkat.

  • Menghubungkan layanan mikro dengan HTTP. Layanan mikro bisnis tidak boleh digabungkan dengan layanan mikro bisnis lainnya. Idealnya, mereka seharusnya tidak "tahu" tentang keberadaan layanan mikro lainnya. Jika aplikasi Anda mengandalkan layanan mikro kopling seperti dalam contoh, mencapai otonomi per layanan mikro akan hampir tidak mungkin.

  • Kegagalan dalam satu layanan mikro. Jika Anda menerapkan rantai layanan mikro yang ditautkan oleh panggilan HTTP, ketika salah satu layanan mikro gagal (dan akhirnya mereka akan gagal) seluruh rantai layanan mikro akan gagal. Sistem berbasis layanan mikro harus dirancang untuk terus bekerja sebaik mungkin selama kegagalan parsial. Bahkan jika Anda menerapkan logika klien yang menggunakan percobaan ulang dengan backoff eksponensial atau mekanisme pemutus sirkuit, semakin kompleks rangkaian panggilan HTTP, semakin kompleks pula untuk menerapkan strategi kegagalan yang berbasis HTTP.

Bahkan, jika layanan mikro internal Anda berkomunikasi melalui serangkaian permintaan HTTP seperti yang dijelaskan, dapat dibilang bahwa Anda memiliki aplikasi monolitik, namun berbasis HTTP antar proses alih-alih menggunakan mekanisme komunikasi intra-proses.

Oleh karena itu, untuk memberlakukan otonomi layanan mikro dan memiliki ketahanan yang lebih baik, Anda harus meminimalkan penggunaan rantai komunikasi permintaan/respons di seluruh layanan mikro. Disarankan agar Anda hanya menggunakan interaksi asinkron untuk komunikasi antar layanan mikro, baik dengan menggunakan komunikasi berbasis pesan dan peristiwa asinkron, atau dengan menggunakan polling HTTP (asinkron) secara independen dari siklus permintaan/respons HTTP asli.

Penggunaan komunikasi asinkron dijelaskan dengan detail tambahan nanti dalam panduan ini di bagian Integrasi layanan mikro asinkron memberlakukan otonomi layanan mikro dan komunikasi berbasis pesan Asinkron.

Sumber daya tambahan