成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Apollo GraphQL 在 webapp 中應(yīng)用的思考

weapon / 1401人閱讀

摘要:在中應(yīng)用的思考原文發(fā)表在簡介熟悉的同學(xué)可直接跳過這一章,從實(shí)踐一章看起。這也是官方建議的最佳實(shí)踐。也就是說,只有在客戶端提交了包含相應(yīng)字段的時(shí),才會(huì)真正去發(fā)送相應(yīng)的請(qǐng)求。在客戶端與服務(wù)端均不考慮緩存的情況,客戶端反而會(huì)少一個(gè)請(qǐng)求。。。

Apollo GraphQL 在 webapp 中應(yīng)用的思考

原文發(fā)表在: https://github.com/kuitos/kui...

簡介

熟悉 Apollo GraphQL 的同學(xué)可直接跳過這一章,從 實(shí)踐 一章看起。

GraphQL 作為 FaceBook 2015年推出的 API 定義/查詢 語言,在歷經(jīng)了兩年的發(fā)展之后,社區(qū)已相對(duì)發(fā)達(dá)和完善。對(duì)于 GraphQL 的一些基礎(chǔ)概念,本文不再一一贅述,目前社區(qū)相關(guān)的文章已經(jīng)很多,有興趣的同學(xué)可以去 google,或者直接看GraphQL 官方教程 Apollo GraphQL Server 官方文檔。

而 Apollo GraphQL 作為目前社區(qū)最流行的 GraphQL 解決方案提供商,提供了從 client 到 server 的一整套完整的工具鏈。在這里我也準(zhǔn)備以 Apollo 為例,通過一步步搭建 Apollo GraphQL Server 的方式,來給大家展示 GraphQL 的特點(diǎn),以及我的一些思考(主要是我的思考?)。

setup

創(chuàng)建基于 express 的 GraphQL server

// server.js
import express from "express";
import { graphiqlExpress, graphqlExpress } from "apollo-server-express";
import schema from "./models";

const PORT = 8080;
const app = express();

...
app.use("/graphql", graphqlExpress({ schema }));
app.use("/graphiql", graphiqlExpress({
    endpointURL: "/graphql"
}));

if (process.env.NODE_ENV === "development") {
    glob(path.resolve(__dirname, "./mock/**/*.js"), {}, (er, modules) => modules.forEach(module => require(module).default(app)));
}

app.listen(PORT, () => console.log(`> Listening at port ${PORT}`));

執(zhí)行 node server.js,這樣我們就能啟動(dòng)一個(gè) GraphQL server 了。

注意我們這里使用了 apollo-server-express 提供的 graphiqlExpress 插件,graphiql 是一個(gè)用于瀏覽器端調(diào)試 graphql 接口的 GUI 工具。服務(wù)啟動(dòng)后,我們?cè)跒g覽器打開 http://localhost:8080/graphiql就可以看到這樣一個(gè)頁面

定義 API schema

我們?cè)?server.js 中定義了這樣一個(gè) endpoint : app.use("/graphql", graphqlExpress({ schema }));

這里傳入的 schema 是什么呢?它大概長這樣:

import { makeExecutableSchema } from "graphql-tools";
// The GraphQL schema in string form
const typeDefs = `
  type User { 
    id: ID!
    name: String
    age: Int
  }
  type Query { user(id: ID!): User }
  schema { query: Query }
`;

// The resolvers
const resolvers = {
  Query: { user({id}) { return http.get(`/users/${id}`)}}
};

// Put together a schema
const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});

app.use("/graphql", graphqlExpress({ schema }));

這里的關(guān)鍵是用了 graphql-tools 這個(gè)庫提供的 makeExecutableSchema 組合了 schema 定義和對(duì)應(yīng)的 resolver。resolver 是 Apollo GraphQL 工具鏈中提出的一個(gè)概念,什么用呢?就是在我們客戶端請(qǐng)求過來的 schema 中的 field 如果在 GraphQL Server 中有對(duì)應(yīng)的 resolver,那么在返回?cái)?shù)據(jù)時(shí)候,這些 field 就由對(duì)應(yīng)的 resolver 的執(zhí)行結(jié)果填充(支持返回 promise)。

客戶端請(qǐng)求

這里借助 graphiql 面板的功能來發(fā)送請(qǐng)求:

看一下 http request payload 信息:

響應(yīng)體:

也就是說,無論你是用你熟悉的 http lib 還是社區(qū)的 apollo client,只要按照 GraphQL Server 要求的既定格式發(fā)請(qǐng)求就 ok 了。

