Membangun Aplikasi Front-End Tanpa Server Menggunakan Google Cloud Platform

Diterbitkan: 2022-03-10
Ringkasan cepat Penggunaan aplikasi tanpa server oleh pengembang untuk menangani logika bisnis aplikasi mereka meningkat pesat, tetapi bagaimana Google Cloud — penyedia layanan utama dalam cloud publik — memungkinkan pengembang mengelola aplikasi tanpa server? Dalam artikel ini, Anda akan mempelajari apa itu aplikasi tanpa server, bagaimana aplikasi tersebut digunakan di Google Cloud, dan juga skenario di mana aplikasi tersebut dapat digunakan di aplikasi front-end.

Baru-baru ini, paradigma pengembangan aplikasi mulai bergeser dari secara manual harus menyebarkan, menskalakan, dan memperbarui sumber daya yang digunakan dalam aplikasi menjadi mengandalkan penyedia layanan cloud pihak ketiga untuk melakukan sebagian besar pengelolaan sumber daya ini.

Sebagai pengembang atau organisasi yang ingin membangun aplikasi yang sesuai dengan pasar dalam waktu secepat mungkin, fokus utama Anda mungkin adalah memberikan layanan aplikasi inti Anda kepada pengguna sementara Anda menghabiskan lebih sedikit waktu untuk mengonfigurasi, menerapkan, dan menguji stres aplikasi Anda. Jika ini adalah kasus penggunaan Anda, menangani logika bisnis aplikasi Anda dengan cara tanpa server mungkin merupakan pilihan terbaik Anda. Tapi bagaimana caranya?

Artikel ini bermanfaat bagi engineer front-end yang ingin membangun fungsionalitas tertentu dalam aplikasi mereka atau engineer back-end yang ingin mengekstrak dan menangani fungsionalitas tertentu dari layanan back-end yang ada menggunakan aplikasi tanpa server yang diterapkan ke Google Cloud Platform.

Catatan : Untuk mendapatkan manfaat dari apa yang akan dibahas di sini, Anda harus memiliki pengalaman bekerja dengan React. Tidak diperlukan pengalaman sebelumnya dalam aplikasi tanpa server.

Sebelum kita mulai, mari kita pahami apa itu aplikasi tanpa server sebenarnya dan bagaimana arsitektur tanpa server dapat digunakan saat membangun aplikasi dalam konteks teknisi front-end.

Aplikasi Tanpa Server

Aplikasi tanpa server adalah aplikasi yang dipecah menjadi fungsi-fungsi kecil yang digerakkan oleh peristiwa yang dapat digunakan kembali, dihosting dan dikelola oleh penyedia layanan cloud pihak ketiga dalam cloud publik atas nama pembuat aplikasi. Ini dipicu oleh peristiwa tertentu dan dieksekusi sesuai permintaan. Meskipun akhiran “ kurang ” yang dilampirkan pada kata tanpa server menunjukkan tidak adanya server, hal ini tidak 100% terjadi. Aplikasi ini masih berjalan di server dan sumber daya perangkat keras lainnya, tetapi dalam kasus ini, sumber daya tersebut tidak disediakan oleh pengembang melainkan oleh penyedia layanan cloud pihak ketiga. Jadi mereka tanpa server untuk pembuat aplikasi tetapi masih berjalan di server dan dapat diakses melalui internet publik.

Contoh kasus penggunaan aplikasi tanpa server adalah mengirim email ke calon pengguna yang mengunjungi halaman arahan Anda dan berlangganan untuk menerima email peluncuran produk. Pada tahap ini, Anda mungkin tidak menjalankan layanan back-end dan tidak ingin mengorbankan waktu dan sumber daya yang diperlukan untuk membuat, menerapkan, dan mengelolanya, semua karena Anda perlu mengirim email. Di sini, Anda dapat menulis satu file yang menggunakan klien email dan menyebarkan ke penyedia cloud mana pun yang mendukung aplikasi tanpa server dan membiarkan mereka mengelola aplikasi ini atas nama Anda saat Anda menghubungkan aplikasi tanpa server ini ke halaman arahan Anda.

Meskipun ada banyak alasan mengapa Anda mungkin mempertimbangkan untuk memanfaatkan aplikasi tanpa server atau Functions As A Service (FAAS) sebagaimana mereka dipanggil, untuk aplikasi front-end Anda, berikut adalah beberapa alasan penting yang harus Anda pertimbangkan:

  • Penskalaan otomatis aplikasi
    Aplikasi tanpa server diskalakan secara horizontal dan " penskalaan " ini secara otomatis dilakukan oleh penyedia Cloud berdasarkan jumlah permintaan, sehingga pengembang tidak perlu menambahkan atau menghapus sumber daya secara manual saat aplikasi sedang dalam beban berat.
  • Efektivitas biaya
    Karena didorong oleh peristiwa, aplikasi tanpa server hanya berjalan saat dibutuhkan dan ini mencerminkan biaya karena ditagih berdasarkan jumlah waktu yang dipanggil.
  • Fleksibilitas
    Aplikasi tanpa server dibuat agar sangat dapat digunakan kembali dan ini berarti mereka tidak terikat pada satu proyek atau aplikasi. Fungsionalitas tertentu dapat diekstraksi ke dalam aplikasi tanpa server, disebarkan dan digunakan di beberapa proyek atau aplikasi. Aplikasi tanpa server juga dapat ditulis dalam bahasa yang disukai pembuat aplikasi, meskipun beberapa penyedia cloud hanya mendukung sedikit bahasa.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

