Antipatterns dari arsitektur berorientasi peristiwa

Halo lagi! Untuk mengantisipasi dimulainya kursus "Arsitek Perangkat Lunak", kami menyiapkan terjemahan materi menarik lainnya.




Beberapa tahun terakhir telah melihat peningkatan popularitas arsitektur layanan-mikro. Ada banyak sumber daya yang mengajarkan Anda bagaimana menerapkannya dengan benar, tetapi cukup sering orang membicarakannya sebagai kumpulan. Ada banyak argumen yang menentang penggunaan layanan mikro, tetapi yang paling signifikan adalah bahwa jenis arsitektur ini penuh dengan kompleksitas yang tidak pasti, yang tingkatannya tergantung pada bagaimana Anda mengelola hubungan antara layanan dan tim Anda. Anda dapat menemukan banyak literatur yang akan menjelaskan mengapa (mungkin) dalam kasus Anda, layanan microser tidak akan menjadi pilihan terbaik.

Kami di letgo bermigrasi dari monolith ke layanan microser untuk memenuhi kebutuhan skalabilitas, dan segera menjadi yakin akan efek menguntungkannya pada kerja tim. Ketika digunakan dengan benar, layanan Microsoft telah memberi kami beberapa keuntungan, yaitu:

  • : , . ( ..) . ( ) Users.
  • : , . . , , , , . .

-


Tidak semua arsitektur microservice dikendalikan oleh peristiwa. Beberapa orang menganjurkan komunikasi sinkron antara layanan dalam arsitektur ini menggunakan HTTP (gRPC, REST, dll.). Di letgo, kami mencoba untuk tidak mengikuti pola ini dan secara asinkron mengaitkan layanan kami dengan peristiwa domain . Inilah alasan kami melakukan ini:

  • : . , DDoS . , DDoS . , . .



  • (bulkheads) – , , .
  • : . , , , , . , , API, , , , . Users , Chat.

Berdasarkan hal ini, kami di letgo mencoba mematuhi komunikasi asinkron antara layanan, dan sinkron hanya berfungsi dalam kasus luar biasa seperti fitur MVP. Kami melakukan ini karena kami ingin setiap layanan untuk menghasilkan entitas sendiri berdasarkan peristiwa domain yang diterbitkan oleh layanan lain di Bus Pesan kami.

Menurut pendapat kami, keberhasilan atau kegagalan dalam implementasi arsitektur layanan mikro tergantung pada bagaimana Anda menangani kompleksitas yang melekat dan bagaimana layanan Anda berinteraksi satu sama lain. Membagi kode tanpa mentransfer infrastruktur komunikasi ke asynchronous akan mengubah aplikasi Anda menjadi monolit terdistribusi.



Arsitektur berbasis event di letgo

Hari ini saya ingin berbagi contoh bagaimana kami menggunakan peristiwa domain dan komunikasi asinkron di letgo: entitas Pengguna kami ada di banyak layanan, tetapi pembuatan dan pengeditannya pada awalnya diproses oleh layanan Pengguna. Dalam basis data layanan Pengguna, kami menyimpan banyak data, seperti nama, alamat email, avatar, negara, dll. Dalam layanan Obrolan kami, kami juga memiliki konsep pengguna, tetapi kami tidak memerlukan data yang dimiliki entitas Pengguna dari layanan Pengguna. Nama pengguna, avatar, dan ID (tautan ke profil) ditampilkan dalam daftar dialog. Kami mengatakan bahwa dalam obrolan hanya ada proyeksi entitas Pengguna, yang berisi sebagian data. Bahkan, dalam Obrolan kami tidak berbicara tentang pengguna, kami menyebutnya "pembicara". Proyeksi ini merujuk ke layanan Obrolan dan dibuat berdasarkan peristiwa yang diterima Obrolan dari layanan Pengguna.

Kami melakukan hal yang sama dengan daftar. Dalam layanan Produk, kami menyimpan n gambar dari setiap daftar, tetapi dalam daftar dialog, kami menampilkan satu gambar utama, sehingga proyeksi kami dari Produk ke Obrolan hanya membutuhkan satu gambar alih-alih n.


Lihat daftar dialog di obrolan kami. Ini menunjukkan layanan spesifik mana di backend yang memberikan informasi.

Jika Anda melihat daftar dialog lagi, Anda akan melihat bahwa hampir semua data yang kami tampilkan tidak dibuat oleh layanan Obrolan, tetapi menjadi miliknya, karena proyeksi Pengguna dan Obrolan dimiliki oleh Obrolan. Ada trade-off antara aksesibilitas dan konsistensi proyeksi, yang tidak akan kita bahas dalam artikel ini, tetapi saya hanya akan mengatakan bahwa itu jelas lebih mudah untuk skala banyak database kecil daripada yang besar.