這里我們使用了 GraphQL 中的 variable 語法,事實(shí)上在這種需要傳參的動(dòng)態(tài)查詢場(chǎng)景下,我們應(yīng)該總是使用這種方式發(fā)送請(qǐng)求:即一個(gè) static query + variable 的方式,而不是在運(yùn)行時(shí)動(dòng)態(tài)的生成 query string。這也是官方建議的最佳實(shí)踐。

更復(fù)雜的嵌套查詢場(chǎng)景

假設(shè)我們有這樣一個(gè)場(chǎng)景,即我們需要取到 User Entity 下的 nick 字段,而 nick 數(shù)據(jù)并不來自于 user 接口,而是需要根據(jù) userId 調(diào)用另一個(gè)接口取得。這時(shí)候我們服務(wù)端的代碼需要這樣寫。

// schema
type User {
  id: ID!
  name: String
  age: Int
  nick: String
}
// resolver
User: {
  nick({ id }) {
    return getUserNick(id);
  }
}

resolver 的參數(shù)列表中包含了當(dāng)前所在 Entity 已有的數(shù)據(jù),所以這里可以直接在函數(shù)的入?yún)⒗锶〉揭巡樵兂鰜淼?userId。

看下效果:

服務(wù)端的請(qǐng)求:

可以看到,這里多出了查詢 nick 的請(qǐng)求。也就是說,GraphQL Server 只有在客戶端提交了包含相應(yīng)字段的 query 時(shí),才會(huì)真正去發(fā)送相應(yīng)的請(qǐng)求。更多 resolver 說明可以看這里。

其他

在真實(shí)的生產(chǎn)環(huán)境中,我們通常會(huì)有更多更復(fù)雜的場(chǎng)景,比如接口的權(quán)限認(rèn)證、分頁、緩存、批量提交、schema 模塊化等需求,好在社區(qū)都有相對(duì)應(yīng)的一些解決方案,這不是本文的重點(diǎn)所以不在這里一一介紹了,有興趣的可以去看下我之前寫的 graphql-server-startkit,或者官方的 demo。

實(shí)踐

如果你真實(shí)的使用過 Apollo GraphQL,你會(huì)經(jīng)歷如下過程:

定義一個(gè) schema 用于描述查詢?nèi)肟?/p>

// schema.graphql
type User {
    id: ID!
    name: String
    nick: String
    age: Int
    gender: String
}
type Query {
    user(id: ID!): User
}
schema {
    query: Query
}

編寫 resolver 解析對(duì)應(yīng)類型

const resolvers = {
    Query: {
        user(root, { id }) {
            return getUser(id);
        }
    },
    User: {
        nick({ id }) {
            return getUserNick(id);
        }
    }
};

編寫客戶端請(qǐng)求代碼調(diào)用 GraphQL 接口,通常我們會(huì)封裝一個(gè) get 方法

function getUser(id) {
  // 以 axios 為例
  return axios.post("/graphql", { query: "query userQuery($id: ID!) {?    user(id: $id) {?    id?    name?    nick?  }?}", operationName: "userQuery", variables: {id}});
}

如果你的項(xiàng)目中加入了靜態(tài)類型系統(tǒng),那么你的代碼可能就會(huì)變成這樣:

// 以 ts 為例
interface User {
  id: number
  name: string
  nick: string
  age: number
  gender: string
}
function getUser(id: number): User {
  return axios.post("/graphql", { query: "query userQuery($id: ID!) {?    user(id: $id) {?    id?    name?    nick?  }?}", operationName: "userQuery", variables: {id}});
}

寫到這里你可能已經(jīng)發(fā)現(xiàn),不僅是 entity 類型定義,就連接口的封裝,我們?cè)诜?wù)端和客戶端都重復(fù)了一遍(雖然一個(gè)用的 GraphQL Type Language 一個(gè)用的 TS)… 這還是最簡單的場(chǎng)景,如果業(yè)務(wù)模型復(fù)雜起來,你在兩端需要重復(fù)的代碼會(huì)更多(比如類型的嵌套定義和 resolve)。這時(shí)候你可能會(huì)想起 DRY 原則,然后開始思考有沒有什么方式可以使得類型及接口定義能兩端復(fù)用,或者根據(jù)一端的定義自動(dòng)生成另一端的代碼?甚至你開始懷疑,到底有沒有引入 GraphQL 的必要?

思考

