Transisi dari monolit ke layanan mikro: sejarah dan praktik

Pada artikel ini, saya akan berbicara tentang bagaimana proyek yang saya kerjakan berubah dari sebuah monolit besar menjadi serangkaian layanan mikro.

Proyek ini memulai sejarahnya cukup lama, pada awal tahun 2000. Versi pertama ditulis dalam Visual Basic 6. Seiring waktu, menjadi jelas bahwa pengembangan bahasa ini akan sulit untuk didukung di masa depan, karena IDE dan bahasanya sendiri kurang berkembang. Pada akhir tahun 2000-an, diputuskan untuk beralih ke C# yang lebih menjanjikan. Versi baru ditulis bersamaan dengan revisi yang lama, lambat laun semakin banyak kode yang ditulis dalam .NET. Backend di C# awalnya berfokus pada arsitektur layanan, namun selama pengembangan, perpustakaan umum dengan logika digunakan, dan layanan diluncurkan dalam satu proses. Hasilnya adalah sebuah aplikasi yang kami sebut β€œlayanan monolit.”

Salah satu dari sedikit keuntungan dari kombinasi ini adalah kemampuan layanan untuk saling memanggil melalui API eksternal. Terdapat prasyarat yang jelas untuk transisi ke layanan yang lebih tepat, dan di masa depan, arsitektur layanan mikro.

Kami memulai pekerjaan dekomposisi sekitar tahun 2015. Kami belum mencapai kondisi ideal - masih ada bagian dari proyek besar yang sulit disebut monolit, namun juga tidak terlihat seperti layanan mikro. Meskipun demikian, kemajuannya signifikan.
Saya akan membicarakannya di artikel.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

kadar

Arsitektur dan masalah solusi yang ada


Awalnya, arsitekturnya terlihat seperti ini: UI adalah aplikasi terpisah, bagian monolitik ditulis dalam Visual Basic 6, aplikasi .NET adalah sekumpulan layanan terkait yang bekerja dengan database yang cukup besar.

Kerugian dari solusi sebelumnya

Titik kegagalan
Kami mengalami satu titik kegagalan: aplikasi .NET berjalan dalam satu proses. Jika ada modul yang gagal, seluruh aplikasi gagal dan harus dimulai ulang. Karena kami mengotomatiskan sejumlah besar proses untuk pengguna yang berbeda, karena kegagalan pada salah satu proses, semuanya tidak dapat berfungsi selama beberapa waktu. Dan jika terjadi kesalahan perangkat lunak, bahkan pencadangan tidak membantu.

Antrian perbaikan
Kelemahan ini lebih bersifat organisasional. Aplikasi kami memiliki banyak pelanggan, dan mereka semua ingin memperbaikinya secepat mungkin. Sebelumnya, hal ini tidak mungkin dilakukan secara paralel, dan semua pelanggan mengantre. Proses ini berdampak negatif bagi bisnis karena mereka harus membuktikan bahwa tugas mereka berharga. Dan tim pengembangan menghabiskan waktu mengatur antrean ini. Hal ini memerlukan banyak waktu dan tenaga, dan produk pada akhirnya tidak dapat berubah secepat yang diinginkan.

Penggunaan sumber daya yang tidak optimal
Saat menghosting layanan dalam satu proses, kami selalu menyalin konfigurasi lengkap dari server ke server. Kami ingin menempatkan layanan yang paling banyak dimuat secara terpisah agar tidak menyia-nyiakan sumber daya dan mendapatkan kontrol yang lebih fleksibel atas skema penerapan kami.

Sulit untuk menerapkan teknologi modern
Masalah yang akrab bagi semua pengembang: ada keinginan untuk memperkenalkan teknologi modern ke dalam proyek, tetapi tidak ada peluang. Dengan solusi monolitik yang besar, setiap pembaruan perpustakaan saat ini, belum lagi transisi ke yang baru, berubah menjadi tugas yang tidak sepele. Butuh waktu lama untuk membuktikan kepada pemimpin tim bahwa ini akan membawa lebih banyak bonus daripada membuang-buang tenaga.

Kesulitan mengeluarkan perubahan
Ini adalah masalah paling serius - kami merilis rilis setiap dua bulan.
Setiap rilis berubah menjadi bencana nyata bagi bank, meskipun telah dilakukan pengujian dan upaya dari para pengembang. Bisnis tersebut memahami bahwa pada awal minggu beberapa fungsinya tidak akan berfungsi. Dan para pengembang memahami bahwa seminggu insiden serius menanti mereka.
Setiap orang mempunyai keinginan untuk mengubah situasi.