Saat menggunakan aplikasi tanpa server, setiap pengembang memiliki beragam penyedia cloud di dalam cloud publik untuk digunakan. Dalam konteks artikel ini, kami akan fokus pada aplikasi tanpa server di Google Cloud Platform — bagaimana aplikasi itu dibuat, dikelola, diterapkan, dan bagaimana aplikasi tersebut juga terintegrasi dengan produk lain di Google Cloud. Untuk melakukan ini, kami akan menambahkan fungsionalitas baru ke aplikasi React yang ada saat bekerja melalui proses:

  • Menyimpan dan mengambil data pengguna di cloud;
  • Membuat dan mengelola tugas cron di Google Cloud;
  • Menerapkan Fungsi Cloud ke Google Cloud.

Catatan : Aplikasi tanpa server tidak terikat pada React saja, selama kerangka kerja front-end atau pustaka pilihan Anda dapat membuat permintaan HTTP , itu dapat menggunakan aplikasi tanpa server.

Fungsi Google Cloud

Google Cloud memungkinkan pengembang untuk membuat aplikasi tanpa server menggunakan Cloud Functions dan menjalankannya menggunakan Functions Framework. Seperti namanya, fungsi Cloud adalah fungsi berbasis peristiwa yang dapat digunakan kembali yang di-deploy ke Google Cloud untuk mendengarkan pemicu spesifik dari enam pemicu peristiwa yang tersedia dan kemudian melakukan operasi yang ditulis untuk dieksekusi.

Fungsi cloud yang berumur pendek, ( dengan batas waktu eksekusi default 60 detik dan maksimal 9 menit ) dapat ditulis menggunakan JavaScript, Python, Golang dan Java dan dieksekusi menggunakan runtime mereka. Dalam JavaScript, mereka dapat dieksekusi hanya menggunakan beberapa versi runtime Node yang tersedia dan ditulis dalam bentuk modul CommonJS menggunakan JavaScript biasa karena diekspor sebagai fungsi utama untuk dijalankan di Google Cloud.

Contoh fungsi cloud adalah yang di bawah ini yang merupakan boilerplate kosong untuk fungsi menangani data pengguna.

 // index.js exports.firestoreFunction = function (req, res) { return res.status(200).send({ data: `Hello ${req.query.name}` }); }

Di atas kami memiliki modul yang mengekspor fungsi. Saat dieksekusi, ia menerima argumen permintaan dan respons yang mirip dengan rute HTTP .

Catatan : Fungsi cloud cocok dengan setiap protokol HTTP saat permintaan dibuat. Ini perlu diperhatikan saat mengharapkan data dalam argumen permintaan karena data yang dilampirkan saat membuat permintaan untuk menjalankan fungsi cloud akan ada di badan permintaan untuk permintaan POST sementara di badan kueri untuk permintaan GET .

Fungsi cloud dapat dijalankan secara lokal selama pengembangan dengan menginstal paket @google-cloud/functions-framework dalam folder yang sama di mana fungsi tertulis ditempatkan atau melakukan instalasi global untuk menggunakannya untuk beberapa fungsi dengan menjalankan npm i -g @google-cloud/functions-framework dari baris perintah Anda. Setelah diinstal, itu harus ditambahkan ke skrip package.json dengan nama modul yang diekspor mirip dengan yang di bawah ini:

 "scripts": { "start": "functions-framework --target=firestoreFunction --port=8000", }

Di atas kami memiliki satu perintah dalam skrip kami di file package.json yang menjalankan fungsi-framework dan juga menentukan firestoreFunction sebagai fungsi target untuk dijalankan secara lokal pada port 8000 .

Kita dapat menguji titik akhir fungsi ini dengan membuat permintaan GET ke port 8000 di localhost menggunakan curl. Menempelkan perintah di bawah ini di terminal akan melakukannya dan mengembalikan respons.

 curl https://localhost:8000?name="Smashing Magazine Author"

Perintah di atas membuat permintaan dengan metode GET HTTP dan merespons dengan kode status 200 dan data objek yang berisi nama yang ditambahkan dalam kueri.

Menyebarkan Fungsi Cloud

Dari metode penerapan yang tersedia,, satu cara cepat untuk menerapkan fungsi cloud dari mesin lokal adalah dengan menggunakan cloud SDK setelah menginstalnya. Menjalankan perintah di bawah dari terminal setelah mengautentikasi gcloud SDK dengan proyek Anda di Google Cloud, akan menerapkan fungsi yang dibuat secara lokal ke layanan Cloud Function.

 gcloud functions deploy "demo-function" --runtime nodejs10 --trigger-http --entry-point=demo --timeout=60 --set-env-vars=[name="Developer"] --allow-unauthenticated

