Bagikan melalui


Merancang aplikasi berorientasi layanan mikro

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.

Bagian ini berfokus pada pengembangan aplikasi perusahaan sisi server hipotetis.

Spesifikasi aplikasi

Aplikasi hipotetis menangani permintaan dengan menjalankan logika bisnis, mengakses database, lalu mengembalikan respons HTML, JSON, atau XML. Kami akan mengatakan bahwa aplikasi harus mendukung berbagai klien, termasuk browser desktop yang menjalankan Aplikasi Halaman Tunggal (SPAs), aplikasi web tradisional, aplikasi web seluler, dan aplikasi seluler asli. Aplikasi ini mungkin juga mengekspos API untuk dikonsumsi pihak ketiga. Ini juga harus dapat mengintegrasikan layanan mikro atau aplikasi eksternalnya secara asinkron, sehingga pendekatan tersebut akan membantu ketahanan layanan mikro dalam kasus kegagalan parsial.

Aplikasi akan terdiri dari jenis komponen ini:

  • Komponen presentasi. Komponen-komponen ini bertanggung jawab untuk menangani UI dan menggunakan layanan jarak jauh.

  • Logika domain dan bisnis. Komponen ini adalah logika domain aplikasi.

  • Logika akses database. Komponen ini terdiri dari komponen akses data yang bertanggung jawab untuk mengakses database (SQL atau NoSQL).

  • Logika integrasi aplikasi. Komponen ini mencakup saluran pesan, yang didasarkan pada broker pesan.

Aplikasi akan membutuhkan skalabilitas tinggi, sambil memungkinkan subsistem vertikalnya untuk menskalakan secara otonom, karena subsistem tertentu akan membutuhkan lebih banyak skalabilitas daripada yang lain.

Aplikasi harus dapat disebarkan di beberapa lingkungan infrastruktur (beberapa cloud publik dan lokal) dan idealnya harus lintas platform, dapat berpindah dari Linux ke Windows (atau sebaliknya) dengan mudah.

Konteks tim pengembangan

Kami juga mengasumsikan hal berikut tentang proses pengembangan untuk aplikasi:

  • Anda memiliki beberapa tim pengembang yang berfokus pada berbagai bidang bisnis aplikasi.

  • Anggota tim baru harus menjadi produktif dengan cepat, dan aplikasi harus mudah dipahami dan dimodifikasi.

  • Aplikasi ini akan memiliki evolusi jangka panjang dan aturan bisnis yang terus berubah.

  • Anda memerlukan pemeliharaan jangka panjang yang baik, yang berarti memiliki kelincahan saat menerapkan perubahan baru di masa depan sambil dapat memperbarui beberapa subsistem dengan dampak minimum pada subsistem lainnya.

  • Anda ingin mempraktikkan integrasi berkelanjutan dan penyebaran aplikasi berkelanjutan.

  • Anda ingin memanfaatkan teknologi yang muncul (kerangka kerja, bahasa pemrograman, dll.) sambil mengembangkan aplikasi. Anda tidak ingin melakukan migrasi penuh aplikasi saat pindah ke teknologi baru, karena itu akan mengakibatkan biaya tinggi dan berdampak pada prediksi dan stabilitas aplikasi.

Memilih arsitektur

Apa yang harus menjadi arsitektur penyebaran aplikasi? Spesifikasi untuk aplikasi, bersama dengan konteks pengembangan, sangat menyarankan agar Anda harus merancang aplikasi dengan menguraikannya menjadi subsistem otonom dalam bentuk kolaborasi layanan mikro dan kontainer , di mana layanan mikro adalah kontainer.

Dalam pendekatan ini, setiap layanan (kontainer) mengimplementasikan satu set fungsi terkait kohesif dan sempit. Misalnya, aplikasi mungkin terdiri dari layanan seperti layanan katalog, layanan pemesanan, layanan keranjang, layanan profil pengguna, dan lain-lain.

Layanan mikro berkomunikasi menggunakan protokol seperti HTTP (REST), tetapi juga secara asinkron (misalnya, menggunakan AMQP) jika memungkinkan, terutama saat menyebarkan pembaruan dengan peristiwa integrasi.

