Cómo crear e implementar una aplicación Full-Stack React

Hola Habr! Yo represento a la atención de la traducción del artículo «Cómo construir y desplegar una completa -Stack reaccionan-App» Autor Frank Zickert.

Los componentes de infraestructura facilitan la creación, ejecución e implementación de una aplicación React completa. Con estos componentes React, puede concentrarse en escribir la lógica empresarial de su aplicación. No necesita preocuparse por su configuración.

¿Desea convertirse en un desarrollador full-stack? La aplicación full-stack complementa la interfaz web interactiva React con un servidor y una base de datos. Sin embargo, esta aplicación requiere mucho más opciones que un simple solicitud de una página.

Utilizamos componentes de la infraestructura . Estos componentes React nos permiten definir nuestra arquitectura de infraestructura como parte de nuestra aplicación React. Ya no necesitamos ninguna otra configuración, como Webpack, Babel o Serverless.

Inicio


Puede configurar su proyecto de tres maneras:


Una vez que haya instalado las dependencias (ejecute npm install ), puede compilar el proyecto con un comando: npm run build .

El script de compilación agrega tres scripts más a package.json:

  • npm run{your-project-name} inicia su aplicación React localmente (modo hot-dev, sin parte del servidor y base de datos)
  • npm run start-{your-env-name} nombre} recorre toda la pila de software a nivel local. Nota: Se requiere Java 8 JDK para ejecutar la base de datos fuera de línea. Aquí es cómo puede instalar el JDK .
  • npm run deploy-{your-env-name} nombre} despliega su aplicación en AWS.

Nota Para implementar su aplicación en AWS, necesita un usuario técnico de IAM con estos derechos. Coloque las credenciales del usuario en su .env-archivo de la siguiente manera:

 AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = *** 

Define la arquitectura de tu aplicación


Los proyectos basados ​​en componentes de infraestructura tienen una estructura clara. Tienes un componente de nivel superior. Esto define la arquitectura general de su aplicación.

Los subcomponentes (componentes secundarios) refinan (amplían) el comportamiento de la aplicación y agregan funciones.

En el siguiente ejemplo, el componente <ServiceOrientedApp /> es nuestro componente de nivel superior. Lo exportamos como el archivo predeterminado a nuestro archivo de punto de entrada ( 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> ); DataForm />} 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 /> es una aplicación web interactiva. Puede aclarar (expandir) la funcionalidad de esta aplicación utilizando los componentes secundarios que proporcionó. Admite los <DataLayer /> <Environment /> , <Route /> , <Service /> y <DataLayer /> .

<Envrionment /> determina el tiempo de ejecución de su aplicación. Por ejemplo, puede tener una versión de desarrollo y producción. Puede ejecutar e implementar cada uno por separado.

<Route /> es la página de su aplicación. funciona como <Route /> en react-router. Aquí hay un tutorial sobre cómo trabajar con rutas .

<Service /> define una función que se ejecuta en el lado del servidor. Puede tener uno o más <Middleware /> - componentes como los niños.

<Middleware /> funciona como Express.js-middleware.
<DataLayer /> agrega una base de datos NoSQL a su aplicación. Se necesita <Entrada /> - componentes como los niños. <Entrada /> describe el tipo de elementos en su base de datos.

Estos componentes son todo lo que necesitamos para crear nuestra aplicación de pila completa. Como puede ver, nuestra aplicación tiene: un tiempo de ejecución, una página, dos servicios y una base de datos con un registro.

La estructura del componente proporciona una vista clara de su aplicación. Cuanto mayor sea su aplicación obtiene, más importante es.

Usted puede haber notado que el <Service /> son hijos de la <DataLayer /> . Esto tiene una explicación simple. Queremos que nuestros servicios tengan acceso a la base de datos. Realmente es así de simple!

Diseño de bases de datos


<DataLayer /> crea Amazon DynamoDB. Esta es una base de datos de valores clave (NoSQL). Proporciona un alto rendimiento en cualquier escala. Pero a diferencia de las bases de datos relacionales, no admite consultas complejas.

El esquema de la base tiene tres campos: primaryKey , rangeKey y data . Esto es importante porque necesita saber que solo puede encontrar entradas por sus teclas. Ya sea por primaryKey , o por rangeKey , o ambos.

Con este conocimiento, echemos un vistazo a nuestra <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 /> describe la estructura de nuestros datos. Definimos nombres para nuestra primaryKey y rangeKey. Puede usar cualquier nombre que no sean algunas palabras clave de DynamoDB que puede encontrar aquí. Pero los nombres que usamos tienen implicaciones funcionales:

  • Cuando añadimos los elementos en nuestra base de datos, tenemos que proporcionar valores para estos nombres clave.
  • La combinación de las dos teclas describe el elemento único en la base de datos.
  • No debería haber otra <Entrada /> con los mismos nombres de clave (un nombre puede ser el mismo, pero no ambos).
  • Podemos encontrar elementos en la base de datos solo cuando tenemos el valor de al menos un nombre de clave.

En nuestro ejemplo, esto significa que:

  • Cada usuario debe tener un nombre de usuario y un ID de usuario.
  • no puede haber un segundo Usuario con el mismo nombre de usuario y el mismo ID de usuario. Desde la perspectiva de la base de datos que sería bueno si los dos usuarios fue el mismo que el nombre de usuario, cuando tienen identificador de usuario diferente (o viceversa).
  • No podemos tener otra <Entrada /> en nuestra base de datos con primaryKey = "username" y rangeKey = "userid".
  • Podemos consultar la base de datos para los usuarios cuando tenemos un nombre de usuario o id usuario. Pero no podemos solicitar por edad o dirección.

