Selamat siang, Habrazhiteli sayang!
Hari ini DevOps berada di gelombang kesuksesan. Di hampir semua konferensi yang didedikasikan untuk otomatisasi, Anda dapat mendengar dari pembicara mengatakan "kami menerapkan DevOps di sana-sini, menerapkan ini dan itu, menjadi lebih mudah untuk melakukan proyek, dll, dll.". Dan itu patut dipuji. Tetapi, sebagai aturan, implementasi DevOps di banyak perusahaan berakhir pada tahap otomatisasi Operasi TI, dan sangat sedikit orang yang berbicara tentang penerapan DevOps secara langsung dalam proses pengembangan itu sendiri.
Saya ingin memperbaiki kesalahpahaman kecil ini. DevOps dapat dikembangkan melalui formalisasi basis kode, misalnya saat menulis GUI untuk REST API.
Pada artikel ini, saya ingin berbagi dengan Anda solusi untuk kasus non-standar yang ditemui perusahaan kami - kami dapat mengotomatisasi pembentukan antarmuka aplikasi web. Saya akan memberi tahu Anda tentang bagaimana kami sampai pada tugas ini dan apa yang kami gunakan untuk menyelesaikannya. Kami tidak percaya bahwa pendekatan kami adalah satu-satunya yang benar, tetapi kami benar-benar menyukainya.
Saya berharap materi ini akan menarik dan bermanfaat bagi Anda.
Baiklah, mari kita mulai!
Latar belakang
Kisah ini dimulai sekitar setahun yang lalu: itu adalah hari musim panas yang indah dan departemen pengembangan kami menciptakan aplikasi web berikutnya. Dalam agenda adalah tugas memperkenalkan fitur baru ke dalam aplikasi - perlu untuk menambahkan kemampuan untuk membuat kait kustom.

Pada saat itu, arsitektur aplikasi web kami dibangun sedemikian rupa sehingga untuk mengimplementasikan fitur baru, kami perlu melakukan hal berikut:
- Di bagian belakang: buat model untuk entitas baru (kait), jelaskan bidang model ini, jelaskan seluruh logika tindakan yang dapat dilakukan model, dll.
- Di front-end'e: buat kelas presentasi yang sesuai dengan model baru di API, uraikan secara manual semua bidang yang dimiliki model ini, tambahkan semua jenis tindakan yang dapat dijalankan tampilan ini, dll.
Ternyata kita sekaligus di dua tempat secara bersamaan, itu perlu untuk membuat perubahan yang sangat mirip dalam kode, satu atau lain cara, "menduplikasi" satu sama lain. Dan ini, seperti yang Anda tahu, tidak baik, karena dengan perubahan lebih lanjut, pengembang perlu membuat perubahan di tempat yang sama di dua tempat pada saat yang sama.
Misalkan kita perlu mengubah jenis bidang "nama" dari "string" ke "textarea". Untuk melakukan ini, kita perlu membuat amandemen ini dalam kode model di server, dan kemudian membuat perubahan yang mirip dengan kode presentasi pada klien.
Apakah ini terlalu rumit?
Sebelumnya, kami menerima kenyataan ini, karena banyak aplikasi tidak terlalu besar dan ada tempat untuk "menduplikasi" kode pada server dan pada klien. Tetapi pada hari musim panas itu, sebelum pengenalan fitur baru, sesuatu diklik di dalam diri kami, dan kami menyadari bahwa kami tidak dapat bekerja lagi. Pendekatan saat ini sangat tidak masuk akal dan membutuhkan banyak waktu dan tenaga. Selain itu, "duplikasi" kode pada back-end dan front-end dapat menyebabkan bug yang tidak terduga di masa depan: pengembang dapat membuat perubahan pada server dan lupa untuk membuat perubahan serupa pada klien, dan kemudian semuanya tidak berjalan dengan baik sesuai rencana.
Bagaimana cara menghindari duplikasi kode? Cari solusinya
Kami mulai bertanya-tanya bagaimana kami dapat mengoptimalkan proses memperkenalkan fitur-fitur baru.
Kami bertanya pada diri sendiri pertanyaan: "Bisakah kita sekarang menghindari duplikasi perubahan dalam representasi model di front-end'e, setelah ada perubahan dalam strukturnya di back-end'e?"
Kami berpikir dan menjawab: "Tidak, kami tidak bisa."
Kemudian kami bertanya pada diri sendiri, "Oke, lalu apa alasan duplikasi kode seperti itu?"
Dan kemudian kami sadar: masalahnya, pada kenyataannya, adalah front-end kami tidak menerima data pada struktur API saat ini. Front-end tidak tahu apa-apa tentang model yang ada di API sampai kami sendiri yang mengetahuinya.
Dan kemudian kita mendapat ide: bagaimana jika kita membangun arsitektur aplikasi sedemikian rupa sehingga:
- Front-end yang diterima dari API tidak hanya data model, tetapi juga struktur model ini;
- Representasi front-end yang dibentuk secara dinamis berdasarkan pada struktur model;
- Setiap perubahan dalam struktur API secara otomatis ditampilkan di front-end.
Menerapkan fitur baru akan memakan waktu jauh lebih sedikit, karena hanya akan membutuhkan perubahan di sisi back-end, dan front-end akan secara otomatis mengambil semuanya dan menyajikannya kepada pengguna dengan benar.
Fleksibilitas arsitektur baru
Dan kemudian, kami memutuskan untuk berpikir sedikit lebih luas: apakah arsitektur baru hanya cocok untuk aplikasi kita saat ini, atau bisakah kita menggunakannya di tempat lain?