Layanan mikro dikembangkan dan disebarkan sebagai kontainer secara independen satu sama lain. Pendekatan ini berarti bahwa tim pengembangan dapat mengembangkan dan menyebarkan layanan mikro tertentu tanpa memengaruhi subsistem lain.

Setiap layanan mikro memiliki database sendiri, memungkinkannya untuk sepenuhnya dipisahkan dari layanan mikro lainnya. Jika perlu, konsistensi antara database dari layanan mikro yang berbeda dicapai menggunakan peristiwa integrasi tingkat aplikasi (melalui bus peristiwa logis), seperti yang ditangani dalam Pemisahan Tanggung Jawab Perintah dan Kueri (CQRS). Karena itu, kendala bisnis harus merangkul konsistensi akhir antara beberapa layanan mikro dan database terkait.

eShopOnContainers: Aplikasi referensi untuk .NET dan layanan mikro yang disebarkan menggunakan kontainer

Sehingga Anda dapat fokus pada arsitektur dan teknologi alih-alih memikirkan domain bisnis hipotetis yang mungkin tidak Anda ketahui, kami telah memilih domain bisnis terkenal—yaitu, aplikasi e-niaga (e-shop) yang disederhanakan yang menyajikan katalog produk, mengambil pesanan dari pelanggan, memverifikasi inventarisasi, dan melakukan fungsi bisnis lainnya. Kode sumber aplikasi berbasis kontainer ini tersedia di repositori GitHub eShopOnContainers .

Aplikasi ini terdiri dari beberapa subsistem, termasuk beberapa antarmuka pengguna depan toko (aplikasi Web dan aplikasi seluler bawaan), bersama dengan layanan mikro back-end dan kontainer untuk semua operasi sisi server yang diperlukan dengan beberapa Gateway API sebagai titik masuk terkonsolidasi ke layanan mikro internal. Gambar 6-1 menunjukkan arsitektur aplikasi referensi.

Diagram aplikasi klien menggunakan eShopOnContainers dalam satu host Docker.

Gambar 6-1. Arsitektur aplikasi referensi eShopOnContainers untuk lingkungan pengembangan

Diagram di atas menunjukkan bahwa klien Seluler dan SPA berkomunikasi ke titik akhir gateway API tunggal, yang kemudian berkomunikasi dengan layanan mikro. Klien web tradisional berkomunikasi dengan layanan mikro MVC, yang berkomunikasi dengan layanan mikro melalui gateway API.

Lingkungan hosting. Pada Gambar 6-1, Anda melihat beberapa kontainer yang disebarkan dalam satu host Docker. Itu akan terjadi saat menyebarkan ke satu host Docker dengan perintah docker-compose up. Namun, jika Anda menggunakan orkestrator atau kluster kontainer, setiap kontainer dapat berjalan di host (node) yang berbeda, dan node apa pun dapat menjalankan sejumlah kontainer, seperti yang kami jelaskan sebelumnya di bagian arsitektur.

Arsitektur komunikasi. Aplikasi eShopOnContainers menggunakan dua jenis komunikasi, tergantung pada jenis tindakan fungsional (kueri versus pembaruan dan transaksi):

  • Komunikasi client HTTP ke microservice melalui Gerbang API. Pendekatan ini digunakan untuk kueri dan saat menerima pembaruan atau perintah transaksi dari aplikasi klien. Pendekatan menggunakan API Gateway dijelaskan secara rinci di bagian selanjutnya.

  • Komunikasi berbasis peristiwa asinkron. Komunikasi ini terjadi melalui bus peristiwa untuk menyebarluaskan pembaruan di seluruh layanan mikro atau untuk berintegrasi dengan aplikasi eksternal. Bus acara dapat diimplementasikan dengan teknologi infrastruktur pialang pesan apa pun seperti RabbitMQ, atau menggunakan bus layanan tingkat tinggi (abstraksi tinggi) seperti Azure Service Bus, NServiceBus, MassTransit, atau Brighter.

