Cara memasak bubur dari microservices

Salah satu alasan untuk popularitas layanan-mikro adalah kemungkinan pengembangan yang mandiri dan mandiri. Pada dasarnya, arsitektur layanan mikro adalah pertukaran kemungkinan pengembangan otonom untuk penyebaran, pengujian, debugging, dan pemantauan yang lebih kompleks (dibandingkan dengan monolit). Tetapi perlu diingat bahwa layanan mikro tidak memaafkan pemisahan tanggung jawab. Jika pemisahan tugas tidak benar, perubahan tergantung yang sering terjadi di berbagai layanan. Dan ini jauh lebih menyakitkan dan lebih rumit daripada perubahan terkoordinasi dalam kerangka berbagai modul atau paket di dalam monolit. Perubahan yang konsisten dalam layanan microser diperumit oleh tata letak, penyebaran, pengujian, dll. Yang konsisten

Dan saya ingin berbicara tentang berbagai pola dan antipattern dari pembagian tanggung jawab ke dalam layanan mikro.

Entitas Layanan sebagai Antipattern


"Service Entity" adalah salah satu kemungkinan pola (anti) desain arsitektur layanan mikro, yang mengarah pada kode yang sangat tergantung pada layanan yang berbeda dan secara longgar digabungkan dalam layanan.

Bagi sebagian besar pengembang, tampaknya ketika memilih layanan sesuai dengan esensi area subjek: "kesepakatan", "orang", "klien", "pesanan", "gambar", ia mengikuti prinsip-prinsip tanggung jawab tunggal, dan terlebih lagi, sering kali ini tampak logis. Tetapi pendekatan entitas layanan dapat berubah menjadi antipattern. Ini terjadi karena sebagian besar fitur atau perubahan memengaruhi beberapa entitas, dan bukan satu. Akibatnya, setiap layanan menggabungkan logika dari proses bisnis yang berbeda.

Misalnya, ambil toko online. Kami memutuskan untuk menyoroti layanan "produk", "pesanan", "klien".

Perubahan dan layanan apa yang harus saya lakukan untuk menambahkan pengiriman ke rumah?
Misalnya, Anda dapat melakukan ini:

  • dalam layanan "pesanan" tambahkan alamat pengiriman, waktu yang diinginkan dan petugas pengiriman
  • di layanan klien tambahkan daftar alamat pengiriman yang dipilih untuk klien
  • dalam "produk" layanan menambahkan daftar barang entitas

Untuk antarmuka pemasok, penting untuk membuat metode API terpisah dalam layanan "pesanan", yang akan memberikan daftar pesanan yang ditetapkan untuk penyedia khusus ini. Selain itu, metode akan diperlukan untuk mengeluarkan barang dari pesanan yang tidak sesuai atau yang ditolak oleh klien pada saat pengiriman.

Atau perubahan apa dan layanan apa yang harus saya buat untuk menambahkan diskon pada kode promosi?
Minimal yang Anda butuhkan:

  • tambahkan kode promosi ke layanan "pesanan"
  • dalam layanan "produk" tambahkan apakah diskon berlaku pada kode promosi untuk produk ini
  • di layanan klien tambahkan daftar kode promosi yang dikeluarkan untuk klien

Di antarmuka manajer, menambahkan kode promosi yang dipersonalisasi ke klien adalah metode terpisah dalam layanan klien, yang hanya tersedia untuk manajer toko, tetapi tidak tersedia untuk klien sendiri. Dan dalam layanan "produk", buat metode yang memberikan daftar produk yang dipengaruhi oleh kode promosi, sehingga lebih mudah bagi klien untuk memilih di antarmuka-nya.

Sumber perubahan dalam layanan dapat berupa beberapa proses bisnis - pemilihan dan desain, pembayaran dan penagihan, pengiriman. Setiap area masalah memiliki keterbatasan, invarian, dan persyaratan untuk pesanan. Akibatnya, ternyata dalam layanan "produk" kami menyimpan informasi tentang produk, tentang diskon, dan saldo produk di gudang. Dan dalam "urutan" disimpan logika pengirim barang.