Harapan dari layanan mikro


Masalah komponen saat siap. Pengiriman komponen ketika sudah siap dengan menguraikan solusi dan memisahkan proses yang berbeda.

Tim produk kecil. Hal ini penting karena sulit untuk mengelola tim besar yang mengerjakan monolit lama. Tim seperti itu dipaksa bekerja sesuai proses yang ketat, namun mereka menginginkan lebih banyak kreativitas dan kemandirian. Hanya tim kecil yang mampu melakukan hal ini.

Isolasi layanan dalam proses terpisah. Idealnya, saya ingin mengisolasinya dalam wadah, tetapi sejumlah besar layanan yang ditulis dalam .NET Framework hanya berjalan di Windows. Layanan berbasis .NET Core kini sudah bermunculan, namun jumlahnya masih sedikit.

Fleksibilitas penerapan. Kami ingin menggabungkan layanan sesuai kebutuhan kami, dan bukan sesuai kode yang memaksanya.

Penggunaan teknologi baru. Ini menarik bagi programmer mana pun.

Masalah transisi


Tentu saja, jika monolit menjadi layanan mikro dapat dengan mudah dipecah, maka tidak perlu membicarakannya di konferensi dan menulis artikel. Ada banyak kendala dalam proses ini; saya akan menjelaskan kendala utama yang menghambat kita.

Masalah pertama khas untuk sebagian besar monolit: koherensi logika bisnis. Saat kami menulis monolit, kami ingin menggunakan kembali kelas kami agar tidak menulis kode yang tidak perlu. Dan ketika berpindah ke layanan mikro, ini menjadi masalah: semua kode digabungkan dengan cukup erat, dan sulit untuk memisahkan layanan.

Pada saat dimulainya pekerjaan, repositori memiliki lebih dari 500 proyek dan lebih dari 700 ribu baris kode. Ini adalah keputusan yang cukup besar dan masalah kedua. Tidak mungkin untuk mengambilnya begitu saja dan membaginya menjadi layanan-layanan mikro.

Masalah ketiga β€” kurangnya infrastruktur yang diperlukan. Faktanya, kami menyalin kode sumber ke server secara manual.

Cara berpindah dari monolit ke layanan mikro


Penyediaan layanan mikro

Pertama, kami segera menentukan sendiri bahwa pemisahan layanan mikro adalah proses yang berulang. Kami selalu dituntut untuk mengembangkan masalah bisnis secara paralel. Bagaimana kami menerapkan hal ini secara teknis sudah menjadi masalah kami. Oleh karena itu, kami bersiap untuk proses berulang. Ini tidak akan berfungsi dengan cara lain jika Anda memiliki aplikasi besar dan pada awalnya belum siap untuk ditulis ulang.

Metode apa yang kami gunakan untuk mengisolasi layanan mikro?

Cara pertama β€” memindahkan modul yang ada sebagai layanan. Dalam hal ini, kami beruntung: sudah ada layanan terdaftar yang bekerja menggunakan protokol WCF. Mereka dipisahkan menjadi majelis terpisah. Kami mem-portingnya secara terpisah, menambahkan peluncur kecil ke setiap build. Itu ditulis menggunakan perpustakaan Topshelf yang luar biasa, yang memungkinkan Anda menjalankan aplikasi baik sebagai layanan maupun sebagai konsol. Ini memudahkan untuk melakukan debug karena tidak diperlukan proyek tambahan dalam solusinya.

Layanan-layanan tersebut terhubung berdasarkan logika bisnis, karena mereka menggunakan rakitan umum dan bekerja dengan database umum. Mereka hampir tidak bisa disebut layanan mikro dalam bentuknya yang murni. Namun, kami dapat menyediakan layanan ini secara terpisah, dalam proses yang berbeda. Hal ini saja telah memungkinkan untuk mengurangi pengaruh mereka satu sama lain, mengurangi masalah dengan pengembangan paralel dan satu titik kegagalan.

Perakitan dengan host hanyalah satu baris kode di kelas Program. Kami menyembunyikan pekerjaan dengan Topshelf di kelas tambahan.

