写点什么

Salesforce 构建可扩展 API 的旅程

  • 2022-09-13
    北京
  • 本文字数:3881 字

    阅读完需:约 13 分钟

Salesforce 构建可扩展 API 的旅程

API 对于组织来讲正变得越来越重要,但是,构建安全、可扩展的 API 并非易事。本文从执行环境、API 技术、安全性等角度出发,介绍了如何构建高效、可扩展的 API。


本文最初发表于 Salesforce 站点,经作者 Nitesh Kumar 授权,由 InfoQ 中文站翻译分享。


API 是一个重要的工具,允许合作伙伴、开发人员和其他应用消费我们提供的微服务,与之进行通信,并基于此构建各种各样的功能。


高质量的 API 要能够随着业务生态系统的发展而扩展,构建这样的 API 并不是一件容易的事情,需要对所有的事情进行通盘思考和规划,涉及到选择哪种执行环境,甚至要决定该使用哪种 API 技术。


那么,我们是如何实现的呢?在本文中,我将会分析在 Salesforce 为 Activity Platform 构建 API 的经验,它可以作为你自己编写 API 的一个指南。Activity Platform 是一个大数据处理引擎,每天会摄取和分析超过 1 亿次的客户互动,以自动捕获数据并产生分析、推荐和 feed。Activity Platform 提供了 API 来为我们的客户交付这些功能。


选择执行环境


根据需求不同,执行环境可以是裸机、虚拟机(VM)或者应用容器。我们选择了使用应用容器,因为它可以在物理机或 VM 上运行,一个操作系统实例能够支持多个容器,每个容器都在自己独立的执行环境中运行。简而言之,容器是轻量级、可移植、快捷的,并且易于部署和扩展,所以它们天然适合微服务。



关于容器编排


如果你像我们这样决定使用容器,容器编排能够帮助你实现自动化部署,管理容器、扩展以及网络。在这方面,有很多可选的容器编排工具,比如 Kubernetes、Apache Mesos、DC/OS(with Marathon)、Amazon EKS、Google Kubernetes Engine(GKE)等。


我们使用的是 Hashicorp 的 Nomad 集群。它非常简单、轻量级,并且能够编排任何类型的应用,而不仅仅是容器。它能够无缝与 Consul 和 Vault 集成,实现服务发现和 secret 管理。我们可以很容易地将需求描述为一个待执行的任务(task),比如内存、网络、CPU,以及我们水平扩展服务所需的实例数量。



选择 API 技术


为了构建 API,我们选择了使用 GraphQL。如果你没有听说过它的话,它是其他可选技术(如 REST、SOAP、Apache Thrift、OpenAPI/Swagger 或 gRPC)的一个替代方案。


我们为什么选择 GraphQL


我们想要构建的 API 能够服务于多种客户端,涵盖 Web 和移动应用。它需要具备高效、强大和灵活的特点。


鉴于以下的原因,GraphQL 是最合适的方案:


  • GraphQL 是数据库无关的技术,能够从任何地方为我们预先定义的业务领域提供数据。这意味着为了满足一个查询,底层可以使用 Cassandra、Elasticsearch 或其他模块的现有 API。



  • 它允许客户端精确请求想要的数据,避免过量加载(overfetching)或加载不足(underfetching)。如果 API 返回的数据超出了客户端的需求,这会导致性能问题,如果返回的数据比预期要少,那么会进行多次网络调用,从而减缓渲染时间。GraphQL 能够避免这两种情况。

  • 尽管大多数的 API 都实现了版本管理,但是 GraphQL 是一个无版本化的 API。因为它只会返回明确请求的数据,所以我们可以通过添加新的类型以及类型上的新字段来增加功能,避免带来破坏性的变更。

  • GraphQL 使用强类型系统,所有的类型都是使用 Graph SDL 以模式(schema)的方式进行定义的。它可以作为客户端和服务器的契约,避免请求 / 响应结构的混淆。

  • GraphLQ 支持内省(introspection),所以模式定义可以通过各种工具进行共享和下载,如 GraphiQL、GraphQL-playground 或 cli 工具。


GraphQL 实战


我们在 Classification Insight API 中使用了 GraphQL。Classification Insight 提供了用户的信息,并且能够帮助会议的参加者了解其他参会人员的头衔和角色。我们使用 Kotlin 和 graphql-java(GraphQL 的一个 Java 实现)实现该 API。


第一步:定义模式(如 schema.graphqls)。每个 GraphQL 服务会定义一组类型。GraphQL 模式中最基本的组件是对象类型,它代表了一种我们可以从服务中获取的对象。


在如下的模式中,我定义了一个名为“getClassificationInsightsByUser”的查询,在后面的内容中,我们可以通过发送如下的载荷到 API 来调用查询:{ getClassificationInsightsByUser(emailAddresses: [“test1@gmail.com”, “test2@gmail.com”]) { userId, title } }


