Ktor和GraphQL

45 篇文章 0 订阅
9 篇文章 0 订阅

在本教程中,您将重点关注:

使用Ktor创建使用KGraphQL库的GraphQL API。
使用KGraphQL丰富的DSL来设置GraphQL模式。
围绕内置数据执行基本操作(查询和变异)。

什么是GraphQL?

GraphQL是API的查询语言,也是处理查询的服务器端运行时。把GraphQL看作是现有API或数据库的外观。GraphQL还利用类型系统来定义后备数据。GraphQL没有绑定到任何特定的数据库或存储引擎。相反,它是由您现有的代码和数据支持的。

API开发人员使用GraphQL创建架构描述客户机可以通过该服务查询的所有可能的数据。你可以定义一个对象的类型和类型。作为查询来吧,GraphQL根据模式验证查询。然后,GraphQL执行已验证的查询。API开发人员将模式中的每个字段附加到名为分解器。在执行期间,调用解析程序以生成值。您将在本教程的后面创建解析器。

区分GraphQL和REST

GraphQL和REST的主要区别在于REST是基于网络的软件的一个体系结构概念。另一方面,GraphQL是一种查询语言、规范和一组工具,它使用HTTP在单个端点上操作。
但它们也有相似之处。它们都以JSON格式向前端客户机提供信息,并有方法识别某个操作是否要读或写数据。两者最后都会调用服务器上的函数来执行不同类型的请求。
REST往往有多个端点,GraphQL只有一个端点。

什么是Ktor?

Ktor是一个用于轻松构建连接应用程序的框架,包括移动应用程序、web应用程序、浏览器应用程序和桌面应用程序。Ktor应用程序是用Kotlin编写的,使开发人员能够快速构建异步web应用程序。

与Ktor一起发展的一些更显著的原因包括:

轻量级:使用你需要的。Ktor允许您透明地配置项目所需的功能。
可扩展:扩展您需要的。通过一个可配置的管道,您可以创建所需的扩展,并将它们放在任何您想要的地方。
跨平台:在需要的地方运行。使用Kotlin多平台技术从头开始构建,您可以在任何地方部署Ktor应用程序。
异步:根据需要缩放。使用Kotlin协程,Ktor是真正的异步和高度可伸缩的。
开发项目
GraphQL能够与各种数据源交互,包括数据库、REST端点等……但是,为了管理本教程的范围,您将使用存储在内存中的数据。在IDE中打开starter项目之后,第一步是在公开GraphQL模型之前设置对象模型类。

设置模型类

在创建GraphQL模式之前,有必要定义服务将公开的对象模型。然后,创建型号.kt归档模型目录。删除为您创建的任何Models类并替换为:

// 1
enum class Position {
  GK, // Goalkeeper
  DEF, // Defender
  MID, // Midfielder
  FRW  // Forward
}

// 2
data class Player(var uid: String, var name: String, var team: String, var position: Position)

// 3
data class PlayerInput(val name: String, val team: String, val position: Position)

上面的代码定义如下:

这个Position枚举类描述玩家的不同位置。
这个Player数据类提供模型的属性。
这个PlayerInput是要使用数据源操作时要发送的模型。
添加内置数据
定义对象模型之后,就可以创建模拟数据库的内置数据了。创建一个名为数据在src目录。然后,创建一个名为数据库.kt存储玩家列表。添加以下代码:

import com.raywenderlich.kgraphqlfootball.models.Player
import com.raywenderlich.kgraphqlfootball.models.Position
    
val players = mutableListOf(
  Player("abc123", "Sergio Ramos", "Real Madrid", Position.DEF),
  Player("abc124", "Lionel Messi", "Barcelona", Position.FRW),
  Player("abc125", "Cristiano Ronaldo", "Juventus", Position.FRW),
  Player("abc126", "Leon Goretzka", "Bayern Munich", Position.MID),
  Player("abc127", "Manuel Neuer", "Bayern Munich", Position.GK),
  Player("abc128", "Neymar Jr", "PSG", Position.FRW),
  Player("abc129", "Casemiro", "Real Madrid", Position.MID)
)

创建了数据之后,您将把注意力转向围绕数据包装一个接口,以方便对列表的读/写。为此,您将使用存储库模式。

添加存储库模式

存储库模式分离数据访问逻辑并将其映射到业务逻辑中的业务实体。数据访问逻辑层和业务逻辑层通过接口进行通信。所以是时候创建存储库了。首先,创建存储库目录。然后,创建 IPlayerRepository.kt文件。添加:

import com.raywenderlich.kgraphqlfootball.data.players
import com.raywenderlich.kgraphqlfootball.models.Player
import com.raywenderlich.kgraphqlfootball.models.PlayerInput
import com.raywenderlich.kgraphqlfootball.models.Position

interface IPlayerRepository {
  fun createPlayer(player: Player)
  fun deletePlayer(uid: String)
  fun listPlayers() : List<Player>
  fun filterPlayersByPosition(position: Position): List<Player>
  fun filterPlayersByTeam(team: String): List<Player>
  fun updatePlayer(uid: String, playerInput: PlayerInput)
}

播放器定义了这个存储库的功能。这个接口可以重用到对数据库或另一个restapi的抽象调用。在这个项目中,你将定义每个方法的实现。在界面下方添加以下内容:

class PlayerRepository : IPlayerRepository {
  override fun createPlayer(player: Player) {
    players.add(player)
  }
    
  override fun deletePlayer(uid: String) {
    players.removeIf { it.uid == uid }
  }
    
  override fun listPlayers(): List<Player> {
    return players
  }
    
  override fun filterPlayersByPosition(position: Position):List<Player> {
    return players.filter { it.position == position }
  }
    