namespace RBA.Services.Accounts.Host
{
   internal class Program
   {
      private static void Main(string[] args)
      {
        HostRunner<Accounts>.Run("RBA.Services.Accounts.Host");

       }
    }
}

Cara kedua untuk mengalokasikan layanan mikro adalah: menciptakannya untuk memecahkan masalah baru. Jika pada saat yang sama monolit tidak tumbuh, ini sudah bagus, artinya kita bergerak ke arah yang benar. Untuk mengatasi masalah baru, kami mencoba membuat layanan terpisah. Jika ada peluang seperti itu, maka kami menciptakan lebih banyak layanan β€œkanonik” yang sepenuhnya mengelola model datanya sendiri, database terpisah.

Kami, seperti kebanyakan orang, memulai dengan layanan otentikasi dan otorisasi. Mereka sempurna untuk ini. Mereka independen, sebagai suatu peraturan, mereka memiliki model data yang terpisah. Mereka sendiri tidak berinteraksi dengan monolit, hanya monolit yang digunakan untuk memecahkan beberapa masalah. Dengan menggunakan layanan ini, Anda dapat memulai transisi ke arsitektur baru, men-debug infrastrukturnya, mencoba beberapa pendekatan yang terkait dengan perpustakaan jaringan, dll. Kami tidak memiliki tim di organisasi kami yang tidak dapat membuat layanan autentikasi.

Cara ketiga untuk mengalokasikan layanan mikroYang kami gunakan sedikit spesifik untuk kami. Ini adalah penghapusan logika bisnis dari lapisan UI. Aplikasi UI utama kami adalah desktop; seperti backend, ditulis dalam C#. Pengembang secara berkala membuat kesalahan dan mentransfer bagian logika ke UI yang seharusnya ada di backend dan digunakan kembali.

Jika Anda melihat contoh nyata dari kode bagian UI, Anda dapat melihat bahwa sebagian besar solusi ini berisi logika bisnis nyata yang berguna dalam proses lain, tidak hanya untuk membangun formulir UI.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Logika UI sebenarnya hanya ada di beberapa baris terakhir. Kami mentransfernya ke server agar dapat digunakan kembali, sehingga mengurangi UI dan mencapai arsitektur yang benar.

Cara keempat dan terpenting untuk mengisolasi layanan mikro, yang memungkinkan untuk mengurangi monolit, adalah penghapusan layanan yang ada dengan pemrosesan. Saat kami mengeluarkan modul yang ada sebagaimana adanya, hasilnya tidak selalu sesuai dengan keinginan pengembang, dan proses bisnis mungkin sudah ketinggalan zaman sejak fungsionalitas tersebut dibuat. Dengan refactoring, kami dapat mendukung proses bisnis baru karena kebutuhan bisnis terus berubah. Kita dapat memperbaiki kode sumber, menghilangkan cacat yang diketahui, dan membuat model data yang lebih baik. Ada banyak manfaat yang didapat.

Memisahkan layanan dari pemrosesan terkait erat dengan konsep konteks terbatas. Ini adalah konsep dari Domain Driven Design. Ini berarti bagian dari model domain di mana semua istilah dalam satu bahasa didefinisikan secara unik. Mari kita lihat konteks asuransi dan tagihan sebagai contoh. Kami memiliki aplikasi monolitik, dan kami perlu bekerja dengan akun asuransi. Kami berharap pengembang menemukan kelas Akun yang ada di perakitan lain, mereferensikannya dari kelas Asuransi, dan kami akan memiliki kode yang berfungsi. Prinsip KERING akan dipatuhi, tugas akan selesai lebih cepat dengan menggunakan kode yang ada.

Hasilnya, ternyata konteks akun dan asuransi saling berkaitan. Ketika persyaratan baru muncul, penggabungan ini akan mengganggu pengembangan dan meningkatkan kompleksitas logika bisnis yang sudah rumit. Untuk mengatasi masalah ini, Anda perlu menemukan batasan antar konteks dalam kode dan menghilangkan pelanggarannya. Misalnya, dalam konteks asuransi, kemungkinan besar 20 digit nomor rekening Bank Sentral dan tanggal pembukaan rekening sudah cukup.

