Cara membuat dan menyebarkan Full-Stack Bereaksi-aplikasi

Halo, Habr! Saya mempersembahkan kepada Anda terjemahan artikel "Cara Membangun dan Menyebarkan Aplikasi Bereaksi Lengkap" oleh Frank Zickert.

Komponen infrastruktur memudahkan untuk membuat, menjalankan, dan menggunakan aplikasi Bereaksi lengkap. Dengan komponen Bereaksi ini, Anda dapat fokus pada penulisan logika bisnis aplikasi Anda. Anda tidak perlu khawatir tentang konfigurasinya.

Ingin menjadi pengembang tumpukan penuh? Aplikasi full-stack melengkapi antarmuka web interaktif Bereaksi dengan server dan database. Tetapi aplikasi semacam itu membutuhkan lebih banyak pengaturan daripada aplikasi satu halaman yang sederhana.

Kami menggunakan komponen infrastruktur . Komponen Bereaksi ini memungkinkan kami untuk menentukan arsitektur infrastruktur kami sebagai bagian dari aplikasi Bereaksi kami. Kami tidak lagi memerlukan pengaturan lain, seperti Webpack, Babel atau Serverless.

Mulai


Anda dapat mengatur proyek Anda dengan tiga cara:


Setelah Anda menginstal dependensi (jalankan npm install ), Anda dapat membangun proyek dengan satu perintah: npm run build .

membangun script menambahkan tiga skrip di package.json:

  • npm run{your-project-name} meluncurkan Bereaksi aplikasi Anda secara lokal (mode hot-dev, tanpa bagian server dan database)
  • npm run start-{your-env-name} memulai seluruh tumpukan perangkat lunak secara lokal. Catatan: Java 8 JDK diperlukan untuk menjalankan database secara offline. Inilah cara Anda dapat menginstal JDK .
  • npm run deploy-{your-env-name} menyebarkan aplikasi Anda ke AWS.

Catatan Untuk menyebarkan aplikasi Anda ke AWS, Anda memerlukan pengguna teknis IAM dengan hak-hak ini. Masukkan kredensial pengguna dalam file .env Anda sebagai berikut:

 AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = *** 

Tentukan arsitektur aplikasi Anda


Proyek berdasarkan komponen infrastruktur memiliki struktur yang jelas. Anda memiliki satu komponen tingkat atas. Ini mendefinisikan keseluruhan arsitektur aplikasi Anda.

Subkomponen (komponen anak) memperbaiki (memperluas) perilaku aplikasi dan menambahkan fungsi.

Dalam contoh berikut, komponen <ServiceOrientedApp /> adalah komponen tingkat atas kami. Kami mengekspornya sebagai file default ke file titik entri kami ( src / index.tsx) .

 export default ( <ServiceOrientedApp stackName = "soa-dl" buildPath = 'build' region='eu-west-1'> <Environment name="dev" /> <Route path='/' name='My Service-Oriented React App' render={()=><DataForm />} /> <DataLayer id="datalayer"> <UserEntry /> <GetUserService /> <AddUserService /> </DataLayer> </ServiceOrientedApp> ); > export default ( <ServiceOrientedApp stackName = "soa-dl" buildPath = 'build' region='eu-west-1'> <Environment name="dev" /> <Route path='/' name='My Service-Oriented React App' render={()=><DataForm />} /> <DataLayer id="datalayer"> <UserEntry /> <GetUserService /> <AddUserService /> </DataLayer> </ServiceOrientedApp> ); 

<ServiceOrientedApp /> - adalah sebuah aplikasi berbasis web interaktif. Anda dapat memperbaiki (memperpanjang) fungsionalitas dari aplikasi menggunakan Anda menyediakan komponen anak. Ini mendukung komponen <Environment /> , <Route /> , <Service /> dan <DataLayer /> .

<Envrionment /> menentukan runtime aplikasi Anda. Misalnya, Anda dapat memiliki versi dev dan prod. Anda dapat menjalankan dan menggunakan masing-masing secara terpisah.

<Route /> adalah halaman aplikasi Anda. ini bekerja seperti <Route /> di react-router. Berikut ini adalah tutorial tentang cara bekerja dengan rute .

<Service /> mendefinisikan fungsi yang berjalan di sisi server. Ini mungkin memiliki satu atau lebih <Middleware /> - komponen sebagai anak-anak.

<Middleware /> bekerja seperti Express.js-middleware.
<DataLayer /> menambahkan database NoSQL ke aplikasi Anda. Menerima <Entry /> - komponen sebagai anak-anak. <Entri /> menggambarkan jenis item dalam database Anda.

