Pengembangan Aplikasi dengan Kerangka Pengembangan Aplikasi Cepat AllcountJS
Diterbitkan: 2022-03-11Ide Pengembangan Aplikasi Cepat (RAD) lahir sebagai tanggapan terhadap model pengembangan air terjun tradisional. Banyak variasi RAD yang ada; misalnya, pengembangan Agile dan Proses Terpadu Rasional. Namun, semua model tersebut memiliki satu kesamaan: mereka bertujuan untuk menghasilkan nilai bisnis maksimum dengan waktu pengembangan minimal melalui pembuatan prototipe dan pengembangan berulang. Untuk mencapai ini, model Pengembangan Aplikasi Cepat bergantung pada alat yang memudahkan proses. Dalam artikel ini, kita akan mengeksplorasi salah satu alat tersebut, dan bagaimana alat tersebut dapat digunakan untuk fokus pada nilai bisnis dan optimalisasi proses pengembangan.
AllcountJS adalah kerangka kerja sumber terbuka baru yang dibangun dengan mempertimbangkan pengembangan aplikasi yang cepat. Ini didasarkan pada gagasan pengembangan aplikasi deklaratif menggunakan kode konfigurasi mirip JSON yang menggambarkan struktur dan perilaku aplikasi. Kerangka kerja telah dibangun di atas Node.js, Express, MongoDB dan sangat bergantung pada AngularJS dan Twitter Bootstrap. Meskipun bergantung pada pola deklaratif, kerangka kerja ini masih memungkinkan penyesuaian lebih lanjut melalui akses langsung ke API jika diperlukan.
Mengapa AllcountJS sebagai Kerangka RAD Anda?
Menurut Wikipedia, setidaknya ada seratus alat yang menjanjikan pengembangan aplikasi yang cepat, tetapi ini menimbulkan pertanyaan: seberapa cepat "cepat". Apakah alat ini memungkinkan aplikasi pusat data tertentu dikembangkan dalam beberapa jam? Atau, mungkin "cepat" jika aplikasi dapat dikembangkan dalam beberapa hari atau beberapa minggu. Beberapa alat ini bahkan mengklaim bahwa hanya beberapa menit yang diperlukan untuk menghasilkan aplikasi yang berfungsi. Namun, kecil kemungkinan Anda dapat membuat aplikasi yang berguna dalam waktu kurang dari lima menit dan masih mengklaim telah memenuhi setiap kebutuhan bisnis. AllcountJS tidak mengklaim sebagai alat seperti itu; apa yang ditawarkan AllcountJS adalah cara untuk membuat prototipe sebuah ide dalam waktu singkat.
Dengan kerangka kerja AllcountJS, dimungkinkan untuk membangun aplikasi dengan antarmuka pengguna yang dibuat secara otomatis, fitur manajemen pengguna, RESTful API, dan beberapa fitur lainnya dengan sedikit usaha dan waktu. Dimungkinkan untuk menggunakan AllcountJS untuk berbagai kasus penggunaan, tetapi paling cocok untuk aplikasi di mana Anda memiliki koleksi objek yang berbeda dengan tampilan yang berbeda untuk mereka. Biasanya, aplikasi bisnis cocok untuk model ini.
AllcountJS telah digunakan untuk membangun allcountjs.com, ditambah pelacak proyek untuk itu. Perlu dicatat bahwa allcountjs.com adalah aplikasi AllcountJS yang disesuaikan, dan AllcountJS memungkinkan tampilan statis dan dinamis digabungkan dengan sedikit kerumitan. Bahkan memungkinkan bagian yang dimuat secara dinamis untuk dimasukkan ke dalam konten statis. Misalnya, AllcountJS mengelola kumpulan template aplikasi demo. Ada widget demo di halaman utama allcountjs.com yang memuat template aplikasi acak dari koleksi itu. Beberapa contoh aplikasi lain tersedia di galeri di allcountjs.com.
Mulai
Untuk mendemonstrasikan beberapa kemampuan kerangka kerja RAD AllcountJS, kami akan membuat aplikasi sederhana untuk Toptal, yang akan kami sebut Komunitas Toptal. Jika Anda mengikuti blog kami, Anda mungkin sudah tahu bahwa aplikasi serupa dibuat menggunakan Hoodie sebagai bagian dari salah satu posting blog kami sebelumnya. Aplikasi ini akan memungkinkan anggota komunitas untuk mendaftar, membuat acara, dan mendaftar untuk menghadirinya.
Untuk mengatur lingkungan, Anda harus menginstal Node.js, MongoDB dan Git. Kemudian, instal AllcountJS CLI dengan menjalankan perintah “npm install” dan lakukan init proyek:
npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install
AllcountJS CLI akan meminta Anda untuk memasukkan beberapa info tentang proyek Anda untuk melakukan pra-isi package.json.
AllcountJS dapat digunakan sebagai server mandiri atau sebagai ketergantungan. Dalam contoh pertama kami, kami tidak akan memperluas AllcountJS, jadi server mandiri seharusnya berfungsi untuk kami.
Di dalam direktori app-config yang baru dibuat ini, kami akan mengganti konten file JavaScript main.js dengan potongan kode berikut:
A.app({ appName: "Toptal Community", onlyAuthenticated: true, allowSignUp: true, appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }] }, UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } }, User: { views: { OnlyNameUser: { permissions: { read: null, write: ['admin'] } }, fields: { username: Fields.text("User name") } } } } } });
Meskipun AllcountJS bekerja dengan repositori Git, demi kesederhanaan kami tidak akan menggunakannya dalam tutorial ini. Untuk menjalankan aplikasi Toptal Community, yang harus kita lakukan adalah menjalankan perintah run AllcountJS CLI di direktori toptal-community-allcount.
allcountjs run
Perlu dicatat bahwa MongoDB harus berjalan ketika perintah ini dijalankan. Jika semuanya berjalan dengan baik, aplikasi harus aktif dan berjalan di http://localhost:9080.
Untuk login silahkan gunakan username “admin” dan password “admin”.
Kurang dari 100 Baris
Anda mungkin telah memperhatikan bahwa aplikasi yang didefinisikan di main.js hanya membutuhkan 91 baris kode. Baris ini menyertakan deklarasi semua perilaku yang mungkin Anda amati saat menavigasi ke http://localhost:9080. Jadi, apa sebenarnya yang terjadi, di bawah tenda? Mari kita lihat lebih dekat setiap aspek aplikasi, dan lihat bagaimana kode terkait dengannya.
Masuk mendaftar
Halaman pertama yang Anda lihat setelah membuka aplikasi adalah halaman masuk. Halaman ini berfungsi ganda sebagai halaman pendaftaran, dengan asumsi bahwa kotak centang - berlabel "Daftar" - dicentang sebelum mengirimkan formulir.
Halaman ini ditampilkan karena file main.js menyatakan bahwa hanya pengguna yang diautentikasi yang boleh menggunakan aplikasi ini. Selain itu, memungkinkan kemampuan bagi pengguna untuk mendaftar dari halaman ini. Hanya dua baris berikut yang diperlukan untuk ini:
A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })
Halaman Selamat Datang
Setelah masuk, Anda akan diarahkan ke halaman selamat datang dengan menu aplikasi. Bagian aplikasi ini dibuat secara otomatis, berdasarkan item menu yang ditentukan di bawah tombol "menuItems".
Bersama dengan beberapa konfigurasi lain yang relevan, menu didefinisikan dalam file main.js sebagai berikut:
A.app({ ..., appName: "Toptal Community", appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], ... });
AllcountJS menggunakan ikon Font Awesome, jadi semua nama ikon yang dirujuk dalam konfigurasi dipetakan ke nama ikon Font Awesome.
Acara Penjelajahan & Pengeditan
Setelah mengklik "Acara" dari menu, Anda akan dibawa ke tampilan Acara yang ditunjukkan pada tangkapan layar di bawah. Ini adalah tampilan AllcountJS standar yang menyediakan beberapa fungsi CRUD generik pada entitas terkait. Di sini, Anda dapat mencari acara, membuat acara baru dan mengedit atau menghapus yang sudah ada. Ada dua mode antarmuka CRUD ini: daftar dan formulir. Bagian aplikasi ini dikonfigurasi melalui beberapa baris kode JavaScript berikut.
A.app({ ..., entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], ... } } } });
Contoh ini menunjukkan bagaimana deskripsi entitas dikonfigurasi di AllcountJS. Perhatikan bagaimana kita menggunakan fungsi untuk mendefinisikan entitas; setiap properti konfigurasi AllcountJS dapat berupa fungsi. Fungsi-fungsi ini dapat meminta dependensi untuk diselesaikan melalui nama argumennya. Sebelum fungsi dipanggil, dependensi yang sesuai disuntikkan. Di sini, "Fields" adalah salah satu API konfigurasi AllcountJS yang digunakan untuk mendeskripsikan bidang entitas. Properti "Entitas" berisi pasangan nama-nilai di mana nama adalah pengidentifikasi tipe entitas dan nilai adalah deskripsinya. Jenis entitas untuk peristiwa dijelaskan, dalam contoh ini, dengan judul "Peristiwa". Konfigurasi lain, seperti pengurutan pengurutan default, nama referensi, dan sejenisnya, juga dapat didefinisikan di sini. Urutan pengurutan default didefinisikan melalui larik nama bidang dan arah, sedangkan nama referensi ditentukan melalui string (baca selengkapnya di sini).
Jenis entitas khusus ini telah didefinisikan memiliki empat bidang: "nama acara," "tanggal," "waktu" dan "pengguna terapan," tiga yang pertama tetap ada di database. Bidang ini wajib, seperti yang ditunjukkan oleh penggunaan "wajib()." Nilai di bidang ini dengan aturan tersebut divalidasi sebelum formulir dikirimkan di front-end seperti yang ditunjukkan pada tangkapan layar di bawah. AllcountJS menggabungkan validasi sisi klien dan sisi server untuk memberikan pengalaman pengguna terbaik. Kolom keempat adalah hubungan yang memuat daftar pengguna yang telah mendaftar untuk menghadiri acara tersebut. Secara alami, bidang ini tidak dipertahankan dalam database, dan diisi dengan memilih hanya entitas AppliedUser yang relevan dengan acara tersebut.
Mendaftar untuk Menghadiri Acara
Saat pengguna memilih acara tertentu, bilah alat menampilkan tombol berlabel "Terapkan." Mengkliknya menambahkan acara ke jadwal pengguna. Di AllcountJS, tindakan yang mirip dengan ini dapat dikonfigurasi hanya dengan mendeklarasikannya dalam konfigurasi:

actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }]
Properti "tindakan" dari tipe entitas apa pun mengambil larik objek yang menjelaskan perilaku setiap tindakan kustom. Setiap objek memiliki properti "id" yang mendefinisikan pengidentifikasi unik untuk tindakan, properti "nama" mendefinisikan nama tampilan dan properti "actionTarget" digunakan untuk menentukan konteks tindakan. Menyetel "actionTarget" ke "single-item" menunjukkan bahwa tindakan harus dilakukan dengan peristiwa tertentu. Fungsi yang didefinisikan di bawah properti "perform" adalah logika yang dijalankan saat tindakan ini dilakukan, biasanya saat pengguna mengklik tombol yang sesuai.
Dependensi dapat diminta oleh fungsi ini. Misalnya, dalam contoh ini fungsinya tergantung pada "Pengguna," "Tindakan" dan "Crud." Ketika suatu tindakan terjadi, referensi ke pengguna, yang menjalankan tindakan ini, dapat diperoleh dengan meminta ketergantungan "Pengguna". Ketergantungan "Mentah", yang memungkinkan manipulasi status basis data untuk entitas ini, juga diminta di sini. Dua metode yang mengembalikan instance objek Crud adalah: Metode "actionContextCrud()" - mengembalikan CRUD untuk tipe entitas "Event" karena tindakan "Terapkan" adalah miliknya, sedangkan metode "crudForEntityType()" - mengembalikan CRUD untuk setiap jenis entitas yang diidentifikasi oleh ID jenisnya.
Implementasi tindakan dimulai dengan memeriksa apakah acara ini sudah dijadwalkan untuk pengguna, dan jika tidak, itu akan membuatnya. Jika sudah dijadwalkan, kotak dialog ditampilkan dengan mengembalikan nilai dari memanggil "Actions.modalResult()". Selain menunjukkan modal, suatu tindakan dapat melakukan berbagai jenis operasi dengan cara yang sama, seperti "navigasi untuk melihat", "tampilkan tampilan", "tampilkan dialog", dan seterusnya.
Jadwal Pengguna Acara Terapan
Setelah berhasil menerapkan ke suatu acara, browser diarahkan ke tampilan "Acara Saya", yang menunjukkan daftar acara yang telah diterapkan pengguna. Tampilan ditentukan oleh konfigurasi berikut:
UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } },
Dalam hal ini, kami menggunakan properti konfigurasi baru, "pemfilteran." Seperti contoh sebelumnya, fungsi ini juga bergantung pada ketergantungan "Pengguna". Jika fungsi mengembalikan objek, itu diperlakukan sebagai kueri MongoDB; kueri memfilter koleksi untuk peristiwa yang hanya dimiliki oleh pengguna saat ini.
Properti menarik lainnya adalah “Views.” “View” adalah tipe entitas biasa, tetapi koleksi MongoDB-nya sama dengan tipe entitas induk. Ini memungkinkan untuk membuat tampilan yang berbeda secara visual untuk data yang sama dalam database. Faktanya, kami menggunakan fitur ini untuk membuat dua tampilan berbeda untuk "UserEvent:" "MyEvent" dan "AppliedUser." Karena prototipe sub-tampilan disetel ke tipe entitas induk, properti yang tidak diganti "diwarisi" dari tipe induk.
Daftar Peserta Acara
Setelah mendaftar ke suatu acara, pengguna lain dapat melihat daftar semua pengguna yang berencana untuk hadir. Ini dihasilkan sebagai hasil dari elemen konfigurasi berikut di main.js:
AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation("Applied users", "AppliedUser", "event")
"AppliedUser" adalah tampilan hanya baca untuk tipe entitas "MyEvent". Izin baca-saja ini diterapkan dengan menyetel larik kosong ke properti "Tulis" dari objek izin. Juga, karena izin "Baca" tidak ditentukan, secara default, membaca diizinkan untuk semua pengguna.
Memperluas Implementasi Default
Latar belakang khas kerangka kerja RAD adalah kurangnya fleksibilitas. Setelah Anda membangun aplikasi Anda dan Anda perlu menyesuaikannya, Anda mungkin menghadapi kendala yang signifikan. AllcountJS dikembangkan dengan mempertimbangkan ekstensibilitas dan memungkinkan penggantian setiap blok bangunan di dalamnya.
Untuk mencapai itu, AllcountJS menggunakan implementasi Dependency Injection (DI) itu sendiri. DI memungkinkan pengembang untuk mengganti perilaku default kerangka kerja melalui titik ekstensi, dan, pada saat yang sama, memungkinkannya melalui penggunaan kembali implementasi yang ada. Banyak aspek ekstensi kerangka kerja RAD dijelaskan dalam dokumentasi. Di bagian ini, kita akan mengeksplorasi bagaimana kita dapat memperluas dua dari banyak komponen dalam kerangka kerja, logika sisi server, dan tampilan.
Melanjutkan contoh Komunitas Toptal kami, mari kita integrasikan sumber data eksternal untuk menggabungkan data peristiwa. Mari kita bayangkan bahwa ada posting Blog Toptal yang membahas rencana acara sehari sebelum setiap acara. Dengan Node.js, seharusnya dimungkinkan untuk mengurai umpan RSS blog dan mengekstrak data tersebut. Untuk melakukan ini, kita memerlukan beberapa dependensi npm tambahan, seperti “request,” “xml2js” (untuk memuat RSS feed Toptal Blog), “q” (untuk mengimplementasikan janji) dan “moment” (untuk mengurai tanggal). Dependensi ini dapat diinstal dengan menjalankan serangkaian perintah berikut:
npm install xml2js npm install request npm install q npm install moment
Mari buat file JavaScript lain, beri nama "toptal-community.js" di direktori toptal-community-allcount dan isi dengan yang berikut:
var request = require('request'); var Q = require('q'); var xml2js = require('xml2js'); var moment = require('moment'); var injection = require('allcountjs'); injection.bindFactory('port', 9080); injection.bindFactory('dbUrl', 'mongodb://localhost:27017/toptal-community'); injection.bindFactory('gitRepoUrl', 'app-config'); injection.bindFactory('DiscussionEventsImport', function (Crud) { return { importEvents: function () { return Q.nfcall(request, "https://www.toptal.com/blog.rss").then(function (responseAndBody) { var body = responseAndBody[1]; return Q.nfcall(xml2js.parseString, body).then (function (feed) { var events = feed.rss.channel[0].item.map(function (item) { return { eventName: "Discussion of " + item.title, date: moment(item.pubDate, "DD MMM YYYY").add(1, 'day').toDate(), time: "12:00" }}); var crud = Crud.crudForEntityType('Event'); return Q.all(events.map(function (event) { return crud.find({query: {eventName: event.eventName}}).then(function (createdEvent) { if (!createdEvent[0]) { return crud.createEntity(event); } }); } )); }); }) } }; }); var server = injection.inject('allcountServerStartup'); server.startup(function (errors) { if (errors) { throw new Error(errors.join('\n')); } });
Dalam file ini, kami mendefinisikan dependensi yang disebut "DiscussionEventsImport," yang dapat kami gunakan di file main.js kami dengan menambahkan tindakan impor pada tipe entitas "Event".
{ id: "import-blog-events", name: "Import Blog Events", actionTarget: "all-items", perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }
Karena penting untuk me-restart server setelah membuat beberapa perubahan pada file JavaScript, Anda dapat mematikan instance sebelumnya dan memulainya kembali dengan menjalankan perintah yang sama seperti sebelumnya:
node toptal-community.js
Jika semuanya berjalan dengan benar, Anda akan melihat sesuatu seperti tangkapan layar di bawah ini setelah menjalankan tindakan "Impor Acara Blog".
Sejauh ini bagus, tapi jangan berhenti di sini. Tampilan default berfungsi, tetapi terkadang bisa membosankan. Mari kita menyesuaikan mereka sedikit.
Apakah Anda suka kartu? Semua orang suka kartu! Untuk membuat tampilan kartu, letakkan yang berikut ini di file bernama events.jade di dalam direktori app-config:
extends main include mixins block vars - var hasToolbar = true block content .refresh-form-controller(ng-app='allcount', ng-controller='EntityViewController') +defaultToolbar() .container.screen-container(ng-cloak) +defaultList() .row: .col-lg-4.col-md-6.col-xs-12(ng-repeat="item in items") .panel.panel-default .panel-heading h3 {{item.date | date}} {{item.time}} div button.btn.btn-default.btn-xs(ng-if="!isInEditMode", lc-tooltip="View", ng-click="navigate(item.id)"): i.glyphicon.glyphicon-chevron-right | button.btn.btn-danger.btn-xs(ng-if="isInEditMode", lc-tooltip="Delete", ng-click="deleteEntity(item)"): i.glyphicon.glyphicon-trash .panel-body h3 {{item.eventName}} +noEntries() +defaultEditAndCreateForms() block js +entityJs()
Setelah itu, cukup rujuk dari entitas "Acara" di main.js sebagai "customView: "events"." Jalankan aplikasi Anda dan Anda akan melihat antarmuka berbasis kartu alih-alih antarmuka tabular default.
Kesimpulan
Saat ini, alur pengembangan aplikasi web serupa di banyak teknologi web, di mana beberapa operasi diulang-ulang. Apakah itu benar-benar layak? Mungkin, sudah waktunya untuk memikirkan kembali cara aplikasi web Anda dikembangkan?
AllcountJS menyediakan pendekatan alternatif untuk kerangka kerja pengembangan aplikasi cepat; Anda mulai dengan membuat kerangka untuk aplikasi dengan mendefinisikan deskripsi entitas, lalu menambahkan tampilan dan penyesuaian perilaku di sekitarnya. Seperti yang Anda lihat, dengan AllcountJS, kami membuat aplikasi sederhana namun berfungsi penuh, dalam waktu kurang dari seratus baris kode. Mungkin, itu tidak memenuhi semua persyaratan produksi, tetapi dapat disesuaikan. Semua ini menjadikan AllcountJS alat yang bagus untuk mem-bootstrap aplikasi web dengan cepat.