Tampilan sederhana arsitektur letgo

Antipatterns


Beberapa solusi intuitif sering menjadi kesalahan. Berikut adalah daftar antipattern yang paling penting yang kami temui dalam arsitektur terkait domain kami.

1. Tebal acara
Kami mencoba membuat acara domain kami sekecil mungkin tanpa kehilangan nilai domainnya. Kita seharusnya berhati-hati ketika refactoring basis kode lama dengan entitas besar dan beralih ke arsitektur acara. Entitas semacam itu dapat membawa kita ke acara-acara besar, tetapi karena acara domain kita berubah menjadi kontrak publik, kita perlu membuatnya sesederhana mungkin. Dalam hal ini, refactoring paling baik dilihat dari samping. Untuk memulai, kami merancang acara kami menggunakan teknik badai acaradan kemudian memperbaiki kode layanan untuk menyesuaikannya dengan acara kami.

Kita juga harus lebih berhati-hati dengan masalah "produk dan pengguna": banyak sistem menggunakan entitas produk dan pengguna, dan entitas ini, sebagai aturan, menarik semua logika di belakangnya, dan ini berarti bahwa semua peristiwa domain terkait dengan mereka.

2. Peristiwa sebagai niat
Peristiwa domain, menurut definisi, adalah peristiwa yang telah terjadi. Jika Anda menerbitkan sesuatu ke bus pesan untuk meminta apa yang terjadi di beberapa layanan lain, Anda kemungkinan besar menjalankan perintah asinkron daripada membuat acara domain. Sebagai aturan, kami merujuk ke peristiwa domain sebelumnya: ser_registered , product_publisheddll. Semakin sedikit satu layanan yang tahu tentang yang lain, semakin baik. Menggunakan acara sebagai perintah menautkan layanan dan meningkatkan kemungkinan perubahan dalam satu layanan akan memengaruhi layanan lain.

3. Kurangnya serialisasi atau kompresi independen.
Sistem serialisasi dan kompresi peristiwa di area subjek kami tidak harus bergantung pada bahasa pemrograman. Anda bahkan tidak perlu tahu dalam bahasa apa layanan konsumen ditulis. Itu sebabnya kita bisa menggunakan serializers Java atau PHP, misalnya. Biarkan tim Anda menghabiskan waktu untuk membahas dan memilih serializer, karena mengubahnya di masa depan akan sulit dan memakan waktu. Kami di letgo menggunakan JSON, namun ada banyak format serialisasi lainnya dengan kinerja yang baik.

4. Kurangnya struktur standar
Ketika kami mulai memindahkan backend letgo ke arsitektur berorientasi-peristiwa, kami menyetujui struktur umum untuk acara domain. Itu terlihat seperti ini:

{
  “data”: {
    “id”: [uuid], // event id.type”: “user_registered”,
    “attributes”: {
      “id”: [uuid], // aggregate/entity id, in this case user_id
      “user_name”: “John Doe”,
    }
  },
  “meta” : {
    “created_at”: timestamp, // when was the event created?
    “host”: “users-service” // where was the event created?
  }
}

Memiliki struktur umum untuk acara domain kami memungkinkan kami untuk dengan cepat mengintegrasikan layanan dan mengimplementasikan beberapa perpustakaan dengan abstraksi.

5. Kurangnya validasi skema
Selama serialisasi, kami di letgo mengalami masalah dengan bahasa pemrograman tanpa pengetikan yang kuat.


{
  “null_value_one”: null, // thank god
  “null_value_two”: “null”,
  “null_value_three”: “”,
}

Budaya pengujian yang mapan yang menjamin serialisasi acara kami, dan pemahaman tentang bagaimana perpustakaan serialisasi bekerja, membantu mengatasinya. Kami di letgo beralih ke Avro dan Confluent Schema Registry, yang memberi kami satu titik untuk menentukan struktur peristiwa domain kami dan menghindari kesalahan jenis ini, serta dokumentasi yang usang.

6. Acara domain anemia
Seperti yang saya katakan sebelumnya, dan seperti namanya, peristiwa domain harus memiliki nilai di tingkat domain. Sama seperti kita berusaha menghindari ketidakkonsistenan negara bagian dalam entitas kita, kita harus menghindari ini dalam peristiwa domain. Mari kita ilustrasikan ini dengan contoh berikut: produk dalam sistem kami memiliki geolokasi dengan lintang dan bujur, yang disimpan dalam dua bidang berbeda dari tabel produk dari layanan Produk. Semua produk dapat "dipindahkan", jadi kami akan memiliki acara domain untuk menyajikan pembaruan ini. Sebelumnya, untuk ini kami memiliki dua acara: product_latitude_updated dan product_longitude_updated , yang tidak masuk akal jika Anda bukan benteng di papan catur. Dalam hal ini, product_location_updated events akan lebih masuk akal.atau product_moved .


