介绍:架构

本文属使用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的简化版本,为简洁起见,InputPayload类型已被省略。如果你很好奇,你可以在这里找到完整的schema

Prisma API提供了CRUD操作,这意味着您现在可以通过发送相应的查询和突变来创建,读取,更新和删除Post元素。该API使用Prisma bindings封装在应用层上。

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