Bagaimana cara menghindari antarmuka yang cerewet

10

Latar Belakang: Saya merancang aplikasi server dan membuat dll terpisah untuk berbagai subsistem. Untuk menyederhanakan banyak hal, katakanlah saya memiliki dua subsistem: 1) Users2)Projects

Antarmuka publik pengguna memiliki metode seperti:

IEnumerable<User> GetUser(int id);

Dan antarmuka publik Projects memiliki metode seperti:

IEnumerable<User> GetProjectUsers(int projectId);

Jadi, misalnya, ketika kita perlu menampilkan pengguna untuk proyek tertentu, kita dapat memanggil GetProjectUsersdan itu akan memberikan kembali objek dengan info yang cukup untuk ditampilkan dalam datagrid atau serupa.

Masalah: Idealnya, Projectssubsistem seharusnya tidak juga menyimpan informasi pengguna dan itu hanya harus menyimpan Id dari pengguna yang berpartisipasi dalam suatu proyek. Dalam rangka untuk melayani GetProjectUsers, perlu untuk memanggil GetUserdari Userssistem untuk setiap user id disimpan dalam database sendiri. Namun, ini membutuhkan banyak GetUserpanggilan terpisah , menyebabkan banyak permintaan sql terpisah di dalam Usersubsistem. Saya belum benar-benar menguji ini tetapi memiliki desain cerewet ini akan mempengaruhi skalabilitas sistem.

Jika saya mengesampingkan pemisahan subsistem, saya bisa menyimpan semua info dalam skema tunggal yang dapat diakses oleh kedua sistem dan Projectshanya bisa melakukan JOINuntuk mendapatkan semua pengguna proyek dalam satu permintaan tunggal. Projectsjuga perlu tahu cara menghasilkan Userobjek dari hasil permintaan. Tetapi ini mematahkan pemisahan yang memiliki banyak keuntungan.

Pertanyaan: Adakah yang bisa menyarankan cara untuk menjaga pemisahan sambil menghindari semua GetUserpanggilan individu ini selama GetProjectUsers?


Misalnya, satu ide yang saya miliki adalah agar Pengguna memberikan kemampuan sistem eksternal untuk "menandai" pengguna dengan pasangan nilai-label, dan untuk meminta pengguna dengan nilai tertentu, misalnya:

void AddUserTag(int userId, string tag, string value);
IEnumerable<User> GetUsersByTag(string tag, string value);

Kemudian sistem Proyek dapat menandai setiap pengguna saat ditambahkan ke proyek:

AddUserTag(userId,"project id", myProjectId.ToString());

dan selama GetProjectUsers, ini dapat meminta semua pengguna proyek dalam satu panggilan:

var projectUsers = usersService.GetUsersByTag("project id", myProjectId.ToString());

bagian yang saya tidak yakin tentang ini adalah: ya, Pengguna adalah agnostik proyek tetapi sebenarnya informasi tentang keanggotaan proyek disimpan dalam sistem Pengguna, bukan Proyek. Aku hanya merasa tidak alami jadi aku mencoba untuk menentukan apakah ada kerugian besar di sini bahwa aku hilang.

Eren Ersönmez
sumber

Jawaban:

10

Apa yang hilang di sistem Anda adalah cache.

Kamu bilang:

Namun, ini membutuhkan banyak GetUserpanggilan terpisah , menyebabkan banyak permintaan sql terpisah di dalam Usersubsistem.

Jumlah panggilan ke metode tidak harus sama dengan jumlah kueri SQL. Anda mendapatkan informasi tentang pengguna satu kali, mengapa Anda meminta informasi yang sama lagi jika tidak berubah? Sangat mungkin, Anda bahkan dapat men-cache semua pengguna di memori, yang akan menghasilkan nol query SQL (kecuali jika pengguna berubah).

Di sisi lain, dengan membuat Projectspermintaan subsistem, baik proyek maupun pengguna dengan INNER JOIN, Anda memperkenalkan masalah tambahan: Anda meminta informasi yang sama di dua lokasi berbeda dalam kode Anda, membuat pembatalan cache sangat sulit. Sebagai konsekuensi:

  • Entah Anda tidak akan memperkenalkan cache sama sekali nanti,

  • Atau Anda akan menghabiskan berminggu-minggu atau berbulan-bulan mempelajari apa yang harus dibatalkan ketika sepotong informasi berubah,

  • Atau Anda akan menambahkan pembatalan cache di lokasi langsung, melupakan yang lain dan mengakibatkan sulit menemukan bug.