Dengan kata lain, perubahan logika bisnis yang tersebar di beberapa layanan mengarah ke perubahan dependen di beberapa layanan. Dan pada saat yang sama dalam satu layanan adalah kode yang tidak terhubung satu sama lain.

Layanan Penyimpanan


Tampaknya masalah ini dapat diselesaikan jika layanan "lapisan" terpisah dibuat di atas layanan entitas, yang merangkum seluruh logika. Tapi biasanya ini juga berakhir buruk. Karena kemudian layanan entitas menjadi layanan penyimpanan, mis. semua logika bisnis terhapus dari mereka, kecuali untuk penyimpanan.

Jika data disimpan dalam database yang berbeda, pada mesin yang berbeda, maka kami

  • kami kehilangan kinerja karena kami tidak memberikan data langsung dari basis data, tetapi melalui lapisan layanan
  • kami kehilangan fleksibilitas karena API layanan biasanya jauh kurang fleksibel daripada SQL atau bahasa permintaan lainnya
  • kami kehilangan fleksibilitas, karena sulit membuat penggabungan data dari berbagai layanan

Jika layanan entitas yang berbeda memiliki akses ke database lain, maka komunikasi antara layanan terjadi secara implisit - melalui database umum, kemudian untuk membuat perubahan apa pun yang mempengaruhi perubahan skema data, hanya mungkin setelah memeriksa bahwa perubahan ini tidak akan merusak semua layanan lain yang menggunakan database atau tablet ini. .

Selain pengembangan yang kompleks, layanan seperti itu menjadi sangat kritis dan sarat beban - dengan hampir setiap permintaan layanan tingkat atas, Anda harus membuat beberapa permintaan ke entitas layanan yang berbeda, yang berarti bahwa mengeditnya menjadi lebih sulit untuk memenuhi peningkatan persyaratan keandalan dan kinerja.

Karena kesulitan dengan pengembangan dan dukungan layanan entitas dalam bentuk murni mereka, Anda jarang melihat suatu pola; biasanya layanan entitas berubah menjadi satu atau dua “monervice-monoliths” pusat, yang sering berubah dan mengandung logika bisnis utama dan placer dari layanan mikro kecil yang biasanya berupa infrastruktur dan yang kecil yang jarang berubah.

Pemisahan berdasarkan bidang masalah


Perubahan pada diri mereka sendiri tidak dilahirkan, mereka datang dari beberapa bidang masalah. Area masalah adalah area tugas di mana masalah yang memerlukan perubahan kode dirumuskan dalam satu bahasa, menggunakan satu set konsep atau saling berhubungan oleh logika bisnis. Dengan demikian, dalam kerangka satu bidang masalah, kemungkinan besar akan ada satu set kendala, invarian yang dapat Anda andalkan saat menulis kode.

Pemisahan tanggung jawab layanan oleh bidang masalah, bukan oleh entitas, biasanya mengarah pada arsitektur yang lebih didukung dan dimengerti. Area bermasalah paling sering berhubungan dengan proses bisnis. Untuk toko online, area masalah yang paling mungkin adalah "pembayaran dan penagihan", "pengiriman", "proses pemesanan".

Perubahan yang akan memengaruhi beberapa area masalah pada saat yang sama lebih kecil dari perubahan yang akan memengaruhi beberapa entitas.

Selain itu, layanan yang dipecah oleh proses bisnis dapat digunakan kembali di masa depan. Misalnya, jika di sebelah toko online kami ingin melakukan penjualan tiket pesawat lagi, kami dapat menggunakan kembali layanan umum "Penagihan dan Pembayaran". Dan jangan membuat yang lain serupa, tetapi khusus untuk penjualan tiket.

Sebagai contoh, kami dapat membagi layanan menjadi:

  • Suatu layanan atau sekelompok layanan “Pengiriman”, yang akan menyimpan logika kerja dengan pengiriman pesanan tertentu, organisasi pekerjaan pemasok, penilaian kualitas pekerjaan mereka, aplikasi mobile pemasok, dll.
  • Layanan atau sekelompok layanan "Penagihan dan Pembayaran", yang akan menyimpan logika kerja dengan pembayaran, akun pembayaran untuk badan hukum, pembuatan kontrak dan dokumen penutup.
  • Layanan atau grup layanan "Proses Pemesanan", yang menyimpan logika pilihan produk, katalog, merek, logika keranjang pelanggan, dll.
  • Layanan "otorisasi dan otentikasi".
  • Bahkan mungkin masuk akal untuk memisahkan layanan diskon.

