Como criar e implantar full-stack Reagir aplicação

Olá Habr! Eu represento a sua atenção a tradução do artigo «Como construir e implantar uma completa -Stack Reagir-App» Autor Frank Zickert.

Os componentes de infraestrutura facilitam a criação, execução e implantação de um aplicativo React completo. Com esses componentes do React, você pode se concentrar em escrever a lógica comercial do seu aplicativo. Você não tem que se preocupar com a sua configuração.

Deseja se tornar um desenvolvedor full-stack? O aplicativo de pilha completa complementa a interface da web interativa React com um servidor e um banco de dados. Mas este aplicativo requer muito mais opções do que uma simples aplicação de uma página.

Usamos componentes de infraestrutura . Esses componentes do React nos permitem definir nossa arquitetura de infraestrutura como parte de nosso aplicativo React. Nós não precisamos de quaisquer outras configurações, como Webpack, Babel, ou sem servidor.

Iniciar


Você pode configurar seu projeto de três maneiras:


Depois de instalar as dependências (execute o npm install ), você poderá criar o projeto com um comando: npm run build .

O script de construção adiciona mais três scripts ao package.json:

  • npm run{your-project-name} } lançamentos Reagir-o seu aplicativo localmente (modo-dev quente, não o servidor e banco de dados)
  • npm run start-{your-env-name} inicia toda a pilha de software localmente. Nota: O Java 8 JDK é necessário para executar o banco de dados offline. Aqui está como você pode instalar o JDK .
  • npm run deploy-{your-env-name} implanta seu aplicativo na AWS.

Nota. Para aplicações Implantar nas AWS você precisa usuário IAM técnica com esses direitos. Coloque as credenciais do usuário no seu arquivo .env da seguinte maneira:

 AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = *** 

Defina sua arquitetura de aplicativo


Projetos baseados em componentes de infraestrutura têm uma estrutura clara. Você tem um componente de nível superior. Ele define a arquitetura global da sua aplicação.

Subcomponentes (componente controlada) especificar (dilatam) o comportamento da aplicação e a função adicional.

No exemplo a seguir, o componente <ServiceOrientedApp /> é nosso componente de nível superior. Nós o exportamos como o arquivo padrão para nosso arquivo de ponto 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> ); 

<ServiceOrientedApp /> é um aplicativo da web interativo. Você pode refinar (estender) a funcionalidade do aplicativo usando você fornecer componentes filhos. Ele suporta os <DataLayer /> <Environment /> , <Route /> , <Service /> e <DataLayer /> .

<Envrionment /> determina o tempo de execução do seu aplicativo. Por exemplo, você pode ter a versão dev e prod. Você pode executar e implantar cada um separadamente.

<Route /> - uma página de sua aplicação. Ele funciona como um <Route /> para reagir-router. Aqui está um tutorial sobre como trabalhar com rotas .

<Service /> define uma função que é executada no lado do servidor. Pode ter um ou vários componentes <Middleware /> - como filhos.

<Middleware /> funciona como o Express.js-middleware.
<DataLayer /> adiciona um banco de dados NoSQL ao seu aplicativo. Aceita <Entry /> - componentes como filhos. <Entry /> descreve o tipo de itens no seu banco de dados.

Esses componentes são tudo o que precisamos para criar nosso aplicativo de pilha completa. Como você pode ver, a nossa aplicação é: um ambiente de tempo de execução, uma página, dois serviços e um banco de dados com uma entrada.

estrutura componente fornece uma visão clara de sua aplicação. Quanto maior for a sua aplicação fica, mais importante ele é.

Você deve ter notado que o <Service /> são filhos do <DataLayer /> . Isso tem uma explicação simples. Queremos que nossos serviços tenham acesso ao banco de dados. É realmente assim tão simples!

projetar bancos de dados


<DataLayer /> cria o Amazon DynamoDB. Esta base de dados é uma chave-valor (NoSQL). Ele fornece alto desempenho em qualquer escala. Mas em contraste com o banco de dados relacional, ele não suporta consultas complexas.

O esquema do banco de dados possui três campos: primaryKey , rangeKey e data . Isto é importante porque você precisa saber que você pode encontrar registros somente para as chaves. Por primaryKey , ou rangeKey , ou ambos.

Com esse conhecimento, vamos dar uma olhada no nosso <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 /> descreve a estrutura dos nossos dados. Definimos nomes para nossa primaryKey e rangeKey. Você pode usar qualquer nome, exceto algumas palavras-chave do DynamoDB, que você pode encontrar aqui. Mas os nomes que usamos são as conseqüências funcionais:

  • Quando adicionamos itens ao nosso banco de dados, devemos fornecer valores para esses nomes de chave.
  • A combinação de ambas as chaves descreve um elemento exclusivo no banco de dados.
  • Não deve haver outra <Entrada /> com os mesmos nomes de chave (um nome pode ser o mesmo, mas não ambos).
  • Podemos encontrar os itens no banco de dados somente se tivermos um valor de pelo menos um nome da chave.

No nosso exemplo, isso significa que:

  • Cada usuário deve ter um nome de usuário e ID de usuário.
  • não pode haver um segundo usuário com o mesmo nome de usuário e o mesmo ID de usuário. Do ponto de vista do banco de dados, seria bom se dois usuários tivessem o mesmo nome de usuário quando tivessem uma identificação de usuário diferente (ou vice-versa).
  • Nós não podemos ter outra <Entrada /> em nosso banco de dados primaryKey = «nome» e rangeKey = «UserID».
  • Podemos consultar usuários no banco de dados quando temos um nome de usuário ou id usuário. Mas não podemos pedir para a idade ou endereço.