Aplikasi ini disebarkan sebagai sekumpulan layanan mikro dalam bentuk kontainer. Aplikasi klien dapat berkomunikasi dengan layanan mikro yang berjalan sebagai kontainer melalui URL publik yang diterbitkan oleh API Gateway.

Kedaulatan data per layanan mikro

Dalam aplikasi sampel, setiap layanan mikro memiliki database atau sumber datanya sendiri, meskipun semua database SQL Server disebarkan sebagai kontainer tunggal. Keputusan desain ini dibuat hanya untuk memudahkan pengembang mendapatkan kode dari GitHub, mengkloningnya, dan membukanya di Visual Studio atau Visual Studio Code. Atau, memudahkan untuk mengkompilasi gambar Docker kustom menggunakan .NET CLI dan Docker CLI, lalu menyebarkan dan menjalankannya di lingkungan pengembangan Docker. Bagaimanapun, menggunakan kontainer untuk sumber data memungkinkan pengembang membangun dan menyebarkan dalam hitungan menit tanpa harus menyediakan database eksternal atau sumber data lain dengan dependensi keras pada infrastruktur (cloud atau lokal).

Dalam lingkungan produksi nyata, untuk ketersediaan tinggi dan untuk skalabilitas, database harus didasarkan pada server database di cloud atau lokal, tetapi tidak dalam kontainer.

Oleh karena itu, unit penyebaran untuk layanan mikro (dan bahkan untuk database dalam aplikasi ini) adalah kontainer Docker, dan aplikasi referensi adalah aplikasi multi-kontainer yang merangkul prinsip layanan mikro.

Sumber daya tambahan

Manfaat solusi berbasis layanan mikro

Solusi berbasis layanan mikro seperti ini memiliki banyak manfaat:

Setiap layanan mikro relatif kecil—mudah dikelola dan berkembang. Khususnya:

  • Sangat mudah bagi pengembang untuk memahami dan memulai dengan cepat dengan produktivitas yang baik.

  • Kontainer dimulai dengan cepat, yang membuat pengembang lebih produktif.

  • IDE seperti Visual Studio dapat memuat proyek yang lebih kecil dengan cepat, membuat pengembang produktif.

  • Setiap layanan mikro dapat dirancang, dikembangkan, dan disebarkan secara independen dari layanan mikro lainnya, yang memberikan kelincahan karena lebih mudah untuk sering menyebarkan versi baru layanan mikro.

Dimungkinkan untuk menskalakan area individual aplikasi. Misalnya, layanan katalog atau layanan keranjang mungkin perlu diskalakan, tetapi tidak untuk proses pemesanan. Infrastruktur layanan mikro akan jauh lebih efisien sehubungan dengan sumber daya yang digunakan saat memperluas skala daripada arsitektur monolitik.

Anda dapat membagi pekerjaan pengembangan antara beberapa tim. Setiap layanan dapat dimiliki oleh satu tim pengembangan. Setiap tim dapat mengelola, mengembangkan, menyebarkan, dan menskalakan layanan mereka secara independen dari tim lainnya.

Masalah lebih terisolasi. Jika ada masalah dalam satu layanan, hanya layanan tersebut yang awalnya terpengaruh (kecuali ketika desain yang salah digunakan, dengan dependensi langsung antara layanan mikro), dan layanan lain dapat terus menangani permintaan. Sebaliknya, satu komponen yang tidak berfungsi dalam arsitektur penyebaran monolitik dapat menurunkan seluruh sistem, terutama ketika melibatkan sumber daya, seperti kebocoran memori. Selain itu, ketika masalah dalam layanan mikro diselesaikan, Anda dapat menyebarkan hanya layanan mikro yang terpengaruh tanpa memengaruhi aplikasi lainnya.

Anda dapat menggunakan teknologi terbaru. Karena Anda dapat mulai mengembangkan layanan secara independen dan menjalankannya secara berdampingan (berkat kontainer dan .NET), Anda dapat mulai menggunakan teknologi dan kerangka kerja terbaru dengan mudah alih-alih terjebak pada tumpukan atau kerangka kerja yang lebih lama untuk seluruh aplikasi.

