”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 适用于 AWS 云的简单 SaaS 的技术堆栈

适用于 AWS 云的简单 SaaS 的技术堆栈

发布于2024-11-08
浏览:555

The Tech Stack of a Simple SaaS for AWS Cloud

介绍


注1:这里是托管的交互式演示:demo.saasconstruct.com

注 2:我每个 SaaS 设置的每月账单为 3-5 美元,其中大部分是 CI/CD 成本。

注3:模板在这里:saasconstruct.com。


我在 AWS 上完成了多个 AI PoC 和 MVP,而且总是类似的事情

  • 在某处托管前端
  • 调用后端
  • 后端从二进制存储/数据库获取/更新数据
  • 后端执行一些AI逻辑或调用另一个服务并返回结果
  • 有两个独立的 AWS 账户:dev 和 prod
  • 用于部署的 CI/CD
  • 云资源声明的基础设施即代码

因此,我想构建一个简单的解决方案来在 AWS 上引导此类内容。并写一篇关于它的博客文章。

我决定添加一些功能,例如 Stripe 付款(如果您不想担心销售税/增值税,则可以添加 LemonSqueezy 付款)以及付款管理、身份验证、交通警报等。我还认为它需要是可配置的,例如用 ELB 和 ECS 替换 API Gateway 和 AWS Lambda 来执行较长的任务。

前端

我选择了普遍认为最简单的框架作为开始。它是 Vue,据我所知,它是第二受欢迎的框架。我选择它不仅是因为它是最简单的,而且我也有一些使用它的经验。

该网站是一个标准的SPA应用程序,以Vite为构建工具。对于样式设计,我使用 Bootstrap,因为它也非常易于使用,而且还因为从一个版本的前端框架迁移到另一个版本时它不会造成很多痛苦。

前端托管

有两个选项:

  • S3 和 CloudFront (CDN)
  • AWS Amplify Hosting,它是 S3 和 CloudFront 的包装器,易于使用,但可配置性较差。例如,您无法使用 CloudFront 分发执行任何操作,因为它不可见。除了使用重定向之外,您也无法对您的应用程序进行地理阻止。

我选择Amplify Hosting作为AWS在前端托管解决方案中的主要焦点,因为它很容易设置、附加域等。

由于是按量付费,我设置了流量警报:如果每10秒的点击次数超过一定数量,我会收到通知。

后端

后端是API Gateway,它执行速率限制,以及AWS Lambda (Python),它执行业务和一般逻辑:

  • 检查用户是否通过身份验证
  • 处理付款和管理订阅(客户门户)
  • 发送电子邮件
  • ETC。

我还有另一个 AWS Lambda 函数,可以在 Cognito 中注册后在数据库中创建用户。

有共享实用程序,我在其中放置了一些共享功能,例如电子邮件。此外,还有记录功能,例如,如果出现付款错误,则会向我发送电子邮件。

验证

我知道身份验证很痛苦,而且我不想使用第三方服务。所以我继续使用 AWS Cognito。还蛮便宜的。

您可以说,只需使用AWS Amplify Auth(这是 AWS Cognito 的包装器),但我遇到了一些问题。我什至在 Reddit 上写了一篇文章:

我的 Amplify 身份验证问题列表

还有另一篇文章,其中包含来自一些沮丧的用户的更大列表(尽管这是一篇旧文章)。

这里

此外,如果您只使用 Amplify,您就会被整个生态系统困住,没有机会做出改变。例如,如果您想访问 CloudFront 发行版(例如,当您想对某些区域进行地理封锁时),那么运气不好,您无法通过 Amplify Hosting 看到它。我还有其他问题:其中一个例子是从 Amplify 资源创建 CDK,这对我来说是一个痛点。

因此,我采用了一种混合方法(根据 Reddit 的说法,这种方法有些流行):AWS Amplify JS 库 允许您导入自己创建的云资源,例如用户池,因此我创建了它们使用 CDK,然后仅使用 Amplify JS 库进行身份验证。

