介绍:架构

本文属使用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封装在应用层上。

你可能感兴趣的:(介绍:架构)