schema.graphqls


# 描述我们能够获取什么内容的对象类型type ClassificationInsightByUser {  organizationId: ID!  userId: String!  emailAddress: String!    title: String!}# 定义所有查询的Query类型type Query {  getClassificationInsightsByUser(    emailAddresses: [String!]!    ): [ClassificationInsightByUser]}
schema { query: Query}

复制代码


第二步:实现 Datafetcher(也被称为解析器)来解析 getClassificationInsightsByUser 字段。简单来讲,解析器就是由开发人员提供的一个函数,用来解析模式中定义的每个字段并从配置的资源(如数据库、其他 API 或缓存等)中返回值。


在本例中,我们的 Query 类型提供了一个名为 getClassificationInsightsByUser 的字段,它接受 emailAddresses 参数。该字段的解析器函数很可能会访问一个数据库,并构造和返回 ClassificationInsightByUser 对象的一个列表。


// 假设我们已经定义了数据类// (如ClassificationInsightByUser)来存放数据
// 编写自己的datafetcher类class ClassificationInsightByUserDataFetcher: DataFetcher<List<ClassificationInsightByUser>?> // 重载DataFetcher的get函数 override fun get(env: DataFetchingEnvironment): List<ClassificationInsightByUser>? { // 在提交的查询中获取参数 val emailAddresses = env.getArgument<List<String>> (EMAIL_ADDRESSES) // 编写逻辑从其他API或者通过调用控制器/服务从业务层获取数据 // 在这里,为了简单,返回静态数据 return EntityData.getClassificationInsightByUser(emailAddresses) }}
复制代码


第三步:初始化 GraphQLSchema 和 GraphQL Object(借助 graphql-java)来辅助执行查询。


// 借助工具函数,将所有模式文件加载为字符串String schema = getResourceFileAsString("schema.graphqls")// 根据模式文件创建typeRegistryval schemaParser = SchemaParser()val typeDefinitionRegistry = TypeDefinitionRegistry()typeDefinitionRegistry.merge(schemaParser.parse(schema))// 运行时装配,我们将自己的查询类型装配到解析器中val runtimeWiring = RuntimeWiring()  .type("Query", builder -> builder.dataFetcher(            "getClassificationInsightsByUser", ClassificationInsightByUserDataFetcher()          )  )  .build();// 创建graphQL Schemaval schemaGenerator = SchemaGenerator();val graphQLSchema = schemaGenerator  .makeExecutableSchema(typeDefinitionRegistry,runtimeWiring);// 创建graphQLval graphQL = GraphQL.newGraphQL(graphQLSchema).build();
复制代码


第四步:编写 servlet(MyAppServlet),处理传入的请求