Untuk memisahkan konteks yang terikat ini satu sama lain dan memulai proses pemisahan layanan mikro dari solusi monolitik, kami menggunakan pendekatan seperti membuat API eksternal dalam aplikasi. Jika kami mengetahui bahwa beberapa modul harus menjadi layanan mikro, entah bagaimana dimodifikasi dalam proses, maka kami segera melakukan panggilan ke logika yang termasuk dalam konteks terbatas lainnya melalui panggilan eksternal. Misalnya melalui REST atau WCF.

Kami dengan tegas memutuskan bahwa kami tidak akan menghindari kode yang memerlukan transaksi terdistribusi. Dalam kasus kami, mengikuti aturan ini ternyata cukup mudah. Kami belum menemukan situasi di mana transaksi terdistribusi yang ketat benar-benar diperlukan - konsistensi akhir antar modul sudah cukup.

Mari kita lihat contoh spesifiknya. Kami memiliki konsep orkestrator - saluran pipa yang memproses entitas "aplikasi". Dia menciptakan klien, akun, dan kartu bank secara bergantian. Jika klien dan akun berhasil dibuat, tetapi pembuatan kartu gagal, aplikasi tidak berpindah ke status β€œberhasil” dan tetap dalam status β€œkartu tidak dibuat”. Di masa depan, aktivitas latar belakang akan mengambil dan menyelesaikannya. Sistem ini telah berada dalam keadaan tidak konsisten selama beberapa waktu, namun secara umum kami puas dengan hal ini.

Jika situasi muncul ketika perlu untuk menyimpan sebagian data secara konsisten, kemungkinan besar kami akan melakukan konsolidasi layanan untuk memprosesnya dalam satu proses.

Mari kita lihat contoh pengalokasian layanan mikro. Bagaimana cara membawanya ke produksi dengan relatif aman? Dalam contoh ini, kami memiliki bagian terpisah dari sistem - modul layanan penggajian, salah satu bagian kodenya ingin kami jadikan layanan mikro.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Pertama-tama, kita membuat layanan mikro dengan menulis ulang kodenya. Kami memperbaiki beberapa aspek yang tidak kami sukai. Kami menerapkan persyaratan bisnis baru dari pelanggan. Kami menambahkan API Gateway ke koneksi antara UI dan backend, yang akan menyediakan penerusan panggilan.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Selanjutnya, kami meluncurkan konfigurasi ini, tetapi dalam keadaan uji coba. Sebagian besar pengguna kami masih bekerja dengan proses bisnis lama. Untuk pengguna baru, kami sedang mengembangkan versi baru aplikasi monolitik yang tidak lagi memuat proses ini. Pada dasarnya, kami memiliki kombinasi monolit dan layanan mikro yang berfungsi sebagai percontohan.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Dengan uji coba yang berhasil, kami memahami bahwa konfigurasi baru memang bisa diterapkan, kami dapat menghilangkan monolit lama dari persamaan dan membiarkan konfigurasi baru menggantikan solusi lama.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Secara total, kami menggunakan hampir semua metode yang ada untuk memisahkan kode sumber monolit. Semuanya memungkinkan kita mengurangi ukuran bagian aplikasi dan menerjemahkannya ke perpustakaan baru, membuat kode sumber lebih baik.

Bekerja dengan basis data


Basis data dapat dibagi lebih buruk daripada kode sumber, karena tidak hanya berisi skema saat ini, tetapi juga akumulasi data historis.

Basis data kami, seperti banyak basis data lainnya, memiliki kelemahan penting lainnya - ukurannya yang sangat besar. Basis data ini dirancang sesuai dengan logika bisnis monolit yang rumit, dan hubungan yang terakumulasi antara tabel-tabel dari berbagai konteks yang dibatasi.

Dalam kasus kami, untuk mengatasi semua masalah (database besar, banyak koneksi, terkadang batas antar tabel tidak jelas), muncul masalah yang terjadi di banyak proyek besar: penggunaan template database bersama. Data diambil dari tabel melalui tampilan, melalui replikasi, dan dikirim ke sistem lain yang memerlukan replikasi ini. Akibatnya, kami tidak dapat memindahkan tabel ke dalam skema terpisah karena tabel tersebut digunakan secara aktif.

Pembagian yang sama ke dalam konteks terbatas dalam kode membantu kita dalam pemisahan. Biasanya ini memberi kita gambaran yang cukup bagus tentang bagaimana kita memecah data di tingkat database. Kami memahami tabel mana yang termasuk dalam satu konteks terbatas dan mana yang termasuk dalam konteks lain.

