服务配置:数据建模(SDL)

本文属使用Prisma构建GraphQL服务系列。

概述

Prisma使用GraphQL Schema Definition Language(SDL)进行数据建模。您的数据模型是用一个或多个.graphql文件编写的,并且是Prisma在底层生成的实际数据库schema的基础。如果您使用单个文件来进行类型定义,则该文件通常称为datamodel.graphql

要了解有关SDL的更多信息,可以查看官方GraphQL文档

包含数据模型的.graphql文件,需要在prisma.yml文件中datamodel属性下的指定。例如:

datamodel:
  - types.graphql
  - enums.graphql

如果只有一个文件定义数据模型,则可以如下方式指定:

datamodel: datamodel.graphql

数据模型是Prisma服务的GraphQL API的基础。基于数据模型,Prisma将生成一个强大的GraphQL schema(称为Prisma database schema),该schema为数据模型中的类型定义了CRUD操作。

GraphQL模式定义了GraphQL API的操作。它实际上是用SDL编写的类型集合(SDL还支持接口,枚举,联合类型等基元,您可以在这里了解有关GraphQL类型系统的内容)。 GraphQL schema有三种特殊的根类型:查询,突变和订阅。这些类型定义了API的入口点并定义了API将接受的操作。要了解更多关于GraphQL schema的信息,请查看这篇文章

示例

一个简单的datamodel.graphql文件:

type Tweet {
  id: ID! @unique
  createdAt: DateTime!
  text: String!
  owner: User!
  location: Location!
}

type User {
  id: ID! @unique
  createdAt: DateTime!
  updatedAt: DateTime!
  handle: String! @unique
  name: String
  tweets: [Tweet!]!
}

type Location {
  latitude: Float!
  longitude: Float!
}

这个例子展示了使用数据模型时的一些重要概念:

  • TweetUserLocation这三种类型映射到数据库中的表(table)。
  • User和Tweet之间存在双向关系
  • TweetLocation之间存在单向关系
  • User上的name字段外,数据模型中所有字段都是必须的(如类型后面的!所示)
  • idcreatedAtupdatedAt字段由Prisma管理,在公开的GraphQL API中为只读(意味着它们不能通过突变进行更改)。

创建和更新数据模型与编写文本文件一样简单。对数据模型满意后,您可以通过运行prisma deploy将更改应用到Prisma服务:

$ prisma deploy

Changes:

  Tweet (Type)
  + Created type `Tweet`
  + Created field `id` of type `GraphQLID!`
  + Created field `createdAt` of type `DateTime!`
  + Created field `text` of type `String!`
  + Created field `owner` of type `Relation!`
  + Created field `location` of type `Relation!`
  + Created field `updatedAt` of type `DateTime!`

  User (Type)
  + Created type `User`
  + Created field `id` of type `GraphQLID!`
  + Created field `createdAt` of type `DateTime!`
  + Created field `updatedAt` of type `DateTime!`
  + Created field `handle` of type `String!`
  + Created field `name` of type `String`
  + Created field `tweets` of type `[Relation!]!`

  Location (Type)
  + Created type `Location`
  + Created field `latitude` of type `Float!`
  + Created field `longitude` of type `Float!`
  + Created field `id` of type `GraphQLID!`
  + Created field `updatedAt` of type `DateTime!`
  + Created field `createdAt` of type `DateTime!`

  TweetToUser (Relation)
  + Created relation between Tweet and User

  LocationToTweet (Relation)
  + Created relation between Location and Tweet

Applying changes... (22/22)
Applying changes... 0.4s

数据模型的构建块

有几种可用的构建模块来构造您的数据模型:

  • 类型由多个字段组成,用于将相似的实体组合在一起。数据模型中的每种类型都映射到数据库,并将CRUD操作添加到GraphQL schema中。
  • 关系描述类型之间的关系。
  • 接口是抽象类型,它包含一组必须包含的用于实现接口的字段。目前,接口不能由用户定义,但有高级接口支持的待定功能请求
  • 特殊指令涵盖不同的用例,如类型约束或级联删除行为。