Untuk berinteraksi satu sama lain, layanan dapat menggunakan model acara atau bertukar objek sederhana satu sama lain (api tenang, grpc, dll.). Benar, perlu dicatat bahwa tidak mudah untuk mengatur interaksi antara layanan tersebut dengan benar. Paling tidak, desentralisasi data memiliki masalah dengan konsistensi kadang-kadang (konsistensi akhirnya) dan transaksionalitas (dalam kasus ketika itu penting).

Desentralisasi data, pertukaran benda-benda sederhana memiliki kelebihan, kekurangan, dan kekurangannya. Di satu sisi, desentralisasi memungkinkan untuk mengembangkan dan mengoperasikan beberapa layanan secara mandiri. Di sisi lain, biaya penyimpanan dua atau tiga salinan data dan menjaga konsistensi dalam sistem yang berbeda.

Dalam kehidupan nyata, sesuatu sering terjadi di antaranya. Entitas layanan dengan sekumpulan atribut minimum yang digunakan oleh semua layanan oleh konsumen. Dan beberapa lapisan logika minimum - misalnya, model status, dan peristiwa dalam antrian dengan pemberitahuan semua perubahan dalam entitas. Pada saat yang sama, layanan konsumen masih cukup sering menyimpan "cache" data. Segala sesuatu yang mungkin sedang dilakukan sehingga ada perubahan sesedikit mungkin dalam layanan seperti itu, dan ini, pada prinsipnya, sulit dilakukan karena kenyataan bahwa ada banyak konsumen.

Pada saat yang sama, penting untuk memahami bahwa partisi apa pun - baik berdasarkan sifat maupun masalah - bukan merupakan peluru perak, akan selalu ada fitur yang akan memerlukan perubahan bergantung pada beberapa layanan. Hanya saja dengan satu gangguan akan ada lebih banyak perubahan seperti itu dibandingkan dengan yang lain. Dan tugas pengembangan adalah meminimalkan jumlah perubahan yang tergantung.

Perpecahan ideal hanya mungkin jika Anda memiliki dua produk yang sepenuhnya independen. Dalam bisnis apa pun Anda semua terhubung dengan segalanya, satu-satunya pertanyaan adalah berapa banyak yang terhubung.

Dan pertanyaannya adalah pemisahan tanggung jawab dan tingginya hambatan terhadap abstraksi.

API Layanan Desain


Merancang antarmuka dalam layanan mengulangi sejarah dengan penguraian menjadi layanan, hanya pada skala yang lebih kecil. Mengubah antarmuka (bukan hanya ekstensi) sangat rumit dan memakan waktu. Dalam aplikasi yang kompleks, antarmuka harus cukup universal untuk tidak menyebabkan perubahan konstan, dan harus spesifik dan spesifik agar tidak menyebabkan penyebaran tanggung jawab dan semantik.

Oleh karena itu, antarmuka layanan harus dirancang agar semantiknya tahan terhadap perubahan. Dan ini dimungkinkan jika semantik atau area tanggung jawab antarmuka bergantung pada keterbatasan area masalah.

Antarmuka CRUD untuk layanan dengan logika bisnis yang kompleks


Antarmuka yang terlalu lebar dan tidak spesifik berkontribusi pada erosi tanggung jawab atau kompleksitas yang berlebihan.

Misalnya, CRUD API untuk layanan dengan logika bisnis yang kompleks. Antarmuka seperti itu tidak merangkum perilaku. Mereka tidak hanya memungkinkan logika bisnis bocor ke layanan lain dan mengikis tanggung jawab layanan, mereka memprovokasi penyebaran logika bisnis - pembatasan, invarian dan metode bekerja dengan data sekarang di layanan lain. Layanan antarmuka pengguna (API) harus mengimplementasikan logika sendiri.

Jika kami mencoba, tanpa mengubah antarmuka secara signifikan, untuk mentransfer logika bisnis ke layanan, kami akan mendapatkan metode yang terlalu universal dan terlalu rumit.