Agregar elementos a la base de datos


Definimos dos componentes <Service /> en nuestro <ServiceOrientedApp /> . Servicio POST que agrega al usuario a la base de datos y servicio GET que recupera al usuario de ella.

Comencemos con <AddUserService /> . Aquí está el código para este servicio:

 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> }; DataLayer, req, res, próximos) { 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> }; 

Componente <Service /> - toma tres parámetros:

  • El identificador ( id ) debe ser una cadena única. Utilizamos un identificador ( id ), cuando tenemos que hacer referencia al service de los demás componentes.
  • La path (con inicial /) indica la ruta URL relativa de su servicio
  • El method debe ser GET , POST , UPDATE , DELETE . Indica la solicitud HTTP que usamos cuando llamamos al servicio.

<Middleware /> como un niño. Este <Middleware /> toma una función de devolución de llamada como parámetro. Podríamos proporcionar directamente el middleware Express.js. Como queremos acceder a la base de datos, serviceWithDataLayer función en serviceWithDataLayer . Esto agrega dataLayer como el primer parámetro de nuestra devolución de llamada.

DataLayer proporciona acceso a la base de datos. Vamos a ver cómo!

La función asincrónica de mutate aplica los cambios a los datos en nuestra base de datos. Esto requiere que el cliente y las mutaciones de comandos como parámetros.

Estos elementos - un objeto de Javascript, que tiene todos los pares de valores clave. En nuestro servicio, obtenemos este objeto del cuerpo de la solicitud. Para el User objeto tiene la siguiente estructura:

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

Este objeto toma los nombres primaryKey y rangeKey y todas las claves de datos que definimos en <Entry /> .

Nota: en el momento del tipo admitido es una cadena, que corresponde a la definición de GraphQLString <Entrada />.
Hemos mencionado anteriormente que tomamos los datos IUserEntry del cuerpo. ¿Cómo va esto?

Los componentes de infraestructura proporcionan la función asincrónica callService (serviceId, dataObject) . Esta función toma el identificador de servicio, Javascript-sitios (como para el envío del cuerpo de la petición usando POST ), la función del success - y una función de devolución de llamada con un error.

El siguiente fragmento muestra cómo usamos esta función para llamar a nuestro <AddUserService /> . Especificamos serviceId . Y pasamos userData , que tomamos como parámetro de nuestra función.

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

Ahora callAddUserService función - eso es todo lo que necesitamos cuando queremos añadir un nuevo usuario. Por ejemplo, llámelo cuando el usuario hace clic en un botón:

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

Simplemente lo llamamos usando el objeto IUserEntry . Llama al servicio correcto (como lo indica su identificador ( id )). Pone userData en el cuerpo de la solicitud. <AddUserService /> toma datos del cuerpo y los coloca en la base de datos.

Obtener elementos de la base de datos


Obtener los elementos de la base de datos es tan simple como la adición de ellos.

 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> } ": "*", // Necesario para CORS apoyo para trabajar 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> } 

Nuevamente, usamos <Servicio />, <Middleware /> y una función de devolución de llamada con acceso a la base de datos.

En lugar de la función mutate , que agrega un elemento a la base de datos, usamos la función select . Esta función pregunta por el cliente que estamos tomando de dataLayer . El segundo parámetro es el comando de select . Al igual que el comando de mutation , podemos crear un comando de select usando dataLayer .

Esta vez usamos la función getEntryQuery . Proporcionamos el identificador ( id ) <Entry /> cuyo elemento queremos recibir. Y proporcionamos las teclas ( primaryKey y rangeKey ) un elemento específico en el objeto Javascript. Como proporcionamos ambas claves, recuperamos un elemento. Si es que existe.

Como puede ver, tomamos los valores clave de la solicitud. Pero esta vez los tomamos de request.query , y no de request.body . La razón es que el servicio utiliza el GET -method. Este método no admite el cuerpo en la solicitud. Pero proporciona todos los datos como parámetros de consulta.

La función callService maneja esto por nosotros. Como en la callAddUserService-function , proporcionamos el identificador ( id ) <Service /> que queremos llamar. Proporcionamos los datos necesarios. Aquí están solo las llaves. Y proporcionamos una función de devolución de llamada.

Una devolución de llamada exitosa proporciona una respuesta. El cuerpo de respuesta en formato json contiene nuestro elemento encontrado. Podemos acceder a este elemento a través de la clave get_user_entry . " Get_ " define la solicitud que colocamos en nuestra función de selección. « User_entry » - esta es la clave de nuestra <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) } ); } 

Echa un vistazo a tu aplicación Full-Stack en acción.


Si no ha lanzado su aplicación, ahora es el momento de hacerlo: npm run start-{your-env-name} .

Incluso se puede implementar la aplicación de AWS con un solo comando: npm run deploy-{your-env-name} . (Recuerde poner las credenciales de AWS en el archivo .env).

Esta publicación no describe cómo ingresa los datos que ingresa en la base de datos y cómo muestra los resultados. callAddUserService y callGetUserService encapsulan todo lo que es específico de los servicios y la base de datos. Simplemente coloque el objeto Javascript allí y lo recupere.

Puede encontrar el código fuente de este ejemplo en este GitHub-repositorio . Incluye una interfaz de usuario muy simple.

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


All Articles