
Komunitas pengembang modern sekarang lebih dari sebelumnya tunduk pada mode dan tren, dan ini terutama berlaku untuk dunia pengembangan front-end. Kerangka kerja dan praktik baru kami adalah nilai utama, dan sebagian besar CV, lowongan, dan program konferensi terdiri dari daftar mereka. Dan meskipun pengembangan ide dan alat itu sendiri tidak negatif, tetapi karena keinginan konstan pengembang untuk mengikuti tren yang sulit dipahami, kami mulai melupakan pentingnya pengetahuan teoritis umum tentang arsitektur aplikasi.
Prevalensi penyetelan pengetahuan tentang teori dan praktik terbaik telah mengarah pada fakta bahwa sebagian besar proyek baru saat ini memiliki tingkat pemeliharaan yang sangat rendah, sehingga menciptakan ketidaknyamanan yang signifikan bagi pengembang (kompleksitas tinggi yang konsisten dalam mempelajari dan memodifikasi kode) dan untuk pelanggan (tarif rendah dan biaya pengembangan tinggi).
Untuk setidaknya mempengaruhi entah bagaimana situasi saat ini, hari ini saya ingin memberi tahu Anda tentang apa arsitektur yang baik, bagaimana itu berlaku untuk antarmuka web, dan yang paling penting, bagaimana itu berkembang dari waktu ke waktu.
NB : Sebagai contoh dalam artikel ini, hanya kerangka kerja yang penulis tangani langsung akan digunakan, dan perhatian yang signifikan akan dibayarkan untuk Bereaksi dan Redux di sini. Namun, terlepas dari ini, banyak ide dan prinsip yang dijelaskan di sini bersifat umum dan dapat lebih atau kurang berhasil diproyeksikan ke teknologi pengembangan antarmuka lainnya.Arsitektur untuk Dummies
Untuk memulai, mari kita berurusan dengan istilah itu sendiri. Dengan kata sederhana, arsitektur sistem apa pun adalah definisi komponennya dan skema interaksi di antara mereka. Ini adalah semacam landasan konseptual di atas implementasi yang nantinya akan dibangun.
Tugas arsitektur adalah untuk memenuhi persyaratan eksternal untuk sistem yang dirancang. Persyaratan ini bervariasi dari proyek ke proyek dan bisa sangat spesifik, tetapi dalam kasus umum mereka adalah untuk memfasilitasi proses modifikasi dan perluasan solusi yang dikembangkan.
Adapun kualitas arsitektur, biasanya dinyatakan dalam properti berikut:
-
Pengiring : kecenderungan sistem untuk mempelajari dan modifikasi yang telah disebutkan (kesulitan mendeteksi dan memperbaiki kesalahan, memperluas fungsionalitas, mengadaptasi solusi ke lingkungan atau kondisi lain)
-
Replaceability : kemampuan untuk mengubah implementasi setiap elemen sistem tanpa mempengaruhi elemen lainnya
-
Testability : kemampuan untuk memverifikasi operasi elemen yang benar (kemampuan untuk mengontrol elemen dan mengamati kondisinya)
-
Portabilitas : kemampuan untuk menggunakan kembali elemen dalam sistem lain
-
Kegunaan : keseluruhan tingkat kenyamanan sistem ketika dioperasikan oleh pengguna akhir
Penyebutan terpisah juga dibuat dari salah satu prinsip utama membangun arsitektur yang berkualitas: prinsip
pemisahan masalah . Terdiri dari fakta bahwa setiap elemen sistem harus bertanggung jawab secara eksklusif untuk satu tugas tunggal (diterapkan, secara tidak sengaja, ke kode aplikasi: lihat
prinsip tanggung jawab tunggal ).
Sekarang kita memiliki gagasan tentang konsep arsitektur, mari kita lihat apa pola desain arsitektur dapat menawarkan kita dalam konteks antarmuka.
Tiga kata terpenting
Salah satu pola pengembangan antarmuka yang paling terkenal adalah MVC (Model-View-Controller), konsep kuncinya adalah membagi logika antarmuka menjadi tiga bagian terpisah:
1.
Model - bertanggung jawab untuk menerima, menyimpan, dan memproses data
2.
Lihat - bertanggung jawab untuk visualisasi data
3.
Kontroler - mengontrol Model dan Tampilan
Pola ini juga mencakup deskripsi skema interaksi di antara mereka, tetapi di sini informasi ini akan dihilangkan karena fakta bahwa setelah waktu tertentu masyarakat umum disajikan dengan modifikasi yang diperbaiki dari pola ini yang disebut MVP (Model-View-Presenter), yang skema asli ini interaksi sangat disederhanakan:

