本文属使用Prisma构建GraphQL服务系列。
应用层和数据库层分离
两个GraphQL API层
在使用Prisma构建GraphQL服务器时,您需要处理两个GraphQL API:
- database layer:由Prisma负责的数据库层
- application layer:应用层负责与写入或读取数据库数据无关的任何功能(如业务逻辑,身份验证和权限,第三方集成等)。
数据库层完全通过prisma.yml
进行配置,并使用Prisma CLI进行管理。它是您的数据库接口,现在可以用GraphQL而不是SQL或其他数据库API来管理数据库。请注意,此接口适用于两个级别:
- 使用GraphQL查询,突变和订阅来读取和写入数据。
- 使用GraphQL的简洁直观的SDL(Schema Definition Language)管理数据库schema和迁移。
应用层定义了暴露给客户端的GraphQL API。你又必须要定义其schema,实现解析器(如果您使用GraphQL绑定连接Prisma)并将其部署到网络(例如使用Now,Heroku或AWS)。如果您使用JavaScript构建服务端,应用层最好使用graphql-yoga
(基于Express的简单灵活的GraphQL服务)来实现。
极少数情况下,您的后端不需要任何附加功能,只需读写数据即可,可以直接从前端连接到Prisma GraphQL API,从而省略应用层。但请记住,这意味着任何有权访问Prisma API的人都能够看到您的整个GraphQL schema。
构建GraphQL服务的另一个利器是graphql-config
,您可以在GraphQL Playground内同时与两个GraphQL API进行交互!就像Postman和Sequel Pro在同一个应用程序中一样。
Prisma不是数据库
虽然Prisma有效地代表后端技术栈中的数据库层,但实际上并不是数据库!它是对数据库层的抽象层,可以让您使用GraphQL而不是SQL或其他数据库API与数据库进行交互。使用GraphQL作为数据库抽象是使GraphQL成为通用查询语言的第一步。
这意味着可以完全掌控数据,同时享受围绕数据的大量工作流程的简化。
Prisma的另一个核心优势在于它保留了它正在抽象化的特定数据库的“特色”。一个很好的例子是时间序列数据库或地理数据库。当使用Prisma与这些类型的数据库进行交互时,您仍然可以获得期望的性能,同时将GraphQL作为简单接口。
多层架构的优点
多层架构是过去几年出现的一种架构趋势,到现在已成为设计后端基础架构的最佳实践。它背后的核心目的是在不同层次之间清晰的分离关注点。
当今的后端架构中分层的两个方向:
- 横向分层(Horizontal layers)有效地对应于微服务,其中后端的功能被分解。
- 纵向分层(Vertical layers)负责从持久层到HTTP服务器的数据流。包括数据库,ORM或其他数据访问层,API网关,各种Web服务器等组件。
与分层体系结构相反,整个后端是一个巨大的服务器应用程序。但是请注意,即使是巨石(monoliths),通常也会有某种层(如数据库,ORM和HTTP服务器) - 与分层架构的主要区别在于巨石(monoliths)中的层通常没有定义良好的接口,从而导致层之间的强耦合(这是失败的分层架构)。
实质上,只要它保持与前一个接口相同的接口,分层体系结构允许切换一层 - 只有实现发生变化。分层架构为您的架构带来更多的灵活性,长远来看更容易维护。
现实场景
如果您正在寻找可以引导您完成以下示例的分步教程,则可以在此处找到它。
应用层
为了更好地理解使用Prisma时GraphQL服务器的体系结构,我们来看一个实际的例子。假设您正在编写简单的博客应用程序的后端。你可能会想出下面的schema:
type Query {
feed: [Post!]!
post(id: ID!): Post
}
type Mutation {
createDraft(title: String!): Post!
publish(id: ID!): Post
deletePost(id: ID!): Post
}
type Post {
id: ID!
title: String!
published: Boolean!
}
该schema定义了应用层的API。称其为应用schema,它定义的API将被客户端调用。
作为示例,此schema将允许您的客户端将以下查询和突变发送到API:
query {
feed {
id
title
}
}
mutation {
createDraft(title: "I like GraphQL") {
id
}
}
作为后端开发人员的任务是实现解析器函数。由于您的schema具有五个根字段,因此您需要实现(至少)五个解析器。
通常,在这些解析器中,您可以访问数据库(或其他数据源)。这意味着您必须编写SQL查询或使用其他数据库API。使用Prisma时,解析器的实现变得简单明了,因为它极大地简化了从GraphQL解析器到实际数据库的连接 - 您只需将传入的查询转发到底层的Prisma引擎(这意味着解析器通常最终只是简单的一行)。
这是实现的样子:
const resolvers = {
Query: {
feed: (parent, args, context, info) => {
return context.db.query.posts({ where: { published: true } }, info)
},
post: (parent, args, context, info) => {
return context.db.query.post({ where: { id: args.id } }, info)
},
},
Mutation: {
createDraft: (parent, args, context, info) => {
return context.db.mutation.createPost(
{
data: {
title: args.title,
published: false,
},
},
info,
)
},
publish: (parent, args, context, info) => {
return context.db.mutation.updatePost(
{
where: { id: args.id },
data: { published: true },
},
info,
)
},
deletePost: (parent, args, context, info) => {
return context.db.mutation.deletePost({ where: { id: args.id } }, info)
},
},
}
由于Prisma bindings,每个解析器的实现几乎是微不足道。但是什么是context.db
让你能够访问Prisma API?部分答案是context
是四个标准解析器参数之一。它只是解析器链中的每个解析器都可以写入和读取的对象,所以它基本上是解析器通信的一种手段。那么,它的db
属性从哪里来?
为了回答这个问题,让我们看看如何将graphql-yoga
用作GraphQL服务器时,应用程序schema的定义和解析器实现如何联系在一起。假设上述shcema定义存储在一个名为schema.graphql
的文件中。然后,您按以下方式实例化并启动服务器:
const server = new GraphQLServer({
typeDefs: './schema.graphql', // reference to the application schema
resolvers, // the resolver implementations from above
context: req => ({
...req,
db: new Prisma({
typeDefs: prismaSchema,
endpoint: prismaEndpoint,
secret: prismaSecret,
}),
}),
})
server.start()
在实例化GraphQLServer时,可以为context
设置初始值。在这种情况下,您将一个db
属性附加到该属性上,该属性使用prisma
绑定实例进行初始化。这个prisma
实例是Prisma API的接口,它允许解析器通过调用专用绑定函数来方便地将传入查询转发到Prisma。当调用这些函数时,绑定实例将在相应的引擎下组合相应的GraphQL查询并通过HTTP发送给Prisma。
数据库层
您现在对应用层的实现方式有了深刻的理解。您定义一个GraphQL schema并实现其解析器。自从您使用Prisma bindings以来,解析器实现很简单。这允许简单地将传入查询的执行委托(delegate)给Prisma,Prisma完成查询解析的重要任务(即从/向/从数据库读取/写入)。为了这种运行方式,你显然需要一个Prisma服务,那么你如何到达那里?
每个Prisma服务都以两个文件开头:
- 一个名为
prisma.yml
的服务配置文件 - 数据模型的定义(通常在名为
datamodel.graphql
的文件中)
生成Prisma API的最小版本prisma.yml
与上面的示例一起使用,如下所示:
# 服务名称
# (会作为服务的HTTP端点的一部分)
service: blogr
# the cluster and stage the service is deployed to;
# you can choose any string for that
# (will be also part of the service's HTTP endpoint)
stage: dev
# 保护你的Prisma API;
# this is the value for the 'secret' argument when
# instantiating the 'Prisma' binding instance
secret: mysecret123
# 数据模型文件路径
datamodel: datamodel.graphql
相应的datamodel.graphql
可能如下所示:
type Post {
id: ID! @unique
title: String!
published: Boolean!
}
请注意,尽管使用了SDL,但该文件不是正确的GraphQL schema!它缺少定义实际API操作的根类型 - datamodel.graphql
仅包含数据模型中类型的定义。这个数据模型被用作生成Prisma API的基础。
有了这两个文件,您就可以部署Prisma服务了 - 您只需从包含这些文件的目录运行prisma deploy
即可。您现在可以访问的Prisma API为Post
类型提供CRUD和实时操作。以下是生成的GraphQL schema的样子:
type Query {
posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post]!
post(where: PostWhereUniqueInput!): Post
}
type Mutation {
createPost(data: PostCreateInput!): Post!
updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post
deletePost(where: PostWhereUniqueInput!): Post
}
type Subscription {
post(where: PostSubscriptionWhereInput): PostSubscriptionPayload
}
type Post implements Node {
id: ID!
title: String!
published: Boolean
}
请注意,这是schema的简化版本,为简洁起见,Input
和Payload
类型已被省略。如果你很好奇,你可以在这里找到完整的schema。
Prisma API提供了CRUD操作,这意味着您现在可以通过发送相应的查询和突变来创建,读取,更新和删除Post
元素。该API使用Prisma bindings封装在应用层上。