发布于 2026-01-06 1 阅读
0

AWS Serverless 入门 - SNS AWS 安全直播!

AWS Serverless 入门 - SNS

AWS 安全直播!

太长不看

在本系列文章中,我将尝试讲解 AWS 上无服务器架构的基础知识,帮助您构建自己的无服务器应用程序。在上一篇文章中,我们学习了如何使用 Aurora Serverless 在 AWS 上部署 SQL 数据库并与之交互。在本文中,我们将探讨 SNS 相关主题,它可以让您在应用程序中创建发布/订阅模式!

我们今天要做什么?

本文将创建一个 SNS 主题,并使用它来向多个 Lambda 函数发送通知。我们将了解如何利用此功能,根据上下文向应用程序的特定部分发送定向通知,以及如何使用它来实现代码解耦。

⬇️ 我会定期发布无服务器内容,如果您想了解更多 ⬇️

关注我的推特🚀

快速公告:我还在开发一个名为🛡 sls-mentor 🛡的库。它汇集了 30 条无服务器最佳实践,可以自动检查您的 AWS 无服务器项目(无论使用什么框架)。它是免费开源的,欢迎体验!

在 GitHub 上找到 sls-mentor ⭐️

亚马逊简单通知服务(SNS)

什么是SNS?

Amazon SNS 是一种无服务器的发布/订阅服务。它允许您创建主题。主题包含生产者和消费者。生产者可以向主题发布消息,消费者可以订阅主题以接收消息。当消息发布到主题时,每个消费者最终都会收到它,这称为扇出模式。

这是一种非常强大的模式,因为它允许生产者和消费者解耦。生产者无需知道谁会消费他们的消息,消费者也无需知道谁会生产他们的消息。一个操作可以触发多个消费者。

SNS 还允许您创建主题过滤器,以便用户可以订阅特定子集的消息。这有助于创建定向通知,并进一步解耦您的应用程序。例如,RequestDelivery Lambda 函数可以只关注与交付请求相关的消息,而不关注与支付请求相关的消息。

让我们构建一个简单的应用程序来演示社交网络服务 (SNS)!

今天,我们将构建一个非常简单的应用程序:

  • Lambda函数OrderItem由 REST API 上的 POST 请求触发。用户可以指定是否接收通知,以及是否请求配送其订单。
  • Lambda函数OrderItem会将订单详情发布到 SNS 主题。
  • 下游,SNS 主题将触发三个 lambda 函数:

    • 一个ExecuteOrderLambda 函数,用于模拟支付。
    • Lambda函数RequestDelivery仅在用户请求时才会模拟送货。
    • Lambda函数Notification,仅在用户请求时才会向用户发送通知。

我们的应用程序架构如下所示:

建筑学

每个操作都将通过简单的模拟来呈现console.log,但结合本系列课程的知识,您可以轻松地将它们替换为 DynamoDB、S3、SES 或任何其他 AWS 服务上的真实操作!

创建 SNS 主题

为了构建这个应用,我们将使用 AWS CDK。如果您还不熟悉它,我建议您先阅读我的系列文章的第一篇,其中有详细的介绍。我们将创建一个新的 CDK 项目,将@aws-cdk/aws-sns所需的软件包添加到项目中,并在堆栈中创建一个新的 SNS 主题,以及一个 API 网关来触发我们的 OrderItem Lambda 函数。

import * as cdk from 'aws-cdk-lib';

import path from 'path';

export class ArticleSNS extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const topic = new cdk.aws_sns.Topic(this, 'topic');

    const api = new cdk.aws_apigateway.RestApi(this, 'api', {});

    const orderItem = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'OrderItem', {
      entry: path.join(__dirname, 'orderItem', 'handler.ts'),
      handler: 'handler',
      environment: {
        TOPIC_ARN: topic.topicArn,
      },
    });
    topic.grantPublish(orderItem);
    api.root.addResource('orderItem').addMethod('POST', new cdk.aws_apigateway.LambdaIntegration(orderItem));
  }
}
Enter fullscreen mode Exit fullscreen mode

请注意,我们已授予Lambdapublish函数权限OrderItem,使其能够向该主题发布消息。我们还将主题 ARN 作为环境变量传递给 Lambda 函数,以便其使用该 ARN 发布消息。

将 Lambda 订阅到 SNS 主题

现在,我们来创建 3 个下游 Lambda 函数,并让它们订阅该主题。我们将在该主题上实现过滤器,以便每个 Lambda 函数只接收它感兴趣的消息。

// ... previous code

const executeOrder = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'ExecuteOrder', {
  entry: path.join(__dirname, 'executeOrder', 'handler.ts'),
  handler: 'handler',
});
topic.addSubscription(new cdk.aws_sns_subscriptions.LambdaSubscription(executeOrder));

const requestDelivery = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'RequestDelivery', {
  entry: path.join(__dirname, 'requestDelivery', 'handler.ts'),
  handler: 'handler',
});
topic.addSubscription(
  new cdk.aws_sns_subscriptions.LambdaSubscription(requestDelivery, {
    filterPolicy: {
      // Only triggers when the "requestDelivery" attribute is set to "true"
      requestDelivery: cdk.aws_sns.SubscriptionFilter.stringFilter({ allowlist: ['true'] }),
    },
  }),
);

const sendNotification = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'SendNotification', {
  entry: path.join(__dirname, 'sendNotification', 'handler.ts'),
  handler: 'handler',
});
topic.addSubscription(
  new cdk.aws_sns_subscriptions.LambdaSubscription(sendNotification, {
    filterPolicy: {
      // Only triggers when the "sendNotification" attribute is set to "true"
      sendNotification: cdk.aws_sns.SubscriptionFilter.stringFilter({ allowlist: ['true'] }),
    },
  }),
);
Enter fullscreen mode Exit fullscreen mode