Membaca ulang pertanyaan Anda, saya perhatikan kata kunci yang saya lewatkan pertama kali: skalabilitas . Sebagai patokan, Anda dapat mengikuti pola berikut:

  1. Tanyakan kepada diri Anda apakah sistem ini lambat (yaitu melanggar persyaratan kinerja yang tidak berfungsi, atau sekadar mimpi buruk untuk digunakan).

    Jika sistem tidak lambat, jangan pedulikan kinerja. Repot-repot soal kode bersih, keterbacaan, pemeliharaan, pengujian, cakupan cabang, desain bersih, dokumentasi terperinci dan mudah dimengerti, komentar kode yang baik.

  2. Jika ya, cari kemacetan. Anda melakukannya bukan dengan menebak, tetapi dengan membuat profil . Dengan membuat profil, Anda menentukan lokasi pasti kemacetan (mengingat bahwa ketika Anda menebak , Anda mungkin hampir setiap kali salah), dan sekarang dapat fokus pada bagian kode tersebut.

  3. Setelah kemacetan ditemukan, cari solusi. Anda melakukannya dengan menebak, membuat tolok ukur, membuat profil, menulis alternatif, memahami optimisasi kompiler, memahami optimisasi yang terserah Anda, mengajukan pertanyaan tentang Stack Overflow dan pindah ke bahasa tingkat rendah (termasuk Assembler, bila perlu).

Apa masalah aktual dengan Projectssubsistem yang meminta info ke Userssubsistem?

Masalah skalabilitas yang akan datang? Ini bukan masalah. Skalabilitas dapat menjadi mimpi buruk jika Anda mulai menggabungkan semuanya menjadi satu solusi monolitik atau meminta data yang sama dari beberapa lokasi (seperti yang dijelaskan di bawah ini, karena kesulitan untuk memperkenalkan cache).

Jika sudah ada masalah kinerja yang terlihat, maka, langkah 2, cari bottleneck.

Jika tampaknya, memang, kemacetan ada dan karena fakta bahwa Projectspermintaan untuk pengguna melalui Userssubsistem (dan terletak pada tingkat permintaan basis data), maka Anda harus mencari alternatif.

Alternatif paling umum adalah menerapkan caching, secara drastis mengurangi jumlah kueri. Jika Anda berada dalam situasi di mana caching tidak membantu, maka profil lebih lanjut mungkin menunjukkan kepada Anda bahwa Anda perlu mengurangi jumlah kueri, atau menambahkan (atau menghapus) indeks basis data, atau membuang lebih banyak perangkat keras, atau mendesain ulang seluruh sistem secara keseluruhan .

Arseni Mourzenko
sumber
Kecuali saya salah paham dengan Anda, Anda mengatakan "simpan panggilan GetUser individual, tetapi gunakan caching untuk menghindari db pulang pergi".
Eren Ersönmez
@ ErenErsönmez:, GetUseralih-alih menanyakan database, akan mencari dalam cache. Ini berarti bahwa sebenarnya tidak masalah berapa kali Anda akan menelepon GetUser, karena itu akan memuat data dari memori daripada database (kecuali cache sudah tidak valid).
Arseni Mourzenko
ini adalah saran yang bagus, mengingat saya belum melakukan pekerjaan dengan baik menyoroti masalah utama, yaitu "untuk menghilangkan chattiness tanpa menggabungkan sistem ke dalam satu sistem". Contoh Pengguna dan Proyek saya secara alami akan membuat Anda percaya bahwa ada sejumlah kecil pengguna yang jarang berubah. Mungkin contoh yang lebih baik adalah Dokumen dan Proyek. Bayangkan Anda memiliki beberapa juta dokumen, ribuan ditambahkan setiap hari dan sistem Proyek menggunakan sistem Dokumen untuk menyimpan dokumen-dokumennya. Apakah Anda masih merekomendasikan caching? Mungkin tidak, kan?
Eren Ersönmez
@ ErenErsönmez: semakin banyak data yang Anda miliki, caching yang lebih kritis akan muncul. Sebagai patokan, bandingkan jumlah bacaan dengan jumlah tulisan. Jika "ribuan" dokumen ditambahkan per hari dan ada jutaan selectpermintaan per hari, Anda sebaiknya menggunakan caching. Di sisi lain, jika Anda menambahkan miliaran entitas ke dalam basis data tetapi hanya mendapatkan beberapa ribu entitas selectdengan selektif yang sangat where, caching mungkin tidak bermanfaat.
Arseni Mourzenko
Anda mungkin benar - saya mungkin sedang mencoba untuk memperbaiki masalah yang belum saya miliki. Saya mungkin akan menerapkan apa adanya dan mencoba untuk meningkatkan nanti jika diperlukan. Jika caching tidak tepat karena, misalnya, entitas cenderung dibaca hanya 1-2 kali setelah ditambahkan, apakah Anda pikir solusi yang mungkin saya tambahkan ke pertanyaan mungkin berhasil? Apakah Anda melihat masalah besar dengan itu?
Eren Ersönmez