Menggunakan flag yang dijelaskan di bawah ini, perintah di atas menyebarkan fungsi yang dipicu HTTP ke google cloud dengan nama " demo-function ".

  • NAMA
    Ini adalah nama yang diberikan untuk fungsi cloud saat menerapkannya dan diperlukan.
  • region
    Ini adalah wilayah tempat fungsi cloud akan digunakan. Secara default, ini diterapkan ke us-central1 .
  • trigger-http
    Ini memilih HTTP sebagai jenis pemicu fungsi.
  • allow-unauthenticated
    Ini memungkinkan fungsi dipanggil di luar Google Cloud melalui Internet menggunakan titik akhir yang dihasilkan tanpa memeriksa apakah pemanggil diautentikasi.
  • source
    Jalur lokal dari terminal ke file yang berisi fungsi yang akan digunakan.
  • entry-point
    Ini modul ekspor khusus yang akan digunakan dari file tempat fungsi ditulis.
  • runtime
    Ini adalah runtime bahasa yang akan digunakan untuk fungsi di antara daftar runtime yang diterima ini.
  • timeout
    Ini adalah waktu maksimum yang dapat dijalankan suatu fungsi sebelum waktu habis. Ini adalah 60 detik secara default dan dapat diatur ke maksimum 9 menit.

Catatan : Membuat fungsi mengizinkan permintaan yang tidak diautentikasi berarti bahwa siapa pun dengan titik akhir fungsi Anda juga dapat membuat permintaan tanpa Anda memberikannya. Untuk mengurangi ini, kami dapat memastikan titik akhir tetap pribadi dengan menggunakannya melalui variabel lingkungan, atau dengan meminta header otorisasi pada setiap permintaan.

Sekarang setelah fungsi demo kami telah dikerahkan dan kami memiliki titik akhir, kami dapat menguji fungsi ini seolah-olah sedang digunakan dalam aplikasi dunia nyata menggunakan instalasi global autocannon. Menjalankan autocannon -d=5 -c=300 CLOUD_FUNCTION_URL dari terminal yang dibuka akan menghasilkan 300 permintaan serentak ke fungsi cloud dalam durasi 5 detik. Ini lebih dari cukup untuk memulai fungsi cloud dan juga menghasilkan beberapa metrik yang dapat kita jelajahi di dasbor fungsi.

Catatan : Titik akhir suatu fungsi akan dicetak di terminal setelah penerapan. Jika tidak, jalankan gcloud function describe FUNCTION_NAME dari terminal untuk mendapatkan detail tentang fungsi yang diterapkan termasuk titik akhir.

Menggunakan tab metrik di dasbor, kita dapat melihat representasi visual dari permintaan terakhir yang terdiri dari berapa banyak permintaan yang dibuat, berapa lama mereka bertahan, jejak memori fungsi dan berapa banyak contoh yang diputar untuk menangani permintaan yang dibuat.

Dasbor fungsi menampilkan bagan metrik yang dikumpulkan dari semua permintaan terbaru yang dibuat.
Dasbor fungsi cloud menampilkan semua permintaan yang dibuat. (Pratinjau besar)

Melihat lebih dekat pada bagan Instance Aktif dalam gambar di atas menunjukkan kapasitas penskalaan horizontal Cloud Functions, karena kita dapat melihat bahwa 209 instans diputar dalam beberapa detik untuk menangani permintaan yang dibuat menggunakan autocannon.

Log Fungsi Cloud

Setiap fungsi yang diterapkan ke Google cloud memiliki log dan setiap kali fungsi ini dijalankan, entri baru ke dalam log tersebut dibuat. Dari tab Log di dasbor fungsi, kita dapat melihat daftar semua entri log dari fungsi cloud.

Di bawah ini adalah entri log dari demo-function kami terapkan yang dibuat sebagai hasil dari permintaan yang kami buat menggunakan autocannon .

Log fungsi cloud menampilkan log dari waktu eksekusi fungsi.
Tab log fungsi cloud menampilkan semua log eksekusi. (Pratinjau besar)

Setiap entri log di atas menunjukkan dengan tepat kapan suatu fungsi dijalankan, berapa lama waktu eksekusi, dan kode status apa yang diakhirinya. Jika ada kesalahan yang dihasilkan dari suatu fungsi, rincian kesalahan termasuk baris yang terjadi akan ditampilkan di log di sini.

Penjelajah Log di Google Cloud dapat digunakan untuk melihat detail yang lebih komprehensif tentang log dari fungsi cloud.

Fungsi Cloud Dengan Aplikasi Front-End

Fungsi cloud sangat berguna dan kuat bagi para insinyur front-end. Seorang insinyur front-end tanpa pengetahuan mengelola aplikasi back-end dapat mengekstrak fungsionalitas ke dalam fungsi cloud, menyebarkan ke Google Cloud dan menggunakan dalam aplikasi front-end dengan membuat permintaan HTTP ke fungsi cloud melalui titik akhirnya.

Untuk menunjukkan bagaimana fungsi cloud dapat digunakan di aplikasi front-end, kami akan menambahkan lebih banyak fitur ke aplikasi React ini. Aplikasi sudah memiliki perutean dasar antara otentikasi dan pengaturan halaman rumah. Kami akan memperluasnya untuk menggunakan React Context API untuk mengelola status aplikasi kami karena penggunaan fungsi cloud yang dibuat akan dilakukan di dalam reduksi aplikasi.