本页面的其余部分将更详细地介绍这些构建块。

Prisma database schema 与 Data Model

当开始使用GraphQL和Prisma时,您正在使用的.graphql文件的数量可能会令人困惑。然而,了解每个的角色至关重要。

通常,.graphql文件可以包含以下任一项:

  • GraphQL操作(即查询,突变或订阅)
  • SDL中的GraphQL类型定义

在区分Prisma database schema 与 Data Model的前提下,只有后者才相关!

请注意,并非每个后一类别的.graphql文件都是有效的GraphQL schema。正如上面的提到的,GraphQL schema的特点是它有三种根类型:查询,突变和订阅以及API所需的任何其他类型。

现在,通过该定义,数据模型实际上并不是GraphQL schema,尽管它是用SDL编写的.graphql文件。它缺少根类型,因此实际上并没有定义API操作! Prisma像是使用数据模型作为一个方便的工具来表示数据模型。

如上所述,Prisma将生成一个包含QueryMutationSubscription根类型的实际GraphQL schema。该schema通常作为prisma.graphql存储在您的项目中,并称为Prisma database schema。请注意,您不应该对此文件进行任何手动更改!

作为一个例子,请考虑以下非常简单的数据模型:

datamodel.graphql

type User {
  id: ID! @uniue
  name: String!
}

如果您将此数据模型部署到您的Prisma服务中,Prisma将生成以下Prisma database schema,该schema定义您的服务的GraphQL API:

prisma.graphql

type Query {
  users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
  user(where: UserWhereUniqueInput!): User
}

type Mutation {
  createUser(data: UserCreateInput!): User!
  updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
  deleteUser(where: UserWhereUniqueInput!): User
}

type Subscription {
  user(where: UserSubscriptionWhereInput): UserSubscriptionPayload
}

请注意,这是生成的schema的简化版本,您可以在此处找到完整的schema

如果您已经考虑构建基于Prisma的自己的GraphQL服务,则可能会遇到另一个被称为application schema的.graphql文件。这是另一个适当的GraphQL schema(意味着它包含QueryMutationSubscription根类型),它定义暴露给客户端应用程序的API。它使用底层的Prisma GraphQL API作为“查询引擎”来实际运行数据库的查询,变异和订阅。 基于Prisma的GraphQL服务通常具有两个GraphQL API,将它们视为服务的两个层次:

  • 应用层:由应用模式定义(这里是您实现业务逻辑,认证,与第三方服务集成等的地方)
  • 数据库层:由Prisma数据库服务定义

对象类型(Object types)

对象类型(简称为类型)定义数据模型的一个具体的结构。它用于表示应用程序域(application domain)的实体。

如果您熟悉SQL数据库,则可以将对象类型视为关系数据库中的表的schema。一个类型有一个名字和一个或多个字段。

一个类型的实例被称为节点(node)。这个术语是指数据图(data graph)中的一个节点。您在数据模型中定义的每种类型都将作为生成的Prisma database schema中的类似类型提供。

定义对象类型

在数据模型中使用type关键字定义对象类型:

type Article {
  id: ID! @unique
  text: String!
  isPublished: Boolean @default(value: "false")
}

上面定义的类型具有以下属性:

  • 名称:Article
  • 字段:idtextisPublished(默认值为false)

为类型生成操作API

数据模型中的类型会影响Prisma GraphQL API中的可用操作。对于每种类型,

  • query 允许获取一个或多个类型节点
  • mutation 允许创建、修改或删除类型的节点
  • subscription 允许当节点改变时获取通知(例如新节点添加,现有节点修改或删除)

字段(Fields)

字段是一种类型的构成,表示节点的形状。每个字段是标量或关系字段,可以使用其名称引用。

标量类型

字符串(string)

