服务端开发
服务端开发
服务端开发
基础服务
express-graphql
最简单的构建 GraphQL API 服务器的方式就是基于 Express,添加自定义的处理器:
import express from "express";
import graphqlHTTP from "express-graphql";
import { buildSchema } from "graphql";
// Construct a schema, using GraphQL schema language
const schema = buildSchema(`
type Query {
hello: String
}
`);
// The root provides a resolver function for each API endpoint
const root = {
hello: () => "Hello world!"
};
const app = express();
app.use(
"/graphql",
graphqlHTTP({
schema,
rootValue: root,
graphiql: true
})
);
// http://localhost:4000/graphql 进入 GraphiQL 交互查询工具
app.listen(4000);
Prisma 是非常不错的全栈架构,开发者只需要定义好数据结构,Prisma 即能够为我们自动构建包含数据库(Docker)的 GraphQL API,Prisma 也为我们提供了便捷的云化部署方案,较为适合个人项目。
Apollo Server
const schema = `
type Todo {
...
}
type TodoList {
todos: [Todo]
}
type Query {
todoList: Todo List
}
type Mutation {
addTodo(
text: String!
): Todo,
toggleTodo(
id: String!
): Todo
}
type Subscription {
todoUpdated: Todo
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
`;
const resolvers = {
TodoList: {
todos() {
return todos;
}
},
Query: {
todoList() {
return true;
}
},
Mutation: {
addTodo(_, { text }) {
...
},
toggleTodo(_, { id }, { ctx }) {
...
}
},
Subscription: {
todoUpdated: {
...
}
}
};
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers
});
router.post(
'/graphql',
graphqlKoa(ctx => ({
schema: executableSchema,
context: { ctx }
}))
)
数据模型层
GraphQL 的一大优势就是避免了单个业务逻辑与查询语句的强绑定,SQL 具备良好的声明式可读性,但是其编程可组合性较差。在 GraphQL 的图模型中,无法确定单次查询涉及的资源数目,自然也就无法预先编写出完整的 SQL 语句,而需要在程序里进行资源的获取与组合。REST 架构开发模式下,我们往往会为某个涉及多资源的查询编写复杂的关联语句:
select * from user left join asset on user.asset_id = asset.id;
而 GraphQL 中则是将复杂的逻辑划分为多个原子查询,并在编程语言中完成数据的聚合,譬如我们需要查询用户关联的资产时,其 Schema 定义如下:
type User {
asset: Asset
}
此时对于 Asset 资源,其并不需要了解完整的业务逻辑,只需要根据输入的 userId 获取到 asset 对象,对于数据的封装则是由 GraphQL 自动完成:
const user = getUserById(userId);
const asset = getAssetById(user.assetId);
user.asset = asset;
显而易见地,这种方式可能导致单次请求处理中对于某个表的多次查询,dataloader 即是 Facebook 开源的数据访问层辅助工具,其能够将多次对于数据库或者外部服务查询的语句合并处理。dataloader 的核心理念在于接收用户定义的批次加载函数:
const DataLoader = require("dataloader");
const userLoader = new DataLoader(keys => myBatchGetUsers(keys));
当我们在业务逻辑中进行多次查询时,譬如:
userLoader
.load(1)
.then(user => userLoader.load(user.invitedByID))
.then(invitedBy => console.log(`User 1 was invited by ${invitedBy}`));
// Elsewhere in your application
userLoader
.load(2)
.then(user => userLoader.load(user.lastInvitedID))
.then(lastInvited => console.log(`User 2 last invited ${lastInvited}`));
// 也可以同时加载多个数据
const [a, b] = await myLoader.loadMany(["a", "b"]);
dataloader 会把某个执行时间片(参考 EventLoop)内的独立查询合并处理,调用批量查询函数来进行单次查询;dataloader 还提供了缓存机制,当我们在某个执行时间片内查询了相同键的数据,dataloader 会自动返回缓存的 Promise 对象:
const userLoader = new DataLoader(...)
const promise1A = userLoader.load(1)
const promise1B = userLoader.load(1)
assert(promise1A === promise1B)
分页与搜索
GitHub GraphQL API,可以在 Explorer
query {
viewer {
login
name
starredRepositories(first: 3, after: "put_in_a_cursor_value_here") {
edges {
cursor
node {
id
name
primaryLanguage {
id
name
color
}
}
}
}
}
}