Kelemahan solusi berbasis layanan mikro

Solusi berbasis layanan mikro seperti ini juga memiliki beberapa kelemahan:

Aplikasi terdistribusi. Mendistribusikan aplikasi menambah kompleksitas bagi pengembang saat merancang dan membangun layanan. Misalnya, pengembang harus menerapkan komunikasi antar-layanan menggunakan protokol seperti HTTP atau AMQP, yang menambah kompleksitas untuk pengujian dan penanganan pengecualian. Ini juga menambahkan latensi ke sistem.

Kompleksitas penyebaran. Aplikasi yang memiliki puluhan jenis layanan mikro dan membutuhkan skalabilitas tinggi (perlu dapat membuat banyak instans per layanan dan menyeimbangkan layanan tersebut di banyak host) berarti tingkat kompleksitas penyebaran yang tinggi untuk operasi dan manajemen TI. Jika Anda tidak menggunakan infrastruktur berorientasi layanan mikro (seperti orkestrator dan penjadwal), kompleksitas tambahan tersebut dapat memerlukan upaya pengembangan yang jauh lebih banyak daripada aplikasi bisnis itu sendiri.

Transaksi atomik. Transaksi atomik di antara beberapa layanan mikro biasanya tidak memungkinkan. Persyaratan bisnis harus merangkul konsistensi akhir antara beberapa layanan mikro. Untuk informasi selengkapnya, lihat tantangan pemrosesan pesan idempotensi.

Peningkatan kebutuhan sumber daya global (total memori, drive, dan sumber daya jaringan untuk semua server atau host). Dalam banyak kasus, ketika Anda mengganti aplikasi monolitik dengan pendekatan layanan mikro, jumlah sumber daya global awal yang diperlukan oleh aplikasi berbasis layanan mikro baru akan lebih besar dari kebutuhan infrastruktur aplikasi monolitik asli. Pendekatan ini karena tingkat granularitas dan layanan terdistribusi yang lebih tinggi membutuhkan lebih banyak sumber daya global. Namun, mengingat biaya sumber daya yang rendah secara umum dan manfaat mampu menskalakan area tertentu dari aplikasi dibandingkan dengan biaya jangka panjang ketika mengembangkan aplikasi monolitik, peningkatan penggunaan sumber daya biasanya merupakan tradeoff yang baik untuk aplikasi jangka panjang yang besar.

Masalah dengan komunikasi langsung antara klien dan mikrolayanan. Ketika aplikasi besar, dengan puluhan layanan mikro, ada tantangan dan batasan jika aplikasi memerlukan komunikasi klien-ke-layanan mikro langsung. Salah satu masalah adalah potensi ketidakcocokan antara kebutuhan klien dan API yang diekspos oleh masing-masing layanan mikro. Dalam kasus tertentu, aplikasi klien mungkin perlu membuat banyak permintaan terpisah untuk menyusun UI, yang dapat tidak efisien melalui Internet dan tidak akan praktis melalui jaringan seluler. Oleh karena itu, permintaan dari aplikasi klien ke sistem back-end harus diminimalkan.

Masalah lain dengan komunikasi klien-ke-layanan mikro langsung adalah bahwa beberapa layanan mikro mungkin menggunakan protokol yang tidak ramah Web. Satu layanan mungkin menggunakan protokol biner, sementara layanan lain mungkin menggunakan pesan AMQP. Protokol tersebut tidak ramah firewall dan paling baik digunakan secara internal. Biasanya, aplikasi harus menggunakan protokol seperti HTTP dan WebSocket untuk komunikasi di luar firewall.

Kelemahan lain dengan pendekatan klien-ke-layanan langsung ini adalah sulit untuk merefaktor kontrak untuk layanan mikro tersebut. Seiring waktu pengembang mungkin ingin mengubah bagaimana sistem dipartisi menjadi layanan. Misalnya, layanan mungkin menggabungkan dua layanan atau membagi layanan menjadi dua layanan atau lebih. Namun, jika klien berkomunikasi langsung dengan layanan, melakukan pemfaktoran ulang semacam ini dapat merusak kompatibilitas dengan aplikasi klien.