Komponen-komponen ini adalah semua yang kita butuhkan untuk membuat aplikasi full-stack. Seperti yang Anda lihat, aplikasi kami memiliki: satu runtime, satu halaman, dua layanan, dan database dengan satu catatan.

Struktur komponen memberikan pandangan yang jelas dari aplikasi Anda. Semakin besar aplikasi Anda, semakin penting itu.

Anda mungkin telah memperhatikan bahwa <Service /> adalah anak-anak dari <DataLayer /> . Ini memiliki penjelasan sederhana. Kami ingin layanan kami memiliki akses ke database. Sesederhana itu!

Desain Basis Data


<DataLayer /> menciptakan Amazon DynamoDB. Database ini adalah kunci-nilai (NoSQL). Ini memberikan kinerja tinggi pada skala apa pun. Tetapi tidak seperti database relasional, itu tidak mendukung permintaan kompleks.

Skema basis data memiliki tiga bidang: primaryKey , rangeKey dan data . Ini penting karena Anda perlu tahu bahwa Anda hanya dapat menemukan entri dengan kuncinya. Baik dengan primaryKey , atau dengan rangeKey , atau keduanya.

Dengan pengetahuan ini, mari kita lihat <Entry /> :

 export const USER_ENTRY_ID = "user_entry"; export default function UserEntry (props) { return <Entry id={ USER_ENTRY_ID } primaryKey="username" rangeKey="userid" data={{ age: GraphQLString, address: GraphQLString }} /> }; 

<Entry /> menggambarkan struktur data kami. Kami mendefinisikan nama untuk primaryKey dan rangeKey kami. Anda dapat menggunakan nama apa pun selain beberapa kata kunci DynamoDB yang dapat Anda temukan di sini. Namun nama yang kami gunakan memiliki implikasi fungsional:

  • Ketika kita menambahkan unsur-unsur dalam database kami, kita perlu menyediakan nilai-nilai untuk nama-nama kunci.
  • Kombinasi kedua tombol menjelaskan elemen unik dalam database.
  • Seharusnya tidak ada lagi <Entri /> dengan nama kunci yang sama (satu nama mungkin sama, tetapi tidak keduanya).
  • Kita dapat menemukan item dalam database hanya jika kita memiliki nilai minimal satu nama kunci.

Dalam contoh kita, ini berarti bahwa:

  • Setiap Pengguna harus memiliki nama pengguna dan nama pengguna.
  • tidak bisa menjadi pengguna kedua dengan username dan userid yang sama yang sama. Dari perspektif basis data, alangkah baiknya jika dua Pengguna memiliki nama pengguna yang sama ketika mereka memiliki userid yang berbeda (atau sebaliknya).
  • Kami tidak dapat memiliki <entri /> lain dalam database kami dengan primaryKey = "nama pengguna" dan rangeKey = "userid".
  • Kita bisa query database bagi pengguna, ketika kita memiliki nama pengguna atau id pengguna. Tetapi kami tidak dapat meminta berdasarkan umur atau alamat.

Menambahkan item ke database


Kami mendefinisikan dua komponen <Service /> di <ServiceOrientedApp /> . Layanan POST yang menambahkan pengguna ke database dan layanan GET yang mengambil pengguna dari itu.

Mari kita mulai dengan <AddUserService /> . Berikut adalah kode untuk layanan ini:

 import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; ./user-entry'; import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; .; import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; 

Komponen <Service /> - menerima tiga parameter:

  • Identifier ( id ) harus berupa string unik. Kami menggunakan pengidentifikasi ( id ) ketika kami perlu memanggil service di komponen lain.
  • path (dengan inisial /) menunjukkan jalur URL relatif dari layanan Anda
  • method harus salah satu dari GET , POST , UPDATE , DELETE . Dia HTTP-request menunjukkan bahwa kita gunakan saat memanggil layanan.

Kami menambahkan <Middleware /> sebagai seorang anak. <Middleware /> ini menggunakan fungsi panggilan balik sebagai parameter. Kami dapat langsung menyediakan middleware Express.js. Karena kami ingin mengakses database, kami serviceWithDataLayer fungsi di serviceWithDataLayer . Ini menambahkan dataLayer sebagai parameter pertama ke panggilan balik kami.

DataLayer menyediakan akses ke database. Mari kita lihat caranya!

Fungsi asinkron mutate menerapkan perubahan pada data dalam database kami. Ini memerlukan klien dan mutasi perintah sebagai parameter.

Unsur-unsur ini - obyek Javascript, yang memiliki semua pasangan nilai kunci. Dalam layanan kami, kami mendapatkan objek ini dari badan permintaan. Untuk User objek memiliki struktur berikut:

 export interface IUserEntry { username: string, userid: string, age: string, address: string } 

