软件即服务(SaaS)是一种灵活的软件分发模型,可以由少到一个人或多至上千人的组织来运作。云服务的问世让任何人都可以独立运行自己的 SaaS,并在此基础上建立免费增值模式的业务。
与其他类型的软件服务相比,它的系统设计相对简单。但是,由于没有合适的基准架构,如果我们在设计 SaaS 时没有认真思考,其结果可能会变得一团糟。我见过的一些例子中,SaaS 平台最终成为一个庞大却脆弱的单体 Web 应用程序,其中遍布冗杂的功能。
本文的目的是为你提供一种参考架构,为 SaaS 实现可伸缩性和可维护性。
将服务隔离到逻辑组件中的设计可以让我们的 SaaS 有更好的可扩展性。我们应该针对我们的架构作出改进,在规模扩展时打破常规:规模越大,架构却变得越容易管理。就算只有一位孤独的车间开发人员,他也可以很容易运行并管理多个服务,尤其是现在我们已经拥有了各种各样可以编排微服务的云服务,这种事情做起来就更简单了。
因为计划在 SaaS 平台上运行这些功能 Web 应用(feature Web app),所以我们应该先思考“我们可以重用什么?”这个问题。
隔离的 Web 应用程序是将相关功能分组在一起形成的一个 Web 应用程序,或是代表一款产品的一组 Web 应用程序。具体而言,我将其称为“产品 Web 应用”,后文将做更进一步的介绍。
例如,所有 " 分析报告功能 " 都可以组合在一起,成为一个独立的 Web 应用程序,该应用将由一个专门的团队来构建和维护,且这个团队在构建分析报告产品方面具有相关的领域知识。在大型公司中,每个产品 Web 应用都有自己专门的团队来构建和运行。
你的产品 Web 应用也可以分为两个部分:内部和公共部分。内部和公共部分之间明确区分开来后,你就能在自己的专用网络内路由一组专用的安全管理工具。
在内部和公共区域代理这些服务的工作由 " 路由服务 " 处理,具体将在后面的章节中介绍。
这些是不同的功能 Web 应用及其相应服务共享的通用功能。每种功能 Web 应用都需要利用下面列出的这些功能。
下面这些是构成我们 SaaS 的逻辑组件。
每个请求都需要路由到正确页面。当用户访问 yourWebsite.com/your-path 时,/your-path 需要将用户转发到其关联的请求页面或功能 Web 应用。路由服务可确保用户得到他们所请求的内容。
在本文的例子中,我们将需要路由服务(routing service)调用另一个服务以获取路径和 Web 应用程序 URL 映射,以及所需权限。
这些映射预计将在运行时更新。例如,在需要引入新页面或更改用户权限而无需发布的情况下更新。
路由服务还负责内部或公共的产品 Web 应用。可以将其部署到两个单独的运行实例中。
实现技巧:Node.js 的 http-proxy-middleware 或 Nginx 的 Reverseproxy 是两个可行的选择。如果你不需要在运行时从其他服务中以编程方式检索路径、URL 和权限,则后者是理想的选择。有关更多信息,请参见后文的 "Web 应用仓库 " 章节。
每个产品 Web 应用都通过一个可在内部访问的 URL 公开,例如 https://my-product-app-1.mydomain.local。其中一些由路由服务路由,以供订阅的用户访问,而另一些仍在内部访问。
在后台,这可以是单个或一组带有 Web 前端的微服务。这些 Web 应用彼此独立,有自己独立的代码库、支持服务、部署管道,并且理想情况下是由不同的团队所有。在 SaaS 平台中,它们的分组方式与其所有团队的结构保持一致。
这样以来我们就能将大型 Web 项目分解为许多可管理的单元。可以将这种模式想象成是将大块牛排切成许多小块。一个产品 Web 应用包含一组不应与其他 Web 应用重叠的相关功能。
实现技巧:产品 Web 应用可以是任何 Web 应用程序,例如常见的 Express、Node.js 和 React 堆栈。将设计系统与可复用的微前端(例如 Mosaic 或 Open Components)搭配使用,可以让你的产品拥有一致的外观和体验。
这个服务负责保存多个团队拥有的 Web 应用程序的记录。路由服务调用 Web 应用存储库,以获取与所请求路径对应的 Web 应用 URL(匹配某个条目)。
下面是一个 Web 应用存储库表示例,用来说明它的功能。
基于角色的访问控制(RBAC)服务将提供权限信息,以帮助我们检查登录用户是否有权访问产品 Web 应用。
如果要维护的产品 Web 应用条目数量很少,这里就可以用一个简单的路由和硬编码哈希映射的代理。
实现技巧:这既可以是具有自己的 Web 管理 UI 的 RESTful 服务,也可以是简单的基础架构,比如说是位于路由服务中,或者来自对象存储(如 AWSS3)的代码配置文件。具体的实现方式将取决于所需的规模和可配置性。如果你要继续考虑服务实现,则可以将 Java、Go 或 C#与 RDBMS 或 NoSQL 数据存储搭配使用。
对于这个服务来说,验证过程是非常重要的,如果路径或 Web 应用 URL 的配置不正确,则可能会影响你服务的可用性。如果无法确定要支持的产品数量,请使用进化原型方法。你不必在开发周期的早期就构建这个服务,因为一开始只用一个简单的配置文件就够了。
应该有一个跨服务共享的专用身份验证服务。这里 JSON Web 令牌就可以派上用场,因为我们能轻松地通过标头跨多个服务传递令牌和其他有用的用户信息。
可以从路由服务或产品 Web 应用中调用身份验证服务。如果从产品 Web 应用调用身份验证服务,就能让产品 Web 应用所有者灵活地对他们想要保护的任何页面实施身份验证。这种灵活性让服务所有者可以管理公共页面,例如用户注册页。这种方法还减少了在路由服务中执行的逻辑。
或者也可以在路由服务中处理身份验证过程。从安全角度来看,这使身份验证处理更容易审核,因为我们无需查看每一个产品 Web 应用页面。这也意味着,如果需要让一个网页开启公开访问权限,则应该采用某种形式的白名单。这是另一个可配置性折衷。
实现技巧:AWSCognito、Auth0 或任意 JSON Web 令牌(JWT)身份验证服务。
验证用户身份后,我们需要能够回答以下问题:
可能的答案有:
可能的答案有:
RBAC 帮助我们回答上述问题,从而让我们的 Web 应用可以允许或撤消用户执行的特定动作。
下图显示了对用户进行身份验证并使用 RBAC 检查其权限时发生的事件流。
下面的伪代码说明了如何在特定用例中使用 RBAC 服务。
检查用户是否具有查看页面的权限。
复制代码
if(userHasPermissionToViewPage(userId)) { showPage(); }else{ showNoPermissionError(); }
检查用户是否有权执行某个动作。
复制代码
if(userHasPermissionToPerformAction(userId)) { showButton(); }else{ doNothing(); }
实现提示:与其他服务相比,这个服务的通信量预计会更高,具体取决于你的权限会有多少粒度。你可以考虑使用和 Web 应用存储库实现类似的选择:Java、Go 或 C#,搭配 RDBMS 数据存储和一个缓存系统。用户权限管理需要一个 Web 管理 UI。
下图显示了运行时中组件之间的交互方式。
这种解决方案的好处包括:明确所有权、重用通用功能以及关注点分离。
下图显示了路由服务将用户路由到请求页面的序列。从 Web 应用存储库中检索到产品 Web 应用详细信息之后,便会发生这些序列。
在产品 Web 应用中调用身份验证。
在路由服务中调用身份验证。
Web 应用存储库、RBAC 和身份验证服务需要它们自己的 Web UI 来管理数据或接收用户输入。Web 应用存储库、RBAC 和身份验证服务均有它们自己的 Web 应用。使用另一个具有管理 UI 的 Web 应用可以管理对某个 Web 应用的访问权限。
Web 应用存储库 UI 允许我们使用所需的权限来管理路径和 Web 应用 URL。
RBAC UI 用于管理用户、组织、角色和权限。
身份验证服务的前端 UI(即登录页面)也被部署为 Web 应用。
下面总结了一些应该牢记的准则,可以帮助你成功运营 SaaS 平台。
关键在于要让团队能通过良好的文档管理产品 Web 应用,同时充分利用可用的工具。好的文档可以最大程度地减少你的团队重复询问 SaaS 平台相关问题的需求。
你可以在用于 RBAC 和 Web 应用存储库的 Web UI 管理工具之上,再开发一个 NodeJS/React 模板,其中包括开箱即用的 RBAC 和身份验证的客户端,以尽量减少将 Web 应用添加到 SaaS 平台所需的工作。
这样你的工程师或你自己就可以方便地从这个模板创建新的 Web 应用,而无需花费很多精力将其集成到平台中。
糟糕的代码可能会浪费数天甚至数周的开发时间,但相比之下,糟糕的架构决策可能会浪费数月至数年的时间。对于大型公司而言,工程设计审查可能更容易推进;但如果你自己是一名开发人员,那么也可以从社区中认识的其他工程师那里获得反馈。
花费合理的时间预先编写工程设计文档,并在工程设计审查期间获得反馈,可以为你节省很多时间。应该验证软件设计假设,并让其他工程师为你设计的软件架构找出尽可能多的漏洞。
英文原文:
Architecting a Scalable Software as a Service