你看,这并不复杂。通过 filterPolicy 参数,我们可以指定哪些消息应该触发 Lambda 函数。在本例中,我们希望仅当属性requestDelivery设置为 true时才触发 RequestDelivery Lambda 函数true,仅当sendNotification属性设置为 false时才触发 SendNotification Lambda 函数true

向 SNS 主题发布消息

最后,到了最有趣的部分:编写 Lambda 函数的代码!让我们从OrderItemLambda 函数开始,它将向主题发布消息。

// orderItem/handler.ts
import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';

const client = new SNSClient({});

export const handler = async (event: { body: string }): Promise<{ statusCode: number; body: string }> => {
  const topicArn = process.env.TOPIC_ARN;

  if (topicArn === undefined) {
    throw new Error('TOPIC_ARN is undefined');
  }

  const { requestDelivery, sendNotification, item, quantity } = JSON.parse(event.body) as {
    requestDelivery?: boolean;
    sendNotification?: boolean;
    item?: string;
    quantity?: number;
  };

  if (requestDelivery === undefined || sendNotification === undefined || item === undefined || quantity === undefined) {
    return {
      statusCode: 400,
      body: 'Bad request',
    };
  }

  await client.send(
    new PublishCommand({
      Message: JSON.stringify({ item, quantity }),
      TopicArn: topicArn,
      MessageAttributes: {
        sendNotification: {
          DataType: 'String',
          StringValue: sendNotification.toString(),
        },
        requestDelivery: {
          DataType: 'String',
          StringValue: requestDelivery.toString(),
        },
      },
    }),
  );

  return {
    statusCode: 200,
    body: 'Item ordered',
  };
};
Enter fullscreen mode Exit fullscreen mode

这段代码做了三件事:

  • 它从环境变量中获取主题 ARN。
  • 解析请求体,并提取requestDeliverysendNotification属性。itemquantity
  • 它向该主题发布了一条消息:
    • 消息正文包含业务数据,item以及quantity
    • 属性 ` requestDeliveryand` 的值根据请求的不同而sendNotification设置为true`or`false或 `or`。这些属性将由我们之前定义的过滤器使用。

对于三个下游 Lambda 函数,我们将使用 console.log 在 AWS 控制台中查看它们是否被触发。

// executeOrder/handler.ts
export const handler = async (event: {
  Records: {
    Sns: {
      Message: string;
    };
  }[];
}): Promise<void> => {
  event.Records.forEach(({ Sns: { Message } }) => {
    const { item, quantity } = JSON.parse(Message) as { item: string; quantity: number };

    console.log(`ORDER EXECUTED - Item: ${item}, Quantity: ${quantity}`);
  });
};
Enter fullscreen mode Exit fullscreen mode

在这个 Lambda 函数中,我记录了从主题接收到的消息内容。有趣的是事件参数的类型:它是一个记录数组。这是因为 SNS 可以一次发送多条消息,所以我们需要处理这种情况。

// requestDelivery/handler.ts
export const handler = (): Promise<void> => {
  console.log('DELIVERY REQUESTED');
};
Enter fullscreen mode Exit fullscreen mode
// sendNotification/handler.ts
export const handler = (): Promise<void> => {
  console.log('NOTIFICATION SENT');
};
Enter fullscreen mode Exit fullscreen mode

正如我之前所说,如果你从一开始就跟着这个系列学习,你应该能够用实际操作替换这些 console.log 语句,例如更新 DynamoDB 或 SQL 数据库、使用 SES 发送电子邮件等等!

是时候测试一下了!

首先,让我们借助 AWS CDK 部署该应用程序:

npm run cdk deploy
Enter fullscreen mode Exit fullscreen mode

接下来,让我们用 Postman 发送 API 调用来测试一下:

在第一次调用中,我将sendNotification`and`设置requestDelivery为 `false`。ExecuteOrder正如预期,只有 Lambda 函数被触发。我们可以在 CloudWatch 日志中看到消息内容。

邮差

CloudWatch

在第二次调用中,我将其设置sendNotification为 true。Lambda 函数ExecuteOrderSendNotification触发。我们可以在 CloudWatch 中看到日志SendNotification

邮差

CloudWatch

在第三次调用中,我将其设置requestDelivery为 true。Lambda 函数ExecuteOrderRequestDelivery触发。我们可以在 CloudWatch 中看到日志RequestDelivery

邮差

CloudWatch

结论

这篇教程只是对社交网络服务 (SNS) 的一个非常浅显的介绍。我演示了如何创建主题、订阅 Lambda 函数以及向主题发布消息。我没有编写任何实际的副作用代码,但你应该能够运用本系列前几篇文章中学到的知识自行完成。

社交网络服务(SNS)还允许向移动应用发送电子邮件、短信或推送通知,但本文并未涉及这方面内容。未来我可能会专门撰写一篇文章来介绍它!

我计划继续以双月刊的形式更新这个系列文章。我已经介绍了如何创建简单的 Lambda 函数和 REST API,以及如何与 DynamoDB 数据库和 S3 存储桶进行交互。您可以在我的代码仓库中关注更新进度!接下来,我将介绍前端部署、类型安全、更高级的模式等新主题……如果您有任何建议,欢迎随时联系我!

如果您能点赞并分享这篇文章给您的朋友和同事,我将不胜感激。这对我扩大读者群非常有帮助。另外,别忘了订阅,以便在下一篇文章发布时收到通知!

如果你想和我保持联系,这是我的推特账号。我经常发布或转发一些关于AWS和无服务器架构的有趣内容,欢迎关注我!

关注我的推特🚀

文章来源:https://dev.to/slsbytheodo/learn-serverless-on-aws-step-by-step-sns-2b46