Untuk memulai, kami membuat konteks aplikasi kami menggunakan createContext API dan juga membuat peredam untuk menangani tindakan dalam aplikasi kami.

 // state/index.js import { createContext } from “react”;

export const UserReducer = (action, state) => { switch (action.type) { case “CREATE-USER”: break; kasus “UPLOAD-USER-IMAGE”: istirahat; case “FETCH-DATA” : break case “LOGOUT” : break; default: console.log( ${action.type} is not recognized ) } };

export const userState = { pengguna: null, isLoggedIn : false };

ekspor const UserContext = createContext(userState);

Di atas, kita mulai dengan membuat fungsi UserReducer yang berisi pernyataan switch, yang memungkinkannya melakukan operasi berdasarkan jenis tindakan yang dikirim ke dalamnya. Pernyataan switch memiliki empat kasus dan ini adalah tindakan yang akan kami tangani. Untuk saat ini mereka belum melakukan apa pun, tetapi ketika kami mulai berintegrasi dengan fungsi cloud kami, kami akan secara bertahap mengimplementasikan tindakan yang akan dilakukan di dalamnya.

Kami juga membuat dan mengekspor konteks aplikasi kami menggunakan React createContext API dan memberikannya nilai default dari objek userState yang berisi nilai pengguna saat ini yang akan diperbarui dari nol ke data pengguna setelah otentikasi dan juga nilai boolean isLoggedIn untuk mengetahui apakah pengguna login atau tidak.

Sekarang kita dapat melanjutkan untuk menggunakan konteks kita, tetapi sebelum kita melakukannya, kita perlu membungkus seluruh pohon aplikasi kita dengan Penyedia yang dilampirkan ke UserContext untuk komponen anak-anak agar dapat berlangganan perubahan nilai konteks kita.

 // index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./app"; import { UserContext, userState } from "./state/"; ReactDOM.render( <React.StrictMode> <UserContext.Provider value={userState}> <App /> </UserContext.Provider> </React.StrictMode>, document.getElementById("root") ); serviceWorker.unregister();

Kami membungkus aplikasi enter kami dengan penyedia UserContext di komponen root dan meneruskan nilai default userState yang kami buat sebelumnya di prop nilai.

Sekarang setelah status aplikasi kita sepenuhnya disetel, kita dapat beralih ke pembuatan model data pengguna menggunakan Google Cloud Firestore melalui fungsi cloud.

Menangani Data Aplikasi

Data pengguna dalam aplikasi ini terdiri dari id unik, email, kata sandi, dan URL ke gambar. Menggunakan fungsi cloud, data ini akan disimpan di cloud menggunakan Layanan Cloud Firestore yang ditawarkan di Google Cloud Platform.

Google Cloud Firestore , database NoSQL yang fleksibel dibuat dari Firebase Realtime Database dengan fitur baru yang disempurnakan yang memungkinkan kueri yang lebih kaya dan lebih cepat di samping dukungan data offline. Data dalam layanan Firestore diatur ke dalam kumpulan dan dokumen yang mirip dengan database NoSQL lainnya seperti MongoDB.

Firestore dapat diakses secara visual melalui Google Cloud Console. Untuk meluncurkannya, buka panel navigasi kiri dan gulir ke bawah ke bagian Database dan klik Firestore. Itu akan menampilkan daftar koleksi untuk pengguna dengan data yang ada atau meminta pengguna untuk membuat koleksi baru ketika tidak ada koleksi yang ada. Kami akan membuat koleksi pengguna untuk digunakan oleh aplikasi kami.

Serupa dengan layanan lain di Google Cloud Platform, Cloud Firestore juga memiliki library klien JavaScript yang dibuat untuk digunakan di lingkungan node ( kesalahan akan muncul jika digunakan di browser ). Untuk berimprovisasi, kami menggunakan Cloud Firestore dalam fungsi cloud menggunakan paket @google-cloud/firestore .

Menggunakan Cloud Firestore Dengan Fungsi Cloud

Untuk memulai, kami akan mengganti nama fungsi pertama yang kami buat dari demo-function menjadi firestoreFunction dan kemudian memperluasnya untuk terhubung dengan Firestore dan menyimpan data ke dalam koleksi pengguna kami.

 require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const { SecretManagerServiceClient } = require("@google-cloud/secret-manager"); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); console.log(document) // prints details of the collection to the function logs if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": break case "LOGIN-USER": break; default: res.status(422).send(`${type} is not a valid function action`) } };

Untuk menangani lebih banyak operasi yang melibatkan penyimpanan api, kami telah menambahkan pernyataan sakelar dengan dua kasus untuk menangani kebutuhan otentikasi aplikasi kami. Pernyataan sakelar kami mengevaluasi ekspresi type yang kami tambahkan ke badan permintaan saat membuat permintaan ke fungsi ini dari aplikasi kami dan setiap kali data type ini tidak ada di badan permintaan kami, permintaan diidentifikasi sebagai Permintaan Buruk dan kode status 400 di samping pesan untuk menunjukkan type yang hilang dikirim sebagai tanggapan.