Adicionar itens ao banco de dados


Definimos dois componentes <Service /> em nosso <ServiceOrientedApp /> . Serviço POST que adiciona o usuário ao banco de dados e serviço GET que recupera o usuário dele.

Vamos começar com <AddUserService /> . Aqui está o código para este serviço:

 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> }; 

Componente <Service /> - usa três parâmetros:

  • O identificador ( id ) deve ser string única. Usamos um identificador ( id ) quando precisamos chamar o service em outros componentes.
  • O path (com / inicial) indica o caminho relativo da URL do seu serviço
  • O method deve ser um de GET , POST , UPDATE , DELETE . Ele HTTP-solicitação indica que usamos ao chamar o serviço.

Adicionamos <Middleware /> quando criança. Este <Middleware /> aceita uma função de retorno de chamada como parâmetro. Poderíamos fornecer diretamente o middleware Express.js. Como queremos acessar o banco de dados, serviceWithDataLayer função em serviceWithDataLayer . Isso adiciona dataLayer como o primeiro parâmetro para o nosso retorno.

DataLayer fornece acesso ao banco de dados. Vamos ver como!

A função mutate assíncrona aplica as alterações aos dados em nosso banco de dados. Isso requer um cliente e um comando de mutação como parâmetros.

Os dados do elemento são um objeto Javascript que possui todos os pares de valores-chave necessários. Em nosso serviço, obtemos esse objeto do corpo da solicitação. Para User objeto tem a seguinte estrutura:

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

Este objeto usa os nomes primaryKey e rangeKey e todas as chaves de dados que definimos em <Entry /> .

Nota: por enquanto, o único tipo suportado é uma sequência que corresponde a GraphQLString na definição de <Entry />.
Mencionamos acima que IUserEntry dados IUserEntry do corpo. Como está indo isso?

Os componentes de infraestrutura fornecem a função assíncrona callService (serviceId, dataObject) . Essa função aceita um identificador de serviço, um objeto Javascript (para enviar como um corpo de solicitação ao usar POST ), uma função de success e uma função de retorno de chamada de erro.

O seguinte snippet mostra como usamos essa função para chamar nosso <AddUserService /> . Nós especificamos serviceId . E passamos userData , que tomamos como parâmetro para nossa função.

 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) } ); }; 

Agora, a função callAddUserService é tudo o que precisamos quando queremos adicionar um novo usuário. Por exemplo, chame-o quando o usuário clicar em um botão:

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

Nós apenas chamá-lo usando o objeto IUserEntry . Ele é o serviço certo (como definiu a sua identificação ( id )). Ele coloca userData no corpo da solicitação. <AddUserService /> pega os dados do corpo e os coloca no banco de dados.

Obter itens do banco de dados


Recuperar itens de um banco de dados é tão fácil quanto adicioná-los.

 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> } 

Novamente, usamos <Service />, <Middleware /> e uma função de retorno de chamada com acesso ao banco de dados.

Em vez da função mutate , que acrescenta um elemento à base de dados, usamos o select . Essa função solicita o cliente que estamos recebendo do dataLayer . O segundo parâmetro é o comando select . Como uma equipe mutation , podemos construir uma equipa de select usando dataLayer .

Desta vez usamos o getEntryQuery . Nós fornecemos o identificador ( id ) <Entry /> cujo elemento queremos receber. E fornecemos as chaves ( primaryKey e rangeKey ) de um elemento específico em um objeto Javascript. Porque nós fornecemos ambas as teclas, temos uma parte traseira item. Se ele existir.

Como você pode ver, pegamos os valores-chave da solicitação. Mas desta vez, nós os retiramos de request.query e não de request.body . O motivo é que este serviço usa o método GET . Este método não suporta o corpo no pedido. Mas ele fornece todos os dados como um parâmetro de consulta.

A função callService lida com isso para nós. Como em callAddUserService-function , nós fornecemos um identificador ( id ) <Service /> , que deseja chamar. Nós fornecemos os dados necessários. Aqui estão apenas as chaves. E nós fornecemos uma função de chamada de retorno.

A chamada de retorno bem-sucedido fornece uma resposta. O corpo da resposta no formato json contém nosso elemento encontrado. Nós podemos acessá-lo via o elemento chave get_user_entry . « Get_ » define uma consulta que colocamos na nossa selecção. " User_entry " é a chave do nosso <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) } ); } (dados) { 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) } ); } 

Dê uma olhada no seu aplicativo Full-Stack em ação.


Se você ainda não iniciou seu aplicativo, agora é a hora de fazê-lo: npm run start-{your-env-name} .

Você pode até implantar seu aplicativo na AWS com um único comando: npm run deploy-{your-env-name} . (Lembre-se de colocar as credenciais da AWS no arquivo .env).

Este post não descreve como inserir os dados que você colocar em um banco de dados, e como você exibir resultados. callAddUserService e callGetUserService encapsular tudo o que é específico para os serviços e bancos de dados. Basta colocar a Javascript objeto e recuperá-lo.

Você encontrará o código fonte deste exemplo neste repositório GitHub . Inclui uma interface de usuário muito simples.

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


All Articles