Seperti disebutkan di bagian arsitektur, saat merancang dan membangun aplikasi kompleks berdasarkan layanan mikro, Anda mungkin mempertimbangkan penggunaan beberapa GATEWAY API terperinci alih-alih pendekatan komunikasi klien-ke-layanan mikro langsung yang lebih sederhana.

Mempartisi layanan mikro. Akhirnya, tidak peduli, pendekatan mana yang Anda ambil untuk arsitektur layanan mikro Anda, tantangan lain adalah memutuskan cara mempartisi aplikasi end-to-end menjadi beberapa layanan mikro. Seperti yang disebutkan di bagian arsitektur panduan, ada beberapa teknik dan pendekatan yang dapat Anda ambil. Pada dasarnya, Anda perlu mengidentifikasi area aplikasi yang dipisahkan dari area lain dan yang memiliki jumlah dependensi keras yang rendah. Dalam banyak kasus, pendekatan ini selaras dengan pembagian layanan berdasarkan kasus penggunaan. Misalnya, dalam aplikasi e-shop kami, kami memiliki layanan pemesanan yang bertanggung jawab atas semua logika bisnis yang terkait dengan proses pesanan. Kami juga memiliki layanan katalog dan layanan keranjang yang mengimplementasikan kemampuan lain. Idealnya, setiap layanan hanya boleh memiliki serangkaian tanggung jawab kecil. Pendekatan ini mirip dengan prinsip tanggung jawab tunggal (SRP) yang diterapkan pada kelas, yang menyatakan bahwa kelas hanya boleh memiliki satu alasan untuk berubah. Tetapi dalam hal ini, ini adalah tentang layanan mikro, sehingga cakupannya akan lebih besar dari satu kelas. Yang terpenting, layanan mikro harus otonom, end to end, termasuk tanggung jawab atas sumber datanya sendiri.

Arsitektur eksternal versus arsitektur internal dan pola desain

Arsitektur eksternal adalah arsitektur layanan mikro yang terdiri dari beberapa layanan, mengikuti prinsip-prinsip yang dijelaskan di bagian arsitektur panduan ini. Namun, tergantung pada sifat setiap layanan mikro, dan secara independen dari arsitektur layanan mikro tingkat tinggi yang Anda pilih, adalah umum dan kadang-kadang disarankan untuk memiliki arsitektur internal yang berbeda, masing-masing berdasarkan pola yang berbeda, untuk layanan mikro yang berbeda. Layanan mikro bahkan dapat menggunakan teknologi dan bahasa pemrograman yang berbeda. Gambar 6-2 menggambarkan keragaman ini.

Diagram membandingkan pola arsitektur eksternal dan internal.

Gambar 6-2. Arsitektur dan desain eksternal versus internal

Misalnya, dalam sampel eShopOnContainers kami, katalog, keranjang, dan profil pengguna adalah layanan mikro yang sederhana (dasarnya, sistem CRUD). Oleh karena itu, arsitektur dan desain internal mereka mudah. Namun, Anda mungkin memiliki layanan mikro lain, seperti layanan mikro pemesanan, yang lebih kompleks dan mewakili aturan bisnis yang terus berubah dengan tingkat kompleksitas domain yang tinggi. Dalam kasus seperti ini, Anda mungkin ingin menerapkan pola yang lebih canggih dalam layanan mikro tertentu, seperti yang didefinisikan dengan pendekatan desain berbasis domain (DDD), seperti yang kita lakukan di eShopOnContainers yang memesan layanan mikro. (Kami akan meninjau pola DDD ini di bagian yang menjelaskan implementasi layanan mikro pemesanan eShopOnContainers.)

