从图中构建基础架构
#aws #serverless #node #infrastructure

你们中的许多人可能已经听说过基础架构作为代码(IAC)流程,该过程采用声明性方法通过机器可读定义文件来管理和提供系统基础架构。如今,有很多工具/框架/云服务(Terraform by HashiCorpAWS CDKAWS Serverless Application Model (SAM)AWS CloudFormationGoogle Deployment Manager)可以开箱即用。但是,如果我告诉您可以通过在图表上拖放和删除块而不编写配置和/或代码来实现这一目标,该怎么办?

在RE:Invent 2022,AWS宣布了Application Composer服务(目前在预览中),该服务有助于简化和加速无服务器应用程序的体系结构,配置和构建。

本动手教程将包括以下主题:

  • 如何通过AWS应用程序作曲家控制台创建基础架构
  • 如何使用SAM和DynamoDB本地测试生成的基础架构模板
  • 创建简单的node.js应用程序以读取/将数据读取到dynamodb
  • 使用SAM
  • 将基础架构和代码部署到您的AWS帐户中

开发环境先决条件

开始之前您需要做的事情:

图形基础架构图

让我们想象我们需要为一个简单的待办器应用程序构建一个基础架构,用户可以:

  • 阅读所有待办事项
  • 创建新的todo
  • 通过更改标题或完成标题来更新todo

转到Application Composer控制台,然后单击“创建项目”按钮。确保您正在以连接模式创建一个项目。借助浏览器本地File System Access API,可以使用此酷功能。请注意,如果您使用的是Chrome/Edge/Opera,浏览器将请求特殊权限以授予对您指定的文件夹的读取版访问。

如果您使用的是Firefox或Safari,则该模式将无法使用。但是,这确实意味着您无法享受基础架构模板的自动同步更改,并且每次将更改应用于图表时都必须手动下载。

现在,让我们转到最有趣的部分 - 拖放和删除ð我在项目中绘制以下图:

在设计基础架构时,我决定预测我的应用程序的读写模式,并假设它将更加读取,因此,可能需要不同的缩放方法来读取和写作操作。因此,我创建了两个使用API​​网关作为事件源的lambda函数。基本上是这样!

现在您可以转到项目文件夹并找到生成的SAM模板。应用程序作曲家甚至为我的lambda函数定义了DynamoDBCrudPolicy执行策略(缩小到可稳定的资源 - 最小特权原则,那有多酷?)。但是,我仍然建议您手动将其更改为DynamoDBReadPolicyGetToDos函数。

执行

现在让我们在本地调试我们的应用程序之前添加一些代码。

让我们首先定义DynamoDB映射器(我为此使用@aws/dynamodb-data-mapper库):

const {
  DynamoDbSchema,
  DynamoDbTable,
  DataMapper,
} = require("@aws/dynamodb-data-mapper");

class ToDoItem {
  get [DynamoDbTable]() {
    return process.env.TABLE_NAME; // Table name will be passed via environment variables
  }
  get [DynamoDbSchema]() {
    return {
      Id: {
        type: "String",
        keyType: "HASH",
      },
      Title: { type: "String" },
      CreatedAt: {
        type: "Number",
      },
      ModifiedAt: {
        type: "Number",
      },
      CompletedAt: {
        type: "Number",
      },
    };
  }
}

class ToDoItemMapper {
  constructor(client) {
    this.mapper = new DataMapper({
      client, // the SDK client used to execute operations
    });
  }
  scan() {
    return this.mapper.scan(ToDoItem);
  }
  getById(id) {
    const item = new ToDoItem();
    item.Id = id;
    return this.mapper.get(item);
  }
  put(item) {
    return this.mapper.put(item);
  }
  update(item) {
    return this.mapper.update(item);
  }
}

我们的列表处理程序看起来很简单:

const mapper = new ToDoItemMapper(ddbClient);

exports.handler = async () => {
  const iterator = mapper.scan();
  const todoItems = [];
  for await (const record of iterator) {
    todoItems.push(transform(record));
  }
  return {
    statusCode: 200,
    body: JSON.stringify(todoItems),
  };
};

CreateOrupdate处理程序:

const mapper = new ToDoItemMapper(ddbClient);

const createToDo = async ({ title }) => {
  if (!title) {
    throw new Error(
      "InvalidParameterException: title attribute is required"
    );
  }
  const item = new ToDoItem();
  const now = Date.now();
  item.Id = uuid.v4();
  item.Title = title;
  item.CreatedAt = now;
  item.ModifiedAt = now;

  const persisted = await mapper.put(item);
  return transformToModel(persisted);
};