GraphQL 作為一個(gè)標(biāo)準(zhǔn)化并自帶類型系統(tǒng)的 API Layer,其工程價(jià)值我也不再過多廣告了。只是在實(shí)踐過程中,既然我們無法完全避免服務(wù)端與客戶端的實(shí)體與接口定義重復(fù)(使用 apollo-codegen 可以避免一部分),而且對(duì)于大部分小團(tuán)隊(duì)而言,運(yùn)維一個(gè) productive nodejs system 實(shí)際上都是力有未逮。那么我們是不是可以考慮在純客戶端構(gòu)建一個(gè)類 GraphQL 的 API Layer 呢?這樣既可以有效的避免編碼重復(fù),也能大大的降低對(duì)團(tuán)隊(duì)的要求,可操作的空間也比增加一個(gè) nodejs 中間層大得多。

我們可以回憶一下,通常對(duì)于一個(gè)前端而言,促使我們需要一個(gè) API Layer 的原因是什么:

后端接口設(shè)計(jì)不夠 restful,命名垃圾,用的時(shí)候看見那個(gè)*一樣的 url 就難受。

后端同學(xué)只愿意寫 microservice,提供聚合服務(wù)的 web api 被認(rèn)為沒有技術(shù)含量,不愿意寫。你需要一個(gè)數(shù)據(jù),他告訴你需要調(diào) a、b、c 三個(gè)接口,然后根據(jù) id 組裝合并。

接口返回的數(shù)據(jù)格式各種嵌套及不合理,不是前端想要的結(jié)構(gòu)。

接口返回的數(shù)據(jù)字段命名隨意或者風(fēng)格不統(tǒng)一,我有強(qiáng)迫癥用這種接口會(huì)發(fā)瘋。

后端返回的 數(shù)據(jù)格式/字段名 一旦變了,前端視圖綁定部分的代碼需要修改。

通常情況下,碰到這些問題,你可能去跟后端同學(xué)據(jù)理力爭,要求他們提供調(diào)用體驗(yàn)更良好設(shè)計(jì)更優(yōu)雅的接口。沒錯(cuò)這很好,畢竟為了追求完美去跟各種人撕(跟后端撕、跟產(chǎn)品撕、跟UI撕)是一個(gè)前端工程師基本的職業(yè)素養(yǎng)。但是如果你每天都被撕逼弄得心力交瘁,甚至是你根本找不到撕的對(duì)象(比如數(shù)據(jù)來源接口來著幾個(gè)不同部門,甚至是一些祖?zhèn)鞯臎]人敢動(dòng)的接口),這些時(shí)候大概就是你迫切希望有一個(gè) API Layer 的時(shí)候了。

如何在客戶端實(shí)現(xiàn)一個(gè) API Layer

其實(shí)很簡單,你只需要在客戶端把 Apollo Server 中要寫的 resolvers 寫一遍,然后配上一些性能提升手段(如緩存等),你的 API Layer 就完成了。

比如我們?cè)?b>src下新建一個(gè) loaders/apis 目錄,所有的數(shù)據(jù)拉取接口都放在這里。比如這樣:

// UserLoader.ts
export interface User {
  id: number
  name: string
  nick: string
}

export default class UserLoader {
  
  async getUser(id: number): User {
    const base = await Promise.all([http.get("http://xxx.com/users/${id}"), this.getUserNick(id)]);
    const user = base.reduce((acc, info) => ({...acc, ...info}), {});
    return user;
  }
  
  getUserNick(id: number): string {
    return http.get(`//xxx.com/nicks/${id}`);
  }
}

然后在你業(yè)務(wù)需要的地方注入相應(yīng) loader 調(diào)用接口即可,如:

import { inject } from "mmlpx";
import UserLoader from "./UserLoader";
// Controller.ts
export default class Controller {
  
  @inject(UserLoader)
  userLoader = null;
  
  async doSomething() {
    // ...
    const user = await this.userLoader.getUser(this.id);
    // ...
  }
}

如果你不喜歡依賴注入的方式,loaders/apis 層直接 export function getUser 也可以。

如果你碰到了上面描述的第 3、4 、5 三種問題,你可能還需要在這一層做一下數(shù)據(jù)格式化。比如這樣:

async getUser(id: number): User {
  const base = await Promise.all([http.get("http://xxx.com/users/${id}"), this.getUserNick(id)]);
  const user = base.reduce((acc, info) => ({...acc, ...info}), {});
  
  return {
    id: user.id,
    name: user.user_name, // 重命名字段
    nick: user.nick.userNick  // 剔除原始數(shù)據(jù)中無意義的層次結(jié)構(gòu)
  };
}