一个字符串String为文本,用于username类型、博客文章的内容或任何适合文本表示的内容。

注意:共享演示群集上的字符串值目前限制为256KB。使用集群(cluster)配置的其他群集可以提高此限制。

在查询或突变中,必须使用括住双引号("")来指定字符串字段:string:“some-string”

整型(nteger)

Int是一个不能有小数的数字。使用它来存储值,例如配方所需的配料重量或最小年龄。

注意:Int 值范围从-2147483648到2147483647。

在查询或突变中,指定Int字段:int:42

浮点(Float)

浮点数(Float)是可以有小数的数字。使用它可以存储诸如商店中商品价格或复杂计算结果等值。

在查询或突变中,指定Float字段:float:42,float:4.2

布尔量(Boolean)

布尔量其值truefalse

如:boolean: true, boolean: false

日期时间(DateTime)

DateTime类型可用于存储日期或时间值。一个很好的例子可能是一个人的出生日期。

在查询或突变中,必须用ISO 8601格式指定DateTime字段并加上双引号:

  • datetime: "2015"
  • datetime: "2015-11"
  • datetime: "2015-11-22"
  • datetime: "2015-11-22T13:57:31.123Z"

枚举(Enum)

枚举被定义在范围内。

像布尔值一样,枚举可以有一组预定义的值。区别在于您可以定义可能的值。例如,您可以通过创建具有可能值COMPACTWIDECOVER的Enum来指定文章的格式。

注意:枚举值最多可以有191个字符长。

在查询或突变中,必须指定Enum字段,而不使用任何包围字符。您只能使用您为枚举定义的值:enum:COMPACTenum:WIDE

JSON

有时您需要为松散结构化的数据存储任意的Json值。 Json类型确保它实际上是有效的Json,并将该值作为解析的Json对象/数组而不是字符串返回。

注意:共享演示群集上的Json值目前限制为256KB。使用集群配置(cluster configuration)的其他群集可以提高此限制。

在查询或突变中,Json字段必须用双引号括起来。特殊字符必须转义:json: "{\"int\": 1, \"string\": \"value\"}"

ID

ID值是基于cuid生成的唯一的25个字符的字符串。具有ID值的字段是系统字段,只是在内部使用,因此无法使用ID类型创建新字段。

类型修饰符

列表(List)

标量字段可以用列表字段类型标记。具有多重性的关系的字段也将被标记为列表。

在查询或突变中,列表字段必须用方括号([])括起来,而列表中的每项遵守与上面标量列出的相同的格式化规则:listString: ["a string", "another string"], listInt: [12, 24]

必须(Required)

必须字段(有时也称为“非空”)。在创建新节点时,您需要为所需字段提供一个值,并且没有默认值。

必填字段在字段类型后使用!标记:name:String!.

字段约束

可以使用特定的字段约束来配置字段,以便将更多语义添加到数据模型中。

唯一(Unique)

设置unique可确保相关类型的两个节点对于某个字段的值不能重复。唯一的例外是null,这意味着多个节点可以具有null值而不违反约束。

典型的例子是User类型的email字段,其中假设每个user都有唯一的电子邮件地址。

注意,只有字符串字段中的前191个字符才被认为是唯一性的,并且唯一性检查是不区分大小写的。 如果前191个字符相同或仅大小写不同,则不能存储两个不同的字符串。

要将某个字段标记为唯一,只需将@unique添加到其后面:

type User {
  email: String! @unique
  age: Int!
}

对于每个使用@unique标记的字段,您都可以通过为该字段提供一个值来查询相应的节点。

例如,对于上述数据模型,您现在可以通过其电子邮件地址检索特定的用户节点:

query {
  user(where: {
    email: "alice@graph.cool"
  }) {
    age
  }
}

默认值

您可以为标量字段设置默认值。在创建期间未提供值时,将为新节点采用该值。

要为字段指定默认值,可以使用@default指令:

type Story {
  isPublished: Boolean @default(value: "false")
  someNumber: Int! @default(value: "42")
  title: String! @default(value: "My New Post")
  publishDate: DateTime! @default(value: "2018-01-26")
}

请注意,必须使用双引号提供默认值,即使对于非字符串类型(例如Boolean或Int)也是如此

系统字段

三个字段idcreatedAtupdatedAt都有特殊的含义。 它们在您的数据模型中是可选的,但将始终保留在底层数据库中。 这样,您可以随后将字段添加到数据模型中,并且数据将可用于现有节点。

这些字段的值目前在GraphQL API中是只读的(除了导入数据时).

请注意,您不能拥有称为idcreatedAtupdatedAt的自定义字段,因为这些字段名称是为系统字段保留的。以下是这三个字段唯一支持的声明:

id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!

id

节点在创建时会自动获取全局唯一标识符,该标识符存储在id字段中。

无论何时将id字段添加到类型定义以将其暴露在GraphQL API中,您都必须使用@unique指令对其进行标记。

id具有以下属性:

  • 由25个字母数字字符组成(字母总是小写)
  • 始终以(小写)字母c开头
  • 遵循 cuid(collision resistant unique identifiers)方案

请注意,所有对象类型都将在数据库模式中实现Node接口。这就是Node接口的样子:

interface Node {
  id: ID! @unique
}

createdAtupdatedAt

数据模型还提供了两个可以添加到类型中的特殊字段:

  • createdAt:DateTime ! 存储创建此对象类型的节点时的实际日期和时间。
  • updatedAt: DateTime! 存储上次更新此对象类型的节点时的确切日期和时间。

如果你想让你的类型公开这些字段,你可以简单地将它们添加到类型定义中,例如:

type User {
  id: ID! @unique
  createdAt: DateTime!
  updatedAt: DateTime!
}

标量字段值迁移

您可以使用updateManyXs突变为所有节点或仅特定子集迁移标量字段的值。

mutation {
  # update the email of all users with no email address to the empty string
  updateManyUsers(
    where: {
      email: null
    }
    data: {
      email: ""
    }
  )
}

向数据模型添加一个必填字段

将必需的字段添加到已包含节点的模型时,您会收到此错误消息:

You are making a field required, but there are already nodes that would violate that constraint.您正在创建一个必需的字段,但已有节点会违反该限制。

这是因为所有节点对于这个字段都是空的。

以下是添加必填字段所需的步骤:

  • 添加可选(optional)的字段。
  • 使用 updateManyXs将字段的所有节点从null变为一个非空值。
  • 现在您可以根据需要标记该字段并部署

关系

关系定义了两种类型之间连接的语义。 关系中的两种类型通过关系字段连接。 当关系可能不明确时,关系字段需要用@relation指令注释以消除它的歧义。

一个关系也可以将一个类型与自己连接起来——称为自我关系。

所需的关系

对于一对一关系字段,您可以配置它是必需的还是可选的。所需的标志在GraphQL中充当契约,该字段不能为null。因此用户地址的字段将是AddressAddress!类型。

包含必需的一对一关系字段的类型的节点只能使用嵌套突变(nested mutation),以确保相关字段不会为null

请注意,多对多关系字段始终设置为必需。例如,包含许多用户地址的字段总是使用类型[Address!]!并且不能是[Address!]类型。原因是,如果该字段不包含任何节点,将返回[],该值不为null

@relation 指令

定义类型之间的关系时,有@relation指令提供关于关系的元信息。它可以有两个参数:

  • name: 此关系的标识符(以字符串形式提供)。这个论点只有在关系不明确的情况下才需要。请注意,每次使用@relation指令时,name参数都是必需的。
  • onDelete: 指定删除行为并启用级联删除。如果具有相关节点的节点被删除,则删除行为决定了相关节点应该发生什么。该参数的输入值被定义为具有以下可能值的枚举:
    • SET_NULL(默认):将相关节点设置为null
    • CASCADE:删除相关的节点。请注意,无法将双向关系的两端设置为CASCADE

