如何创建和部署全栈阵营的应用

哈Ha! 我向您介绍Frank Zickert撰写的文章“如何构建和部署全栈React-App”的翻译。

基础设施组件使创建,运行和部署成熟的React应用程序变得容易。 借助这些React组件,您可以专注于编写应用程序的业务逻辑。 你不必担心它的配置。

想成为一名全栈开发人员吗? 全栈应用程序通过服务器和数据库补充了React交互式Web界面。 但是,与简单的一页应用程序相比,此类应用程序需要更多的设置。

我们使用基础架构组件 。 这些React组件使我们能够将基础架构定义为React应用程序的一部分。 我们不再需要任何其他设置,例如Webpack,Babel或Serverless。

开始


您可以通过三种方式设置项目:


一旦安装了依赖项(运行npm install ),就可以使用一个命令来构建项目: npm run build

构建脚本向package.json添加了三个脚本:

  • npm run{your-project-name}在本地启动您的React应用程序(热开发模式,没有服务器部分和数据库)
  • npm run start-{your-env-name}在本地启动整个软件堆栈。 注意:需要Java 8 JDK才能离线运行数据库。 这是安装JDK的方法
  • npm run deploy-{your-env-name}将您的应用程序部署到AWS。

注意事项 要将应用程序部署到AWS,您需要具有这些权限的IAM技术用户。 将用户凭据放在您的.env文件中,如下所示:

 AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = *** 

定义您的应用架构


基于基础架构组件的项目具有清晰的结构。 你有一个顶级组件。 这定义了应用程序的整体架构。

子组件(子组件)完善(扩展)应用程序的行为并添加功能。

在下面的示例中,组件<ServiceOrientedApp /> -这是我们顶级组件。 我们将其作为默认文件导出到我们的入口点文件( 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 />是一个交互式Web应用程序。 您可以使用提供的子组件来阐明(扩展)此应用程序的功能。 它支持<Environment /><Route /><Service /><DataLayer />

<Envrionment />确定应用程序的运行时。 例如,您可以具有dev和prod版本。 您可以分别运行和部署每个。

<Route />是您的应用程序页面。 它的工作方式类似于react-router中的<Route />这是有关如何使用路线的教程

<Service />定义了在服务器端运行的功能。 它可以具有一个或多个<Middleware /> -子组件。

<Middleware />工作方式类似于Express.js-middleware。
<DataLayer />将NoSQL数据库添加到您的应用程序。 接受<Entry />-组件作为子组件。 <Entry />描述数据库中项目的类型。

这些组件 - 所有这些,我们需要建立我们的全栈的应用程序。 正如你所看到的,我们的应用程序是:一个运行环境,一个页面,两个服务,并与一个条目的数据库。

组件结构为您的应用程序提供了清晰的视图。 你的应用程序变得更大,更重要的是。

您可能已经注意到<Service /><DataLayer /> 。 它有一个简单的解释。 我们希望我们的服务可以访问数据库。 真的就是这么简单!

数据库设计


<DataLayer />创建Amazon DynamoDB。 这是一个键值数据库(NoSQL)。 它可提供任何规模的高性能。 但是与关系数据库不同,它不支持复杂的查询。

数据库模式具有三个字段: primaryKeyrangeKeydata 。 这很重要,因为您需要知道只能通过其键查找条目。 通过primaryKeyrangeKey ,或同时通过两者。

有了这些知识,让我们看一下<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 />描述了我们数据的结构。 我们为primaryKey和rangeKey定义名称。 您可以使用除此处可以找到的某些DynamoDB关键字以外的任何名称。 但是我们使用的名称具有功能含义:

  • 将项目添加到数据库时,必须提供这些键名的值。
  • 这两个键的组合描述了数据库中的唯一元素。
  • 另一个<Entry />不应具有相同的键名(一个名称可以相同,但不能全部相同)。
  • 仅当我们拥有至少一个键名的值时,才能在数据库中找到元素。

在我们的示例中,这意味着:

  • 每个用户必须具有用户名和用户ID。
  • 不能再有一个具有相同用户名和相同用户名的用户。 从数据库的角度来看,如果两个User具有相同的用户名(而反之亦然),则最好使用相同的用户名。
  • 我们的数据库中不能有另一个<Entry />,其primaryKey =“ username”和rangeKey =“ userid”。
  • 当我们有用户名或用户id时,我们可以查询数据库中的用户。 但是我们不能按年龄或地址要求。

将项目添加到数据库


我们在<ServiceOrientedApp />定义了两个<Service />组件。 将用户添加到数据库的POST服务,以及从数据库中检索用户的GET服务。

让我们从<AddUserService /> 。 下面是该服务的代码:

 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> }; 数据层,REQ,RES,下一个){ 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> }; 

