arrow-left

All pages
gitbookPowered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

WebSocket

hashtag
C. WebSocket

    WebSocket: {
      // TODO: WIP
    },

hashtag

Protocol Plugin

  protocol: {

μ΄ν•˜ protocol ν•­λͺ©μ— 외뢀에 μ œκ³΅ν•˜λ €λŠ” APIλ₯Ό μž‘μ„±ν•˜κ³  call, publish, subscribe, map 컀λ„₯터에 λ§΅ν•‘ν•©λ‹ˆλ‹€. 각 컀λ„₯터에 λŒ€ν•œ 좔가적인 λ‚΄μš©μ€ μ•„λž˜ Connectors for API Handler μ„Ήμ…˜μ—μ„œ λ‹€λ£Ήλ‹ˆλ‹€.

REST

hashtag
A. REST

REST API λ§΅ν•‘μ—λŠ” subscribeλ₯Ό μ œμ™Έν•œ call, publish, map 컀λ„₯ν„°λ₯Ό 이용 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

basePathλ₯Ό 기반으둜 μ΄ν•˜ REST μ—”λ“œν¬μΈνŠΈκ°€ μƒμ„±λ©λ‹ˆλ‹€.

description은 λ¬Έμ„œ μƒμ„±μ‹œ ν™œμš©λ˜λ©° Markdown을 μ§€μ›ν•©λ‹ˆλ‹€ (μ˜΅μ…˜).

Call

GET /players/1 μš”μ²­μ΄ player.get μ•‘μ…˜μ„ { id: 1 } νŽ˜μ΄λ‘œλ“œμ™€ ν•¨κ»˜ ν˜ΈμΆœν•˜κ³  μ„±κ³΅μ‹œ κ·Έ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

depreactedλŠ” λ¬Έμ„œ μƒμ„±μ‹œ ν™œμš©λ©λ‹ˆλ‹€ (μ˜΅μ…˜).

라우트 pathλ₯Ό κ΅¬μ„±ν•˜λŠ” κ·œμΉ™μ€ λ₯Ό μ°Έκ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GET /players/me μš”μ²­μ΄ player.get μ•‘μ…˜μ„ { id: <인증 μ»¨ν…μŠ€νŠΈμ˜ player.id> } μ •λ³΄λ‘œλΆ€ν„° νŽ˜μ΄λ‘œλ“œμ™€ ν•¨κ»˜ ν˜ΈμΆœν•˜κ³  μ„±κ³΅μ‹œ κ·Έ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

Map

λ˜λŠ” map 컀λ„₯ν„° (Inline JavaScript Function String)λ₯Ό 톡해 인증 μ»¨ν…μŠ€νŠΈμ˜ player 객체λ₯Ό λ°”λ‘œ λ°˜ν™˜ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이후에 λ‹€μ‹œ λ‹€λ£¨λŠ” Inline JavaScript Function String은 API Gateway의 Node.js VM μƒŒλ“œλ°•μŠ€μ—μ„œ ν•΄μ„λ©λ‹ˆλ‹€.

Publish

POST /players/1 (body: { message: "blabla" }) μš”μ²­μ€ player.message 이벀트λ₯Ό { userId: id: <인증 μ»¨ν…μŠ€νŠΈμ˜ player.id>, message: "blabla" } νŽ˜μ΄λ‘œλ“œμ™€ ν•¨κ»˜ publishν•˜κ³  μ„±κ³΅μ‹œ λ°œμ†‘λœ νŽ˜μ΄λ‘œλ“œλ₯Ό μ‘λ‹΅ν•©λ‹ˆλ‹€.

Params

REST API의 params λ§΅ν•‘μ—λŠ” @path, @body, @query, @context 객체λ₯Ό 이용 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

hashtag

    REST: {
      basePath: "/players",
      description: "player service REST API",
      routes: [
path-to-regexparrow-up-right
        {
          method: "GET",
          path: "/:id",
          deprecated: false,
          description: "Get player information by id",
          call: {
            action: "player.get",
            params: {
              id: "@path.id",
            },          
          },
        },
        {
          method: "GET",
          path: "/me",
          deprecated: false,
          description: "Get player information of mine",
          call: {
            action: "player.get",
            params: {
              id: "@context.user.player.id",
            },          
          },
        },
        {
          method: "GET",
          path: "/me",
          deprecated: false,
          description: "Get player information of mine",
          map: `({ path, query, body, context }) => context.user.player`,
        },
        {
          method: "POST",
          path: "/message",
          deprecated: false,
          description: "Push notifications to all players",
          publish: {
            event: "player.message",
            broadcast: false,
            params: {
              userId: "@context.user.player.id",
              message: "@body.message",
            },
          },
        },
      ],
    },
// @body 객체 전체λ₯Ό νŽ˜μ΄λ‘œλ“œλ‘œ μ „λ‹¬ν•˜κ±°λ‚˜ μŠ€νŠΈλ¦Όμ„ 전달 ν•  λ•Œ μ΄μš©λ©λ‹ˆλ‹€.
params: "@body",

// @ λ¬Έμžμ—΄λ‘œ μ‹œμž‘λ˜μ§€ μ•ŠλŠ” 값듀은 ν•΄μ„λ˜μ§€ μ•Šκ³  κ·ΈλŒ€λ‘œ μ „λ‹¬λ©λ‹ˆλ‹€.
params: {
  foo: "@path.foo", // will bar parsed
  bar: "query.bar", // will be "query.bar"
  zzz: ["any", { obj: "ject", can: "be", "use": 2 }],
},

// 항상 string νƒ€μž…μ„ κ°–λŠ” @query, @path 객체의 속성듀에 ν•œν•΄μ„œ νƒ€μž…μ„ booleanμ΄λ‚˜ number둜 λ³€ν™˜ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
params: {
  foo: "@path.foo:number",
  bar: "@query.bar:boolean",
},

GraphQL

hashtag
B. GraphQL

GraphQL API λ§΅ν•‘μ—λŠ” call, publish, subscribe, map 컀λ„₯ν„°λ₯Ό 이용 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

TypeDefs

GraphQL ν”„λ‘œν† μ½œμ—μ„œ typeDefs 속성에 μ„œλΉ„μŠ€μ— ν•„μš”ν•œ μ •μ˜(scalarλ₯Ό μ œμ™Έν•œ νƒ€μž…, μΈν„°νŽ˜μ΄μŠ€, μ—΄κ±°ν˜• λ“± λͺ¨λ“  ν˜•νƒœ)을 μΆ”κ°€ν•˜κ±°λ‚˜ κΈ°μ‘΄ νƒ€μž…(API Gatewayμ—μ„œ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ νƒ€μž…κ³Ό λΆ„μ‚° μ„œλΉ„μŠ€μ—μ„œ μ œκ³΅ν•œ νƒ€μž…λ“€)을 ν™•μž₯ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Resolvers

μ΄ν•˜ 리쑸버에 각 νƒ€μž…λ“€μ˜ ν•„λ“œλ₯Ό call, publish, subscribe, map 컀λ„₯터에 λ§΅ν•‘ν•©λ‹ˆλ‹€.

리쑸버가 ν• λ‹Ήλ˜μ§€ μ•Šμ€ ν•„λ“œλ“€μ€ source κ°μ²΄μ—μ„œ λ™μΌν•œ μ΄λ¦„μ˜ μ†μ„±μœΌλ‘œλΆ€ν„° μ£Όμž…λ©λ‹ˆλ‹€.

Call

GraphQL API의 Query 및 Mutation νƒ€μž…μ˜ ν•„λ“œλ“€μ—λŠ” publish 및 call λ˜λŠ” map 컀λ„₯ν„°λ₯Ό 이용 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. params λ§΅ν•‘μ—λŠ” @source, @args, @context, @infoλ₯Ό 이용 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Map

GraphQL ν”„λ‘œν† μ½œμ—μ„œ map 컀λ„₯ν„° (Inline JavaScript Function String)λŠ” κ°„λž΅ν•˜κ²Œ field: { map: <FN_STRING> } λŒ€μ‹ μ— field: <FN_STRING> λ°©μ‹μœΌλ‘œ μž‘μ„± ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μœ„μ²˜λŸΌ Union, Interface κ΅¬ν˜„ νƒ€μž…μ„ ν•΄μ„ν•˜κΈ° μœ„ν•œ 특수 ν•„λ“œμ—λ„ Inline JavaScript Function Stringλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

Batched Call (DataLoader)

μœ„μ²˜λŸΌ 인증 정보λ₯Ό ν¬ν•¨ν•œ @contextλ‚˜ GraphQL ν•„λ“œ 인자인 @argsλ₯Ό ν™œμš©ν•΄ λ™μΌν•œ μ•‘μ…˜μ„ μ„œλ‘œ λ‹€λ₯Έ λ°©μ‹μœΌλ‘œ λ§΅ν•‘ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ call λ©”μ†Œλ“œλŠ” GraphQL μš”μ²­μ—μ„œ λ°œμƒν•˜κΈ° μ‰¬μš΄ N+1 쿼리λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ μš”μ²­μ„ 배치둜 처리 ν•  수 μžˆλ„λ‘ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. (ref. )

ν•œ μ»¨ν…μŠ€νŠΈμ—μ„œ μ—¬λŸ¬λ²ˆ ν˜ΈμΆœλ˜λŠ” μ•‘μ…˜μ— 배칭을 μ§€μ›ν•˜λ©΄ 응닡 속도λ₯Ό 획기적으둜 λ†’νž 수 μžˆμŠ΅λ‹ˆλ‹€. 배칭을 ν™œμ„±ν™”ν•˜κΈ° μœ„ν•΄μ„œλŠ” call 컀λ„₯ν„°μ˜ batchedParams ν•„λ“œμ— 배치 μ²˜λ¦¬κ°€ κ°€λŠ₯ν•œ ν•„λ“œμ˜ 이름을 μž‘μ„±ν•˜κ³ , μ—°κ²°λœ μ„œλΉ„μŠ€ μ•‘μ…˜μ΄ λ°°μ—΄λ‘œ λ“€μ–΄μ˜€λŠ” 인자 λ¬ΆμŒμ„ 처리 ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

μœ„μ™€ 같은 GraphQL μš”μ²­μ€ player.get μ•‘μ…˜μ„ { id: [context.user.player.id, 1, 2, 3], ...(other common params) } νŽ˜μ΄λ‘œλ“œμ™€ ν•¨κ»˜ ν•œλ²ˆλ§Œ ν˜ΈμΆœν•˜κ²Œ λ©λ‹ˆλ‹€. μ—°κ²°λœ μ•‘μ…˜μ΄ [{ ... }, { ... }, { ... }, { ... }] 묢음으둜 응닡을 μ£Όλ©΄ 각 ν•„λ“œμ— ν•΄λ‹Ήν•˜λŠ” 응닡이 ν• λ‹Ήλ©λ‹ˆλ‹€.

λ§Œμ•½ id: 3인 ν”Œλ ˆμ΄μ–΄κ°€ μ—†λŠ” 경우 배치 μš”μ²­μ„ μ²˜λ¦¬ν•˜λŠ” κ³Όμ •μ—μ„œ μ—λŸ¬λ₯Ό λ°œμƒμ‹œμΌœ μ œμ–΄ 흐름을 λ©ˆμΆ”λŠ” λŒ€μ‹ μ—, μ—λŸ¬λ₯Ό λ°œμƒν‚€μ§€μ•Šκ³  배치 응닡에 ν¬ν•¨μ‹œν‚€κ³  λ‚˜λ¨Έμ§€ μ œμ–΄ 흐름을 λ§ˆλ¬΄λ¦¬ν•©λ‹ˆλ‹€. [{ ... }, { ... }, { ... }, { message: "...", isBatchError: true, ... }] 처럼 isBatchError: true 속성을 κ°–λŠ” μ—λŸ¬ 객체λ₯Ό 응닡에 ν¬ν•¨ν•©λ‹ˆλ‹€.

Subscribe

GraphQL API의 Subscription νƒ€μž…μ˜ ν•„λ“œμ—μ„œλŠ” subscribe 컀λ„₯ν„°λ₯Ό μ‚¬μš© ν•  수 μžˆμŠ΅λ‹ˆλ‹€. params λ§΅ν•‘μ—λŠ” λ§ˆμ°¬κ°€μ§€λ‘œ @source, @args, @context, @infoλ₯Ό 이용 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. @source에 이벀트 객체가 λ§΅ν•‘λ©λ‹ˆλ‹€.

@source κ°μ²΄λŠ” { event, payload }둜 κ΅¬μ„±λ©λ‹ˆλ‹€. Broker에 따라 기타 속성이 μΆ”κ°€ 될 수 μžˆμŠ΅λ‹ˆλ‹€.

subscribe 컀λ„₯ν„°μ—μ„œλŠ” μœ„μ²˜λŸΌ μˆ˜μ‹ λœ 이벀트 νŽ˜μ΄λ‘œλ“œλ₯Ό λ‹€μ‹œ map 컀λ„₯ν„°λ‘œ λ³€ν™˜ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. subscribe 컀λ„₯ν„° μ•ˆμ—μ„œ map 컀λ„₯ν„°κ°€ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” 경우 이벀트 객체 전체(source)λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

    GraphQL: {
      typeDefs: `
        """Soccer Player"""
        type Player implements Node {
          id: ID!
          email: String!
          name: String!
          photoURL: String
          position: String
          """A team player belongs to"""
          team: Team
        }

        extend type Query {
          """Current Player"""
          viewer: Player
          player(id: ID!): Player
        }

        extend type Subscription {
          playerMessage: String!
          playerUpdated: Player
        }
      `,
Dataloaderarrow-up-right
      resolvers: {
        Player: {
          team: {
            call: {
              action: "team.get",
              params: {
                id: "@source.teamId",
              },
            },
          },
          position: `({ source, args, context, info }) => source.position.toUpperCase()`,
`
          // be noted that special field __isTypeOf got only three arguments
          __isTypeOf: `({ source, context, info }) => return source.someSpecialFieldForThisType != null`,

          // be noted that special field __resolveType got only three arguments
          __resolveType: `
            ({ source, context, info }) => {
              if (source.someSpecialFieldForThisType != null) {
                return "TypeA";
              } else {
                return "TypeB";
              }
            }
          `,
        },
        Query: {
          viewer: {
            call: {
              action: "player.get",
              params: {
                id: "@context.user.player.id[]",
              },
            },
          },
          player: {
            call: {
              action: "player.get",
              params: {
                id: "@args.id[]",
              },
            },
          },
        },
query {
  viewer {
    id
    email
  }
  one: player(id: 1) {
    id
    email
  }
  two: player(id: 2) {
    id
    email
  }
  three: player(id: 3) {
    id
    email
  }
}
        Subscription: {
          playerMessage: {
            subscribe: {
              events: ["player.message"],
            },
          },
          playerMessage: {
            subscribe: {
              events: ["player.message"],
              map: `({ source, args, context, info }) => source.payload.message`,
            },
          },
        },
      },
    },