Bonjour, Habr! Je vous présente la traduction de l'article
"Comment construire et déployer une application React Full-Stack" par Frank Zickert.
Les composants d'infrastructure facilitent la création, l'exécution et le déploiement d'une application React à part entière. Avec ces composants, REACT vous pouvez vous concentrer sur l'écriture logique métier de votre application. Vous n'avez pas à vous soucier de sa configuration.
Vous voulez devenir un développeur pile complète? L'application full-stack complète l'interface Web interactive React avec un serveur et une base de données. Mais cette application nécessite beaucoup plus d'options que une simple application d'une page.
Nous utilisons
des composants d'infrastructure . Ces composants React nous permettent de définir notre architecture d'infrastructure dans le cadre de notre application React. Nous n'avons plus besoin d'autres paramètres, tels que Webpack, Babel ou Serverless.
Commencer
Vous pouvez configurer votre projet de trois façons:
Une fois que vous avez installé les dépendances (exécutez
npm install
), vous pouvez créer le projet avec une seule commande:
npm run build
.
Le script de construction ajoute trois autres scripts Ă package.json:
npm run{your-project-name}
lance votre application React localement (mode hot-dev, sans partie serveur et base de données)npm run start-{your-env-name}
name} exécute l'ensemble de la pile logicielle au niveau local. Remarque: Java 8 JDK est requis pour exécuter la base de données hors ligne. Voici comment vous pouvez installer le JDK .npm run deploy-{your-env-name}
name} déploie votre application sur AWS.
Remarque Pour les applications dans les Déployez AWS dont vous avez besoin utilisateur technique IAM avec ces droits. Placez les informations d'identification de l'utilisateur dans votre fichier .env comme suit:
AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = ***
Définissez l'architecture de votre application
Les projets basés sur des composants d'infrastructure ont une structure claire. Avez-vous un composant de niveau supérieur. Cela définit l'architecture globale de votre application.
Les sous-composants (composants enfants) affinent (étendent) le comportement de l'application et ajoutent des fonctions.
Dans l'exemple suivant, le composant
<ServiceOrientedApp />
est notre composant de niveau supérieur. Nous l'exportons en tant que fichier par défaut dans notre fichier de point d'entrée (
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> );
App' 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 />
est une application Web interactive. Vous pouvez affiner (étendre) la fonctionnalité de l'application à l'aide que vous fournissez des composants de l'enfant. Il prend en charge les
<DataLayer />
<Environment />
,
<Route />
,
<Service />
et
<DataLayer />
.
<Envrionment />
détermine le temps d'exécution de votre application. Par exemple, vous pouvez avoir la version dev et prod. Vous pouvez exécuter et déployer chacun séparément.
<Route />
est la page de votre candidature. cela fonctionne comme
<Route />
dans react-router.
Voici un didacticiel sur l'utilisation des itinéraires .
<Service />
définit une fonction qui est exécutée sur le côté serveur. Il peut avoir un ou plusieurs
<Middleware />
- composants comme les enfants.
<Middleware />
fonctionne comme express.js-middleware.
<DataLayer />
ajoute une base de données NoSQL à votre application. Accepte <Entry /> - composants en tant qu'enfants. <Entrée /> décrit le type d'éléments dans votre base de données.
Ces composants sont tout ce dont nous avons besoin pour créer notre application full-stack. Comme vous pouvez le voir, notre application a: un runtime, une page, deux services et une base de données avec un enregistrement.
la structure du composant fournit une vue claire de votre application. Plus votre application obtient, plus il est important.
Vous avez peut - être remarqué que le
<Service />
sont des enfants du
<DataLayer />
. Il a une explication simple. Nous voulons que nos services aient accès à la base de données. Il est vraiment aussi simple que cela!
bases de données conception
<DataLayer />
crée Amazon DynamoDB. Cette base de données est une valeur clé (NoSQL). Il fournit de hautes performances à toute échelle. Mais contrairement à la base de données relationnelle, il ne supporte pas les requêtes complexes.
Le schéma de base de données a trois champs:
primaryKey
,
rangeKey
et
data
. Ceci est important parce que vous devez savoir que vous pouvez trouver des enregistrements que pour ses clés. Soit
primaryKey
, soit
rangeKey
, ou les deux.
Avec cette connaissance, nous allons jeter un oeil Ă notre
<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 />
décrit la structure de nos données. Nous définissons nos noms pour primaryKey et rangeKey. Vous pouvez utiliser n'importe quel nom autre que certains mots clés DynamoDB que vous pouvez trouver ici. Mais les noms que nous utilisons sont les conséquences fonctionnelles:
- Lorsque nous ajoutons des éléments à notre base de données, nous devons fournir des valeurs pour ces noms de clés.
- La combinaison des deux clés décrit un élément unique dans la base de données.
- Il ne doit pas y avoir d'autre <Entrée /> avec les mêmes noms de clé (un nom peut être le même, mais pas les deux).
- Nous ne pouvons trouver des éléments dans la base de données que lorsque nous avons la valeur d'au moins un nom de clé.
Dans notre exemple, cela signifie que:
- Chaque utilisateur doit avoir un nom d'utilisateur et userid.
- il ne peut pas y avoir un deuxième utilisateur avec le même nom d'utilisateur et le même ID utilisateur. Du point de vue de la base de données, il serait bien que deux utilisateurs aient le même nom d'utilisateur lorsqu'ils ont un ID utilisateur différent (ou vice versa).
- Nous ne pouvons pas avoir une autre <Entrée /> dans notre base de données primaryKey = «nom d'utilisateur» et rangeKey = «userid».
- Nous pouvons interroger la base de données pour les utilisateurs lorsque nous avons un nom d'utilisateur ou un
id
utilisateur. Mais nous ne pouvons pas demander par âge ou adresse.
Ajouter des éléments à la base de données
Nous avons défini deux composants
<Service />
dans notre
<ServiceOrientedApp />
. Service
POST
qui ajoute l'utilisateur à la base de données et service
GET
qui en récupère l'utilisateur.
Commençons par
<AddUserService />
. Voici le code de ce 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> };
); 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> };
": "*", // requis pour CORS soutien au travail 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> };
Le composant
<Service />
- accepte trois paramètres:
- L'identifiant (
id
) doit être une chaîne unique. Nous utilisons un identifiant ( id
), quand nous avons besoin de se référer au service
de dans les autres composants. - Le
path
(avec initiale /) indique le chemin URL relatif de votre service - La
method
doit ĂŞtre l'une de GET
, POST
, UPDATE
, DELETE
. Il indique la requĂŞte HTTP que nous utilisons lors de l'appel du service.
Nous ajoutons
<Middleware />
comme un élément enfant. Ce
<Middleware />
prend une fonction de rappel comme paramètre. Nous pourrions fournir directement le middleware Express.js. Puisque nous voulons accéder à la base de données, nous
serviceWithDataLayer
fonction dans
serviceWithDataLayer
. Cela ajoute
dataLayer
comme premier paramètre à notre rappel.
DataLayer
permet d'accéder à la base de données. Voyons comment!
Fonction Asynchrone
mutate
applique les modifications aux données contenues dans notre base de données. Cela nécessite le client et mutations commande en tant que paramètres.
Les données d'élément sont un objet Javascript qui possède toutes les paires clé-valeur nécessaires. Dans notre service, nous obtenons l'objet du corps de la demande. Pour l'
User
objet a la structure suivante:
export interface IUserEntry { username: string, userid: string, age: string, address: string }
d' export interface IUserEntry { username: string, userid: string, age: string, address: string }
Cet objet prend les noms
primaryKey
et
rangeKey
et toutes les clés de données que nous avons définies dans
<Entry />
.
Remarque: pour l'instant, le seul type pris en charge est une chaîne qui correspond à une chaîne GraphQLString dans la définition de <Entrée />.
Nous avons mentionné ci - dessus que nous prenons les données
IUserEntry
du corps. Comment ça se passe?
Les composants d'infrastructure fournissent la fonction asynchrone
callService (serviceId, dataObject)
. Cette fonction accepte un identifiant de service, un objet Javascript (pour l'envoi en tant que corps de requĂŞte lors de l'utilisation de
POST
), une fonction de
success
et une fonction de rappel d'erreur.
Le code suivant montre comment nous utilisons cette fonction Ă notre
<AddUserService />
. Nous
serviceId
. Et nous passons
userData
, que nous prenons comme paramètre à notre fonction.
export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); };
données); 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) } ); };
Maintenant, la fonction
callAddUserService
est tout ce dont nous avons besoin lorsque nous voulons ajouter un nouvel utilisateur. Par exemple, appelez-le lorsque l'utilisateur clique sur un bouton:
<button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button>
d' <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button>
d' <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button>
Nous appelons simplement Ă l'aide de l'objet
IUserEntry
. Il appelle le service correct (comme indiqué par son identifiant (
id
)). Il place
userData
dans le corps de la requĂŞte.
<AddUserService />
prend les données sur le corps et les met dans une base de données.
Récupérer des éléments de la base de données
Récupérer des éléments d'une base de données est aussi simple que de les ajouter.
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> }
dataLayer, req, res, à côté) { 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> }
": "*", // requis pour CORS soutien au travail 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> }
Encore une fois, nous utilisons <service />, <Middleware /> et une fonction de rappel pour accéder à la base de données.
Au lieu de la fonction de
mutate
, ce qui ajoute un élément à la base de données, nous utilisons le
select
. Cette fonction demande au client, que nous prenons de
dataLayer
de
dataLayer
. La deuxième option - l'équipe de
select
. Comme la commande de
mutation
, nous pouvons créer une commande de
select
aide de
dataLayer
.
Cette fois -ci, nous utilisons le
getEntryQuery
. Nous fournissons un identifiant (
id
)
<Entry />
, dont nous voulons obtenir l'article. Et nous fournissons les clés (
primaryKey
et
rangeKey
) d'un élément particulier dans un objet Javascript. Puisque nous fournissons les deux clés, nous récupérons un élément. Si elle existe.
Comme vous pouvez le voir, nous prenons les valeurs clés de la demande. Mais cette fois, nous les prenons de
request.query
, et non de
request.body
. La raison en est que ce service utilise la méthode
GET
. Cette méthode ne prend pas en charge le corps dans la demande. Mais il fournit toutes les données en tant que paramètres de requête.
Fonction
callService
gère pour nous. Comme dans la
callAddUserService-function
, nous fournissons l'identifiant (
id
)
<Service />
que nous voulons appeler. Nous fournissons les données nécessaires. Il n'y a que les clés. Et nous fournissons des fonctions de rappel.
Un rappel réussie apporte une réponse. Le corps de la réponse au format json contient notre élément trouvé. Nous pouvons y accéder via l'élément clé
get_user_entry
. «
Get_
» définit la demande que nous avons placée dans notre fonction de sélection. "
User_entry
" est la clé de notre
<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) } ); }
Jetez un œil à votre application Full-Stack en action.
Si vous n'avez pas encore démarré votre application, c'est le moment de le faire:
npm run start-{your-env-name}
.
Vous pouvez même déployer votre application sur AWS avec une seule commande:
npm run deploy-{your-env-name}
. (Ne pas oublier de mettre les informations d'identification dans le fichier des AWS).
Ce message ne décrit pas comment vous entrez les données que vous mettez dans la base de données et comment vous affichez les résultats.
callAddUserService
et
callGetUserService
encapsulent tout ce qui est spécifique aux services et bases de données. Vous venez de mettre l'objet Javascript à l'intérieur et de le récupérer.
Vous trouverez le code source de cet exemple dans ce
référentiel GitHub . Il comprend une interface utilisateur très simple.