以下是使用@relation指令的数据模型示例:

type User {
  id: ID! @unique
  stories: [Story!]! @relation(name: "StoriesByUser" onDelete: CASCADE)
}

type Story {
  id: ID! @unique
  text: String!
  author: User @relation(name: "StoriesByUser")
}

本例中的删除行为如下所示:

  • 当用户节点被删除时,其所有相关的Story节点也将被删除。
  • Story节点被删除时,它将被简单地从相关User节点上的Story列表中删除。

省略@relation指令

在最简单的情况下,如果两种类型之间的关系是明确的并且应该应用缺省删除行为(SET_NULL),则相应的关系字段不必使用@relation指令进行注释。

这里我们定义了UserStory类型之间的双向一对多关系。由于没有提供onDelete,所以使用了SET_NULL默认的删除行为:

type User {
  id: ID! @unique
  stories: [Story!]!
}

type Story {
  id: ID! @unique
  text: String!
  author: User
}

删除行为如下:

  • 当用户节点被删除时,其所有相关Story节点上的author字段将被设置为null。请注意,如果author字段被标记为必需,则操作会导致错误。
  • Story节点被删除时,它将被简单地从相关User节点上的Story列表中删除。

使用@relation指令的name参数

在某些情况下,您的数据模型可能包含不明确的关系。例如,考虑你不仅想要一个关系来表达UserStory之间的“作者关系”,而且你还需要一个关系来表达一个用户喜欢哪个故事节点。

在这种情况下,UserStory之间会有两种不同的关系!为了消除它们的歧义,你需要给关系一个名字:

type User {
  id: ID! @unique
  writtenStories: [Story!]! @relation(name: "WrittenStories")
  likedStories: [Story!]! @relation(name: "LikedStories")
}

type Story {
  id: ID! @unique
  text: String!
  author: User! @relation(name: "WrittenStories")
  likedBy: [User!]! @relation(name: "LikedStories")
}

如果在这种情况下未提供name参数,则无法确定书写的作品是否应与authorlikedBy字段相关联。

使用@relation指令的onDelete参数

如上所述,您可以为相关节点指定专门的删除行为。这就是@relation指令的onDelete参数所要做的。

考虑下面的例子:

type User {
  id: ID! @unique
  comments: [Comment!]! @relation(name: "CommentAuthor", onDelete: CASCADE)
  blog: Blog @relation(name: "BlogOwner", onDelete: CASCADE)
}

type Blog {
  id: ID! @unique
  comments: [Comment!]! @relation(name: "Comments", onDelete: CASCADE)
  owner: User! @relation(name: "BlogOwner", onDelete: SET_NULL)
}

type Comment {
  id: ID! @unique
  blog: Blog! @relation(name: "Comments", onDelete: SET_NULL)
  author: User @relation(name: "CommentAuthor", onDelete: SET_NULL)
}

我们来研究这三种类型的删除行为:

  • User节点被删除时,
    • 所有相关的Comment节点将被删除。
    • 相关的Blog节点将被删除。
  • Blog节点被删除
    • 所有相关的Comment节点将被删除。
    • 相关的User节点将其Blog字段设置为null
  • Comment节点被删除时,
    • 相关的Blog节点将继续存在,并将删除的Comment节点从其comments列表中删除。
    • 他相关User节点将继续存在,并将删除的Comment节点从其comments列表中删除。

为关系生成API操作

包含在您的schema中的关系会影响GraphQL API中的可用操作。对于每一个关系,

GraphQL指令

指令用于在数据模型中提供附加信息。它们看起来像这样:@name(argument: "value")或者当没有参数时只是@name

数据模型指令

数据模型指令描述了关于GraphQL schema中的类型或字段的附加信息。

唯一标量字段