Kami menggunakan dua metode global untuk mempartisi database: mempartisi tabel yang ada dan mempartisi dengan pemrosesan.

Memisahkan tabel yang ada adalah metode yang baik untuk digunakan jika struktur datanya bagus, memenuhi persyaratan bisnis, dan semua orang menyukainya. Dalam hal ini, kita dapat memisahkan tabel yang ada ke dalam skema terpisah.

Sebuah departemen dengan pemrosesan diperlukan ketika model bisnis telah banyak berubah, dan tabel tidak lagi memuaskan kita sama sekali.

Memisahkan tabel yang ada. Kita perlu menentukan apa yang akan kita pisahkan. Tanpa pengetahuan ini, tidak ada yang akan berhasil, dan di sini pemisahan konteks yang dibatasi dalam kode akan membantu kita. Biasanya, jika Anda dapat memahami batasan konteks dalam kode sumber, akan menjadi jelas tabel mana yang harus disertakan dalam daftar departemen.

Bayangkan kita memiliki solusi di mana dua modul monolit berinteraksi dengan satu database. Kita perlu memastikan bahwa hanya satu modul yang berinteraksi dengan bagian tabel terpisah, dan modul lainnya mulai berinteraksi dengannya melalui API. Untuk memulainya, cukup hanya pencatatan yang dilakukan melalui API. Ini adalah kondisi yang diperlukan bagi kita untuk berbicara tentang independensi layanan mikro. Koneksi membaca bisa tetap ada selama tidak ada masalah besar.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Langkah selanjutnya adalah kita dapat memisahkan bagian kode yang bekerja dengan tabel terpisah, dengan atau tanpa pemrosesan, ke dalam layanan mikro terpisah dan menjalankannya dalam proses terpisah, sebuah wadah. Ini akan menjadi layanan terpisah dengan koneksi ke database monolit dan tabel-tabel yang tidak berhubungan langsung dengannya. Monolit masih berinteraksi untuk membaca dengan bagian yang dapat dilepas.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Nanti kami akan menghapus koneksi ini, yaitu membaca data dari aplikasi monolitik dari tabel terpisah juga akan ditransfer ke API.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Selanjutnya, kita akan memilih dari database umum tabel-tabel yang hanya dapat digunakan oleh layanan mikro baru. Kita dapat memindahkan tabel ke skema terpisah atau bahkan ke database fisik terpisah. Masih ada koneksi baca antara microservice dan database monolith, namun tidak ada yang perlu dikhawatirkan, pada konfigurasi ini bisa hidup cukup lama.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Langkah terakhir adalah menghapus semua koneksi sepenuhnya. Dalam hal ini, kita mungkin perlu memigrasikan data dari database utama. Terkadang kita ingin menggunakan kembali beberapa data atau direktori yang direplikasi dari sistem eksternal di beberapa database. Hal ini terjadi pada kita secara berkala.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Departemen pemrosesan. Cara ini sangat mirip dengan cara pertama, hanya saja urutannya terbalik. Kami segera mengalokasikan database baru dan layanan mikro baru yang berinteraksi dengan monolit melalui API. Namun pada saat yang sama, masih ada sekumpulan tabel database yang ingin kita hapus di kemudian hari. Kami tidak membutuhkannya lagi; kami menggantinya dengan model baru.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Agar skema ini dapat berjalan, kita mungkin memerlukan masa transisi.

Ada dua pendekatan yang mungkin.

Pertama: kami menduplikasi semua data di database baru dan lama. Dalam hal ini, kami mengalami redundansi data dan masalah sinkronisasi mungkin timbul. Tapi kita bisa mengambil dua klien yang berbeda. Yang satu akan bekerja dengan versi baru, yang lain dengan versi lama.

Kedua: kami membagi data menurut beberapa kriteria bisnis. Misalnya, kami memiliki 5 produk di sistem yang disimpan di database lama. Kami menempatkan yang keenam dalam tugas bisnis baru di database baru. Namun kita memerlukan API Gateway yang akan menyinkronkan data ini dan menunjukkan kepada klien dari mana dan apa yang bisa didapat.

Kedua pendekatan berhasil, pilih tergantung situasinya.

