为了学习 GraphQL 和 Apollo,我正在开发一款 Tic Tac Toe 游戏。目前它的工作方式是服务器存储所有游戏信息,因此当客户端单击方块时,它会向服务器发送一个突变,更新 GameState 类型并返回新状态。我希望服务器能够处理所有游戏逻辑,例如检查移动是否合法、玩计算机移动、检查获胜者,然后返回新的 GameState。
我最初的想法是将所有逻辑放入突变解析器中,但在教程中我强烈建议将解析器保持尽可能小,这样看起来就不是最好的设计。我想不出另一种方法来做到这一点,Apollo 服务器通常不这样使用吗?
可能相关的代码:
架构:
const typeDefs = gql`
type GameState {
board: [[String!]!]!
status: String!
winner: String
}
type Mutation {
placeO(x: Int!, y: Int!): GameState
}
type Query {
"Query to get tracks array for the homepage grid"
getGameState: GameState
}
`;
变异解决者:
Mutation: {
placeO: (_, { x, y }, { dataSources }) => {
// check_legal_move(x, y, dataSources.state.board); ??
dataSources.state.board[x][y] = 'O';
// computer_move(dataSources.state.board); ??
return dataSources.state;
}
},
客户端突变请求:
const PLACE_O = gql`
mutation placeO($x: Int!, $y: Int!) {
placeO(x: $x, y: $y) {
board,
status,
winner,
}
}
`;
强烈建议使解析器尽可能小
这意味着:作为一般规则,GraphQL 层(与任何 API 实现层一样)应被视为“翻译器”。应该:
在您的示例中,可能看起来像这样:
const resolvers = {
Mutation: {
placeO: (_, { x, y }, { dataSources }) => {
return dataSources.game.placeMark({ x, y, value: 'O' });
}
},
}
而不是你的数据源状态,它应该是你执行业务逻辑的地方,并且它将控制从那里发生的事情。数据源将具有类似于解析器中的内容
{
placeMark({ x, y, value }) {
const state = this.getState();
// check_legal_move(x, y, state.board); ??
state.board[x][y] = 'O';
// computer_move(state.board); ??
return state;
}
}
但是Daaan,为什么我不应该在解析器中执行此操作?
好问题。对此的一个答案是单一职责原则,它会不赞成您在同一个地方进行路由、业务逻辑和状态管理。虽然遵循这一原则并不总是“最有效”、“最佳答案”,甚至在极少数情况下并不总是“正确答案”,但知道何时在应用程序设计中使用此模式将使您在整个职业生涯中受益匪浅。
我在 GraphQL 中看到的最大价值是它有助于证明 GraphQL 是“图”这一事实。你所做的事情,你代表的数据都只是连接海洋中的节点,你将添加新连接,删除旧连接,弃用事物,重新将一条线从节点 A 指向节点 B,以便现在它到达节点 C ,并且您不想担心 a) 破坏现有 API 或 b) 复制/粘贴所有业务逻辑。
假设您想要进行通用突变,这样您就不必分别进行 X 和 Os。在不破坏任何内容的情况下,您可以添加:
const resolvers = {
Mutation: {
placeO: (_, { x, y }, { dataSources }) => {
return dataSources.game.placeMark({ x, y, value: 'O' });
}
placeMark: (_, { x, y, value }, { dataSources }) => {
return dataSources.game.placeMark({ x, y, value });
}
},
}