Alasan lain untuk teknologi yang berbeda per layanan mikro mungkin sifat dari setiap layanan mikro. Misalnya, mungkin lebih baik menggunakan bahasa pemrograman fungsional seperti F#, atau bahkan bahasa seperti R jika Anda menargetkan AI dan domain pembelajaran mesin, alih-alih bahasa pemrograman yang lebih berorientasi objek seperti C#.

Intinya adalah bahwa setiap layanan mikro dapat memiliki arsitektur internal yang berbeda berdasarkan pola desain yang berbeda. Tidak semua layanan mikro harus diimplementasikan menggunakan pola DDD tingkat lanjut, karena itu akan menjadi rekayasa berlebihan. Demikian pula, layanan mikro kompleks dengan logika bisnis yang selalu berubah tidak boleh diimplementasikan sebagai komponen CRUD, atau Anda dapat berakhir dengan kode berkualitas rendah.

Dunia baru: beberapa pola arsitektur dan layanan mikro poliglot

Ada banyak pola arsitektur yang digunakan oleh arsitek perangkat lunak dan pengembang. Berikut ini adalah beberapa contoh (pencampuran gaya arsitektur dan pola arsitektur):

Anda juga dapat membangun layanan mikro dengan banyak teknologi dan bahasa, seperti ASP.NET Core Web API, NancyFx, ASP.NET Core SignalR (tersedia dengan .NET Core 2 atau yang lebih baru), F#, Node.js, Python, Java, C++, GoLang, dan banyak lagi.

Poin pentingnya adalah bahwa tidak ada pola atau gaya arsitektur tertentu, atau teknologi tertentu, yang tepat untuk semua situasi. Gambar 6-3 menunjukkan beberapa pendekatan dan teknologi (meskipun tidak dalam urutan tertentu) yang dapat digunakan dalam layanan mikro yang berbeda.

Diagram memperlihatkan 12 layanan mikro kompleks dalam arsitektur dunia poliglot.

Gambar 6-3. Pola multiarsitektur dan dunia mikrolayanan poliglot

Pola multi-arsitektur dan layanan mikro yang mendukung berbagai bahasa berarti Anda dapat mencampur dan mencocokkan bahasa dan teknologi sesuai dengan kebutuhan setiap layanan mikro serta tetap memungkinkan mereka berkomunikasi satu sama lain. Seperti yang ditunjukkan pada Gambar 6-3, dalam aplikasi yang terdiri dari banyak layanan mikro (Konteks Terikat dalam terminologi desain berbasis domain, atau hanya "subsistem" sebagai layanan mikro otonom), Anda dapat mengimplementasikan setiap layanan mikro dengan cara yang berbeda. Masing-masing mungkin memiliki pola arsitektur yang berbeda dan menggunakan bahasa dan database yang berbeda tergantung pada sifat aplikasi, persyaratan bisnis, dan prioritas. Dalam beberapa kasus, layanan mikro mungkin serupa. Tetapi itu biasanya tidak terjadi, karena setiap batas konteks dan persyaratan subsistem biasanya berbeda.

Misalnya, untuk aplikasi pemeliharaan CRUD sederhana, mungkin tidak masuk akal untuk merancang dan menerapkan pola DDD. Tetapi untuk domain inti atau bisnis inti, Anda mungkin perlu menerapkan pola yang lebih canggih untuk mengatasi kompleksitas bisnis dengan aturan bisnis yang terus berubah.

Terutama ketika Anda berurusan dengan aplikasi besar yang terdiri dari beberapa subsistem, Anda tidak boleh menerapkan arsitektur tingkat atas tunggal berdasarkan pola arsitektur tunggal. Misalnya, CQRS tidak boleh diterapkan sebagai arsitektur tingkat atas untuk seluruh aplikasi, tetapi mungkin berguna untuk serangkaian layanan tertentu.

Tidak ada poin perak atau pola arsitektur yang tepat untuk setiap kasus tertentu. Anda tidak dapat memiliki "satu pola arsitektur untuk memerintah semuanya." Tergantung pada prioritas setiap layanan mikro, Anda harus memilih pendekatan yang berbeda untuk masing-masing, seperti yang dijelaskan di bagian berikut.