Memang, dengan satu atau lain cara, hampir semua aplikasi memiliki bagian dari fungsi serupa:
- hampir semua aplikasi memiliki pengguna, dan dalam hal ini, perlu memiliki fungsi yang terkait dengan pendaftaran dan otorisasi pengguna;
- hampir semua aplikasi memiliki beberapa jenis representasi: ada tampilan untuk melihat daftar objek model, ada tampilan untuk melihat catatan terperinci dari objek model tunggal, individual, model;
- hampir semua model memiliki atribut yang serupa dalam jenis: data string, angka, dll., dan dalam hal ini, Anda harus dapat bekerja dengan mereka baik di back-end dan di front-end.
Dan karena perusahaan kami sering melakukan pengembangan aplikasi web khusus, kami berpikir: mengapa kita perlu menemukan kembali roda setiap waktu dan mengembangkan fungsionalitas yang serupa setiap kali dari awal, jika Anda dapat menulis kerangka kerja yang akan menggambarkan semua dasar, umum bagi banyak aplikasi, hal-hal, dan kemudian, membuat proyek baru, menggunakan pengembangan yang sudah jadi sebagai dependensi, dan jika perlu, secara deklaratif mengubahnya dalam proyek baru.
Jadi, dalam diskusi panjang, kami memiliki ide untuk membuat VSTUtils - sebuah kerangka kerja yang akan:
- Ini berisi fungsionalitas dasar, paling mirip dengan sebagian besar aplikasi;
- Diizinkan untuk membuat front-end on the fly, berdasarkan pada struktur API.
Bagaimana cara mendapatkan teman back-end dan front-end?
Kalau begitu, harus kita lakukan, pikir kami. Kami sudah memiliki beberapa back-end, beberapa front-end juga, tetapi server maupun klien tidak memiliki alat yang dapat melaporkan atau menerima data pada struktur API.
Dalam mencari solusi untuk masalah ini, mata kami tertuju pada spesifikasi
OpenAPI , yang, berdasarkan deskripsi model dan hubungan di antara mereka, menghasilkan JSON besar yang berisi semua informasi ini.
Dan kami berpikir bahwa, secara teori, ketika menginisialisasi aplikasi pada klien, front-end dapat menerima JSON ini dari API dan membangun semua pandangan yang diperlukan atas dasar. Tinggal mengajarkan front-end kita untuk melakukan semua ini.
Dan setelah beberapa waktu kami mengajarinya.
Versi 1.0 - apa yang keluar darinya
Arsitektur kerangka VSTUtils dari versi pertama terdiri dari 3 bagian bersyarat dan terlihat seperti ini:
- Ujung belakang:
- Django dan Python adalah semua model yang berhubungan dengan logika. Berdasarkan pada basis Model Django, kami telah menciptakan beberapa kelas model inti VSTUtils. Semua tindakan yang dapat dilakukan model ini kami terapkan menggunakan Python;
- Django REST Framework - REST API generation. Berdasarkan uraian model, REST API dibentuk, berkat komunikasi dengan server dan klien;
- Interaksi antara back-end dan front-end:
- OpenAPI - Generasi JSON dengan deskripsi struktur API. Setelah semua model dijelaskan di bagian belakang, tampilan dibuat untuknya. Menambahkan masing-masing tampilan memperkenalkan informasi yang diperlukan ke dalam JSON yang dihasilkan:
Contoh JSON - Skema OpenAPI{ // , (, ), // - , // - . definitions: { // Hook. Hook: { // , (, ), // - , // - (, ..). properties: { id: { title: "Id", type: "integer", readOnly: true, }, name: { title: "Name", type: "string", minLength:1, maxLength: 512, }, type: { title: "Type", type: "string", enum: ["HTTP","SCRIPT"], }, when: { title: "When", type: "string", enum: ["on_object_add","on_object_upd","on_object_del"], }, enable: { title:"Enable", type:"boolean", }, recipients: { title: "Recipients", type: "string", minLength: 1, } }, // , , . required: ["type","recipients"], } }, // , (, ), // - ( URL), // - . paths: { // '/hook/'. '/hook/': { // get /hook/. // , Hook. get: { operationId: "hook_list", description: "Return all hooks.", // , , . parameters: [ { name: "id", in: "query", description: "A unique integer value (or comma separated list) identifying this instance.", required: false, type: "string", }, { name: "name", in: "query", description: "A name string value (or comma separated list) of instance.", required: false, type: "string", }, { name: "type", in: "query", description: "Instance type.", required: false, type: "string", }, ], // , (, ), // - ; // - . responses: { 200: { description: "Action accepted.", schema: { properties: { results: { type: "array", items: { // , . $ref: "#/definitions/Hook", }, }, }, }, }, 400: { description: "Validation error or some data error.", schema: { $ref: "#/definitions/Error", }, }, 401: { // ... }, 403: { // ... }, 404: { // ... }, }, tags: ["hook"], }, // post /hook/. // , Hook. post: { operationId: "hook_add", description: "Create a new hook.", parameters: [ { name: "data", in: "body", required: true, schema: { $ref: "#/definitions/Hook", }, }, ], responses: { 201: { description: "Action accepted.", schema: { $ref: "#/definitions/Hook", }, }, 400: { description: "Validation error or some data error.", schema: { $ref: "#/definitions/Error", }, }, 401: { // ... }, 403: { // ... }, 404: { // ... }, }, tags: ["hook"], }, } } }
- Front-end:
- JavaScript adalah mekanisme yang mem-parsing skema OpenAPI dan menghasilkan tampilan. Mekanisme ini diluncurkan sekali, ketika aplikasi diinisialisasi pada klien. Dengan mengirimkan permintaan ke API, ia menerima JSON yang diminta sebagai tanggapan dengan deskripsi struktur API dan, menganalisisnya, menciptakan semua objek JS yang diperlukan yang berisi parameter dari representasi model. Permintaan API ini cukup berat, jadi kami menyimpannya dan memintanya lagi hanya saat memperbarui versi aplikasi;
- JavaScript SPA libs - menyajikan tampilan dan perutean di antaranya. Perpustakaan ini ditulis oleh salah satu pengembang front-end kami. Ketika pengguna mengakses halaman tertentu, mesin rendering menggambar halaman berdasarkan parameter yang disimpan dalam objek representasi JS.
Jadi, apa yang kita miliki: kita memiliki back-end yang menggambarkan semua logika yang terkait dengan model. Kemudian OpenAPI memasuki permainan, yang, berdasarkan deskripsi model, menghasilkan JSON dengan deskripsi struktur API. Selanjutnya, tongkat ditransmisikan ke klien, yang, menganalisis OpenAPI JSON yang dihasilkan secara otomatis menghasilkan antarmuka web.
Menanamkan fitur dalam aplikasi pada arsitektur baru - cara kerjanya
Ingat tugas menambahkan kait kustom? Inilah cara kami mengimplementasikannya dalam aplikasi berbasis VSTUtils:

Sekarang berkat VSTUtils kita tidak perlu menulis apa pun dari awal. Inilah yang kami lakukan untuk menambah kemampuan membuat kait kustom:
- Di back-end: kami mengambil dan mewarisi dari kelas yang paling cocok di VSTUtils, menambahkan fungsionalitas baru khusus untuk model baru;
- Di ujung depan:
- jika tampilan untuk model ini tidak berbeda dari tampilan dasar VSTUtils, maka kami tidak melakukan apa pun, semuanya ditampilkan secara otomatis dengan benar;
- jika Anda perlu mengubah perilaku tampilan, menggunakan mekanisme sinyal, kami memperluas atau mendeklarasikan perilaku dasar tampilan secara total.
Sebagai hasilnya, kami mendapat solusi yang cukup bagus, kami mencapai tujuan kami, front-end kami menjadi otomatis. Proses memperkenalkan fitur-fitur baru ke dalam proyek-proyek yang ada telah terasa dipercepat: rilis mulai dirilis setiap 2 minggu, sedangkan sebelumnya kami merilis rilis setiap 2-3 bulan dengan jumlah fitur baru yang jauh lebih kecil. Saya ingin mencatat bahwa tim pengembangan tetap sama, itu adalah arsitektur aplikasi baru yang memberi kita buah.
Versi 1.0 - hati kita menuntut perubahan
Tapi, seperti yang Anda tahu, tidak ada batasan untuk kesempurnaan, dan VSTUtils tidak terkecuali.
Terlepas dari kenyataan bahwa kami dapat mengotomatisasi pembentukan front-end, hasilnya bukan solusi langsung yang kami inginkan.
Arsitektur aplikasi sisi klien tidak dipikirkan secara menyeluruh, dan ternyata tidak sefleksibel mungkin:
- proses memperkenalkan kelebihan fungsional tidak selalu nyaman;
- Mekanisme parsing OpenAPI tidak optimal;
- rendering representasi dan perutean di antara mereka dilakukan menggunakan perpustakaan yang ditulis sendiri, yang juga tidak cocok untuk kami karena sejumlah alasan:
- Perpustakaan-perpustakaan ini tidak dicakup oleh tes;
- tidak ada dokumentasi untuk perpustakaan ini;
- mereka tidak memiliki komunitas apa pun - dalam hal mendeteksi bug di dalamnya atau kepergian karyawan yang menulisnya, dukungan untuk kode tersebut akan sangat sulit.
Dan karena di perusahaan kami, kami mematuhi pendekatan DevOps dan mencoba untuk menstandarisasi dan memformalkan kode kami sebanyak mungkin, pada bulan Februari tahun ini kami memutuskan untuk melakukan refactoring global kerangka kerja ujung-depan VSTUtils. Kami memiliki beberapa tugas:
- untuk membentuk tidak hanya kelas presentasi di front-end, tetapi juga kelas model - kami menyadari bahwa akan lebih tepat untuk memisahkan data (dan strukturnya) dari presentasi mereka. Selain itu, kehadiran beberapa abstraksi dalam bentuk representasi dan model akan sangat memudahkan penambahan kelebihan fungsionalitas dasar dalam proyek berdasarkan VSTUtils;
- menggunakan kerangka kerja teruji dengan komunitas besar (Angular, React, Vue) untuk rendering dan routing - ini akan memungkinkan kita untuk memberikan semua sakit kepala dengan dukungan untuk kode yang terkait dengan rendering dan routing di dalam aplikasi kita.
Refactoring - pilihan kerangka kerja JS
Di antara kerangka kerja JS paling populer: Angular, React, Vue, pilihan kami jatuh pada Vue karena:
- Basis kode Vue kurang dari React and Angular;
Bagan Perbandingan Ukuran Kerangka Kerja Gzipped
- Proses rendering halaman Vue membutuhkan waktu lebih sedikit daripada React and Angular;

- Ambang entri di Vue jauh lebih rendah daripada di Bereaksi dan Angular;
- Sintaks templat yang dapat dimengerti secara native;
- Dokumentasi yang elegan dan terperinci tersedia dalam beberapa bahasa, termasuk Rusia;
- Ekosistem yang dikembangkan yang menyediakan, di samping perpustakaan inti Vue, perpustakaan untuk perutean dan untuk membuat gudang data reaktif.
Versi 2.0 - hasil refactoring front-end
Proses refactoring global front-end VSTUtils membutuhkan waktu sekitar 4 bulan dan inilah yang akhirnya kami lakukan dengan:

Kerangka front-end VSTUtils masih terdiri dari dua blok besar: yang pertama terlibat dalam penguraian skema OpenAPI, yang kedua adalah memberikan pandangan dan routing di antara mereka, tetapi kedua blok ini telah mengalami sejumlah perubahan signifikan.
Mekanisme yang mem-parsing skema OpenAPI telah sepenuhnya ditulis ulang. Pendekatan untuk menguraikan skema ini telah berubah. Kami mencoba membuat arsitektur front-end semirip mungkin dengan arsitektur back-end. Sekarang di sisi klien kami tidak hanya memiliki satu abstraksi dalam bentuk representasi, sekarang kami juga memiliki abstraksi dalam bentuk model dan QuerySets:
- objek kelas Model dan turunannya adalah objek yang sesuai dengan abstraksi sisi-server dari Model Django. Objek jenis ini berisi data tentang struktur model (nama model, bidang model, dll.);
- objek kelas QuerySet dan turunannya adalah objek yang sesuai dengan abstraksi Django QuerySets sisi-server. Objek jenis ini berisi metode yang memungkinkan Anda untuk melakukan permintaan API (menambah, memodifikasi, menerima, menghapus data objek model);
- objek kelas Tampilan - objek yang menyimpan informasi tentang cara merepresentasikan model pada halaman tertentu, template mana yang akan digunakan untuk βme-renderβ halaman, yang mana representasi lain dari model-model halaman ini dapat ditautkan, dll.
Unit yang bertanggung jawab untuk rendering dan routing juga telah berubah secara signifikan. Kami meninggalkan perpustakaan JS SPA yang ditulis sendiri demi Vue.js. Kami telah mengembangkan komponen Vue kami sendiri yang membentuk semua halaman aplikasi web kami. Perutean antar tampilan dilakukan menggunakan pustaka vue-router, dan kami menggunakan vuex sebagai penyimpanan reaktif status aplikasi.
Saya juga ingin mencatat bahwa di sisi front-end implementasi kelas Model, QuerySet dan View tidak tergantung pada cara rendering dan routing, yaitu, jika kita tiba-tiba ingin beralih dari Vue ke beberapa framework lain, misalnya, Bereaksi atau sesuatu yang baru, maka yang perlu kita lakukan adalah menulis ulang komponen Vue ke komponen kerangka baru, menulis ulang router, repositori, dan itu saja - kerangka kerja VSTUtils akan bekerja lagi. Implementasi kelas Model, QuerySet, dan View akan tetap sama, karena tidak bergantung pada Vue.js. Kami percaya bahwa ini adalah bantuan yang sangat baik untuk kemungkinan perubahan di masa depan.
Untuk meringkas
Dengan demikian, keengganan untuk menulis kode "duplikat" mengakibatkan tugas mengotomatisasi pembentukan front-end aplikasi web, yang diselesaikan dengan membuat kerangka kerja VSTUtils. Kami berhasil membangun arsitektur aplikasi web sehingga back-end dan front-end secara harmonis saling melengkapi dan setiap perubahan dalam struktur API secara otomatis diambil dan ditampilkan dengan benar pada klien.
Manfaat yang kami terima dari memformalkan arsitektur aplikasi web:
- Rilis aplikasi yang berjalan atas dasar VSTUtils mulai keluar 2 kali lebih sering. Ini disebabkan oleh kenyataan bahwa sekarang untuk memperkenalkan fitur baru, seringkali, kita perlu menambahkan kode hanya di back-end, front-end akan secara otomatis dihasilkan - ini menghemat waktu;
- Pembaruan fungsi dasar yang disederhanakan. Karena sekarang semua fungsionalitas dasar dirangkai dalam satu kerangka kerja, untuk memperbarui beberapa dependensi penting atau melakukan perbaikan pada fungsionalitas dasar, kita perlu melakukan koreksi hanya di satu tempat - dalam basis kode VSTUtils. Saat memperbarui versi VSTUtils dalam proyek anak, semua inovasi akan secara otomatis diambil;
- Menemukan karyawan baru menjadi lebih mudah. Setuju, jauh lebih mudah untuk menemukan pengembang untuk tumpukan teknologi yang diformalkan (Django, Vue) daripada mencari orang yang setuju untuk bekerja dengan perekam yang tidak dikenal. Hasil pencarian untuk pengembang yang menyebutkan Django atau Vue di HeadHunter di CV mereka (di semua wilayah):
- Django - 3.454 resume ditemukan untuk 3.136 pelamar;
- Vue - 4.092 resume ditemukan untuk 3.747 pencari kerja.
Kerugian dari formalisasi arsitektur aplikasi web tersebut adalah sebagai berikut:
- Karena penguraian skema OpenAPI, inisialisasi aplikasi pada klien membutuhkan waktu sedikit lebih lama dari sebelumnya (sekitar 20-30 milidetik lebih lama);
- Pengindeksan pencarian tidak penting. Faktanya adalah bahwa saat ini kami tidak menggunakan rendering server dalam kerangka VSTUtils, dan semua konten aplikasi dibentuk dalam bentuk akhir yang sudah ada di klien. Tetapi untuk proyek kami, seringkali hasil pencarian yang tinggi tidak diperlukan dan bagi kami itu tidak terlalu kritis.
Tentang ini, kisah saya berakhir, terima kasih atas perhatian Anda!
Tautan yang bermanfaat