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 Andamethod
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.