Misalnya, ada layanan tiket. Tiket bisa dari berbagai jenis. Setiap jenis memiliki kumpulan bidang yang berbeda dan validasi yang sedikit berbeda. Tiket juga memiliki model status - mesin negara untuk transisi dari satu status ke status lainnya.

Biarkan API terlihat seperti ini: Metode POST / PATCH / GET, url /api/v1/tickets/{ticket_idasket.json

Jadi, Anda dapat memperbarui tiket

PATCH /api/v1/tickets/{ticket_id}.json { "type": "bug", "status": "closed", "description": "   " } 

Jika model status akan tergantung pada tiket, maka konflik logika bisnis mungkin terjadi. Pertama, ubah status sesuai dengan model status lama, lalu ubah jenis tiket. Atau sebaliknya?

Ternyata di dalam metode API akan ada kode yang tidak terhubung satu sama lain - bidang entitas yang berubah, daftar bidang yang tersedia, tergantung pada jenis tiket, dan model status. Mereka berubah karena berbagai alasan dan masuk akal untuk mendistribusikannya sesuai dengan metode dan antarmuka API yang berbeda.

Jika mengubah bidang dalam kerangka metode API CRUD bukan hanya perubahan data, tetapi operasi yang terkait dengan perubahan terkoordinasi dalam keadaan entitas, maka operasi ini harus dibawa ke metode terpisah dan tidak boleh diubah secara langsung. Jika mengubah API tanpa kompatibilitas ke belakang sangat buruk (untuk API publik), maka lebih baik untuk segera memikirkannya saat merancang API.

Oleh karena itu, untuk menghindari masalah seperti itu, lebih baik untuk membuat antarmuka kecil, spesifik dan berorientasi pada masalah mungkin, daripada yang universal data-sentris.

Pola (anti) ini lebih sering menjadi ciri dari antarmuka RESTful, karena fakta bahwa secara default hanya ada beberapa "kata kerja" data-centric tindakan untuk membuat, menghapus, memperbarui, membaca. Tidak ada operasi entitas khusus bisnis

Apa yang bisa dilakukan untuk membuat RESTful lebih berorientasi pada masalah?
Pertama, Anda bisa menambahkan metode ke entitas. Antarmuka menjadi kurang tenang. Tapi ada peluang seperti itu. Kami masih tidak berjuang untuk kemurnian balapan, tetapi menyelesaikan masalah praktis

Alih-alih sumber daya universal /api/v1/tickets.json tambahkan lebih banyak sumber daya:

/api/v1/tickets/{ticket_id}/migrate.json - bermigrasi dari satu tipe ke tipe lainnya
/api/v1/tickets/{ticket_id}/status.json - jika ada model status

Kedua, Anda dapat membayangkan operasi apa pun sebagai sumber daya dalam kerangka kerja REST. Apakah ada operasi migrasi tiket dari satu jenis ke yang lain (atau dari satu proyek ke yang lain?). Ok, jadi akan ada sumber daya
/api/v1/tickets/migration.json

Apakah ada operasi bisnis untuk membuat langganan uji coba?
/api/v1/subscriptions/trial.json

Apakah ada operasi pengiriman uang?
/api/v1/money_transfers.json

Dll

Antipattern dengan API data-sentris sebenarnya juga mengacu pada interaksi rpc. Misalnya, keberadaan metode yang terlalu umum seperti editAccount (), atau editTicket (). "Modifikasi objek" tidak membawa beban semantik yang terkait dengan area masalah. Ini berarti bahwa metode ini akan dipanggil karena berbagai alasan, karena berbagai alasan untuk berubah.

Perlu dicatat bahwa antarmuka data-sentris cukup ok, jika area masalahnya hanya menyimpan, menerima dan memodifikasi data.

Model acara


Salah satu cara untuk melepaskan potongan kode adalah mengatur interaksi antar layanan melalui antrian pesan.

Misalnya, jika dalam layanan, saat mendaftarkan pengguna, kami perlu mengirimnya surat selamat datang, membuat permintaan dalam CRM untuk manajer klien, dll., Maka logis untuk tidak melakukan panggilan layanan eksternal, tetapi untuk menempatkan pesan "pengguna 123 terdaftar" di layanan pendaftaran ”, Dan semua layanan yang diperlukan akan membaca pesan ini dan mengambil tindakan yang diperlukan. Pada saat yang sama, mengubah logika bisnis tidak akan memerlukan perubahan layanan pendaftaran.

Paling sering, bukan hanya pesan yang dilemparkan ke antrian, tetapi acara. Karena antrian hanyalah protokol transport, pembatasan yang sama berlaku untuk antarmuka data seperti antarmuka sinkron biasa. Oleh karena itu, untuk menghindari masalah dengan mengubah antarmuka dan pengeditan berikutnya di layanan lain, yang terbaik adalah membuat peristiwa yang berorientasi pada masalah mungkin. Tetap saja peristiwa seperti itu sering disebut peristiwa domain. Pada saat yang sama, penggunaan model acara biasanya tidak banyak mempengaruhi batas-batas tempat layanan (mikro) bertarung.

Karena peristiwa domain secara praktis 1 dalam 1 diterjemahkan ke dalam metode API sinkron, kadang-kadang mereka bahkan menyarankan menggunakan aliran peristiwa alih-alih aliran peristiwa alih-alih panggilan API (Perolehan Acara). Dengan arus peristiwa, Anda selalu dapat memulihkan keadaan objek, tetapi juga memiliki riwayat gratis. Faktanya, biasanya pendekatan ini tidak terlalu fleksibel - Anda harus mendukung semua acara, dan seringkali lebih mudah menyimpan cerita bersama API biasa.

Layanan dan kinerja mikro. Cqrs


Pada prinsipnya, area masalah menyiratkan perubahan dalam kode yang terkait tidak hanya dengan persyaratan bisnis fungsional, tetapi juga dengan yang non-fungsional - misalnya, kinerja. Jika ada dua bagian kode dengan persyaratan kinerja yang berbeda, maka ini berarti bahwa kedua bagian kode ini masuk akal untuk dipisahkan. Dan mereka biasanya dibagi menjadi beberapa layanan terpisah untuk dapat menggunakan berbagai bahasa dan teknologi yang lebih cocok untuk tugas tersebut.

Misalnya, ada metode kalkulator terikat-cpu dalam layanan yang ditulis dalam PHP yang melakukan perhitungan kompleks. Dengan bertambahnya beban dan jumlah data, dia berhenti mengatasinya. Dan tentu saja, sebagai salah satu opsi, masuk akal untuk melakukan perhitungan bukan dalam kode php, tetapi dalam daemon sistem kinerja tinggi yang terpisah.

Sebagai salah satu contoh pembagian layanan dengan prinsip produktivitas - pemisahan layanan menjadi baca dan modifikasi (CQRS). Pemisahan ini sering ditawarkan karena persyaratan kinerja layanan membaca dan menulis berbeda. Beban baca sering urutan besarnya lebih tinggi dari beban tulis. Dan persyaratan untuk kecepatan respons permintaan baca jauh lebih tinggi daripada untuk menulis.

Klien menghabiskan 99% waktu untuk mencari barang, dan hanya 1% dari waktu dalam proses pemesanan. Untuk klien dalam kondisi pencarian, kecepatan tampilan penting, dan fitur yang terkait dengan filter, berbagai opsi untuk menampilkan barang, dll. Karena itu, masuk akal untuk menyoroti layanan terpisah yang bertanggung jawab untuk pencarian, penyaringan, dan tampilan barang. Layanan seperti itu kemungkinan besar akan bekerja pada semacam ELK, basis data berorientasi dokumen dengan data yang didenormalkan.

Jelas, divisi naif dalam membaca dan memodifikasi layanan mungkin tidak selalu baik.

Sebuah contoh Untuk seorang manajer yang bekerja dengan mengisi rangkaian produk, fitur utama adalah kemampuan untuk dengan mudah menambahkan barang, menghapus, mengubah dan melihat. Tidak ada banyak beban, jika kita memisahkan bacaan dan mengubah menjadi layanan yang terpisah, kita tidak akan mendapatkan apa pun dari pemisahan tersebut, kecuali untuk masalah ketika perlu untuk membuat perubahan terkoordinasi dalam layanan.

Source: https://habr.com/ru/post/id469001/


All Articles