在这种情况下,我可以随时更改我想要的任何内容,交换云资源(例如,如果我需要访问 CloudFront 发行版,我可以从 Amplify Hosting 转到 CloudFront S3)。

电子邮件

AWS SES。它是主要的 AWS 电子邮件服务。它会发送所有内容,包括 Cognito 身份验证电子邮件、联系表单中的请求等。您唯一需要了解的是,在您的开发 AWS 帐户中,您需要首先创建经过验证的身份才能发送(我通过IaC),并且在生产 AWS 账户中,您需要请求生产访问权限(只需单击几下)。

使用 AWS SES,在以下场景中发送电子邮件通知:

  • 发生付款错误时。
  • 如果网络流量出现峰值。
  • 如果 CI/CD 部署失败。
  • 对于其他情况,例如身份验证电子邮件和联系表单的查询等

贮存

DynamoDB 作为数据库。简单、快速且易于管理。是的,我必须考虑访问模式,但一般来说,它很好用,而且在验证/构建时也不会花费我任何费用。由于我计划开发多个产品并希望将它们隔离,因此我无法将 RDS/DocumentDB 放入每个项目的开发和生产帐户中(成本太高)。

付款方式

我添加了两个支付系统,可以选择使用哪一个,因为它们的工作原理相似:

  • Stripe 很流行且易于集成,简单明了。当用户购买产品时,我使用 Stripe 结帐,而为了管理订阅,我使用 Stripe 客户门户。
  • LemonSqueezy 与 Stripe 非常相似,但它也是记录商家,这意味着它为您处理销售税/增值税。它还具有用于购买订阅的结帐和用于管理订阅的客户门户。

我为 Stripe/LemonSqueezy webhooks 编写了端点,它们处理所有逻辑。

基础设施即代码

所以有很多东西可供选择:

  • 类似 Terraform 或 OpenTofu(基于 Terraform 的完全开源替代方案)
  • 普鲁米
  • CDK
  • 云形成

我选择了AWS CDK,这是我的原因:

  • 使用起来很容易
  • 流行且足够成熟
  • 我认为它比 AWS CloudFormation 好得多
  • 这是一个AWS库,我使用AWS
  • 我可以用 Python、TypeScript 或其他语言编写它。由于我在后端使用Python,在前端使用TypeScript,所以这是一个不错的选择。

我没有选择Terraform的原因是CDK更容易;至少在我看来,它允许以简单的方式创建资源。我喜欢 OOP,并尝试相应地构建我的云基础设施。一个很大的好处是包含了 CI/CD(CDK 管道),所以我不必发明它。

持续集成/持续交付

我选择了CDK pipelines,因为它同样很简单。只需将管道连接到 GitHub 存储库,就可以开始了。 Git 推送到开发分支 -> 它将被推送到开发帐户。 Git 推送到主(或拉取请求)-> 生产部署。

警报和速率限制

我设置了速率限制以防止通过 API 网关收到垃圾邮件。我设置了两个 CloudWatch 警报:

  • 当托管网站收到大量请求时提醒我。
  • 当 API 网关收到大量请求时提醒我。

我还设置了计费警报,以通知我是否要花费太多。

记录

CloudWatch 记录事件,您可以在 AWS 控制台中查看它们,也可以通过扩展直接在 IDE 中查看它们。

人工智能

选择是使用 OpenAI(使用 GPT 模型)或 AWS Bedrock(使用 Claude 模型)。这一决定具有挑战性,因为虽然 AWS Bedrock with Claude 可以轻松地与 AWS 集成,但 OpenAI 更常用。两家公司都提供顶级人工智能模型。目前,我选择坚持使用 AWS Bedrock。这将来可能会改变,但就目前而言,我很欣赏这种简单性。对于矢量数据库,我使用 Pinecone,它具有无服务器索引。

