Tantangan dan solusi untuk manajemen data terdistribusi

Tip

Konten ini adalah kutipan dari eBook, .NET Microservices Architecture for Containerized .NET Applications, tersedia di .NET Docs atau sebagai PDF yang dapat diunduh gratis dan dapat dibaca secara offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Tantangan #1: Cara menentukan batasan setiap layanan mikro

Mendefinisikan batasan 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 dikecilkan 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 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 batasan antara beberapa konteks aplikasi dengan domain yang berbeda untuk setiap konteks adalah bagaimana Anda dapat mengidentifikasi batasan untuk setiap layanan mikro bisnis dan model domain dan data terkait. Anda selalu mencoba untuk meminimalkan koupling antara layanan mikro tersebut. Panduan ini membahas lebih rinci 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 mengimplementasi kueri yang mengambil data dari beberapa layanan mikro, sambil menghindari komunikasi aktif ke layanan mikro dari aplikasi klien jarak jauh. Contohnya bisa berupa satu layar dari aplikasi seluler yang perlu menampilkan informasi pengguna yang dimiliki oleh keranjang, katalog, dan layanan mikro 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 bagaimanapun juga, Anda memerlukan cara untuk mengumpulkan informasi jika Anda ingin meningkatkan efisiensi dalam komunikasi sistem Anda. Solusi paling populer adalah sebagai berikut.

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

GraphQL Federation 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 Terwujud. Dalam pendekatan ini, Anda membuat, terlebih dahulu (siapkan data yang didenormalisasi sebelum kueri aktual terjadi), tabel baca-saja dengan data yang dimiliki oleh beberapa layanan mikro. Tabel memiliki format yang cocok untuk kebutuhan aplikasi klien.

Pertimbangkan sesuatu seperti layar untuk aplikasi seluler. Jika Anda memiliki satu database, 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. Kueri kompleks Anda menjadi tantangan. Anda dapat mengatasi persyaratan menggunakan pendekatan CQRS—Anda membuat tabel yang dinormalisasi dalam database lain yang hanya digunakan 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 Command and Query Responsibility Segregation (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.

"Cold Data" dalam database pusat. Untuk laporan kompleks dan kueri yang mungkin tidak memerlukan data real time, pendekatan umumnya adalah mengekspor "hot data" 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 dalam 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 informasi yang terus-menerus menggabungkan informasi dari beberapa layanan mikro untuk kueri yang kompleks, itu mungkin merupakan gejala dari desain yang buruk -layanan mikro harus seisolasi mungkin dari layanan mikro lainnya. (Ini mengecualikan laporan/analitik yang selalu harus menggunakan database pusat data dingin.) Mengalami masalah ini sering kali mungkin 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 ujung ke ujung 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 Ke basket mengelola data temporal tentang barang-barang produk yang ditambahkan pengguna ke kerajang belanja mereka, yang mencakup harga item pada saat mereka ditambahkan kerajang. 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 Ke basket dimiliki oleh layanan mikro masing-masing. Tidak ada layanan mikro yang 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 showing that microservices database data can't be shared.

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

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

Seperti yang dinyatakan oleh teorema 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, yang khas 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 berlebihan—misalnya, ketika Anda perlu memiliki nama atau deskripsi produk di layanan mikro Katalog dan layanan mikro Ke basket.

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 digabungkan layanan mikro Anda. Tergantung pada tingkat kopling, ketika kegagalan terjadi, dampak kegagalan itu 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 layanan mempertimbangkan risiko umum dalam jenis sistem terdistribusi ini.

Pendekatan populer adalah menerapkan layanan mikro berbasis HTTP (REST), karena kesederhanaannya. Pendekatan berbasis HTTP sangat dapat diterima; 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 tidak masalah. 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 bahwa 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 ketika turun ke jalur ini:

  • Pemblokiran dan performa rendah. Karena sifat HTTP yang sinkron, permintaan asli tidak mendapatkan respons sampai 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.

  • Mengomporasi 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 bergantung pada coupling layanan mikro 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 akan gagal) seluruh rantai layanan mikro akan gagal. Sistem berbasis layanan mikro harus dirancang untuk terus bekerja sebagus mungkin selama kegagalan parsial. Bahkan jika Anda menerapkan logika klien yang menggunakan percobaan ulang dengan backoff eksponensial atau mekanisme pemutus sirkuit, semakin kompleks rantai panggilan HTTP, semakin kompleks untuk menerapkan strategi kegagalan berdasarkan HTTP.

Bahkan, jika layanan mikro internal Anda berkomunikasi dengan membuat rantai permintaan HTTP seperti yang dijelaskan, dapat dikatakan bahwa Anda memiliki aplikasi monolitik, tetapi satu berdasarkan HTTP antara proses alih-alih 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