Kami membuat koneksi dengan Firestore menggunakan library Application Default Credentials (ADC) dalam library klien Cloud Firestore. Pada baris berikutnya, kita memanggil metode koleksi dalam variabel lain dan meneruskan nama koleksi kita. Kami akan menggunakan ini untuk lebih lanjut melakukan operasi lain pada pengumpulan dokumen yang terkandung.

Catatan : Pustaka klien untuk layanan di Google Cloud terhubung ke layanan masing-masing menggunakan kunci akun layanan yang dibuat yang diteruskan saat menginisialisasi konstruktor. Ketika kunci akun layanan tidak ada, defaultnya adalah menggunakan Kredensial Default Aplikasi yang pada gilirannya terhubung menggunakan peran IAM yang ditetapkan ke fungsi cloud.

Setelah mengedit kode sumber fungsi yang diterapkan secara lokal menggunakan SDK Gcloud, kita dapat menjalankan kembali perintah sebelumnya dari terminal untuk memperbarui dan menerapkan kembali fungsi cloud.

Sekarang setelah koneksi dibuat, kita dapat mengimplementasikan kasus CREATE-USER untuk membuat pengguna baru menggunakan data dari badan permintaan.

 require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const path = require("path"); const { v4 : uuid } = require("uuid") const cors = require("cors")({ origin: true }); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": if (!email || !password) { res.status(422).send("email and password fields missing"); } const id = uuid() return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document.doc(id) .set({ id : id email: email, password: hash, img_uri : null }) .then((response) => res.status(200).send(response)) .catch((e) => res.status(501).send({ error : e }) ); }); }); case "LOGIN": break; default: res.status(400).send(`${type} is not a valid function action`) } }); };

Kami membuat UUID menggunakan paket uuid untuk digunakan sebagai ID dokumen yang akan disimpan dengan meneruskannya ke metode yang set pada dokumen dan juga id pengguna. Secara default, ID acak dihasilkan pada setiap dokumen yang dimasukkan tetapi dalam kasus ini, kami akan memperbarui dokumen saat menangani unggahan gambar dan UUID yang akan digunakan untuk memperbarui dokumen tertentu. Daripada menyimpan kata sandi pengguna dalam teks biasa, kami memberi garam terlebih dahulu menggunakan bcryptjs kemudian menyimpan hash hasil sebagai kata sandi pengguna.

Mengintegrasikan fungsi cloud firestoreFunction ke dalam aplikasi, kami menggunakannya dari CREATE_USER dalam peredam pengguna.

Setelah mengklik tombol Buat Akun , tindakan dikirim ke reduksi dengan tipe CREATE_USER untuk membuat permintaan POST yang berisi email dan kata sandi yang diketik ke titik akhir fungsi firestoreFunction .

 import { createContext } from "react"; import { navigate } from "@reach/router"; import Axios from "axios"; export const userState = { user : null, isLoggedIn: false, }; export const UserReducer = (state, action) => { switch (action.type) { case "CREATE_USER": const FIRESTORE_FUNCTION = process.env.REACT_APP_FIRESTORE_FUNCTION; const { userEmail, userPassword } = action; const data = { type: "CREATE-USER", email: userEmail, password: userPassword, }; Axios.post(`${FIRESTORE_FUNCTION}`, data) .then((res) => { navigate("/home"); return { ...state, isLoggedIn: true }; }) .catch((e) => console.log(`couldnt create user. error : ${e}`)); break; case "LOGIN-USER": break; case "UPLOAD-USER-IMAGE": break; case "FETCH-DATA" : break case "LOGOUT": navigate("/login"); return { ...state, isLoggedIn: false }; default: break; } }; export const UserContext = createContext(userState);

Di atas, kami menggunakan Axios untuk membuat permintaan ke firestoreFunction dan setelah permintaan ini diselesaikan, kami menetapkan status awal pengguna dari null ke data yang dikembalikan dari permintaan dan terakhir kami mengarahkan pengguna ke halaman beranda sebagai pengguna yang diautentikasi .

Pada titik ini, pengguna baru berhasil membuat akun dengan sukses dan dialihkan ke halaman beranda. Proses ini menunjukkan cara kami menggunakan Firestore untuk melakukan pembuatan data dasar dari fungsi cloud.

Menangani Penyimpanan File

Menyimpan dan mengambil file pengguna dalam aplikasi seringkali merupakan fitur yang sangat dibutuhkan dalam aplikasi. Dalam aplikasi yang terhubung ke backend node.js, Multer sering digunakan sebagai middleware untuk menangani multipart/form-data tempat file yang diunggah masuk. Tetapi tanpa backend node.js, kita dapat menggunakan file online layanan penyimpanan seperti Google Cloud Storage untuk menyimpan aset aplikasi statis.

Google Cloud Storage adalah layanan penyimpanan file yang tersedia secara global yang digunakan untuk menyimpan data dalam jumlah berapa pun sebagai objek untuk aplikasi ke dalam bucket. Ini cukup fleksibel untuk menangani penyimpanan aset statis untuk aplikasi berukuran kecil dan besar.