Benteng adalah bidak catur. Dulu disebut tur. Benteng hanya bisa bergerak secara vertikal atau horizontal melalui sejumlah bidang yang tidak dihuni.

7. Kurangnya alat debugging
Kami di letgo menghasilkan ribuan acara domain per menit. Semua peristiwa ini menjadi sumber yang sangat berguna untuk memahami apa yang terjadi di sistem kami, mendaftarkan aktivitas pengguna, atau bahkan merekonstruksi keadaan sistem pada titik tertentu menggunakan pencarian peristiwa. Kita perlu terampil menggunakan sumber daya ini, dan untuk ini kita perlu alat untuk memeriksa dan men-debug acara kami. Permintaan seperti "tunjukkan semua acara yang dihasilkan oleh John Doe dalam 3 jam terakhir" juga dapat berguna dalam mendeteksi penipuan. Untuk tujuan ini, kami telah mengembangkan beberapa alat di ElasticSearch, Kibana dan S3.

8. Kurangnya pemantauan acara
Kita dapat menggunakan peristiwa domain untuk menguji kesehatan sistem. Ketika kami menyebarkan sesuatu (yang terjadi beberapa kali sehari tergantung pada layanan), kami membutuhkan alat untuk memverifikasi operasi yang benar dengan cepat. Misalnya, jika kami menggunakan versi baru dari layanan Produk pada produksi dan melihat penurunan jumlah event product_published20%, aman untuk mengatakan bahwa kita telah merusak sesuatu. Kami saat ini menggunakan InfluxDB, Grafana, dan Prometheus untuk mencapai ini dengan fungsi turunan. Jika Anda mengingat jalannya matematika, Anda akan memahami bahwa turunan dari fungsi f (x) pada titik x sama dengan garis singgung dari sudut garis singgung yang digambar ke grafik fungsi pada titik ini. Jika Anda memiliki fungsi untuk mempublikasikan kecepatan acara tertentu di area subjek dan Anda mengambil turunan darinya, Anda akan melihat puncak fungsi ini dan Anda dapat mengatur notifikasi berdasarkan pada mereka. Dengan menggunakan pemberitahuan ini, Anda dapat menghindari frasa seperti "peringatkan saya jika kami menerbitkan kurang dari 200 peristiwa per detik selama 5 menit" dan fokus pada perubahan signifikan dalam kecepatan publikasi.


Sesuatu yang aneh terjadi di sini ... Atau mungkin itu hanya kampanye pemasaran

9. Harapan bahwa semuanya akan baik-baik saja.
Kami berusaha menciptakan sistem yang berkelanjutan dan mengurangi biaya pemulihannya. Selain masalah infrastruktur dan faktor manusia, salah satu hal paling umum yang dapat memengaruhi arsitektur acara adalah hilangnya peristiwa. Kita memerlukan rencana untuk mengembalikan keadaan sistem yang benar dengan memproses ulang semua peristiwa yang hilang. Di sini, strategi kami didasarkan pada dua poin:

  • : , « , », - , . letgo Data, Backend.
  • : - . , , , message bus . – , , . , user_registered Users, , MySQL, user_id . user_registered, , . , , - MySQL ( , 30 ). -, DynamoDB. , , , . , , , , .

10. Kurangnya dokumentasi tentang peristiwa domain Acara domain
kami telah menjadi antarmuka publik kami untuk semua sistem di backend. Sama seperti kami mendokumentasikan API REST kami, kami juga perlu mendokumentasikan peristiwa domain. Setiap karyawan organisasi harus dapat melihat dokumentasi yang diperbarui untuk setiap acara domain yang diterbitkan oleh setiap layanan. Jika kami menggunakan skema untuk memeriksa acara domain, mereka juga dapat digunakan sebagai dokumentasi.

11. Resistensi terhadap konsumsi acara sendiri
Anda diizinkan dan bahkan didorong untuk menggunakan acara domain Anda sendiri untuk membuat proyeksi di sistem Anda, yang, misalnya, dioptimalkan untuk dibaca. Beberapa tim menolak konsep ini, karena mereka terbatas pada konsep konsumsi acara orang lain.

Sampai jumpa di lapangan!

All Articles