經(jīng)過這一層的數(shù)據(jù)處理,我們就能確保我們的應(yīng)用運(yùn)行在前端自己定義的數(shù)據(jù)模型之下。這樣之后后端接口不論是數(shù)據(jù)結(jié)構(gòu)還是字段名的變更,我們只需要在這一層做簡單調(diào)整即可,而不會(huì)影響到我們上層的業(yè)務(wù)及視圖。相應(yīng)的,我們的業(yè)務(wù)層邏輯不再會(huì)直接對(duì)接接口 url,而是將其隱藏在 API Layer 下,這樣不僅能提升業(yè)務(wù)代碼的可讀性,也能做到眼不見為凈。。。

總結(jié)

熟悉 GraphQL 的同學(xué)可能會(huì)很快意識(shí)到,我這不過是在客戶端做了一個(gè)簡單的 API 封裝嘛,并不能解決在 GraphQL 出現(xiàn)之前的 lots of roundtrips 及 overfetching 問題。但事實(shí)上是 roundtrip 的問題我們可以通過客戶端緩存來緩解(如果你用的是 axios 你可能需要 axios-extensions ),而且 roundtrip 的問題其實(shí)本質(zhì)上我們不過是將客戶端的 http 開銷轉(zhuǎn)移到服務(wù)端了而已。在客戶端與服務(wù)端均不考慮緩存的情況,客戶端反而會(huì)少一個(gè)請(qǐng)求。。。overfetching 問題則取決于 backend service 的粒度,如果 endpoint 不夠 micro,即便是 GraphQL,也會(huì)出現(xiàn)接口數(shù)據(jù)冗余問題,畢竟 GraphQL 不生產(chǎn)數(shù)據(jù),它只是數(shù)據(jù)的搬運(yùn)工。。。而如果 endpoint 粒度足夠小,那么我在客戶端 API 層多開幾個(gè)接口(換成 Apollo 也要多寫幾個(gè) resolver),一樣可以按需取數(shù)據(jù)。服務(wù)端 API Layer 只有一個(gè)不可替代的優(yōu)勢(shì)就是,如果我們的數(shù)據(jù)源接口是不支持跨域或者僅內(nèi)網(wǎng)可見的,那么就只能在服務(wù)端開個(gè)口子做代理了。另外一個(gè)優(yōu)勢(shì)就是,GraphQL Server 的 http 開銷是可控的,畢竟機(jī)器是我們自己控制,而客戶端的環(huán)境則不可控(http 開銷受終端設(shè)備及網(wǎng)絡(luò)環(huán)境影響,比如低版本瀏覽器或者低速網(wǎng)絡(luò),均會(huì)導(dǎo)致 http 開銷的性能權(quán)重增大)。

可能有同學(xué)會(huì)說,服務(wù)端 API Layer 部署一次任何系統(tǒng)都可以共享其服務(wù),而客戶端 API Layer 的作用域只在某一項(xiàng)目。其實(shí),如果我們把某一項(xiàng)目需要共享的 API Layer 打成一個(gè) npm 包發(fā)布出去,不也能達(dá)到同樣的效果嗎,很多平臺(tái)的 js sdk 不都是這個(gè)思路么(這里只討論 web 開發(fā)范疇)。

在我看來,不論你是否會(huì)搭建一個(gè)服務(wù)端的 API Layer,我們其實(shí)都需要有一個(gè)客戶端 API Layer 從數(shù)據(jù)源頭來保證客戶端數(shù)據(jù)的模型統(tǒng)一及一致性,從而有足夠的能力應(yīng)對(duì)接口的變遷。如果你考慮的再遠(yuǎn)一點(diǎn),在 API Layer 服務(wù)的業(yè)務(wù)模型層,我們同樣需要有一套獨(dú)立的 Service/Model Layer 來應(yīng)對(duì)視圖框架的變遷。這個(gè)暫且按下不表,后面會(huì)再寫篇文字來詳細(xì)說一下我的思路。

事實(shí)上,對(duì)于大部分團(tuán)隊(duì)而言,客戶端 API Layer 已經(jīng)夠用了,增加一層 GraphQL 并不是那么必要。而且如果沒有很好的支持將客戶端接口轉(zhuǎn)換成 GraphQL Schema 和 resolver 的工具時(shí),我們并不能很愉快的 coding,畢竟兩端重復(fù)的工作還是有點(diǎn)多。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/92152.html