我在这里构建的人工智能应用程序的一个示例是RAG系统,它本质上是一个聊天机器人,可以根据您的数据回答问题。您将信息存储在向量数据库中,并在查询时进行相似性搜索,然后使用 LLM 根据该搜索结果给出答案。我目前使用简单的模型来避免成本,但切换到不同的模型就像更改一行代码一样简单。

编程语言

我最初是一名 Java 开发人员,但后来因为开发机器学习和深度学习服务而成为一名 Python 开发人员。该领域的大多数库都是用 Python 开发的或具有 Python 包装器。此外,Python 与 AWS 无缝集成,无论是在 AWS Lambda(例如,使用 AWS Lambda Powertools 库)还是在 CDK 中。所以最终,后端和云基础设施(通过 CDK)都是用 Python 实现的。

我的第二语言是 TypeScript,因为它在前端框架中很受欢迎。虽然我曾经使用 JavaScript,但随着代码库变得越来越大,我发现类型的缺失令人困惑。 TypeScript 的静态类型在开发过程中提供了急需的清晰度和安全性,尤其是在大型项目中。

AWS 账单

由于我的流量负载不高,因此我的 AWS 成本非常低,通常 每月 3-5 美元,主要是由于 CI/CD 费用。

该设置包括 CDN(由 Amplify Hosting 提供)和 AWS Lambda 内的小型缓存层。此外,某些服务属于 AWS 免费套餐,这进一步降低了我的成本。

随着产品规模扩大并获得更多用户,我可能需要通过切换到配置的 DynamoDB 并实施 DAX(DynamoDB 加速器) 来优化资源。然而,就目前而言,此设置运行良好。

结论

这个解决方案有效地满足了我当前的需求。

我已将整个技术堆栈作为 样板文件(我积极开发和更新)包含在我的 SaaSConstruct 上的 AWS 模板中。

我将继续探索可以合并到此设置中的其他功能,以增强其功能...