Setelah kami yakin semuanya berfungsi, bagian monolit yang berfungsi dengan struktur database lama dapat dinonaktifkan.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Langkah terakhir adalah menghapus struktur data lama.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Ringkasnya, kita dapat mengatakan bahwa kita mempunyai masalah dengan database: sulit untuk dikerjakan dibandingkan dengan kode sumber, lebih sulit untuk dibagikan, tetapi hal ini dapat dan harus dilakukan. Kami telah menemukan beberapa cara yang memungkinkan kami melakukan ini dengan cukup aman, namun masih lebih mudah membuat kesalahan dengan data dibandingkan dengan kode sumber.

Bekerja dengan kode sumber


Seperti inilah diagram kode sumber ketika kami mulai menganalisis proyek monolitik.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Secara kasar dapat dibagi menjadi tiga lapisan. Ini adalah lapisan modul, plugin, layanan, dan aktivitas individual yang diluncurkan. Faktanya, ini adalah titik masuk dalam solusi monolitik. Semuanya tertutup rapat dengan lapisan Umum. Logika bisnisnya adalah bahwa layanan tersebut dibagikan dan banyak koneksi. Setiap layanan dan plugin menggunakan hingga 10 atau lebih rakitan umum, bergantung pada ukurannya dan hati nurani pengembangnya.

Kami beruntung memiliki infrastruktur perpustakaan yang dapat digunakan secara terpisah.

Terkadang situasi muncul ketika beberapa objek umum sebenarnya bukan milik lapisan ini, namun merupakan perpustakaan infrastruktur. Ini diselesaikan dengan mengganti nama.

Kekhawatiran terbesar adalah konteks yang terbatas. Kebetulan 3-4 konteks dicampur dalam satu Majelis Umum dan digunakan satu sama lain dalam fungsi bisnis yang sama. Penting untuk memahami di mana hal ini dapat dibagi dan sepanjang batasan apa, dan apa yang harus dilakukan selanjutnya dengan memetakan pembagian ini ke dalam kumpulan kode sumber.

Kami telah merumuskan beberapa aturan untuk proses pemisahan kode.

Yang pertama: Kami tidak lagi ingin berbagi logika bisnis antara layanan, aktivitas, dan plugin. Kami ingin menjadikan logika bisnis independen dalam layanan mikro. Sebaliknya, layanan mikro idealnya dianggap sebagai layanan yang sepenuhnya berdiri sendiri. Saya percaya bahwa pendekatan ini agak sia-sia, dan sulit untuk dicapai, karena, misalnya, layanan di C# bagaimanapun juga akan dihubungkan oleh perpustakaan standar. Sistem kami ditulis dalam C#; kami belum menggunakan teknologi lain. Oleh karena itu, kami memutuskan bahwa kami mampu menggunakan perangkat teknis umum. Hal utama adalah bahwa mereka tidak mengandung bagian logika bisnis apa pun. Jika Anda memiliki pembungkus praktis atas ORM yang Anda gunakan, maka menyalinnya dari satu layanan ke layanan lainnya akan sangat mahal.

Tim kami adalah penggemar desain berbasis domain, jadi arsitektur bawang sangat cocok untuk kami. Dasar dari layanan kami bukanlah lapisan akses data, namun perakitan dengan logika domain, yang hanya berisi logika bisnis dan tidak memiliki koneksi dengan infrastruktur. Pada saat yang sama, kami dapat secara mandiri memodifikasi rakitan domain untuk memecahkan masalah terkait kerangka kerja.

Pada tahap ini kami menghadapi masalah serius pertama kami. Layanan harus mengacu pada satu rakitan domain, kami ingin membuat logikanya independen, dan prinsip KERING sangat menghambat kami di sini. Pengembang ingin menggunakan kembali kelas dari rakitan tetangga untuk menghindari duplikasi, dan sebagai hasilnya, domain mulai dihubungkan kembali. Kami menganalisis hasilnya dan memutuskan bahwa mungkin masalahnya juga terletak pada area perangkat penyimpanan kode sumber. Kami memiliki repositori besar yang berisi semua kode sumber. Solusi untuk keseluruhan proyek sangat sulit untuk dirakit pada mesin lokal. Oleh karena itu, solusi kecil yang terpisah dibuat untuk bagian-bagian proyek, dan tidak ada yang melarang menambahkan beberapa perakitan umum atau domain ke dalamnya dan menggunakannya kembali. Satu-satunya alat yang tidak memungkinkan kami melakukan ini adalah peninjauan kode. Namun terkadang juga gagal.