<Service />组件-接受三个参数:

  • 标识符( id )必须是唯一字符串。 当需要在其他组件中调用service时,我们使用标识符( id )。
  • path (以/开头)表示您服务的相对URL路径
  • method必须是GETPOSTUPDATEDELETE 。 它指示我们在调用服务时使用的HTTP请求。

我们将<Middleware />添加为子项。 该<Middleware />将回调函数作为参数。 我们可以直接提供Express.js中间件。 由于我们要访问数据库,因此serviceWithDataLayer函数serviceWithDataLayerserviceWithDataLayer 。 这会将dataLayer作为第一个参数添加到我们的回调中。

DataLayer提供对数据库的访问。 让我们看看如何!

mutate异步函数将更改应用于我们数据库中的数据。 这需要一个客户端和一个mutation命令作为参数。

元素数据是一个Javascript对象,具有所有必需的键值对。 在我们的服务中,我们从请求主体获得此对象。 对于User对象具有以下结构:

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

该对象采用名称primaryKeyrangeKey所有这些键,我们在确定<Entry />

注意:目前,唯一受支持的类型是与<Entry />定义中的GraphQLString匹配的字符串。
上面我们提到过,我们采取数据IUserEntry从身体。 怎么样了

基础结构组件提供异步功能callService (serviceId, dataObject) 。 该函数接受服务标识符,Javascript对象(用于在使用POST时作为请求主体发送), success函数和错误回调函数。

下面的代码显示了我们如何使用这个功能对我们的<AddUserService /> 我们点serviceId 。 而我们通过userData ,这是我们需要作为参数传递给我们的函数。

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

现在callAddUserService功能-这是所有我们需要的时候,我们要添加一个新用户。 例如,当用户单击按钮时调用它:

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

我们只是使用IUserEntry对象来调用它。 它调用正确的服务(如其标识符( id )所示)。 它将userData放入请求正文中。 <AddUserService />从正文中获取数据并将其放入数据库中。

从数据库中获取的项目


从数据库中检索项目就像添加它们一样容易。

 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> } ”:‘*’,//需要CORS工作支持 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> } 

再次,我们使用<服务/>,<中间件/>和一个回调函数来访问数据库。

取而代之的是功能mutate ,这将元素添加到数据库中,我们使用select 。 该函数从dataLayer请求我们要使用的客户端。 第二个参数是select命令。 像mutation命令一样,我们可以使用dataLayer创建一个select命令。

这次我们使用getEntryQuery函数。 我们提供了要接收其元素的标识符( id<Entry /> 。 并且我们提供了Javascript对象中特定元素的键( primaryKeyrangeKey )。 由于我们提供了两个键,因此我们返回了一个元素。 如果它的存在。

如您所见,我们从请求中获取键值。 但是这次,我们从request.query获取它们,而不是从request.body获取它们。 其原因是,该服务使用GET -方法。 此方法不支持请求中的正文。 但它提供的全部资料的查询参数。

功能callService处理这对我们来说。 像在callAddUserService-function ,我们提供要调用的标识符( id<Service /> 。 我们提供必要的数据。 只有钥匙。 我们提供了一个回调函数。

成功的回调可提供响应。 json格式的响应主体包含我们的found元素。 我们可以通过get_user_entry键访问此元素。 “ Get_ ”定义了我们放置在选择函数中的请求。 “ User_entry ”是我们<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) } ); } ,用户ID:串,昂达:(用户数据:IUserEntry)=>空隙){ 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) } ); } 

看一下您的全栈应用程序。


如果你还没有启动您的应用程序,现在是这样做的时候: npm run start-{your-env-name}

你甚至可以部署使用一个命令您的应用程序AWS: npm run deploy-{your-env-name} 。 (请记住将AWS凭证放入.env文件中)。

这篇文章没有描述如何输入放入数据库的数据以及如何显示结果。 callAddUserServicecallGetUserService封装了特定于服务和数据库的所有内容。 您只需将Javascript对象放入其中,然后将其取回即可。

您可以在此GitHub存储库中找到此示例的源代码。 它包括一个非常简单的用户界面。

Source: https://habr.com/ru/post/zh-CN476696/


All Articles