[关闭]
@levinzhang 2022-09-07T22:33:42.000000Z 字数 4976 阅读 493

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实战

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

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

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

schema.graphqls

  1. # 描述我们能够获取什么内容的对象类型
  2. type ClassificationInsightByUser {
  3. organizationId: ID!
  4. userId: String!
  5. emailAddress: String!
  6. title: String!
  7. }
  8. # 定义所有查询的Query类型
  9. type Query {
  10. getClassificationInsightsByUser(
  11. emailAddresses: [String!]!
  12. ): [ClassificationInsightByUser]
  13. }
  14. schema {
  15. query: Query
  16. }

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

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

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

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

  1. // 借助工具函数,将所有模式文件加载为字符串
  2. String schema = getResourceFileAsString("schema.graphqls")
  3. // 根据模式文件创建typeRegistry
  4. val schemaParser = SchemaParser()
  5. val typeDefinitionRegistry = TypeDefinitionRegistry()
  6. typeDefinitionRegistry.merge(schemaParser.parse(schema))
  7. // 运行时装配,我们将自己的查询类型装配到解析器中
  8. val runtimeWiring = RuntimeWiring()
  9. .type("Query", builder -> builder.dataFetcher(
  10. "getClassificationInsightsByUser", ClassificationInsightByUserDataFetcher()
  11. )
  12. )
  13. .build();
  14. // 创建graphQL Schema
  15. val schemaGenerator = SchemaGenerator();
  16. val graphQLSchema = schemaGenerator
  17. .makeExecutableSchema(typeDefinitionRegistry,runtimeWiring);
  18. // 创建graphQL
  19. val graphQL = GraphQL.newGraphQL(graphQLSchema).build();

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

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

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

  1. // Server
  2. val server = new Server();
  3. // HTTP连接器,在生产环境中要使用HTTPS
  4. val http = ServerConnector(server)
  5. http.host = "localhost"
  6. http.Port = 8080
  7. http.idleTimeout = 30000
  8. // 搭建handler
  9. val servletContextHandler = ServletContextHandler()
  10. servletContextHandler.contextPath = "/"
  11. servletContextHandler.addServlet(ServletHolder(MyAppServlet()), "/api")
  12. server.handler = servletContextHandler
  13. // 启动jetty服务器以监听请求
  14. server.start()
  15. server.join()

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

确保API的安全性

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

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

总结

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

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注