@unique指令将标量字段标记为唯一(unique)。唯一字段将在底层数据库中应用唯一索引。

# the `User` type has a unique `email` field
type User {
  email: String @unique
}

关系字段

可以将指令@relation(name:String,onDelete:ON_DELETE!=NO_ACTION)附加到关系字段。

标量字段的默认值

指令@default(value: String!)为标量字段设置默认值。请注意,value参数对于所有标量字段都是String类型(即使字段本身不是字符串):

# the `title`, `published` and `someNumber` fields have default values `New Post`, `false` and `42`
type Post {
  title: String! @default(value: "New Post")
  published: Boolean! @default(value: "false")
  someNumber: Int! @default(value: "42")
}

临时指令

临时指令用于执行一次性迁移操作。部署包含临时指令的服务后,需要从类型定义文件中手动删除它。

重命名类型或字段

临时指令@rename(oldName: String!)用于重命名类型或字段。

# renaming the `Post` type to `Story`, and its `text` field to `content`
type Story @rename(oldName: "Post") {
  content: String @rename(oldName: "text")
}

如果没有使用rename指令,Prisma会在创建新类型和字段之前删除旧类型和字段,导致数据丢失!

命名约定

您在Prisma服务中遇到的不同对象(如类型或关系)遵循单独的命名约定来帮助区分它们。

类型

类型名称决定派生查询和变异的名称以及嵌套变异的参数名称。类型名称只能包含字母和数字,并且需要以大写字母开头。它们最多64个字符

建议以单数形式选择类型名称。

类型名称在服务级别上是唯一的。

示例:

  • Post
  • PostCategory

标量和关系字段

标量字段的名称用于查询和突变的查询参数中。字段名称只能包含字母和数字,并且需要以小写字母开头。它们最多可以包含64个字符

关系字段的名称遵循相同的约定,并确定关系突变的参数名称。

建议只为列表字段选择复数名称。

字段名称在类型级别上是唯一的。

示例:

  • name
  • email
  • categoryTags

关系

关系名称只能包含字母和数字,并且需要以大写字母开头。它们最多可以包含64个字符。 关系名称在服务级别上是唯一的。

示例:

  • UserOnPost , UserPosts , PostAuthor, 字段名称为userposts`
  • Appointments , EmployeeOnAppointment , AppointmentEmployee , 字段名称为 employeeappointments

枚举

枚举值只能包含字母、数字和下划线,并且需要以大写字母开头。枚举值的名称可以用于查询过滤器和突变。它们最多可以包含191个字符

枚举名称在服务级别上是唯一的。

枚举值名称在枚举级别上是唯一的。

示例

  • A
  • ROLE_TAG
  • RoleTag

更多SDL功能

在本节中,我们将介绍尚未支持用Prisma进行数据建模的更多SDL功能。

接口

“与许多类型的系统一样,GraphQL支持接口。接口是一种抽象类型,包含一组必须包含的用于实现接口的字段。”——官方GraphQL文档

Union

“联合类型与接口非常相似,但它们不能指定类型之间的任何公共字段。”——官方GraphQL文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 本文属使用Prisma构建GraphQL服务系列。 应用层和数据库层分离 两个GraphQL API层 在使用Pr...
    guog阅读 1,132评论 3 0
  • 本文属使用Prisma构建GraphQL服务系列。 当搞定了GraphQL服务端开发,且经过充分测试,那么接着需要...
    guog阅读 2,585评论 0 1
  • 本文属使用Prisma构建GraphQL服务系列。 概述 服务定义文件prisma.yml具有以下根属性: dat...
    guog阅读 2,315评论 0 0
  • 本文属使用Prisma构建GraphQL服务系列。 本教程学习如何使用Prisma对数据库生成GraphQL AP...
    guog阅读 7,035评论 2 3
  • 本文属使用Prisma构建GraphQL服务系列。 本文介绍如何使用typescript开发prisma服务。将使...
    guog阅读 2,775评论 0 2