Untuk menggunakan layanan Cloud Storage dalam aplikasi, kita dapat menggunakan titik akhir Storage API yang tersedia atau dengan menggunakan pustaka klien Storage node resmi. Namun, pustaka klien Penyimpanan Node tidak berfungsi dalam jendela Browser sehingga kami dapat menggunakan Cloud Function tempat kami akan menggunakan pustaka tersebut.

Contohnya, adalah Cloud Function di bawah ini yang menghubungkan dan mengunggah file ke Cloud Bucket yang dibuat.

 const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); exports.Uploader = (req, res) => { const { file } = req.body; StorageClient.bucket("TEST_BUCKET") .file(file.name) .then((response) => { console.log(response); res.status(200).send(response) }) .catch((e) => res.status(422).send({error : e})); }); };

Dari fungsi cloud di atas, kami melakukan dua operasi utama berikut:

  • Pertama, kami membuat koneksi ke Cloud Storage dalam Storage constructor dan menggunakan fitur Application Default Credentials (ADC) di Google Cloud untuk mengautentikasi dengan Cloud Storage.

  • Kedua, kami mengunggah file yang disertakan dalam badan permintaan ke TEST_BUCKET kami dengan memanggil metode .file dan meneruskan nama file. Karena ini adalah operasi asinkron, kami menggunakan janji untuk mengetahui kapan tindakan ini telah diselesaikan dan kami mengirim 200 respons kembali sehingga mengakhiri siklus hidup pemanggilan.

Sekarang, kita dapat memperluas Fungsi Cloud Uploader di atas untuk menangani unggahan gambar profil pengguna. Fungsi cloud akan menerima gambar profil pengguna, menyimpannya di dalam ember cloud aplikasi kami, dan kemudian memperbarui data img_uri pengguna dalam koleksi pengguna kami di layanan Firestore.

 require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); const BucketName = process.env.STORAGE_BUCKET exports.Uploader = (req, res) => { return Cors(req, res, () => { const { file , userId } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); StorageClient.bucket(BucketName) .file(file.name) .on("finish", () => { StorageClient.bucket(BucketName) .file(file.name) .makePublic() .then(() => { const img_uri = `https://storage.googleapis.com/${Bucket}/${file.path}`; document .doc(userId) .update({ img_uri, }) .then((updateResult) => res.status(200).send(updateResult)) .catch((e) => res.status(500).send(e)); }) .catch((e) => console.log(e)); }); }); };

Sekarang kami telah memperluas fungsi Unggah di atas untuk melakukan operasi tambahan berikut:

  • Pertama, ini membuat koneksi baru ke layanan Firestore untuk mendapatkan koleksi users kami dengan menginisialisasi konstruktor Firestore dan menggunakan Application Default Credentials (ADC) untuk mengautentikasi dengan Cloud Storage.
  • Setelah mengunggah file yang ditambahkan di badan permintaan, kami menjadikannya publik agar dapat diakses melalui URL publik dengan memanggil metode makePublic pada file yang diunggah. Menurut Kontrol Akses default Cloud Storage, tanpa membuat file menjadi publik, file tidak dapat diakses melalui internet dan dapat melakukan ini saat aplikasi dimuat.

Catatan : Membuat file menjadi publik berarti siapa pun yang menggunakan aplikasi Anda dapat menyalin tautan file dan memiliki akses tak terbatas ke file tersebut. Salah satu cara untuk mencegahnya adalah dengan menggunakan URL yang Ditandatangani untuk memberikan akses sementara ke file di dalam keranjang Anda alih-alih menjadikannya publik sepenuhnya.

  • Selanjutnya, kami memperbarui data pengguna yang ada untuk menyertakan URL file yang diunggah. Kami menemukan data pengguna tertentu menggunakan kueri WHERE Firestore dan kami menggunakan userId yang disertakan dalam badan permintaan, lalu kami menyetel bidang img_uri untuk memuat URL gambar yang baru diperbarui.

Fungsi Upload cloud di atas dapat digunakan dalam aplikasi apa pun yang memiliki pengguna terdaftar dalam layanan Firestore. Semua yang diperlukan untuk membuat permintaan POST ke titik akhir, menempatkan IS pengguna dan gambar di badan permintaan.

Contohnya dalam aplikasi adalah kasus UPLOAD-FILE yang membuat permintaan POST ke fungsi dan menempatkan tautan gambar yang dikembalikan dari permintaan dalam status aplikasi.

 # index.js import Axios from 'axios' const UPLOAD_FUNCTION = process.env.REACT_APP_UPLOAD_FUNCTION export const UserReducer = (state, action) => { switch (action.type) { case "CREATE-USER" : # .....CREATE-USER-LOGIC .... case "UPLOAD-FILE": const { file, id } = action return Axios.post(UPLOAD_FUNCTION, { file, id }, { headers: { "Content-Type": "image/png", }, }) .then((response) => {}) .catch((e) => console.log(e)); default : return console.log(`${action.type} case not recognized`) } }