Objek ini mengambil nama primaryKey dan rangeKey dan semua kunci data yang kami definisikan di <Entry /> .

Catatan: saat ini jenis hanya didukung adalah string, yang sesuai dengan definisi GraphQLString <Entri />.
Kami disebutkan di atas bahwa kita mengambil data yang IUserEntry dari tubuh. Bagaimana kabarnya?

Komponen infrastruktur menyediakan fungsi asinkron callService (serviceId, dataObject) . Fungsi ini menerima pengidentifikasi layanan, objek Javascript (untuk mengirim sebagai badan permintaan saat menggunakan POST ), fungsi success , dan fungsi panggilan balik kesalahan.

Cuplikan berikut menunjukkan bagaimana kami menggunakan fungsi ini untuk memanggil <AddUserService /> . Kami menentukan serviceId . Dan kami melewati userData , yang kita ambil sebagai parameter untuk fungsi kita.

 export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; data); export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; 

Sekarang fungsi callAddUserService adalah semua yang kita butuhkan ketika kita ingin menambahkan pengguna baru. Misalnya, panggil ketika pengguna mengklik tombol:

 <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button> 

Kami cukup menyebutnya menggunakan objek IUserEntry . Itu memanggil layanan yang benar (seperti yang ditunjukkan oleh pengidentifikasi ( id )). Itu menempatkan userData di tubuh permintaan. <AddUserService /> mengambil data dari tubuh dan menempatkan mereka ke dalam database.

Dapatkan item dari database


Mengambil item dari database semudah menambahkannya.

 export default function GetUserService () { return <Service id={ GETUSER_SERVICE_ID } path="/getuser" method="GET"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const data = await select( dataLayer.client, dataLayer.getEntryQuery(USER_ENTRY_ID, { username: req.query.username, userid: req.query.userid }) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send(JSON.stringify(data)); })}/> </Service> } 

Sekali lagi, kami menggunakan <Layanan />, <Middleware /> dan fungsi panggilan balik dengan akses ke database.

Alih-alih fungsi mutate , yang menambahkan item ke database, kami menggunakan fungsi select . Fungsi ini meminta klien yang kami ambil dari dataLayer . Pilihan kedua - tim select . Seperti perintah mutation , kita dapat membuat perintah select menggunakan dataLayer .

Kali ini kami menggunakan fungsi getEntryQuery . Kami memberikan pengidentifikasi ( id ) <Entry /> yang elemennya ingin kami terima. Dan kami menyediakan kunci ( primaryKey dan rangeKey ) elemen tertentu dalam objek Javascript. Karena kami menyediakan kedua kunci, kami mendapatkan satu elemen kembali. Jika ada.

Seperti yang Anda lihat, kita mengambil nilai-nilai kunci dari query. Tapi kali ini kami mengambilnya dari request.query , dan bukan dari request.body . Alasannya adalah bahwa layanan ini menggunakan metode GET . Metode ini tidak mendukung badan dalam permintaan. Tetapi ia menyediakan semua data sebagai parameter kueri.

Fungsi callService menangani ini untuk kami. Seperti pada fungsi callAddUserService-function , kami memberikan pengidentifikasi ( id ) <Service /> yang ingin kami panggil. Kami menyediakan data yang diperlukan. Ini hanya kunci-kuncinya. Dan kami menyediakan fungsi panggilan balik.

Panggilan balik yang berhasil memberikan respons. respon tubuh dalam format json mengandung unsur pencocokan kami. Kami dapat mengakses elemen ini melalui kunci get_user_entry . " Get_ " mendefinisikan permintaan yang kami tempatkan di fungsi pilihan kami. " User_entry " adalah kunci dari <Entry /> .

 export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } (data) { export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } ) export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } 

Lihatlah aplikasi penuh-Stack Anda dalam tindakan


Jika Anda belum memulai aplikasi Anda, sekarang saatnya untuk melakukannya: npm run start-{your-env-name} .

Anda bahkan dapat menggunakan aplikasi Anda ke AWS dengan satu perintah: npm run deploy-{your-env-name} . (Ingatlah untuk meletakkan kredensial AWS dalam file .env).

Posting ini tidak menjelaskan bagaimana Anda memasukkan data yang Anda masukkan ke dalam database dan bagaimana Anda menampilkan hasilnya. callAddUserService dan callGetUserService merangkum semua yang spesifik untuk layanan dan database. Anda cukup meletakkan objek Javascript di sana dan mendapatkannya kembali.

Anda akan menemukan kode sumber untuk contoh ini di repositori GitHub ini. Ini termasuk antarmuka pengguna yang sangat sederhana.

Source: https://habr.com/ru/post/id476696/


All Articles