Karena kita berbicara secara khusus tentang antarmuka web, kami menggunakan elemen lain yang agak penting yang biasanya menyertai penerapan pola-pola ini - router. Tugasnya adalah membaca URL dan memanggil presenter yang terkait dengannya.
Skema di atas berfungsi sebagai berikut:
1. Router membaca URL dan memanggil Presenter terkait
2-5. Presenter beralih ke Model dan mendapatkan data yang diperlukan dari itu.
6. Presenter mentransfer data dari Model ke View, yang mengimplementasikan visualisasinya.
7. Selama interaksi pengguna dengan antarmuka, View memberi tahu Presenter tentang ini, yang mengembalikan kita ke poin kedua
Seperti yang telah ditunjukkan oleh praktik, MVC dan MVP bukanlah arsitektur yang ideal dan universal, tetapi mereka masih melakukan satu hal yang sangat penting - mereka menunjukkan tiga bidang tanggung jawab utama, yang tanpanya tidak ada antarmuka yang dapat diimplementasikan dalam satu bentuk atau lainnya.
NB: Secara umum, konsep Pengendali dan Presenter memiliki arti yang sama, dan perbedaan namanya hanya diperlukan untuk membedakan pola-pola yang disebutkan, yang hanya berbeda dalam implementasi komunikasi .MVC dan rendering server
Terlepas dari kenyataan bahwa MVC adalah pola untuk mengimplementasikan klien, ia menemukan aplikasinya di server juga. Selain itu, dalam konteks server yang paling mudah untuk menunjukkan prinsip-prinsip operasinya.
Dalam kasus ketika kita berhadapan dengan situs informasi klasik, di mana tugas server web adalah untuk menghasilkan halaman HTML untuk pengguna, MVC juga memungkinkan kita untuk mengatur arsitektur aplikasi yang cukup ringkas:
- Router membaca data dari permintaan HTTP yang diterima
(GET / user-profile / 1) dan memanggil Controller yang terkait
(UsersController.getProfilePage (1))- Pengontrol memanggil Model untuk mendapatkan informasi yang diperlukan dari database
(UsersModel.get (1))- Pengontrol meneruskan data yang diterima ke Lihat
(View.render ('pengguna / profil', pengguna)) dan menerima markup HTML darinya, yang meneruskannya kembali ke klien
Dalam hal ini, Lihat biasanya diterapkan sebagai berikut:

const templates = { 'users/profile': ` <div class="user-profile"> <h2>{{ name}}</h2> <p>E-mail: {{ email }}</p> <p> Projects: {{#each projects}} <a href="/projects/{{id}}">{{name}}</a> {{/each}} </p> <a href=/user-profile/1/edit>Edit</a> </div> ` }; class View { render(templateName, data) { const htmlMarkup = TemplateEngine.render(templates[templateName], data); return htmlMarkup; } }
NB: Kode di atas sengaja disederhanakan untuk digunakan sebagai contoh. Dalam proyek nyata, template diekspor ke file terpisah dan melewati tahap kompilasi sebelum digunakan (lihat Handlebars.compile () atau _.template () ).Di sini yang disebut engine templat digunakan, yang memberi kami alat untuk memudahkan deskripsi templat teks dan mekanisme untuk menggantikan data nyata di dalamnya.
Pendekatan seperti itu terhadap implementasi Tampilan tidak hanya menunjukkan pemisahan tanggung jawab yang ideal, tetapi juga memberikan tingkat testabilitas yang tinggi: untuk memeriksa kebenaran tampilan, cukup bagi kita untuk membandingkan garis referensi dengan garis yang kita dapatkan dari mesin templat.
Dengan demikian, menggunakan MVC, kita mendapatkan arsitektur yang hampir sempurna, di mana masing-masing elemen memiliki tujuan yang sangat spesifik, konektivitas minimal, dan juga memiliki tingkat testabilitas dan portabilitas yang tinggi.
Adapun pendekatan dengan pembuatan markup HTML menggunakan alat server, karena UX rendah, pendekatan ini secara bertahap mulai digantikan oleh SPA.
Backbone dan MVP
Salah satu kerangka kerja pertama untuk sepenuhnya membawa logika tampilan ke klien adalah
Backbone.js . Implementasi Router, Presenter dan Model di dalamnya cukup standar, tetapi implementasi baru dari View layak mendapat perhatian kita:

const UserProfile = Backbone.View.extend({ tagName: 'div', className: 'user-profile', events: { 'click .button.edit': 'openEditDialog', }, openEditDialog: function(event) {
Jelas, implementasi pemetaan telah menjadi jauh lebih rumit - mendengarkan peristiwa dari model dan DOM, serta logika pemrosesan mereka, telah ditambahkan ke standardisasi dasar. Selain itu, untuk menampilkan perubahan pada antarmuka, sangat diinginkan untuk tidak sepenuhnya merender tampilan, tetapi untuk melakukan pekerjaan yang lebih baik dengan elemen DOM tertentu (biasanya menggunakan jQuery), yang mengharuskan penulisan banyak kode tambahan.
Karena kerumitan umum implementasi Lihat, pengujiannya menjadi lebih rumit - karena sekarang kami bekerja secara langsung dengan pohon DOM, untuk pengujian kami perlu menggunakan alat tambahan yang menyediakan atau meniru lingkungan browser.
Dan masalah dengan implementasi Tampilan baru tidak berakhir di sana:
Selain di atas, agak sulit untuk menggunakan bersarang di satu sama lain. Seiring waktu, masalah ini diselesaikan dengan bantuan
Daerah di
Marionette.js , tetapi sebelum itu, pengembang harus menemukan trik mereka sendiri untuk menyelesaikan masalah yang agak sederhana dan sering timbul ini.
Dan yang terakhir. Antarmuka yang dikembangkan dengan cara ini cenderung untuk data tidak sinkron - karena semua model ada terisolasi pada tingkat penyaji yang berbeda, maka ketika mengubah data di satu bagian dari antarmuka, mereka biasanya tidak memperbarui di yang lain.
Tetapi, terlepas dari masalah ini, pendekatan ini lebih dari layak, dan pengembangan Backbone yang disebutkan sebelumnya dalam bentuk
Marionette masih dapat berhasil diterapkan untuk pengembangan SPA.
Bereaksi dan Membatalkan
Sulit dipercaya, tetapi pada saat rilis awal,
React.js menimbulkan banyak keraguan di kalangan komunitas pengembang. Skeptisisme ini begitu hebat sehingga untuk waktu yang lama teks berikut ini diposting di situs web resmi:
Berikan Lima Menit
Bereaksi banyak tantangan kebijaksanaan konvensional, dan pada pandangan pertama beberapa ide mungkin tampak gila.
Dan ini terlepas dari kenyataan bahwa, tidak seperti sebagian besar pesaing dan pendahulunya, React bukan kerangka kerja lengkap dan hanya perpustakaan kecil untuk memfasilitasi tampilan data di DOM:
React adalah perpustakaan JavaScript untuk membuat antarmuka pengguna oleh Facebook dan Instagram. Banyak orang memilih untuk menganggap React sebagai V di MVC.
Konsep utama yang ditawarkan React adalah konsep komponen, yang, pada kenyataannya, memberi kita cara baru untuk mengimplementasikan Tampilan:
class User extends React.Component { handleEdit() {
Bereaksi sangat menyenangkan untuk digunakan. Di antara kelebihannya yang tak terbantahkan adalah hingga hari ini:
1)
Deklarasi dan reaktivitas . Tidak perlu lagi memperbarui DOM secara manual saat mengubah data yang ditampilkan.
2)
Komposisi komponen . Membangun dan menjelajahi pohon View telah menjadi tindakan yang sepenuhnya elementer.
Namun, sayangnya, Bereaksi memiliki sejumlah masalah. Salah satu yang paling penting adalah kenyataan bahwa Bereaksi bukan kerangka kerja lengkap dan, oleh karena itu, tidak menawarkan kepada kita segala jenis arsitektur aplikasi atau alat lengkap untuk implementasinya.
Mengapa ini ditulis dalam kelemahan? Ya, karena sekarang React adalah solusi paling populer untuk mengembangkan aplikasi web (
bukti ,
bukti lain ,
dan satu lagi bukti ), itu adalah titik masuk bagi pengembang front-end baru, tetapi pada saat yang sama tidak menawarkan atau mempromosikan arsitektur, maupun pendekatan dan praktik terbaik untuk membangun aplikasi lengkap. Selain itu, ia menciptakan dan mempromosikan pendekatan kebiasaannya sendiri seperti
HOC atau
Hooks , yang tidak digunakan di luar ekosistem Bereaksi. Akibatnya, setiap aplikasi Bereaksi memecahkan masalah khas dengan caranya sendiri, dan biasanya tidak melakukannya dengan cara yang paling benar.
Masalah ini dapat didemonstrasikan dengan bantuan salah satu kesalahan paling umum dari pengembang Bereaksi, yang terdiri dari penyalahgunaan komponen:
Jika satu-satunya alat yang Anda miliki adalah palu, semuanya mulai terlihat seperti paku.
Dengan bantuan mereka, pengembang memecahkan serangkaian tugas yang sama sekali tidak terpikirkan yang jauh melampaui lingkup visualisasi data. Sebenarnya, dengan bantuan komponen, mereka benar-benar menerapkan segalanya - mulai dari
permintaan media dari CSS hingga
perutean .
Bereaksi dan Redux
Mengembalikan ketertiban dalam struktur aplikasi Bereaksi sangat difasilitasi oleh penampilan dan mempopulerkan
Redux . Jika React adalah View from MVP, maka Redux menawari kami variasi Model yang cukup nyaman.
Gagasan utama Redux adalah transfer data dan logika bekerja dengannya menjadi satu gudang data terpusat - yang disebut Store. Pendekatan ini sepenuhnya menyelesaikan masalah duplikasi dan desinkronisasi data, yang telah kita bicarakan sedikit lebih awal, dan juga menawarkan banyak fasilitas lainnya, yang, di antaranya, termasuk kemudahan mempelajari keadaan data saat ini dalam aplikasi.
Fitur lain yang sama pentingnya adalah cara komunikasi antara Store dan bagian lain dari aplikasi. Alih-alih langsung mengakses Store atau datanya, kami ditawari untuk menggunakan Tindakan yang disebut (objek sederhana yang menggambarkan peristiwa atau perintah), yang memberikan tingkat
pelepasan lepas yang lemah antara Store dan sumber acara, sehingga secara signifikan meningkatkan tingkat pemeliharaan proyek. Dengan demikian, Redux tidak hanya memaksa pengembang untuk menggunakan pendekatan arsitektur yang lebih tepat, tetapi juga memungkinkan Anda untuk mengambil keuntungan dari berbagai keuntungan dari
sumber acara - sekarang dalam proses debugging kita dapat dengan mudah melihat sejarah tindakan dalam aplikasi, dampaknya pada data, dan jika perlu, semua informasi ini dapat diekspor , Yang juga sangat berguna ketika menganalisis kesalahan dari produksi.
Skema umum aplikasi menggunakan React / Redux dapat direpresentasikan sebagai berikut:

Komponen react masih bertanggung jawab untuk menampilkan data. Idealnya, komponen-komponen ini harus bersih dan fungsional, tetapi jika perlu, mereka mungkin memiliki keadaan lokal dan logika terkait (misalnya, untuk mengimplementasikan menyembunyikan / menampilkan elemen tertentu atau preprocessing dasar dari tindakan pengguna).
Ketika seorang pengguna melakukan suatu tindakan di antarmuka, komponen hanya memanggil fungsi handler yang sesuai, yang diterima dari luar bersama dengan data untuk ditampilkan.
Komponen wadah yang disebut bertindak sebagai Presenter bagi kami - merekalah yang mengendalikan komponen tampilan dan interaksinya dengan data. Mereka dibuat menggunakan fungsi
koneksi , yang memperluas fungsionalitas komponen yang diteruskan ke dalamnya, menambahkan langganan untuk mengubah data di Store dan membiarkan kami menentukan data dan penangan peristiwa mana yang harus diteruskan ke sana.
Dan jika semuanya jelas dengan data di sini (kami hanya memetakan data dari toko ke "alat peraga" yang diharapkan), maka saya ingin membahas tentang penangan acara secara lebih detail - mereka tidak hanya mengirim Tindakan ke Toko, tetapi mungkin juga mengandung logika tambahan untuk memproses acara - misalnya, termasuk percabangan, melakukan pengalihan otomatis, dan melakukan pekerjaan lain yang khusus untuk presenter.
Poin penting lain mengenai komponen wadah: karena fakta bahwa mereka dibuat melalui HOC, pengembang cukup sering menggambarkan komponen tampilan dan komponen wadah dalam satu modul dan hanya mengekspor wadah. Ini bukan pendekatan yang tepat, karena untuk kemungkinan menguji dan menggunakan kembali komponen tampilan, itu harus benar-benar dipisahkan dari wadah dan lebih baik diambil dalam file terpisah.
Nah, hal terakhir yang belum kita pertimbangkan adalah Store. Ini berfungsi sebagai implementasi Model yang agak spesifik dan terdiri dari beberapa komponen: Status (objek yang berisi semua data kami), Middleware (seperangkat fungsi yang memproses semua Tindakan yang diterima), Peredam (fungsi yang mengubah data dalam Status) dan beberapa atau penangan efek samping yang bertanggung jawab untuk menjalankan operasi asinkron (mengakses sistem eksternal, dll.).
Masalah paling umum di sini adalah bentuk Negara kita. Secara formal, Redux tidak memberlakukan batasan apa pun pada kami dan tidak memberikan rekomendasi tentang apa objek ini seharusnya. Pengembang dapat benar-benar menyimpan data apa pun di dalamnya (termasuk
keadaan formulir dan
informasi dari router ), data ini dapat dari jenis apa pun (
tidak dilarang untuk menyimpan bahkan fungsi dan instance objek) dan memiliki tingkat bersarang. Bahkan, ini sekali lagi mengarah pada fakta bahwa dari satu proyek ke proyek yang lain kami mendapatkan pendekatan yang sangat berbeda untuk menggunakan Negara, yang sekali lagi menyebabkan beberapa kebingungan.
Untuk mulai dengan, kami setuju bahwa kami tidak harus menyimpan semua data aplikasi dalam
keadaan mutlak - ini jelas
ditunjukkan oleh dokumentasi . Meskipun menyimpan bagian dari data di dalam status komponen menciptakan ketidaknyamanan tertentu ketika menavigasi melalui sejarah tindakan selama proses debug (keadaan internal komponen selalu tidak berubah), mentransfer data ini ke Negara menciptakan lebih banyak kesulitan - ini secara signifikan meningkatkan ukurannya dan membutuhkan penciptaan lebih banyak lagi Tindakan dan reduksi.
Adapun untuk menyimpan data lokal lainnya di Negara, kami biasanya berurusan dengan beberapa konfigurasi antarmuka umum, yang merupakan seperangkat pasangan nilai kunci. Dalam hal ini, kita dapat dengan mudah melakukannya dengan satu objek sederhana dan reducer untuknya.
Dan jika kita berbicara tentang menyimpan data dari sumber eksternal, maka berdasarkan fakta bahwa dalam pengembangan antarmuka di sebagian besar kasus kita berurusan dengan CRUD klasik, maka untuk menyimpan data dari server masuk akal untuk memperlakukan Negara sebagai RDBMS: kunci adalah nama sumber daya, dan di belakangnya adalah array yang disimpan dari objek yang dimuat (
tanpa bersarang ) dan informasi opsional untuk mereka (misalnya, jumlah total catatan pada server untuk membuat pagination). Bentuk umum dari data ini harus seseragam mungkin - ini akan memungkinkan kami untuk menyederhanakan pembuatan reduksi untuk setiap jenis sumber daya:
const getModelReducer = modelName => (models = [], action) => { const isModelAction = modelActionTypes.includes(action.type); if (isModelAction && action.modelName === modelName) { switch (action.type) { case 'ADD_MODELS': return collection.add(action.models, models); case 'CHANGE_MODEL': return collection.change(action.model, models); case 'REMOVE_MODEL': return collection.remove(action.model, models); case 'RESET_STATE': return []; } } return models; };
Nah, poin lain yang ingin saya bahas dalam konteks menggunakan Redux adalah penerapan efek samping.
Pertama-tama, lupakan
Redux Thunk - transformasi Tindakan yang diusulkan olehnya menjadi fungsi dengan efek samping, meskipun ini merupakan solusi yang berfungsi, tetapi menggabungkan konsep dasar arsitektur kita dan mengurangi kelebihannya menjadi nol.
Redux Saga menawarkan kepada kita pendekatan yang jauh lebih tepat untuk menerapkan efek samping, meskipun ada beberapa pertanyaan mengenai penerapan teknisnya.
Selanjutnya - cobalah untuk menyatukan sebanyak mungkin efek samping Anda yang mengakses server. Seperti formulir dan reduksi Negara, kita hampir selalu dapat mengimplementasikan logika membuat permintaan ke server menggunakan satu penangan tunggal. Misalnya, dalam kasus RESTful API, ini dapat dicapai dengan mendengarkan Tindakan umum seperti:
{ type: 'CREATE_MODEL', payload: { model: 'reviews', attributes: { title: '...', text: '...' } } }
... dan membuat permintaan HTTP umum yang sama pada mereka:
POST /api/reviews { title: '...', text: '...' }
Dengan secara sadar mengikuti semua tips di atas, Anda bisa mendapatkan, jika bukan arsitektur yang ideal, maka setidaknya dekat dengannya.
Masa depan yang cerah
Perkembangan modern antarmuka web benar-benar telah mengambil langkah maju yang signifikan, dan sekarang kita hidup di masa ketika sebagian besar masalah utama telah diselesaikan dengan satu atau lain cara. Tetapi ini tidak berarti sama sekali bahwa di masa depan tidak akan ada revolusi baru.
Jika Anda mencoba melihat ke masa depan, maka kemungkinan besar kita akan melihat yang berikut:
1. Pendekatan komponen tanpa BEJKonsep komponen telah terbukti sangat sukses, dan, kemungkinan besar, kita akan melihat popularisasi mereka yang lebih besar. Tetapi BEJ itu sendiri bisa dan harus mati. Ya, ini benar-benar cukup nyaman untuk digunakan, tetapi, bagaimanapun, ini bukan standar yang diterima secara umum atau kode JS yang valid. Perpustakaan untuk mengimplementasikan antarmuka, betapapun baiknya, tidak boleh menemukan standar baru, yang kemudian harus diimplementasikan berulang-ulang dalam setiap toolkit pengembangan yang memungkinkan.
2. Nyatakan wadah tanpa ReduxPenggunaan gudang data terpusat, yang diusulkan oleh Redux, juga merupakan solusi yang sangat sukses, dan di masa depan harus menjadi semacam standar dalam pengembangan antarmuka, tetapi arsitektur dan implementasinya internal mungkin mengalami perubahan dan penyederhanaan tertentu.
3. Meningkatkan pertukaran perpustakaanSaya percaya bahwa seiring waktu, komunitas pengembang front-end akan menyadari manfaat dari memaksimalkan pertukaran perpustakaan dan akan berhenti mengunci diri mereka ke dalam ekosistem kecil mereka. Semua komponen aplikasi - router, wadah negara, dll. - mereka harus sangat universal, dan penggantiannya tidak memerlukan refactoring massal atau menulis ulang aplikasi dari awal.
Kenapa semua ini?
Jika kami mencoba untuk menggeneralisasi informasi yang disajikan di atas dan menguranginya ke bentuk yang lebih sederhana dan lebih pendek, maka kami mendapatkan beberapa poin yang cukup umum:
- Agar pengembangan aplikasi berhasil, pengetahuan bahasa dan kerangka tidak cukup, perhatian harus diberikan pada hal-hal teoretis umum: arsitektur aplikasi, praktik terbaik, dan pola desain.
"Satu-satunya yang konstan adalah perubahan." Pendekatan pengolahan dan pengembangan akan terus berubah, sehingga proyek-proyek besar dan berumur panjang harus memperhatikan arsitektur dengan tepat - tanpanya, memperkenalkan alat dan praktik baru akan sangat sulit.
Dan mungkin itu saja bagiku. Banyak terima kasih kepada semua orang yang menemukan kekuatan untuk membaca artikel sampai akhir. Jika Anda memiliki pertanyaan atau komentar, saya mengundang Anda untuk berkomentar.