override fun doPost(req: HttpServletRequest, resp:    HttpServletResponse) {  val jsonRequest = JSONObject(payloadString)  val executionInput = ExecutionInput.newExecutionInput()  .query(jsonRequest.getString("query"))  .build()  // 使用graphQL执行查询   // 它将会调用解析器来获取数据并且只返回请求的数据  val executionResult = graphQL.execute(executionInput)    // 发送响应  resp.characterEncoding = "UTF-8"  resp.writer.println(mapper.writeValueAsString(executionResult.toSpecification()))  resp.writer.close()  }
复制代码


第五步:在应用中,嵌入 Web 服务器(本例中使用的是 Jetty)。


// Serverval server = new Server();
// HTTP连接器,在生产环境中要使用HTTPSval http = ServerConnector(server)http.host = "localhost"http.Port = 8080http.idleTimeout = 30000
// 搭建handlerval servletContextHandler = ServletContextHandler()servletContextHandler.contextPath = "/"servletContextHandler.addServlet(ServletHolder(MyAppServlet()), "/api")server.handler = servletContextHandler// 启动jetty服务器以监听请求server.start()server.join()
复制代码


第六步:构建并启动应用,请使用 CI/CD 工具来创建、发布和部署 Docker 镜像到集群中。


确保 API 的安全性


在 Salesforce,安全性是首要任务。我们的 API 仅供注册用户访问,而且他们只能访问有权限的数据。在这方面,你可以探索 OAuth 2.0(JWT 授予类型和基于角色的访问控制)和开放策略代理(Open Policy Agent ,OPA)来满足访问控制的需求。


作为最佳实践,认证中间件应该放在 GraphQL 之前,并且要在业务逻辑层有唯一一个地方负责授权,避免在多个地方都要进行检查。除了认证和授权,在设计 API 时还应考虑速率限制、数据脱敏(data masking)和载荷扫描。


总    结


我们已经展示了如何构建一个可扩展、高效、安全的 API。在这个过程中,我们使用应用容器进行扩展,使用 GraphQL 和嵌入式 Jetty 确保高效和轻量级,并优先考虑了 API 的安全性。


今日好文推荐


“不搞职级、人人平等” 25 年后行不通了?Netflix 破天荒引入细分职级:气走老员工


缺少软件开发文化,大众汽车陷入困境,CEO 也被赶下了台


我庆幸果断放弃了 SwiftUI:它还不够成熟


英伟达回应“对中国断供部分高端 GPU”;月薪 3.6 万工程师日均写 7 行代码被开;12 年黑进 40 多家金融机构老板赚百万获刑 |Q 资讯


2022-09-13 19:375232

评论

发布
暂无评论
发现更多内容

03.接口vs抽象类比较

杨充

天猫商品详情API响应数据解析

tbapi

天猫商品详情接口 天猫API 天猫数据采集 天猫商品详情api

日均处理 PB 级数据,基于 DolphinScheduler 的离线数据治理平台实现精准血缘追踪

白鲸开源

大数据 开源 数据治理 Apache DolphinScheduler 血缘分析

三天接入,零重构:低代码中间件的快速集成机制全解析

星云低代码中间件

ide 低代码 企业应用 可视化编程

合规交易所架构设计:冷热钱包隔离+zk-KYC方案

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 公链开发 代币开发

告别碎片化输入:TextIn xParse如何为RAG打造「零损耗」知识管道

合合技术团队

人工智能 算法 #大数据

Apache DolphinScheduler DataX 数据集成:新手入门全攻略

白鲸开源

大数据 Apache DolphinScheduler 数据集成 DataX 数据调度

深入底层:如何优雅部署 SeaTunnel 分离集群到 Kubernetes

白鲸开源

大数据 开源 Kubernetes 部署 Apache SeaTunnel

程序员专属秋日养生指南(Coding版),文内有秋天第一杯奶茶

Comate编码助手

程序员 文心快码 秋天的第一杯奶茶 程序员养生

Mysql如何迁移数据库数据

秃头小帅oi

中国 Apache 项目 OpenRank 排行榜 Top 20:白鲸开源深度参与两大上榜项目

白鲸开源

开源 Apache DolphinScheduler Apache SeaTunnel 白鲸开源 Apache软件基金会

[鸿蒙征文]钢琴和弦小工具

大展红图

鸿蒙 音乐 HarmonyOS HarmonyOS NEXT 钢琴

DApp极速开发指南:7天搞定Solidity合约+React前端全栈实战

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 代币开发 交易所开发公链开发

中昊芯英全自研 TPU 架构 AI 芯片与计算集群重磅亮相2025WAIC

科技热闻

镜舟科技受邀出席开源赋能金融创新论坛,共同推动金融行业开源技术规范发展

镜舟科技

金融创新 分析型数据库 开源治理 镜舟科技 MPP 架构

Apache Flink:从实时数据分析到实时AI

Apache Flink

flink AI 实时计算

从《中国开源年度报告》看中国开源力量的十年变迁中,Apache SeaTunnel 的跃迁

白鲸开源

大数据 开源 Apache SeaTunnel Apache软件基金会 OpenRank

校招新人如何用文心快码让Landing期不再尴尬

Comate编码助手

职场新人 AI辅助编程 AI 代码助手 文心快码

JNPF 6.0 +AI,低代码开发新体验

引迈信息

技术文档 | 使用 Pulsar Functions 构建实时 AI Pipeline

AscentStream

Machine Learning pulsar

单存储节点支持700+ GPU训练!华瑞指数云首秀登顶MLPerf全球存力榜

科技热闻

这个仓库堪称造轮子的鼻祖,建议看看!

Immerse

教师如何规划教学与科研协同发展|北京理工大学医工交叉教学实践分享(5)

ModelWhale

AI教育 科研 北京理工大学

硅空位中心实现量子网络化的新突破

qife122

量子技术 硅空位中心

品牌出海的隐形风险:海外舆情监测你真的会用吗?

沃观Wovision

数据分析 出海企业 沃观Wovision 舆情监测系统

星云低代码:低代码不再是平台,而是“中间件”

星云低代码中间件

低代码 中间件 企业应用 可视化开发

WAIC2025 | 澳鹏(中国)精彩亮相2025世界人工智能大会

澳鹏Appen

世界人工智能大会 WAIC WAIC2025

利用NLU标签优化ASR重评分模型

qife122

语音识别 自然语言理解

告别碎片化输入:TextIn xParse如何为RAG打造「零损耗」知识管道

合合技术团队

人工智能 算法 #大数据

使用 Apache DolphinScheduler 构建和部署大数据平台,将任务提交至 AWS 的实践经验

白鲸开源

云计算 AWS Apache DolphinScheduler 思科 EKS

从v3.1到v4.3,OceanBase稳定支撑快手PB级核心业务场景

老纪的技术唠嗑局

运维 数据库设计 OceanBase 社区版 mysql'

Salesforce 构建可扩展 API 的旅程_语言 & 开发_Nitesh Kumar_InfoQ精选文章