Kemudian kami mulai berpindah ke model dengan repositori terpisah. Logika bisnis tidak lagi mengalir dari satu layanan ke layanan lainnya, domain telah benar-benar menjadi independen. Konteks yang dibatasi didukung dengan lebih jelas. Bagaimana kita menggunakan kembali perpustakaan infrastruktur? Kami memisahkannya ke dalam repositori terpisah, lalu memasukkannya ke dalam paket Nuget, yang kami masukkan ke Artifactory. Dengan perubahan apa pun, perakitan dan publikasi terjadi secara otomatis.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Layanan kami mulai mereferensikan paket infrastruktur internal dengan cara yang sama seperti paket infrastruktur eksternal. Kami mengunduh perpustakaan eksternal dari Nuget. Untuk bekerja dengan Artifactory, tempat kami menempatkan paket-paket ini, kami menggunakan dua manajer paket. Di repositori kecil kami juga menggunakan Nuget. Dalam repositori dengan banyak layanan, kami menggunakan Paket, yang memberikan lebih banyak konsistensi versi antar modul.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Jadi, dengan mengerjakan kode sumber, sedikit mengubah arsitektur dan memisahkan repositori, kami menjadikan layanan kami lebih mandiri.

Masalah infrastruktur


Sebagian besar kelemahan peralihan ke layanan mikro terkait dengan infrastruktur. Anda memerlukan penerapan otomatis, Anda memerlukan perpustakaan baru untuk menjalankan infrastruktur.

Instalasi manual di lingkungan

Awalnya, kami menginstal solusi untuk lingkungan secara manual. Untuk mengotomatiskan proses ini, kami membuat pipeline CI/CD. Kami memilih proses pengiriman berkelanjutan karena penerapan berkelanjutan belum dapat kami terima dari sudut pandang proses bisnis. Oleh karena itu, pengiriman untuk pengoperasian dilakukan menggunakan tombol, dan untuk pengujian - secara otomatis.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Kami menggunakan Atlassian, Bitbucket untuk penyimpanan kode sumber dan Bamboo untuk bangunan. Kami suka menulis skrip build di Cake karena sama dengan C#. Paket siap pakai datang ke Artifactory, dan Ansible secara otomatis masuk ke server pengujian, setelah itu dapat segera diuji.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Pencatatan log secara terpisah


Pada suatu waktu, salah satu ide monolit adalah menyediakan penebangan bersama. Kami juga perlu memahami apa yang harus dilakukan dengan masing-masing log yang ada di disk. Log kami ditulis ke file teks. Kami memutuskan untuk menggunakan tumpukan ELK standar. Kami tidak menulis ke ELK secara langsung melalui penyedia, tetapi memutuskan bahwa kami akan memodifikasi log teks dan menulis ID jejak di dalamnya sebagai pengidentifikasi, menambahkan nama layanan, sehingga log ini dapat diurai nanti.

Transisi dari monolit ke layanan mikro: sejarah dan praktik

Dengan menggunakan Filebeat, kami mendapat kesempatan untuk mengumpulkan log kami dari server, lalu mengubahnya, menggunakan Kibana untuk membuat kueri di UI dan melihat bagaimana panggilan antar layanan. Trace ID sangat membantu dalam hal ini.

Menguji dan men-debug layanan terkait


Awalnya, kami tidak sepenuhnya memahami cara men-debug layanan yang sedang dikembangkan. Semuanya sederhana dengan monolit; kami menjalankannya di mesin lokal. Pada awalnya mereka mencoba melakukan hal yang sama dengan layanan mikro, tetapi terkadang untuk meluncurkan satu layanan mikro sepenuhnya, Anda perlu meluncurkan beberapa layanan mikro lainnya, dan ini merepotkan. Kami menyadari bahwa kami perlu berpindah ke model di mana kami hanya menyisakan layanan atau layanan yang ingin kami debug di mesin lokal. Layanan lainnya digunakan dari server yang cocok dengan konfigurasi dengan prod. Setelah debugging, selama pengujian, untuk setiap tugas, hanya layanan yang diubah yang dikeluarkan ke server pengujian. Dengan demikian, solusinya diuji dalam bentuk yang akan muncul dalam produksi di masa depan.

