Hallo habr Ich präsentiere Ihnen die Übersetzung des Artikels
"Erstellen und Bereitstellen einer Full-Stack-React-App" von Frank Zickert.
Infrastrukturkomponenten erleichtern das Erstellen, Ausführen und Bereitstellen einer vollständigen React-Anwendung. Mit diesen React-Komponenten können Sie sich darauf konzentrieren, die Geschäftslogik Ihrer Anwendung zu schreiben. Sie müssen sich nicht um die Konfiguration kümmern.
Willst du ein Full-Stack-Entwickler werden? Die Full-Stack-Anwendung ergänzt die interaktive React-Weboberfläche um einen Server und eine Datenbank. Aber diese App erfordert viel mehr Möglichkeiten als eine einfache Ein-Seiten-Anwendung.
Wir verwenden
Komponenten der Infrastruktur . Mit diesen React-Komponenten können wir unsere Infrastrukturarchitektur als Teil unserer React-Anwendung definieren. Wir brauchen keine weiteren Einstellungen wie Webpack, Babel oder Serverless mehr.
Starten Sie
Sie können Ihr Projekt auf drei Arten einrichten:
Sobald Sie die Abhängigkeiten installiert haben (führen Sie
npm install
), können Sie das Projekt mit einem Befehl
npm run build
:
npm run build
.
Das Build-Skript fügt package.json drei weitere Skripten hinzu:
npm run{your-project-name}
startet Ihre React-Anwendung lokal (Hot-Dev-Modus, ohne Serverteil und Datenbank)npm run start-{your-env-name}
startet den gesamten Software-Stack lokal. Hinweis: Um die Datenbank im Offline-Modus erfordert Java 8 JDK ausführen. So können Sie das JDK installieren .npm run deploy-{your-env-name}
Sie npm run deploy-{your-env-name}
Name} setzt Ihre Anwendung auf AWS.
Hinweis Um Ihre Anwendung für AWS bereitzustellen, benötigen Sie einen technischen IAM-Benutzer mit diesen Rechten. Fügen Sie die Benutzeranmeldeinformationen wie folgt in Ihre ENV-Datei ein:
AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = ***
Definieren Sie Ihre Anwendungsarchitektur
Projekte, die auf Infrastrukturkomponenten basieren, sind klar strukturiert. Sie haben eine Komponente der obersten Ebene. Dies definiert die Gesamtarchitektur Ihrer Anwendung.
Unterkomponenten (untergeordnete Komponenten) verfeinern (erweitern) das Verhalten der Anwendung und fügen Funktionen hinzu.
Im folgenden Beispiel ist die Komponente
<ServiceOrientedApp />
unsere Komponente auf oberster Ebene. Wir exportieren es als Standarddatei in unsere Einstiegspunktdatei (
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> );
<ServiceOrientedApp />
ist eine interaktive Webanwendung. Sie können die Funktionalität dieser Anwendung mithilfe der von Ihnen bereitgestellten untergeordneten Komponenten erläutern (erweitern). Es unterstützt die
<DataLayer />
<Environment />
,
<Route />
,
<Service />
und
<DataLayer />
.
<Envrionment />
bestimmt die Laufzeit Ihrer Anwendung. Zum Beispiel können Sie eine Entwicklungs- und eine Produktversion haben. Sie können jedes einzeln ausführen und bereitstellen.
<Route />
ist die Seite Ihrer Anwendung. Es funktioniert wie
<Route />
im React-Router.
Hier finden Sie ein Tutorial zum Arbeiten mit Routen .
<Service />
definiert eine Funktion, die serverseitig ausgeführt wird. Es kann eine oder mehrere
<Middleware />
- Komponenten als Kinder haben.
<Middleware />
funktioniert wie Express.js-Middleware.
<DataLayer />
fügt Ihrer Anwendung eine NoSQL-Datenbank hinzu. Akzeptiert <Entry /> - Komponenten als untergeordnete Elemente. <Entry /> beschreibt die Art der Elemente in Ihrer Datenbank.
Diese Komponenten sind alles, was wir brauchen, um unsere Full-Stack-Anwendung zu erstellen. Wie Sie sehen, hat unsere Anwendung: eine Laufzeit, eine Seite, zwei Dienste und eine Datenbank mit einem Datensatz.
Die Komponentenstruktur bietet eine klare Sicht auf Ihre Anwendung. Je größer Ihre Bewerbung wird, desto wichtiger wird sie.
Möglicherweise haben Sie festgestellt, dass
<Service />
<DataLayer />
von
<DataLayer />
. Dies hat eine einfache Erklärung. Wir wollen, dass unsere Dienste Zugriff auf die Datenbank haben. Es ist wirklich so einfach!
Datenbank-Design
<DataLayer />
erstellt Amazon DynamoDB. Dies ist eine Schlüsselwert-Datenbank (NoSQL). Es bietet eine hohe Leistung in jeder Größenordnung. Aber im Gegensatz zu der relationalen Datenbank, unterstützt es nicht komplexe Abfragen.
Das Datenbankschema enthält drei Felder:
primaryKey
,
rangeKey
und
data
. Dies ist wichtig, weil Sie müssen wissen, dass Sie Datensätze für seine Schlüssel nur finden können. Entweder mit
primaryKey
oder
rangeKey
oder mit beiden.
Schauen wir uns mit diesem Wissen unseren
<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 />
beschreibt die Struktur unserer Daten. Wir definieren Namen für unseren primaryKey und rangeKey. Sie können einen anderen Namen als einige DynamoDB-Schlüsselwörter verwenden, die Sie hier finden. Die von uns verwendeten Namen haben jedoch funktionale Auswirkungen:
- Wenn wir unserer Datenbank Elemente hinzufügen, müssen wir Werte für diese Schlüsselnamen bereitstellen.
- Die Kombination beider Schlüssel beschreibt ein eindeutiges Element in der Datenbank.
- Es sollte keinen anderen <Entry /> mit denselben Tastennamen geben (ein Name kann derselbe sein, aber nicht beide).
- Wir können Elemente in der Datenbank nur finden, wenn wir den Wert von mindestens einem Schlüsselnamen haben.
In unserem Beispiel bedeutet das:
- Jeder Benutzer muss einen Benutzernamen und eine Benutzer-ID haben.
- Es kann keinen zweiten Benutzer mit demselben Benutzernamen und derselben Benutzer-ID geben. Aus Datenbanksicht wäre es schön, wenn zwei Benutzer denselben Benutzernamen hätten, wenn sie unterschiedliche Benutzer-IDs hätten (oder umgekehrt).
- Wir können nicht haben eine andere <Entry /> in unserer Datenbank primaryKey = «Benutzername» und rangeKey = «Benutzer-ID».
- Wir können die Datenbank nach Benutzern abfragen, wenn wir einen Benutzernamen oder eine Benutzer-
id
. Wir können jedoch keine Anfragen nach Alter oder Adresse stellen.
Fügen Sie der Datenbank Elemente hinzu
Wir haben festgestellt , zwei
<Service />
- Komponente in unserem
<ServiceOrientedApp />
.
POST
Dienst, der den Benutzer zur Datenbank hinzufügt, und
GET
Dienst, der den Benutzer von dort abruft.
Beginnen wir mit
<AddUserService />
. Hier ist der Code für diesen Dienst:
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> };
Komponente
<Service />
- drei Parameter:
- Der Bezeichner (
id
) muss eine eindeutige Zeichenfolge sein. Wir verwenden eine Kennung ( id
), wenn wir in anderen Komponenten den service
anrufen müssen. - Der
path
(mit dem Anfangsbuchstaben /) gibt den relativen URL-Pfad Ihres Dienstes an - Die
method
muss GET
, POST
, UPDATE
oder DELETE
. Es gibt die HTTP-Anforderung an, die wir beim Aufrufen des Dienstes verwenden.
Wir fügen als Kind
<Middleware />
. Diese
<Middleware />
empfängt die Callback - Funktion als Parameter. Wir könnten Express.js Middleware direkt bereitstellen. Da wir auf die Datenbank zugreifen möchten,
serviceWithDataLayer
wir
serviceWithDataLayer
Funktion in
serviceWithDataLayer
. Dies fügt
dataLayer
als ersten Parameter zu unserem Rückruf.
DataLayer
bietet Zugriff auf die Datenbank. Mal sehen wie!
Die
mutate
asynchrone Funktion wendet die Änderungen auf die Daten in unserer Datenbank an. Dies erfordert einen Client und einen Mutationsbefehl als Parameter.
Elementdaten sind ein Javascript-Objekt mit allen erforderlichen Schlüssel-Wert-Paaren. In unserem Service erhalten wir dieses Objekt vom Anforderungs-Body. Für
User
Objekt die folgende Struktur:
export interface IUserEntry { username: string, userid: string, age: string, address: string }
Dieses Objekt übernimmt die Namen
primaryKey
und
rangeKey
sowie alle Datenschlüssel, die wir in
<Entry />
.
Hinweis: zur Zeit die einzige unterstützte Typ ist ein String, das entspricht der Definition von GraphQLString <Entry />.
Wir haben oben erwähnt, dass wir
IUserEntry
Daten aus dem Körper nehmen. Wie läuft das
Infrastrukturkomponenten stellen die asynchrone Funktion
callService (serviceId, dataObject)
. Diese Funktion übernimmt die Service - Kennung, Javascript-Seiten (wie für die Anforderung Körper mit Senden
POST
), die Funktion des
success
- und eine Callback - Funktion mit einem Fehler.
Das folgende Snippet zeigt, wie wir mit dieser Funktion unseren
<AddUserService />
. Wir geben
serviceId
. Und wir übergeben
userData
, die wir als Parameter für unsere Funktion übernehmen.
export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); };
- export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); };
) { export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); };
Jetzt brauchen wir nur noch die Funktion
callAddUserService
, um einen neuen Benutzer hinzuzufügen. Rufen Sie es beispielsweise auf, wenn der Benutzer auf eine Schaltfläche klickt:
<button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button>
Wir nennen es nur das Objekt mit
IUserEntry
. Es ruft den richtigen Dienst auf (wie durch seine Kennung (
id
) angegeben). Sie fügt
userData
in den Anforderungshauptteil ein.
<AddUserService />
entnimmt Daten aus dem Body und legt sie in der Datenbank ab.
Holen Sie sich Gegenstände aus der Datenbank
Holen Sie sich die Elemente aus der Datenbank so einfach ist, sie als Zugabe.
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> }
- 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> }
data, req, res, next) { 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> }
Auch hier verwenden wir <Service />, <Middleware /> und eine Rückruffunktion mit Zugriff auf die Datenbank.
Anstelle der
mutate
Funktion, die der Datenbank ein Element hinzufügt, verwenden wir die
select
Funktion. Diese Funktion fragt nach dem Client, den wir von
dataLayer
. Der zweite Parameter ist der Befehl
select
. Wie beim
mutation
können wir mit
dataLayer
einen
dataLayer
.
Dieses Mal werden wir verwenden , um das
getEntryQuery
. Wir geben den Bezeichner (
id
)
<Entry />
dessen Element wir erhalten möchten.
primaryKey
geben wir die Schlüssel (
primaryKey
und
rangeKey
) eines bestimmten Elements in einem Javascript-Objekt an. Da wir beide Schlüssel zur Verfügung stellen, erhalten wir ein Element zurück. Wenn es existiert.
Wie Sie sehen können, nehmen wir die Schlüsselwerte aus der Abfrage. Aber diesmal nehmen wir sie von
request.query
und nicht von
request.body
. Der Grund dafür ist, dass dieser Dienst die
GET
Methode verwendet. Diese Methode unterstützt den Body in der Anfrage nicht. Es liefert aber alle Daten als Abfrageparameter.
Funktion
callService
erledigt dies für uns. Wie in der
callAddUserService-function
wir den Bezeichner (
id
)
<Service />
, den wir aufrufen möchten. Wir liefern die notwendigen Daten. Hier sind nur die Schlüssel. Und wir bieten Rückruffunktionen.
Ein erfolgreicher Rückruf liefert eine Antwort. Der Antworttext im JSON-Format enthält unser gefundenes Element. Auf dieses Element können wir über den Schlüssel
get_user_entry
. "
Get_
" definiert die Anforderung, die wir in unsere Auswahlfunktion gestellt haben. "
User_entry
" ist der Schlüssel unseres
<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) } ); }
- 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) } ); }
Schauen Sie sich Ihre Full-Stack-Anwendung in Aktion
Wenn Sie Ihre Anwendung noch nicht gestartet haben, können Sie dies jetzt tun:
npm run start-{your-env-name}
.
Sie können Ihre Anwendung sogar mit einem einzigen Befehl für AWS bereitstellen:
npm run deploy-{your-env-name}
. (Denken Sie daran, die AWS-Anmeldeinformationen in die ENV-Datei einzutragen.)
In diesem Beitrag wird nicht beschrieben, wie Sie die von Ihnen in die Datenbank eingegebenen Daten eingeben und wie Sie die Ergebnisse anzeigen.
callAddUserService
und
callGetUserService
kapseln alles, was für Dienste und die Datenbank spezifisch ist. Sie müssen nur das Javascript-Objekt dort einfügen und es zurückholen.
Den Quellcode für dieses Beispiel finden Sie in diesem
GitHub-Repository . Es enthält eine sehr einfache Benutzeroberfläche.