Dari kasus sakelar di atas, kami membuat permintaan POST menggunakan Axios ke UPLOAD_FUNCTION yang meneruskan file yang ditambahkan untuk dimasukkan ke dalam badan permintaan dan kami juga menambahkan gambar Content-Type di header permintaan.

Setelah unggahan berhasil, respons yang dikembalikan dari fungsi cloud berisi dokumen data pengguna yang telah diperbarui untuk memuat url valid dari gambar yang diunggah ke penyimpanan cloud google. Kami kemudian dapat memperbarui status pengguna untuk memuat data baru dan ini juga akan memperbarui elemen src gambar profil pengguna di komponen profil.

Halaman profil pengguna dengan gambar profil yang diperbarui
Halaman profil pengguna yang baru saja diperbarui untuk menampilkan gambar profil yang baru diperbarui. (Pratinjau besar)

Menangani Pekerjaan Cron

Tugas otomatis yang berulang seperti mengirim email ke pengguna atau melakukan tindakan internal pada waktu tertentu sering kali merupakan fitur aplikasi yang disertakan. Dalam aplikasi node.js biasa, tugas tersebut dapat ditangani sebagai tugas cron menggunakan node-cron atau node-schedule. Saat membuat aplikasi tanpa server menggunakan Google Cloud Platform, Cloud Scheduler juga dirancang untuk melakukan operasi cron.

Catatan : Meskipun Cloud Scheduler bekerja mirip dengan utilitas cron Unix dalam membuat pekerjaan yang dijalankan di masa mendatang, penting untuk dicatat bahwa Cloud Scheduler tidak menjalankan perintah seperti yang dilakukan utilitas cron. Melainkan melakukan operasi menggunakan target yang ditentukan.

Sesuai dengan namanya, Cloud Scheduler memungkinkan pengguna untuk menjadwalkan operasi yang akan dilakukan di waktu mendatang. Setiap operasi disebut tugas dan tugas dapat dibuat, diperbarui, dan bahkan dimusnahkan secara visual dari bagian Penjadwal di Cloud Console. Selain bidang nama dan deskripsi, pekerjaan di Cloud Scheduler terdiri dari hal-hal berikut:

  • Frekuensi
    Ini digunakan untuk menjadwalkan eksekusi pekerjaan Cron. Jadwal ditentukan menggunakan format unix-cron yang awalnya digunakan saat membuat pekerjaan latar belakang pada tabel cron di lingkungan Linux. Format unix-cron terdiri dari string dengan lima nilai masing-masing mewakili titik waktu. Di bawah ini kita dapat melihat masing-masing dari lima string dan nilai yang diwakilinya.
 - - - - - - - - - - - - - - - - minute ( - 59 ) | - - - - - - - - - - - - - hour ( 0 - 23 ) | | - - - - - - - - - - - - day of month ( 1 - 31 ) | | | - - - - - - - - - month ( 1 - 12 ) | | | | - - - - - -- day of week ( 0 - 6 ) | | | | | | | | | | | | | | | | | | | | | | | | | * * * * *

Alat generator Crontab sangat berguna ketika mencoba menghasilkan nilai frekuensi-waktu untuk suatu pekerjaan. Jika Anda merasa sulit untuk menyatukan nilai waktu, generator Crontab memiliki drop-down visual di mana Anda dapat memilih nilai yang membentuk jadwal dan Anda menyalin nilai yang dihasilkan dan digunakan sebagai frekuensi.

  • Zona waktu
    Zona waktu tempat tugas cron dijalankan. Karena perbedaan waktu antara zona waktu, tugas cron yang dijalankan dengan zona waktu tertentu yang berbeda akan memiliki waktu eksekusi yang berbeda.
  • Target
    Inilah yang digunakan dalam pelaksanaan Pekerjaan yang ditentukan. Target dapat berupa jenis HTTP di mana pekerjaan membuat permintaan pada waktu yang ditentukan ke URL atau topik Pub/Sub tempat pekerjaan dapat memublikasikan pesan atau menarik pesan dari dan terakhir Aplikasi App Engine.

Cloud Scheduler sangat cocok dengan Fungsi Cloud yang dipicu HTTP. Saat tugas dalam Cloud Scheduler dibuat dengan target ditetapkan ke HTTP, tugas ini dapat digunakan untuk menjalankan fungsi cloud. Yang perlu dilakukan hanyalah menentukan titik akhir fungsi cloud, menentukan kata kerja HTTP dari permintaan, lalu menambahkan data apa pun yang perlu diteruskan ke fungsi di bidang isi yang ditampilkan. Seperti yang ditunjukkan pada contoh di bawah ini:

Kolom yang diperlukan untuk membuat tugas cron menggunakan konsol cloud
Kolom yang diperlukan untuk membuat tugas cron menggunakan konsol cloud. (Pratinjau besar)

Pekerjaan cron pada gambar di atas akan dijalankan pada jam 9 pagi setiap hari membuat permintaan POST ke titik akhir sampel fungsi cloud.