版本声明 本文转载于:https://dev.to/server_kota/the-tech-stack-of-a-simple-saas-for-aws-cloud-4lhm?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式界面中实现垂直滚动元素的CSS高度限制 考虑一个布局,其中我们具有与可滚动的映射div一起移动的subollable map div用户的垂直滚动,同时保持其与固定侧边栏的对齐方式。但是,地图的滚动无限期扩展,超过了视口的高度,阻止用户访问页面页脚。 可以限制地图的滚动,我们可以利用CSS...
    编程 发布于2025-02-19
  • 如何在Java字符串中有效替换多个子字符串?
    如何在Java字符串中有效替换多个子字符串?
    Exploiting Regular ExpressionsA more efficient solution involves leveraging regular expressions.正则表达式允许您定义复杂的搜索模式并在单个操作中执行文本转换。示例使用接下来,您可以使用匹配器查找令牌的所...
    编程 发布于2025-02-19
  • 如何修复\“常规错误:2006 MySQL Server在插入数据时已经消失\”?
    如何修复\“常规错误:2006 MySQL Server在插入数据时已经消失\”?
    How to Resolve "General error: 2006 MySQL server has gone away" While Inserting RecordsIntroduction: connect to to to Database connect to t...
    编程 发布于2025-02-19
  • 如何克服PHP的功能重新定义限制?
    如何克服PHP的功能重新定义限制?
    克服PHP的函数重新定义限制在PHP中,多次定义一个相同名称的函数是一个no-no。尝试这样做,如提供的代码段所示,将导致可怕的“不能重新列出”错误。 //错误:“ cance redeclare foo()” 但是,PHP工具腰带中有一个隐藏的宝石:runkit扩展。它使您能够灵活地重新定义...
    编程 发布于2025-02-19
  • PHP阵列键值异常:了解07和08的好奇情况
    PHP阵列键值异常:了解07和08的好奇情况
    PHP数组键值问题,使用07&08 在给定数月的数组中,键值07和08呈现令人困惑的行为时,就会出现一个不寻常的问题。运行print_r($月份)返回意外结果:键“ 07”丢失,而键“ 08”分配给了9月的值。此问题源于PHP对领先零的解释。当一个数字带有0(例如07或08)的前缀时,PHP将...
    编程 发布于2025-02-19
  • 如何使用PHP将斑点(图像)正确插入MySQL?
    如何使用PHP将斑点(图像)正确插入MySQL?
    在尝试将image存储在mysql数据库中时,您可能会遇到一个可能会遇到问题。本指南将提供成功存储您的图像数据的解决方案。 essue values('$ this-> image_id','file_get_contents($ tmp_image)&#...
    编程 发布于2025-02-19
  • 如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    [2最后一行与数据集中的每个不同标识符关联。考虑以下数据: 1 2014-02-01 kjkj 1 2014-03-11 ajskj 3 2014-02-01 sfdg 3 2014-06-12 fdsa 为了检索数据集中每个唯一ID的最后一行信息,您可以在操作员上使用Postgres的有效效...
    编程 发布于2025-02-19
  • 如何可靠地检查MySQL表中的列存在?
    如何可靠地检查MySQL表中的列存在?
    在mySQL中确定列中的列存在,验证表中的列存在与与之相比有点困惑其他数据库系统。常用的方法:如果存在(从信息_schema.columns select * * where table_name ='prefix_topic'和column_name =&...
    编程 发布于2025-02-19
  • 版本5.6.5之前,使用current_timestamp与时间戳列的current_timestamp与时间戳列有什么限制?
    版本5.6.5之前,使用current_timestamp与时间戳列的current_timestamp与时间戳列有什么限制?
    在默认值中使用current_timestamp或mysql版本中的current_timestamp或在5.6.5 这种限制源于遗产实现的关注,这些限制需要为Current_timestamp功能提供特定的实现。消息和相关问题 current_timestamp值: 创建表`foo`( `...
    编程 发布于2025-02-19
  • 为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    [2明确担心Microsoft Visual C(MSVC)在正确实现两相模板实例化方面努力努力。该机制的哪些具体方面无法按预期运行?背景:说明:的初始Syntax检查在范围中受到限制。它未能检查是否存在声明名称的存在,导致名称缺乏正确的声明时会导致编译问题。为了说明这一点,请考虑以下示例:一个符合...
    编程 发布于2025-02-19
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在java中的多个返回类型:一个误解介绍,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但是,情况确实如此吗?通用方法:拆开神秘 [方法仅具有单一的返回类型。相反,它采用机制,如钻石符号“ ”。分解方法签名: :本节定义了一个通用类型参数,E。它表示该方法接受扩展FOO类的任何...
    编程 发布于2025-02-19
  • 为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    网格超过身体,用100%grid-template-columns 问题:考虑以下CSS和HTML: position:fixed; grid-template-columns:40%60%; grid-gap:5px; 背景:#eee; 当位置未固定时,网格将正确显示。但是,当...
    编程 发布于2025-02-19
  • 在没有密码提示的情况下,如何在Ubuntu上安装MySQL?
    在没有密码提示的情况下,如何在Ubuntu上安装MySQL?
    在ubuntu 使用debconf-set-selections 在安装过程中避免密码提示mysql root用户。这需要以下步骤: sudo debconf-set-selections
    编程 发布于2025-02-19
  • 如何使用组在MySQL中旋转数据?
    如何使用组在MySQL中旋转数据?
    在关系数据库中使用mysql组使用mysql组来调整查询结果。在这里,我们面对一个共同的挑战:使用组的组将数据从基于行的基于列的基于列的转换。通过子句以及条件汇总函数,例如总和或情况。让我们考虑以下查询: select d.data_timestamp, sum(data_id = 1 tata...
    编程 发布于2025-02-19
  • \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    答案:在大多数现代编译器中,while(1)和(1)和(;;)之间没有性能差异。 说明: perl: S-> 7 8 unstack v-> 4 -e语法ok 在GCC中,两者都循环到相同的汇编代码中,如下所示:。 globl t_时 t_时: .l2: movl $ .lc0,�i ...
    编程 发布于2025-02-19

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3