相關(guān)文章

  • GraphQL學(xué)習(xí)之實(shí)踐篇

    摘要:前言兩篇文章學(xué)完了基礎(chǔ)篇原理篇,接下去便是實(shí)踐的過程,這個(gè)實(shí)踐我們使用了如下技術(shù)棧去實(shí)現(xiàn)一套任務(wù)管理系統(tǒng),源碼就不公開了等穩(wěn)定后再發(fā)布。后續(xù)我所在的公司網(wǎng)關(guān)團(tuán)隊(duì)會(huì)持續(xù)實(shí)踐,爭取貢獻(xiàn)出更多的解決方案。前言 兩篇文章學(xué)完了GraphQL(基礎(chǔ)篇, 原理篇),接下去便是實(shí)踐的過程,這個(gè)實(shí)踐我們使用了如下技術(shù)棧去實(shí)現(xiàn)一套任務(wù)管理系統(tǒng),源碼就不公開了, 等穩(wěn)定后再發(fā)布。效果如下: showImg(ht...

    Drinkey 評(píng)論0 收藏0
  • GraphQLApollo 為什么能幫助你更快地完成開發(fā)需求?

    摘要:開發(fā)者體驗(yàn)可以幫助團(tuán)隊(duì)更快地實(shí)現(xiàn)功能上線,因?yàn)樗鼘?duì)開發(fā)者的體驗(yàn)非常好。可以顯示每個(gè)的埋點(diǎn)指標(biāo),可以幫忙你定位錯(cuò)誤,可以分析中請(qǐng)求的每個(gè)字段的分布頻率。產(chǎn)品案例雖然規(guī)范是由在年公布的,但是自年以來,就是移動(dòng)應(yīng)用開發(fā)的重要組成部分。 在大前端應(yīng)用的開發(fā)過程中,如何管理好數(shù)據(jù)是一件很有挑戰(zhàn)的事情。后端工程師需要聚合來自多個(gè)數(shù)據(jù)源的數(shù)據(jù),再分發(fā)到大前端的各個(gè)端中,而大前端工程師需要在實(shí)現(xiàn)用戶體...

    LucasTwilight 評(píng)論0 收藏0
  • 全棧 React + GraphQL 教程(一)

    摘要:然而,盡管使用有諸多好處,但邁出第一步可能并不容易。為了簡化初始教程,我們今天只構(gòu)建一個(gè)簡單的列表視圖。是我們將在本教程系列中使用的客戶端的名稱。我們將列表組件命名為。在本教程的其余部分中,你將了解到我們構(gòu)建一個(gè)真正的通信應(yīng)用的基礎(chǔ)。 首發(fā)于眾成翻譯 Part 1——前端:使用 Apollo 聲明式地請(qǐng)求和 mock 數(shù)據(jù) showImg(http://p0.qhimg.com/t0...

    luxixing 評(píng)論0 收藏0
  • Graphql實(shí)踐——像axios一樣使用Graphql

    摘要:初始化項(xiàng)目使用初始化項(xiàng)目安裝項(xiàng)目結(jié)構(gòu)如下接口所有接口對(duì)封裝接下來對(duì)進(jìn)行封裝,加上中間件實(shí)現(xiàn)類似于攔截器的效果。 Graphql嘗鮮 在只學(xué)習(xí)graphql client端知識(shí)的過程中,我們常常需要一個(gè)graphql ide來提示graphql語法,以及實(shí)現(xiàn)graphql的server端來進(jìn)行練手。graphql社區(qū)提供了graphiql讓我們使用 graphiql (npm):一個(gè)交互...

    mumumu 評(píng)論0 收藏0
  • GraphQL 技術(shù)棧揭秘

    摘要:關(guān)注業(yè)務(wù),而不是技術(shù)將數(shù)據(jù)需求放在它們所屬的客戶端。技術(shù)棧中的每一部分都起著作用技術(shù)棧中所有部分之間的協(xié)作可以借助緩存來完成?,F(xiàn)在,我們來看看另一個(gè)貫穿整個(gè)技術(shù)棧的功能的例子。你可以認(rèn)為是首個(gè)內(nèi)置細(xì)粒度查看的技術(shù)。 本文整理自2017年 GraphQL 峰會(huì)上的演講,詳述緩存、追蹤、模式拼接和 GraphQL 未來發(fā)展等有關(guān)話題。 Facebook 開源 GraphQL 至今已兩年有余...

    zzbo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<