Kasus penggunaan pekerjaan cron yang lebih realistis adalah mengirim email terjadwal ke pengguna pada interval tertentu menggunakan layanan pengiriman surat eksternal seperti Mailgun. Untuk melihat ini beraksi, kami akan membuat fungsi cloud baru yang mengirimkan email HTML ke alamat email tertentu menggunakan paket JavaScript nodemailer untuk terhubung ke Mailgun:

 # index.js require("dotenv").config(); const nodemailer = require("nodemailer"); exports.Emailer = (req, res) => { let sender = process.env.SENDER; const { reciever, type } = req.body var transport = nodemailer.createTransport({ host: process.env.HOST, port: process.env.PORT, secure: false, auth: { user: process.env.SMTP_USERNAME, pass: process.env.SMTP_PASSWORD, }, }); if (!reciever) { res.status(400).send({ error: `Empty email address` }); } transport.verify(function (error, success) { if (error) { res .status(401) .send({ error: `failed to connect with stmp. check credentials` }); } }); switch (type) { case "statistics": return transport.sendMail( { from: sender, to: reciever, subject: "Your usage satistics of demo app", html: { path: "./welcome.html" }, }, (error, info) => { if (error) { res.status(401).send({ error : error }); } transport.close(); res.status(200).send({data : info}); } ); default: res.status(500).send({ error: "An available email template type has not been matched.", }); } };

Using the cloud function above we can send an email to any user's email address specified as the receiver value in the request body. It performs the sending of emails through the following steps:

  • It creates an SMTP transport for sending messages by passing the host , user and pass which stands for password, all displayed on the user's Mailgun dashboard when a new account is created.
  • Next, it verifies if the SMTP transport has the credentials needed in order to establish a connection. If there's an error in establishing the connection, it ends the function's invocation and sends back a 401 unauthenticated status code.
  • Next, it calls the sendMail method to send the email containing the HTML file as the email's body to the receiver's email address specified in the to field.

Note : We use a switch statement in the cloud function above to make it more reusable for sending several emails for different recipients. This way we can send different emails based on the type field included in the request body when calling this cloud function.

Now that there is a function that can send an email to a user; we are left with creating the cron job to invoke this cloud function. This time, the cron jobs are created dynamically each time a new user is created using the official Google cloud client library for the Cloud Scheduler from the initial firestoreFunction .

We expand the CREATE-USER case to create the job which sends the email to the created user at a one-day interval.

 require("dotenv").config();cloc const { Firestore } = require("@google-cloud/firestore"); const scheduler = require("@google-cloud/scheduler") const cors = require("cors")({ origin: true }); const EMAILER = proccess.env.EMAILER_ENDPOINT const parent = ScheduleClient.locationPath( process.env.PROJECT_ID, process.env.LOCATION_ID ); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); const client = new Scheduler.CloudSchedulerClient() if (!type) { res.status(422).send({ error : "An action type was not specified"}); } switch (type) { case "CREATE-USER":

      const job = { httpTarget: { uri: process.env.EMAIL_FUNCTION_ENDPOINT, httpMethod: "POST", body: { email: email, }, }, schedule: "*/30 */6 */5 10 4", timezone: "Africa/Lagos", }
 if (!email || !password) { res.status(422).send("email and password fields missing"); } return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document .add({ email: email, password: hash, }) .then((response) => {
                  client.createJob({ parent : parent, job : job }).then(() => res.status(200).send(response)) .catch(e => console.log(`unable to create job : ${e}`) )
 }) .catch((e) => res.status(501).send(`error inserting data : ${e}`) ); }); }); default: res.status(422).send(`${type} is not a valid function action`) } }); };

From the snippet above, we can see the following:

  • A connection to the Cloud Scheduler from the Scheduler constructor using the Application Default Credentials (ADC) is made.
  • We create an object consisting of the following details which make up the cron job to be created:
    • uri
      The endpoint of our email cloud function in which a request would be made to.
    • body
      This is the data containing the email address of the user to be included when the request is made.
    • schedule
      The unix cron format representing the time when this cron job is to be performed.
  • After the promise from inserting the user's data document is resolved, we create the cron job by calling the createJob method and passing in the job object and the parent.
  • The function's execution is ended with a 200 status code after the promise from the createJob operation has been resolved.

After the job is created, we'll see it listed on the scheduler page.

List of all scheduled cron jobs including the last created job.
List of all scheduled cron jobs including the last created job. (Pratinjau besar)

From the image above we can see the time scheduled for this job to be executed. We can decide to manually run this job or wait for it to be executed at the scheduled time.

Kesimpulan

Within this article, we have had a good look into serverless applications and the benefits of using them. We also had an extensive look at how developers can manage their serverless applications on the Google Cloud using Cloud Functions so you now know how the Google Cloud is supporting the use of serverless applications.

Within the next years to come, we will certainly see a large number of developers adapt to the use of serverless applications when building applications. If you are using cloud functions in a production environment, it is recommended that you read this article from a Google Cloud advocate on “6 Strategies For Scaling Your Serverless Applications”.

The source code of the created cloud functions are available within this Github repository and also the used front-end application within this Github repository. The front-end application has been deployed using Netlify and can be tested live here.

Referensi

  • Google Cloud
  • Cloud Functions
  • Cloud Source Repositories
  • Cloud Scheduler overview
  • Cloud Firestore
  • “6 Strategies For Scaling Your Serverless Applications,” Preston Holmes