rest和rest ful_当遵循REST(ful)原理看似不切实际时,GraphQL可能会出现

rest和rest ful

我当然赶不上时髦的火车,但是今天我们要谈论GraphQL ,这是一种构建REST(ful) Web服务和API的非常有趣的方法。 我认为,重申REST(ful)体系结构是建立在相当合理的原则和约束之上的,这是很公平的(尽管对此的争论在业界从未间断)。

那么……什么是GraphQL ? 总的来说,它是另一种查询语言。 但是有趣的是,它旨在使客户(前端)可以根据期望的数据表达其需求(后端)。 坦白地说, GraphQL远远超出了它,但这是其最引人注目的功能之一。

GraphQL只是一个规范,对编程语言或技术堆栈没有任何特殊要求,但是毫不奇怪,它在现代Web开发中得到了广泛的接受,无论是在客户端( ApolloRelay )还是服务器端(这些天,API通常称为BFF )。 在今天的帖子中,我们将带GraphQL一下,讨论它在哪里可能是一个合适的选择,以及为什么您可以考虑采用它。 虽然桌子上有相当多的选择, 桑格利亚 ,了不起斯卡拉基础的实施GraphQL规范,将是我们要建立顶上的基础。

本质上,我们的目标是开发一个简单的用户管理Web API。 数据模型远非完整,但足以满足其目的。 这是User类。

case class User(id: Long, email: String, created: Option[LocalDateTime],
  firstName: Option[String], lastName: Option[String], roles: Option[Seq[Role.Value]],
    active: Option[Boolean], address: Option[Address])

角色是一个硬编码的枚举:

object Role extends Enumeration {
  val USER, ADMIN = Value
}

地址是数据模型中的另一个小类:

case class Address(street: String, city: String, state: String,
zip: String, country: String)

GraphQL的核心是强类型化。 这意味着,应以某种方式在GraphQL中表示特定于应用程序的模型。 要自然地说,我们需要定义schema 。 在Sangria中 ,模式定义非常简单,由GraphQL规范中借鉴的三个主要类别组成: 对象类型查询类型变异类型 。 所有这些我们都将涉及,但是对象类型定义听起来像是一个逻辑起点。

val UserType = ObjectType(
  "User",
  "User Type",
  interfaces[Unit, User](),
  fields[Unit, User](
    Field("id", LongType, Some("User id"), tags = ProjectionName("id") :: Nil,
      resolve = _.value.id),
    Field("email", StringType, Some("User email address"),
      resolve = _.value.email),
    Field("created", OptionType(LocalDateTimeType), Some("User creation date"),
      resolve = _.value.created),
    Field("address", OptionType(AddressType), Some("User address"),
      resolve = _.value.address),
    Field("active", OptionType(BooleanType), Some("User is active or not"),
      resolve = _.value.active),
    Field("firstName", OptionType(StringType), Some("User first name"),
      resolve = _.value.firstName),
    Field("lastName", OptionType(StringType), Some("User last name"),
      resolve = _.value.lastName),
    Field("roles", OptionType(ListType(RoleEnumType)), Some("User roles"),
      resolve = _.value.roles)
  ))

在许多方面,它只是从UserUserType的直接映射。 Sangria通过提供宏支持可以使您轻松完成此工作,从而可以在编译时生成架构。 AddressType定义非常相似,让我们跳过它,看看如何处理枚举,例如Roles