Ada server yang hanya menjalankan layanan versi produksi. Server-server ini diperlukan jika terjadi insiden, untuk memeriksa pengiriman sebelum penerapan dan untuk pelatihan internal.

Kami telah menambahkan proses pengujian otomatis menggunakan perpustakaan Specflow yang populer. Pengujian dijalankan secara otomatis menggunakan NUnit segera setelah penerapan dari Ansible. Jika cakupan tugas sepenuhnya otomatis, maka tidak diperlukan pengujian manual. Meskipun terkadang pengujian manual tambahan masih diperlukan. Kami menggunakan tag di Jira untuk menentukan pengujian mana yang akan dijalankan untuk masalah tertentu.

Selain itu, kebutuhan untuk pengujian beban telah meningkat; sebelumnya pengujian ini hanya dilakukan pada kasus yang jarang terjadi. Kami menggunakan JMeter untuk menjalankan pengujian, InfluxDB untuk menyimpannya, dan Grafana untuk membuat grafik proses.

Apa yang telah kita capai?


Pertama, kami menyingkirkan konsep β€œpelepasan”. Hilang sudah rilis mengerikan selama dua bulan ketika raksasa ini diterapkan di lingkungan produksi, sehingga mengganggu proses bisnis untuk sementara. Sekarang kami menyebarkan layanan rata-rata setiap 1,5 hari, mengelompokkannya berdasarkan layanan yang mulai beroperasi setelah disetujui.

Tidak ada kegagalan fatal dalam sistem kami. Jika kami merilis layanan mikro yang memiliki bug, maka fungsionalitas yang terkait dengannya akan rusak, dan semua fungsi lainnya tidak akan terpengaruh. Ini sangat meningkatkan pengalaman pengguna.

Kita dapat mengontrol pola penerapannya. Anda dapat memilih grup layanan secara terpisah dari solusi lainnya, jika perlu.

Selain itu, kami telah mengurangi masalah secara signifikan dengan sejumlah besar perbaikan. Kami sekarang memiliki tim produk terpisah yang bekerja dengan beberapa layanan secara independen. Proses Scrum sudah berjalan dengan baik di sini. Tim tertentu mungkin memiliki Pemilik Produk terpisah yang menugaskannya.

Ringkasan

  • Layanan mikro sangat cocok untuk mendekomposisi sistem yang kompleks. Dalam prosesnya, kami mulai memahami apa yang ada dalam sistem kami, konteks terbatas apa yang ada, dan di mana letak batasannya. Hal ini memungkinkan Anda mendistribusikan peningkatan antar modul dengan benar dan mencegah kebingungan kode.
  • Layanan mikro memberikan manfaat organisasi. Mereka sering dibicarakan hanya sebagai arsitektur, tetapi arsitektur apa pun diperlukan untuk menyelesaikan kebutuhan bisnis, dan bukan arsitektur itu sendiri. Oleh karena itu, kami dapat mengatakan bahwa layanan mikro sangat cocok untuk memecahkan masalah dalam tim kecil, mengingat Scrum sangat populer saat ini.
  • Pemisahan adalah proses yang berulang. Anda tidak dapat mengambil aplikasi dan membaginya menjadi layanan mikro. Produk yang dihasilkan kemungkinan besar tidak akan berfungsi. Saat mendedikasikan layanan mikro, ada baiknya untuk menulis ulang warisan yang ada, yaitu mengubahnya menjadi kode yang kita sukai dan lebih memenuhi kebutuhan bisnis dalam hal fungsionalitas dan kecepatan.

    Peringatan kecil: Biaya perpindahan ke layanan mikro cukup signifikan. Butuh waktu lama untuk menyelesaikan masalah infrastruktur saja. Jadi, jika Anda memiliki aplikasi kecil yang tidak memerlukan penskalaan khusus, kecuali Anda memiliki banyak pelanggan yang bersaing untuk mendapatkan perhatian dan waktu tim Anda, maka layanan mikro mungkin bukan yang Anda perlukan saat ini. Itu cukup mahal. Jika Anda memulai proses dengan layanan mikro, maka biaya awalnya akan lebih tinggi dibandingkan jika Anda memulai proyek yang sama dengan pengembangan monolit.

    PS Kisah yang lebih emosional (dan seolah-olah untuk Anda secara pribadi) - menurut link.
    Berikut adalah versi lengkap laporannya.

Sumber: www.habr.com

Tambah komentar