  override fun filterPlayersByTeam(team: String): List<Player> {
    return players.filter { it.team == team }
  }
    
  override fun updatePlayer(uid: String, playerInput: PlayerInput) {
    players.find { it.uid == uid }?.apply {
      name = playerInput.name
      position = playerInput.position
      team = playerInput.team
    }
  }
}

现在,您使用的是来自玩家列表的内置数据。因此,您要实现的操作将来自这个本地列表。现在您已经拥有了使用GraphQL公开数据所需的所有类。
使用GraphQL的Ktor客户端
在对象模型和模型接口完成后,您将集中精力包装GraphQL的接口。具体来说,您将使用KGraphQL包装您的接口。KGraphQL是GraphQL的Kotlin实现。它提供了一个丰富的DSL来设置GraphQL模式。首先定义模式。

创建架构

GraphQL服务器使用模式来描述数据图的形状。此架构定义了一个类型层次结构,其中包含从后端数据存储填充的字段。该模式还精确地指定哪些查询和变异可供客户机针对数据图执行。从创建 图表.kt文件src公司目录并添加:

import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
import com.raywenderlich.kgraphqlfootball.models.Player
import com.raywenderlich.kgraphqlfootball.models.PlayerInput
import com.raywenderlich.kgraphqlfootball.models.Position
import com.raywenderlich.kgraphqlfootball.respository.IPlayerRepository
import com.raywenderlich.kgraphqlfootball.respository.PlayerRepository

fun SchemaBuilder.schemaValue() {
  // 1
  val repository: IPlayerRepository = PlayerRepository()

  // TODO: Queries and mutations will go here ...
    
  // 2
  inputType<PlayerInput>{
    description = "The input of the player without the identifier"
  }
  // 3
  type<Player>{
   description = "Player object with the attributes name, team, position and identifier"
  }
  // 4
  enum<Position>()
}

在上面的代码中,您必须定义下列步骤:

为上一节中实现的玩家定义存储库。
这个inputType用作GraphQL模式上输入数据模型的对象类型。
将Kotlin数据类注册到type方法将其包含在已创建的架构类型系统中。
将Kotlin枚举注册到enum方法将其包含在已创建的架构类型系统中。
如果您在IntelliJ中重新启动服务器并刷新浏览器,您应该会注意到最右边的Schema选项卡。如果你点击它,你会看到你的模型描述在面板显示。
你会注意到description您在描述您的模型时填充也显示在这里。这对于那些可能使用您的接口来更好地理解端点提供的模型的开发人员非常有用。

使用模式中定义的数据类型,下一步将添加第一个查询。

使用查询获取数据

GraphQL查询只是从数据存储中读取或获取值。这些查询有助于减少数据的过度获取。与restapi不同,GraphQL允许用户选择从服务器获取哪些字段。这意味着更小的查询和更少的网络流量,减少了响应时间。

首先,您将添加一个方法来按给定位置查询:

// 1  query("playersByPosition") {    // 2    description = "Retrieve all the players by his position"    // 3    resolver { position: Position ->      try {        // 4        repository.filterPlayersByPosition(position)      } catch (e: Exception) {        emptyList<Player>()      }    }  }

在GraphQL中,每个属性都需要一个解析器。解析器是系统逻辑的一部分,需要解析响应图。自从你定义Position枚举类型,您可以接受它作为解析器的一部分。最后一步是在启动应用程序时设置模式。

打开应用.kt并将“Hello,world”查询替换为:

schema { schemaValue() }

要测试实现,请尝试运行playerByPosition查询。在查询部分,添加:

查询fetchplayerbyposition($position:position!){playersbyposition(position:$position){uid name position团队}

单击查询变量选项卡,并将以下表示对象的代码放在参数字段中。在这里,您选择搜索中场球员。

{
  "position": "MID"
}

单击中间部分播放按钮。
为了充实API,添加以下内容players和 球员队查询以检索所有玩家的列表,并按团队分别检索玩家。

query("players") {
    description = "Retrieve all players"
    resolver { ->
      try {
        repository.listPlayers()
      } catch (e: Exception) {
        emptyList<Player>()
      }
    }
  }
    
  query("playersByTeam") {
    description = "Retrieve all the players by his team"
    resolver { team: String ->
      try {
        repository.filterPlayersByTeam(team)
      } catch (e: Exception) {
        emptyList<Player>()
      }
    }
  }

写入带有突变的数据

在REST中,任何请求都可能对服务器造成副作用。但按照惯例,建议不要使用GET请求来修改数据。GraphQL类似-任何查询都不应该执行数据写入。但是,建立一个约定,即任何导致写操作的操作都应该通过突变 .

首先要添加一个突变,以允许创建新的玩家。

 // 1
  mutation("createPlayer") {
    // 2
    description = "Create a new player"
    // 3
    resolver { playerInput: PlayerInput ->
      try {
        // 4
        val uid = java.util.UUID.randomUUID().toString()
        val player = Player(uid, playerInput.name, playerInput.team, playerInput.position)
        repository.createPlayer(player)
        true
      } catch (e: Exception) {
        false
      }
    }
  }

以上代码分解如下:

1、创建mutation有一个独特的名字
2、对该方法进行描述,以帮助开发人员更好地理解该方法。
3、使用resolver接受播放器输入。请记住,您先前使用架构定义了此类,因此可以使用类型化输入。
4、创建Player对象,并将其存储在列表中。
与查询一样,您将为您创建的每个解析器调用相应的存储库方法。

在查询变量,将表示对象的以下代码放在参数字段中:

{
  "playerInput": {
    "name": "Test player",
    "position": "DEF",
    "team": "Barcelona"
  }
}

执行此操作时,请在操场上获得以下结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值