const updateToDo = async (item) => {
  if (!item.id) {
    throw new Error("InvalidParameterException: id attribute is required");
  }
  const itemToUpdate = await mapper.getById(item.id);
  itemToUpdate.ModifiedAt = Date.now();
  itemToUpdate.Title = item.title;
  itemToUpdate.CompletedAt = item.isCompleted === true ? Date.now() : undefined;

  const persisted = await mapper.put(itemToUpdate);
  return transformToModel(itemToUpdate);
};

exports.handler = async (event) => {
  if (event.requestContext.httpMethod === "POST") {
    const newItem = await createToDoItem(JSON.parse(event.body));
    return {
      statusCode: 200,
      body: JSON.stringify(newItem),
    };
  }

  if (event.requestContext.httpMethod === "PUT") {
    const id = event.pathParameters.id;
    const requestPayload = JSON.parse(event.body);
    const updatedItem = await updateToDoItem({ ...requestPayload, id });
    return {
      statusCode: 200,
      body: JSON.stringify(updatedItem),
    };
  }
  return {
    statusCode: 405,
    body: "Method not supported",
  };
};

您可以在我的github repo中找到一个完整的示例。

本地运行

现在,让我们在将其部署到生产之前,尝试在本地运行我们的应用程序。 SAM已经带有start-api命令,该命令将启动本地API Gateway实例路由请求到本地lambda Runtimes。但是,我们需要将数据持续到某个地方。最简单的解决方案是将我们的本地lambdas连接到在云中运行的DynamoDB(例如,如果您有一些复制生产的登台环境)。但是,就我们的示例而言,假设我们还没有任何环境设置,并尝试在本地运行内存DynamoDB数据库:

docker run -p 8000:8000 amazon/dynamodb-local

这是第一个挑战:SAM还使用Docker运行本地API网关和Lambda功能,而Docker容器是 not 不运行Dynamedodb本地流程内部容器( localhost ) - lambda函数中对http://localhost:8000的任何请求都会失败。

修复程序很简单 - 创建一个Docker网络并明确指定SAM和DynamoDB容器!

docker network create sam-demo-net
docker run -p 8000:8000 --network sam-demo-net --name ddblocal amazon/dynamodb-local
sam local start-api --env-vars json/env.json --docker-network sam-demo-net

现在,我们可以使用docker的服务发现功能和访问dynamodb本地端点使用容器名称(ddblocal):

const ddbClient = new DynamoDb({
  ...(process.env.AWS_SAM_LOCAL === "true"
    ? { endpoint: "http://ddblocal:8000" }
    : {}),
});

请参阅README file中的完整启动说明。

现在该测试了!

通过在提示下执行以下curl命令在表中插入一个todo项目:

curl -X POST -d '{"title":"test ToDo"}' http://127.0.0.1:3000/todos

{"id":"25962e09-7f16-4ab9-ac88-64f8c4a20710","title":"test ToDo","isCompleted":false}%    

通过在提示符处执行以下curl命令:

curl http://127.0.0.1:3000/todos 

[{"id":"25962e09-7f16-4ab9-ac88-64f8c4a20710","title":"test ToDo","isCompleted":false}]% 

最后,让我们完成我们的待办事项:

curl -X PUT -d '{"title":"test ToDo (completed)", "isCompleted": true}' http://127.0.0.1:3000/todos/25962e09-7f16-4ab9-ac88-64f8c4a20710

{"id":"25962e09-7f16-4ab9-ac88-64f8c4a20710","title":"test ToDo (completed)","isCompleted":true}% 

所有工作都很棒!让我们部署!

是时候推出了!

使用AWS SAM,部署与运行一个命令一样简单:

sam deploy --guided

--guided标志将启动一个向导,该向导将帮助您配置部署选项(AWS CloudFormation stack名称,AWS区域等)。第一次完成此向导后,您将被提议保存此部署设置并将其重新使用在即将到来的部署中。

由于此操作,SAM将其模板转换为与云形式兼容的格式,并在您在AWS SDK配置中指定的帐户中基于它创建堆栈。

最后的想法

看到这项服务将来会如何发展,这将是令人兴奋的。它会引入一种新方法,该方法将扩展基础架构作为代码(IAC)作为图表(IAD)的基础架构?至少,很高兴看到与SAM API(部署,软件包,管道bootstrap)深入集成,这将为用户提供一种直接从AWS Application Complice Composer Console提供AWS基础架构的本地方法。

最初于2023年1月4日在 https://thesametech.com 发表。

您也可以 follow me on Twitter subscribe to my Telegram channel connect on LinkedIn 以获取有关新帖子的通知!