Prinsip arsitektur
Tip
Konten ini adalah kutipan dari e-book berjudul Architect Modern Web Applications with ASP.NET Core and Azure, yang tersedia di .NET Docs atau sebagai PDF yang dapat diunduh gratis dan dibaca secara offline.
"Jika pembangun membangun bangunan dengan cara programmer menulis program, maka pelatuk pertama yang datang akan menghancurkan peradaban."
- Gerald Weinberg
Anda harus mengarsiteki dan merancang solusi perangkat lunak dengan ingat pemeliharaan. Prinsip-prinsip yang diuraikan dalam bagian ini dapat membantu memandu Anda menuju keputusan arsitektur yang akan menghasilkan aplikasi yang bersih dan dapat dipertahankan. Umumnya, prinsip-prinsip ini akan memandu Anda untuk membangun aplikasi dari komponen diskrit yang tidak digabungkan erat dengan bagian lain dari aplikasi Anda, tetapi lebih berkomunikasi melalui antarmuka eksplisit atau sistem olahpesan.
Prinsip desain umum
Pemisahan masalah
Prinsip panduan saat mengembangkan adalah Pemisahan Kekhawatiran. Prinsip ini menegaskan bahwa perangkat lunak harus dipisahkan berdasarkan jenis pekerjaan yang dilakukannya. Misalnya, pertimbangkan aplikasi yang menyertakan logika untuk mengidentifikasi item penting untuk ditampilkan kepada pengguna, dan yang memformat item tersebut dengan cara tertentu untuk membuatnya lebih terlihat. Perilaku yang bertanggung jawab untuk memilih item mana yang harus diformat harus dipisahkan dari perilaku yang bertanggung jawab untuk memformat item, karena perilaku ini adalah masalah terpisah yang hanya terkait secara kebetulan satu sama lain.
Secara arsitektur, aplikasi dapat dibangun secara logis untuk mengikuti prinsip ini dengan memisahkan perilaku bisnis inti dari infrastruktur dan logika antarmuka pengguna. Idealnya, aturan dan logika bisnis harus berada dalam proyek terpisah, yang seharusnya tidak bergantung pada proyek lain dalam aplikasi. Pemisahan ini membantu memastikan bahwa model bisnis mudah diuji dan dapat berkembang tanpa digabungkan erat dengan detail implementasi tingkat rendah (juga membantu jika kekhawatiran infrastruktur tergantung pada abstraksi yang ditentukan dalam lapisan bisnis). Pemisahan kekhawatiran adalah pertimbangan utama di balik penggunaan lapisan dalam arsitektur aplikasi.
Enkapsulasi
Bagian aplikasi yang berbeda harus menggunakan enkapsulasi untuk mengisolasinya dari bagian lain aplikasi. Komponen dan lapisan aplikasi harus dapat menyesuaikan implementasi internal mereka tanpa melanggar kolaborator mereka selama kontrak eksternal tidak dilanggar. Penggunaan enkapsulasi yang tepat membantu mencapai konektor dan modularitas longgar dalam desain aplikasi, karena objek dan paket dapat diganti dengan implementasi alternatif selama antarmuka yang sama dipertahankan.
Dalam kelas, enkapsulasi dicapai dengan membatasi akses luar ke status internal kelas. Jika aktor luar ingin memanipulasi status objek, itu harus melakukannya melalui fungsi yang ditentukan dengan baik (atau pengatur properti), daripada memiliki akses langsung ke status privat objek. Demikian juga, komponen dan aplikasi itu sendiri harus mengekspos antarmuka yang terdefinisi dengan baik untuk digunakan kolaborator mereka, daripada memungkinkan statusnya dimodifikasi secara langsung. Pendekatan ini membebaskan desain internal aplikasi untuk berkembang dari waktu ke waktu tanpa khawatir bahwa melakukannya akan merusak kolaborator, selama kontrak publik dipertahankan.
Kondisi global yang dapat diubah bersifat antitetis untuk enkapsulasi. Nilai yang diambil dari status global yang dapat diubah dalam satu fungsi tidak dapat diandalkan untuk memiliki nilai yang sama dalam fungsi lain (atau bahkan lebih jauh dalam fungsi yang sama). Memahami kekhawatiran dengan keadaan global yang dapat diubah adalah salah satu alasan bahasa pemrograman seperti C# memiliki dukungan untuk aturan cakupan yang berbeda, yang digunakan di mana pun dari pernyataan hingga metode ke kelas. Perhatikan bahwa arsitektur berbasis data yang mengandalkan database pusat untuk integrasi di dalam dan di antara aplikasi, sendiri, memilih untuk bergantung pada status global yang dapat diubah yang diwakili oleh database. Pertimbangan utama dalam desain berbasis domain dan arsitektur bersih adalah cara merangkum akses ke data, dan cara memastikan status aplikasi tidak dibuat tidak valid dengan akses langsung ke format persistensinya.
Inversi dependensi
Arah dependensi dalam aplikasi harus ke arah abstraksi, bukan detail implementasi. Sebagian besar aplikasi ditulis sedemikian rupa sehingga ketergantungan waktu kompilasi mengalir ke arah eksekusi runtime, menghasilkan grafik ketergantungan langsung. Artinya, jika kelas A memanggil metode kelas B dan kelas B memanggil metode kelas C, maka pada kelas waktu kompilasi A akan bergantung pada kelas B, dan kelas B akan bergantung pada kelas C, seperti yang ditunjukkan pada Gambar 4-1.
Gambar 4-1. Grafik dependensi langsung.
Menerapkan prinsip inversi dependensi memungkinkan A untuk memanggil metode pada abstraksi yang diterapkan B, memungkinkan A untuk memanggil B pada waktu proses, tetapi agar B bergantung pada antarmuka yang dikontrol oleh A pada waktu kompilasi (dengan demikian, membalikkan dependensi waktu kompilasi tertentu). Pada durasi, alur eksekusi program tetap tidak berubah, tetapi pengenalan antarmuka berarti bahwa implementasi antarmuka ini yang berbeda dapat dengan mudah dicolokkan.
Gambar 4-2. Grafik dependensi terbalik.
Inversi dependensi adalah bagian penting dari membangun aplikasi yang digabungkan secara longgar, karena detail implementasi dapat ditulis untuk bergantung pada dan menerapkan abstraksi tingkat yang lebih tinggi, alih-alih sebaliknya. Aplikasi yang dihasilkan lebih dapat diuji, modular, dan dapat dipertahankan sebagai hasilnya. Praktik injeksi dependensi dimungkinkan dengan mengikuti prinsip inversi dependensi.
Dependensi eksplisit
Metode dan kelas harus secara eksplisit memerlukan objek kolaborasi yang dibutuhkan untuk berfungsi dengan benar. Ini disebut Prinsip Dependensi Eksplisit. Konstruktor kelas memberikan kesempatan bagi kelas untuk mengidentifikasi hal-hal yang mereka butuhkan agar dalam keadaan valid dan berfungsi dengan baik. Jika Anda menentukan kelas yang dapat dibangun dan dipanggil, tetapi itu hanya akan berfungsi dengan baik jika komponen global atau infrastruktur tertentu ada, kelas-kelas ini tidak terbuka dengan klien mereka. Kontrak konstruktor memberi tahu klien bahwa itu hanya membutuhkan hal-hal yang ditentukan (mungkin tidak ada jika kelas hanya menggunakan konstruktor tanpa parameter), tetapi kemudian pada runtime ternyata objek benar-benar membutuhkan sesuatu yang lain.
Dengan mengikuti prinsip dependensi eksplisit, kelas dan metode Anda terbuka dengan klien mereka tentang apa yang mereka butuhkan untuk berfungsi. Mengikuti prinsip ini membuat kode Anda lebih mendokumentasikan diri dan kontrak pengodean Anda lebih ramah pengguna, karena pengguna akan percaya bahwa selama mereka memberikan apa yang diperlukan dalam bentuk parameter metode atau konstruktor, objek yang mereka kerjakan akan berperilaku dengan benar pada durasi.
Tanggung jawab tunggal
Prinsip tanggung jawab tunggal berlaku untuk desain berorientasi objek, tetapi juga dapat dianggap sebagai prinsip arsitektur yang mirip dengan pemisahan kekhawatiran. Ini menyatakan bahwa objek hanya boleh memiliki satu tanggung jawab dan bahwa mereka seharusnya hanya memiliki satu alasan untuk berubah. Secara khusus, satu-satunya situasi di mana objek harus berubah adalah jika cara melakukan tanggung jawab tunggalnya harus diperbarui. Mengikuti prinsip ini membantu menghasilkan sistem yang digabungkan dengan lebih longgar dan modular, karena banyak jenis perilaku baru dapat diimplementasikan sebagai kelas baru, daripada dengan menambahkan tanggung jawab tambahan ke kelas yang ada. Menambahkan kelas baru selalu lebih aman dibandingkan mengubah kelas yang ada, karena belum ada kode yang bergantung pada kelas baru.
Dalam aplikasi yang tidak fleksibel, kita dapat menerapkan prinsip tanggung jawab tunggal pada tingkat tinggi ke lapisan dalam aplikasi. Tanggung jawab presentasi harus tetap berada dalam proyek UI, sementara tanggung jawab akses data harus disimpan dalam proyek infrastruktur. Logika bisnis harus disimpan dalam proyek inti aplikasi, di mana dapat dengan mudah diuji dan dapat berkembang secara independen dari tanggung jawab lain.
Ketika prinsip ini diterapkan pada arsitektur aplikasi dan dibawa ke titik akhir logisnya, Anda mendapatkan layanan mikro. Layanan mikro tertentu harus memiliki tanggung jawab tunggal. Jika Anda ingin memperluas perilaku sistem, lebih baik melakukannya dengan menambahkan layanan mikro tambahan, alih-alih menambahkan tanggung jawab ke yang sudah ada.
Pelajari selengkapnya tentang arsitektur layanan mikro
Jangan ulangi sendiri (DRY)
Aplikasi harus menghindari menentukan perilaku yang terkait dengan konsep tertentu di beberapa tempat karena praktik ini adalah sumber kesalahan yang seringkali terjadi. Pada titik tertentu, perubahan persyaratan akan memerlukan perubahan perilaku ini. Kemungkinan minimal satu instans perilaku akan gagal diperbarui, dan sistem akan berperilaku secara tidak konsisten.
Alih-alih menduplikasi logika, enkapsulasi logika dalam konstruksi pemrograman. Jadikan ini membangun otoritas tunggal atas perilaku ini, dan memiliki bagian lain dari aplikasi yang mengharuskan perilaku ini menggunakan konstruksi baru.
Catatan
Hindari mengikat perilaku bersama yang hanya secara kebetulan berulang. Misalnya, hanya karena dua konstanta yang berbeda keduanya memiliki nilai yang sama, itu tidak berarti Anda harus memiliki hanya satu konstanta, jika secara konseptual mengacu pada hal-hal yang berbeda. Duplikasi selalu lebih disukai daripada menggabungkan ke abstraksi yang salah.
Ketidaktahuan persistensi
Ketidaktahuan persistensi (PI) mengacu pada jenis yang perlu dipertahankan, tetapi kodenya tidak terpengaruh oleh pilihan teknologi persistensi. Jenis .NET seperti itu terkadang disebut sebagai Plain Old CLR Objects (POCO), karena tidak perlu mewarisi dari kelas dasar tertentu atau mengimplementasikan antarmuka tertentu. Ketidaktahuan persistensi sangat berharga karena memungkinkan model bisnis yang sama untuk dipertahankan dalam berbagai cara, menawarkan fleksibilitas tambahan pada aplikasi. Pilihan persistensi dapat berubah seiring waktu, dari satu teknologi database ke teknologi database lainnya, atau mungkin perlu bentuk persistensi tambahan selain apa pun yang dimulai dengan aplikasi (misalnya, menggunakan cache Redis atau Azure Cosmos DB selain database hubungan).
Beberapa contoh pelanggaran terhadap prinsip ini meliputi:
Diperlukan kelas dasar.
Diperlukan implementasi antarmuka.
Kelas yang bertanggung jawab untuk menyimpan diri mereka sendiri (seperti pola Catatan Aktif).
Diperlukan konstruktor tanpa parameter.
Properti yang memerlukan kata kunci virtual.
Atribut yang diperlukan khusus persistensi.
Persyaratan bahwa kelas memiliki salah satu fitur atau perilaku di atas menambahkan konektor antara jenis yang akan dipertahankan dan pilihan teknologi persistensi, sehingga lebih sulit untuk mengadopsi strategi akses data baru di masa depan.
Konteks terikat
Konteks terikat adalah pola pusat dalam Desain Berbasis Domain. Mereka menyediakan cara mengatasi kompleksitas dalam aplikasi atau organisasi besar dengan memecahnya menjadi modul konseptual terpisah. Setiap modul konseptual lalu mewakili konteks yang dipisahkan dari konteks lain (karenanya, terikat), dan dapat berkembang secara independen. Setiap konteks terikat idealnya harus bebas memilih nama untuk konsep di dalamnya, dan harus memiliki akses eksklusif atas penyimpanan persistensinya sendiri.
Minimal, aplikasi web individu harus berusaha untuk menjadi konteks terikat sendiri, dengan penyimpanan persistensi sendiri untuk model bisnis mereka, alih-alih berbagi database dengan aplikasi lain. Komunikasi antara konteks terikat terjadi melalui antarmuka terprogram, bukan melalui database bersama, yang memungkinkan logika bisnis dan peristiwa berlangsung sebagai respons terhadap perubahan yang terjadi. Konteks terikat memetakan dengan cermat ke layanan mikro, yang juga idealnya diimplementasikan sebagai konteks terikat individu sendiri.