val RoleEnumType = EnumType(
  "Role",
  Some("List of roles"),
  List(
    EnumValue("USER", value = Role.USER, description = Some("User")),
    EnumValue("ADMIN", value = Role.ADMIN, description = Some("Administrator")
  ))

简单,简单和紧凑…在传统的REST(ful) Web服务中,通常无法直接获得有关资源的元数据。 但是,一些补充规范,例如JSON Schema ,可以通过一些工作来填补这一空白。

很好,这里有类型 ,但是这些查询变异是什么? 查询GraphQL规范中的一种特殊类型,它基本上描述了如何获取数据及其形状。 例如,通常需要通过其标识符来获取用户详细信息,这可以通过以下GraphQL查询来表示:

query {
  user(id: 1) {
    email
    firstName
    lastName
    address {
      country
    }
  }
}

你可以从字面上把它读作-是:用标识1查找用户 ,并只与国家只返回他的电子邮件姓氏名字地址 。 令人敬畏的是,不仅GraphQL查询功能异常强大,而且它们还控制了返回相关方的内容。 如果您必须支持各种不同的客户端而又不增加API端点的数量,那么这将是无价的功能。 在桑格利亚汽酒中定义查询类型也很容易,例如:

val Query = ObjectType(
  "Query", fields[Repository, Unit](
    Field("user", OptionType(UserType), arguments = ID :: Nil,
      resolve = (ctx) => ctx.ctx.findById(ctx.arg(ID))),
    Field("users", ListType(UserType),
      resolve = Projector((ctx, f) => ctx.ctx.findAll(f.map(_.name))))
  ))

上面的代码片段实际上描述了两个查询。 我们之前见过的一个,通过标识符获取用户,另一个,获取所有用户。 这是后者的一个简单示例:

query {
  users {
    id
    email
  }
}

希望您同意不需要任何解释,目的很明确。 查询可以说是支持采用GraphQL的最强论据,其价值主张确实是巨大的。 使用Sangria,您可以访问客户想要返回的字段,因此可以使用投影,选择或类似概念告知数据存储区仅返回这些子集。 为了更接近现实,我们的示例应用程序将数据存储在MongoDB中,因此我们可以要求它仅返回客户端感兴趣的字段。

def findAll(fields: Seq[String]): Future[Seq[User]] = collection.flatMap(
   _.find(document())
    .projection(
      fields
       .foldLeft(document())((doc, field) => doc.merge(field -> BSONInteger(1)))
    )
    .cursor[User]()
    .collect(Int.MaxValue, Cursor.FailOnError[Seq[User]]())
  )

如果我们回到典型的REST(ful) Web API,那么当今最广泛用于概述所需响应形状的方法是传递查询字符串参数,例如/ api / users?fields = email,firstName,姓,… 。 但是,从实现角度看,没有多少框架本身支持这些功能,因此每个人都必须提出自己的方式。 关于查询功能,如果您碰巧是出色的Apache CXF框架的用户,那么您可能会从其功能强大的搜索扩展中受益,该扩展我们已经前段时间进行了讨论。

如果查询通常只是获取数据,那么变异就是达到数据修改的目的。 从句法上讲,它们与查询非常相似,但它们的解释不同。 例如,这是我们可以向应用程序添加新用户的方法之一。

mutation {
  add(email: "a@b.com", firstName: "John", lastName: "Smith", roles: [ADMIN]) {
    id
    active
    email
    firstName
    lastName
    address {
      street
      country
    }
  }
}

在此突变中,将向新用户John Smith发送电子邮件a@b.com,并为其分配ADMIN角色。 与查询一样 ,当变种完成时,客户端始终可以控制服务器需要从服务器获取哪种数据形状。 可以将突变视为对操作的呼吁,类似于很多方法的调用,例如,可以通过以下方式完成用户的激活:

mutation {
  activate(id: 1) {
    active
  }
}

桑格利亚汽酒中突变的描述与查询完全相同,例如,我们之前研究的突变具有以下类型定义:

val Mutation = ObjectType(
  "Mutation", fields[Repository, Unit](
    Field("activate", OptionType(UserType),
      arguments = ID :: Nil,
      resolve = (ctx) => ctx.ctx.activateById(ctx.arg(ID))),
    Field("add", OptionType(UserType),
      arguments = EmailArg :: FirstNameArg :: LastNameArg :: RolesArg :: Nil,
      resolve = (ctx) => ctx.ctx.addUser(ctx.arg(EmailArg), ctx.arg(FirstNameArg),
        ctx.arg(LastNameArg), ctx.arg(RolesArg)))
  ))

这样,我们的GraphQL模式就完成了:

val UserSchema = Schema(Query, Some(Mutation))

很好,但是……我们能做些什么? 如有时间问题,欢迎使用GraphQL服务器。 我们记得,没有特定技术或堆栈的附件,但是在Web API的整个领域中,您可以将GraphQL服务器视为绑定到POST HTTP动词的单个端点。 而且,一旦我们开始谈论HTTPScala ,他们比出色的Akka HTTP可以做得更好,幸运的是, Sangria与它无缝集成。

val route: Route = path("users") {
  post {
    entity(as[String]) { document =>
      QueryParser.parse(document) match {
        case Success(queryAst) =>
          complete(Executor.execute(SchemaDefinition.UserSchema, queryAst, repository)
            .map(OK -> _)
            .recover {
              case error: QueryAnalysisError => BadRequest -> error.resolveError
              case error: ErrorWithResolver => InternalServerError -> error.resolveError
            })
 
        case Failure(error) => complete(BadRequest -> Error(error.getMessage))
      }
    }
  } ~ get {
    complete(SchemaRenderer.renderSchema(SchemaDefinition.UserSchema))
  }
}

您可能会注意到,我们也将模式公开在GET端点下,这是做什么用的? 好吧,如果您对Swagger熟悉,我们在这里已经讨论了很多,这是一个非常相似的概念。 该架构包含所有必要的部分,足以使外部工具自动发现各自的GraphQL查询和变异以及它们所引用的类型。 GraphiQL是用于探究GraphQL的浏览器内置 IDE,它就是其中之一(想想REST(完整)服务世界中的Swagger UI )。

我们大部分时间都在那儿,我们的GraphQL服务器已经准备就绪,让我们运行它并发送几个查询和变异,以获得它的感觉:

[info] Running com.example.graphql.Boot
INFO  akka.event.slf4j.Slf4jLogger  - Slf4jLogger started
INFO  reactivemongo.api.MongoDriver  - No mongo-async-driver configuration found
INFO  reactivemongo.api.MongoDriver  - [Supervisor-1] Creating connection: Connection-2
INFO  r.core.actors.MongoDBSystem  - [Supervisor-1/Connection-2] Starting the MongoDBSystem akka://reactivemongo/user/Connection-2

我们的数据存储区(我们正在使用以Docker容器运行的MongoDB )目前很可能没有用户,因此,立即添加一个存储区听起来是个好主意:

$ curl -vi -X POST http://localhost:48080/users -H "Content-Type: application/json" -d " \
   mutation { \
     add(email: \"a@b.com\", firstName: \"John\", lastName: \"Smith\", roles: [ADMIN]) { \
       id \
       active \
       email \
       firstName \
       lastName \
       address { \
       street \
         country \
       } \
     } \
   }"

HTTP/1.1 200 OK
Server: akka-http/10.0.5
Date: Tue, 25 Apr 2017 01:01:25 GMT
Content-Type: application/json
Content-Length: 123

{
  "data":{
    "add":{
      "email":"a@b.com",
      "lastName":"Smith",
      "firstName":"John",
      "id":1493082085000,
      "address":null,
      "active":false
    }
  }
}

它似乎工作得很好。 无论您正在运行哪种查询变异 ,响应细节都将始终包裹在数据信封中,例如:

$ curl -vi -X POST http://localhost:48080/users -H "Content-Type: application/json" -d " \                                                
   query { \                                                                                                                           
     users { \                                                                                                                         
       id \                                                                                                                            
       email \                                                                                                                         
     } \                                                                                                                               
   }"                                                                                                                                  

HTTP/1.1 200 OK                                                                                                                                   
Server: akka-http/10.0.5                                                                                                                          
Date: Tue, 25 Apr 2017 01:09:21 GMT                                                                                                               
Content-Type: application/json                                                                                                                    
Content-Length: 98                                                                                                                                
                                                                                                                                                  
{
  "data":{
    "users":[
      {
        "id":1493082085000,
        "email":"a@b.com"
      }
    ]
  }
}

完全按照我们的命令…老实说,使用GraphQL感觉很自然,特别是在涉及数据查询时。 而且,我们甚至没有谈论片段变量指令和许多其他内容。

现在是一个问题:我们是否应该放弃所有实践,即JAX-RSSpring MVC …并改用GraphQL ? 坦率地说,事实并非如此, GraphQL非常适合解决某些类型的问题,但是总的来说,与Swagger或任何其他已建立的API规范框架结合使用的传统REST(ful) Web服务大都是可以保留的。

并请注意, GraphQL及其优点是有代价的。 例如, HTTP缓存缓存控制将不再适用, HATEOAS也没有多大意义,无论您叫什么,统一响应,可靠性都高,因为所有事物都位于单个外观下,……因此, GraphQL确实是一个很棒的选择工具,请明智地使用它!

完整的项目源可在Github上获得

翻译自: https://www.javacodegeeks.com/2017/04/following-restful-principles-might-look-impractical-graphql-come-resque.html

rest和rest ful

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值