<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Introducing GraphQL

          共 10451字,需瀏覽 21分鐘

           ·

          2021-09-29 18:28

          GraphQL 是一種用于 API 的查詢語言, 并且提供已有數(shù)據(jù)查詢的運行時, 它誕生于 2015 年, 由 Facebook 開發(fā), 2018 年 11 月 7 日,F(xiàn)acebook 將 GraphQL 項目轉(zhuǎn)移到新成立的 GraphQL 基金會. 目前 Facebook, Twitter, Netflix, PayPal 各廠已經(jīng)在生產(chǎn)環(huán)境使用 GraphQL 了, GitHub API v4 也已全面使用 GraphQL.

          原則

          精準、可預測地返回數(shù)據(jù)

          傳統(tǒng)的 RESTful 接口, 后端傳遞多少字段, 前端就得接收多少字段. 因此, 有時候前端只需要幾個字段, 但后端返回一大串(尤其是歷史悠久的接口), 這不但對前端篩選接口字段增加了難度, 還可能會造成潛在的性能問題. 而 GraphQL 使得客戶端能夠準確地獲得它需要的數(shù)據(jù), 而且沒有任何冗余, 并且 GraphQL 篩選這些字段的過程不依賴于服務器, 而是它自己運行時.

          export const POSTS = gql`  query Posts($input: PaginationInput!) {    posts(input: $input) {      total      page      pageSize      items {        _id        title        summary      }    }  }
          @ObjectType()export class SMSModel {  @Field()  @IsMobilePhone("zh-CN")  @IsNotEmpty()  public readonly phoneNumber: string;
          @Field() @Length(6) @IsNumberString() @IsNotEmpty() public readonly smsCode: string;}

          只請求一個接口

          下面這個例子是一個經(jīng)典的 RESTful 風格接口, 可以看到一套增刪改查需要請求不同的 url, 這就導致了需要進行多個 TCP 連接. 雖然 HTTP2 提供了多路復用(同域名下所有通信都在單個連接上完成, 同個域名只需要占用一個 TCP 連接, 使用一個連接并行發(fā)送多個請求和響應)的特性. 但在網(wǎng)絡(luò)仍然較慢的移動環(huán)境下, 我們?nèi)韵MM可能的減少 HTTP 請求, GraphQL 的應用也能表現(xiàn)得足夠迅速.

          GET /postsGET /post/:idPOST /postPUT /post/:idDELETE /post/:id
          {  operationName: "Posts",  query: "...",  variables: {    input: {      page: 1,      pageSize: 10,    },  },}

          SDL(schema definition languages)

          Type Language

          GraphQL 不依賴于任何編程語言, 因為我們并不依賴于任何特定語言的句法句式, 它有自己的一套模式.

          type Language {  code: String!  name: String!  native: String!}
          type Location { geoname_id: Float! capital: String! languages: [Language!]! country_flag: String! country_flag_emoji: String! country_flag_emoji_unicode: String! calling_code: String! is_eu: Boolean! created_at: DateTime!}
          1. Language 代表 GraphQL 對象類型, 一般用來約定后端的 response.
          2. codenamenative 是 Language 類型上的字段, 這意味著你在查詢 Language 時只能查找這三個字段中的一個或多個, 查找任何其他字段將會報錯.
          3. code: String! 意味著 code 的標量是 String, 感嘆號意味著該字段是非空的, 如果后端返回改字段是空的, 也會報錯.
          4. languages: [Language!]! 意味著 languages 的類型是 Language 數(shù)組, 且該數(shù)組不能為空.
          type Query {  getPosts(input: PaginationInput!): PostModel!}
          type Mutation { createPost(input: CreatePostInput!): PostItemModel!}
          input CreatePostInput { posterUrl: String! title: String! summary: String! content: String! tags: [String!]! lastModifiedDate: String! isPublic: Boolean}

          Query 和 Mutation 是兩個內(nèi)置的特殊類型, 你可以將其理解為 RESTful 中的 GET 和 POST, 前者用于查詢, 后者用于增刪改. 雖然使用 Query 可以進行增刪改, 但為了語義化, 建議分開使用.

          第一個語句定義一個查詢, getPost 可以類比為 RESTful 接口中的路徑; 而 input 則可以類比放在 body 中的參數(shù), 它是 CreatePostInput 類型, 且是必傳的, input 類型定義一次查詢或變更中傳遞的對象參數(shù); 該查詢返回 PostModel 類型的數(shù)據(jù), 且該數(shù)據(jù)必須為非空. 第二個語句定義一次變更, 語義同理.

          Scalar

          "標量", 可以理解為 GraphQL 中字段的基礎(chǔ)類型, 默認有 Int, Float, String, Boolean, ID 五種. 有時候你需要擴展適合自己業(yè)務的標量, 每個標量需要實現(xiàn) parseValueserializeparseLiteral 三個方法, 如下是 DateScalar.

          import { Scalar, CustomScalar } from "@nestjs/graphql";import { Kind, ValueNode } from "graphql";
          @Scalar("Date")export class DateScalar implements CustomScalar<number, Date> { description = "Date custom scalar type";
          parseValue(value: number): Date { return new Date(value); // value from the client }
          serialize(value: Date): number { return value.getTime(); // value sent to the client }
          parseLiteral(ast: ValueNode): Date { if (ast.kind === Kind.INT) { return new Date(ast.value); } return null; }}

          標量的目的是能夠更加精確的確定一個字段的類型, 不過寫個新的確實比較麻煩, 好在 graphql-scalars 預設(shè)了大約 50 個標量, 比如 PositiveInt, NegativeInt, DateTime, Date, EmailAddress, HexColorCode 等等.

          Enum

          枚舉類型是一種特殊的標量, 它限制在一個特殊的可選值集合內(nèi). 這讓你能夠:

          1. 驗證這個類型的任何參數(shù)是可選值的某一個
          2. 與類型系統(tǒng)溝通, 一個字段總是一個有限值集合的其中一個值
          enum PostStatus {  DRAFT  PUBLISH}

          Interfaces

          跟許多類型系統(tǒng)一樣, GraphQL 支持接口. 一個接口是一個抽象類型, 它包含某些字段, 而對象類型必須包含這些字段, 才能算實現(xiàn)了這個接口.

          interface Common {  status_msg: String!  status_code: Int!}
          type User implements Common { id: ID! name: String! email: String! status_msg: String! status_code: Int!}

          代碼優(yōu)先

          在真實的開發(fā)中, 我們可以像上面一樣, 通過編寫 GraphQL 原生語言來創(chuàng)建 GraphQL SDL, 當然我們也可以通過代碼優(yōu)先的方式, 即通過 TypeScript 裝飾器來生成. 下面的代碼, 除了定義字段的類型, 比如 posterUrl 的類型是 String 標量, 且為非空; 還能"夾帶私貨", 比如限制 posterUrl 是 url 格式的字符串, 這樣就更加細粒度的對數(shù)據(jù)類型進行限制.

          @InputType()export class CreatePostInput {  @Field({ nullable: false })  @IsString()  @IsUrl({ protocols: ["https"], require_protocol: true })  @IsNotEmpty()  public readonly posterUrl: string;
          @Field({ nullable: false }) @IsString() @MinLength(1) @MaxLength(20) @IsNotEmpty() public readonly title: string;
          @Field({ nullable: false }) @IsString() @IsNotEmpty() public readonly summary: string;
          @Field({ nullable: false }) @IsString() @IsNotEmpty() public readonly content: string;
          @Field(() => [String], { nullable: false }) @IsArray() @IsString({ each: true }) @ArrayNotEmpty() @ArrayUnique() @IsNotEmpty() public readonly tags: string[];
          @Field({ nullable: false }) @IsString() @IsNotEmpty() public readonly lastModifiedDate: string;
          @Field({ nullable: true }) public readonly isPublic?: boolean;}

          下面的代碼則是 GraphQL 的解析器, 同樣通過注解的方式來創(chuàng)建 Query 和 Mutation:

          • @Query(() => PostItemModel) 代表著返回值為 PostItemModel 類型;
          • getPostById 定義這個查詢的名稱;
          • @Args({ name: "id", type: () => ID }) 用來定義傳參, 我需要傳遞一個字段 id, 它的標量為 ID
          @Resolver()export class PostsResolver {  constructor(private readonly postsService: PostsService) {    this.postsService = postsService;  }
          @Query(() => PostItemModel) public async getPostById(@Args({ name: "id", type: () => ID }) id: string) { return this.postsService.findOneById(id); // 處理 SQL }
          @Mutation(() => PostItemModel) @UseGuards(GqlAuthGuard) public async createPost(@Args("input") input: CreatePostInput) { return this.postsService.create(input); // 處理 SQL }}

          前端

          GrapqhQL

          GraphQL 在前端的本質(zhì)表現(xiàn)就是向你的接口, 如 https://api.example.com/graphql 上發(fā)送一個 POST 請求, 而請求的 body 就如上圖所示. 但為了更加的和 GrapqhQL 語法配合, 前端涌現(xiàn)了一些不錯的庫, 如 Facebook 自家的 relay, relay 經(jīng)歷了兩次重大迭代, 目前 Facebook 官網(wǎng)用的是最新一代, 名字叫 relay morden.

          facebook

          雖然 relay 是一個開源項目, 但它更多是為 Facebook 內(nèi)部業(yè)務服務, 因此外部人用起來比較難受. 目前最廣泛的框架則是 Apollo, 它支持基于 Hooks 的 React 前端框架, 也支持 Vue, Angular, Android 和 iOS, 也提供了基于 Node.js 的后端框架 Appolo Server.

          apollo

          fragment 是用來定義片段, 如下面的例子, 我們定義查詢一篇文章, 返回的是一篇文章實體; 修改一篇文章, 返回的也是文章修改后的實體. 這樣它們的返回值基本都是一樣的, 為了不寫多次, 可以通過 fragment 來進行提取, 簡化代碼書寫.

          第二段代碼, 請求的變更是 createPost, 它的參數(shù) input 是 CreatePostInput 類型, 且為必傳. 因為我們使用了 fragment, 因此需要將相應片段注入進來.

          第三段代碼就是真正在 jsx 中發(fā)起請求了, 通過 hooks 可以方便的處理請求體, loading, 返回值, 錯誤處理等等...

          const POST_FRAGMENT = gql`  fragment PostFragment on PostItemModel {    _id    posterUrl    title    summary    content    tags    lastModifiedDate    like    pv    isPublic    createdAt    updatedAt  }`;
          export const CREATE_ONE_POST = gql` mutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { ...PostFragment } } ${POST_FRAGMENT}`;
          const [createPost, { loading }] = useMutation< CreatePostMutation, CreatePostVars>(CREATE_ONE_POST, { onCompleted(data) { const newPost = data.createPost; enqueueSnackbar("Create success!", { variant: "success" }); }, onError() {},});

          Introspection

          在真實的開發(fā)中, 我們會在后端定義一系列的 query, mutation, input, type, enum, scalar, interface. 而 GraphQL 支持一套強大的內(nèi)省系統(tǒng), 通過內(nèi)省系統(tǒng), 我們可以反查后端設(shè)計的 schema 的集合. 內(nèi)省系統(tǒng)的另一個功能則是輔助開發(fā) GraphQL 工具, 通過查詢出來的內(nèi)部 schema, 可以搭建出強大的 IDE. 如下代碼可以查詢出 PostItemModel 這個類型的所有信息.

          {  __type(name: "PostItemModel") {    name    fields {      name      type {        name        kind      }    }  }}
          {  "data": {    "__type": {      "name": "PostItemModel",      "fields": [        {          "name": "_id",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "posterUrl",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "title",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "summary",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "content",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "tags",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "lastModifiedDate",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "like",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "pv",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "isPublic",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "createdAt",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "updatedAt",          "type": {            "name": null,            "kind": "NON_NULL"          }        },        {          "name": "prev",          "type": {            "name": "PostItemModel",            "kind": "OBJECT"          }        },        {          "name": "next",          "type": {            "name": "PostItemModel",            "kind": "OBJECT"          }        }      ]    }  }}

          安全

          生產(chǎn)環(huán)境關(guān)閉 debug

          如果開啟 debug 模式, 在出錯時會展示錯誤的堆棧信息.

          debug 模式會展示堆棧信息

          生產(chǎn)環(huán)境關(guān)閉 playground

          playground 應當作為一種輔助自測工具, 其不應該暴露到線上.

          生產(chǎn)環(huán)境關(guān)閉 introspection

          得益于自省, 可以輕松獲取到 GraphQL server 內(nèi)部的信息, 如各種類型, 標量等. 這些信息不應該在線上被三方直接通過代碼采集到.

          控制多層深度的查詢

          如下可能會造成昂貴的查詢, 重則導致后端崩潰. 可以使用 graphql-depth-limit 來指定最多查詢的層級.

          query {  author(id: 42) {    posts {      author {        posts {          author {            posts {              author {                # and so on...              }            }          }        }      }    }  }}

          控制分頁數(shù)據(jù)量

          如下最多一次將能獲取十萬條數(shù)據(jù), 顯而易見會帶來性能問題. 你可以通過 graphql-input-number 在 resolver 中限制數(shù)字的最大值.

          query {  authors(first: 1000) {    name    posts(last: 100) {      title      content    }  }}

          當然, 如果你使用了 class-validator, 也可以通過如下方式來限制.

          @InputType()export class SomeNumberInput {  @IsInt()  @Min(1)  @Max(10)  public readonly pageSize: number;}

          參考

          • 9 Ways To Secure your GraphQL API — GraphQL Security Checklist
          • GraphQL | A query language for your API


          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人aV无码精品国产一区二区 | AV乱伦小说 | 成人精品一区二区区别解析 | 视频一区二区三区在线观看 | www.17草|