GraphQL을 통해 RejectionDB의 실시간 마법을 전방으로 가져오기

한 편의 글에서 우리는 RejectionDB의 내장 반응성이 Socket이 있는 채팅 응용 프로그램을 작성하는 데 어떻게 적합한지 연구했다.이오.본 논문에서 GraphQL 구독을 사용하여 RejectionDB에 접근하는 반응성을 배울 것입니다.
RejectionDB는 실시간 문서 데이터베이스입니다.MongodB와 마찬가지로 사용하기 쉽고 모드가 없습니다.또한 조회를 구독하고 데이터가 바뀔 때 알림을 받을 수 있어 실시간 응용 프로그램의 완벽한 선택이 될 수 있습니다.

You can also try the running app, or check out the code repository.



응용 프로그램 설정
우리는 노드를 세울 것이다.js 응용 프로그램이기 때문에 설치nodenpm가 필요합니다.히로쿠에 응용 프로그램을 배치하려면 Heroku account와 그들의 CLI를 설치해야 한다.응용 프로그램을 로컬에서 실행하려면 install and run a RethinkDB instance가 필요합니다.
우리는 간단한 노드를 사용할 것이다.js 서버 및 Vuejs 전단.프런트엔드를 구축해야 하므로 Vue CLI를 사용하여 Vue 애플리케이션을 만듭니다.
$ vue create -d rethink-chat
$ cd rethink-chat
이렇게 하면 노드 항목이 생성되고 Vue가 생성됩니다.js 프레임워크와git 저장소를 초기화합니다.

Heroku 어플리케이션 준비
Heroku에 응용 프로그램을 배치하려면 다음과 같은 Heroku 응용 프로그램을 만들어야 합니다.
$ heroku create
사용자 간에 보내는 채팅 정보를 저장하고 구독하기 위한 RejectDB 실례가 필요합니다.RethinkDB Cloud add-on에서 다음을 수행할 수 있습니다.
$ heroku addons:create rethinkdb

The RethinkDB Cloud add-on is currently in alpha. Request an invite for your Heroku account email.



서버 구축
우리는 server 디렉터리에 서버를 만들 것이다.먼저 디렉토리를 만들고 필요한 종속성을 설치합니다.
$ mkdir server
$ npm install rethinkdb apollo-server-express graphql morgan lorem-ipsum
이제 노드를 설정합니다.js 서버.index.js 파일을 만들고 다음 서버 프레임워크를 추가합니다.우리는 급행열차를 쓴다.js 서버는 전방에 서비스되고 Apollo GraphQL 서버는 채팅 정보를 방문하고 구독하는 데 사용됩니다.
// server/index.js

// Setup Express server
const express = require("express");
const app = express();
const http = require("http").createServer(app);

// Logging middleware
var morgan = require("morgan");
app.use(morgan("combined"));

// Serve frontend
app.use(express.static("dist"));

// Lazy RethinkDB connection
// ...

// Setup Apollo (GraphQL) server
// ...

// HTTP server (start listening)
const listenPort = process.env.PORT || "3000";
http.listen(listenPort, () => {
  console.log("listening on *:" + listenPort);
});
이 뼈대는 dist 폴더의 정적 전단이다.이것이 바로 컴파일된 Vue가 있는 위치입니다.우리는 잠시 후에 js 프로그램을 만들 것입니다.그리고 우리 서버는 세 가지 일을 해야 한다.
  • 데이터베이스
  • 연결 처리
  • 아폴로 서버 설정
  • 유형 정의와 해석기를 포함하는 GraphQL 모드 만들기

  • 데이터베이스 연결 재정의
    우리는 데이터베이스 연결을 게으름 피우며 관리한다. 즉, 우리는 실제 필요할 때만 (다시) 연결을 만든다는 것이다.연결 매개 변수는 환경 변수에서 해석되거나 기본값을 사용합니다.
    // server/index.js
    // ...
    
    // Lazy RethinkDB connection
    var r = require("rethinkdb");
    let rdbConn = null;
    const rdbConnect = async function () {
      try {
        const conn = await r.connect({
          host: process.env.RETHINKDB_HOST || "localhost",
          port: process.env.RETHINKDB_PORT || 28015,
          username: process.env.RETHINKDB_USERNAME || "admin",
          password: process.env.RETHINKDB_PASSWORD || "",
          db: process.env.RETHINKDB_NAME || "test",
        });
    
        // Handle close
        conn.on("close", function (e) {
          console.log("RDB connection closed: ", e);
          rdbConn = null;
        });
    
        console.log("Connected to RethinkDB");
        rdbConn = conn;
        return conn;
      } catch (err) {
        throw err;
      }
    };
    const getRethinkDB = async function () {
      if (rdbConn != null) {
        return rdbConn;
      }
      return await rdbConnect();
    };
    
    Heroku에서 DB Cloud 플러그인은 환경 변수를 설정합니다.로컬에서 실행되는 RejectDB 인스턴스의 경우 기본값을 사용할 수 있어야 합니다.

    Apollo GraphQL 서버 설정
    앞에서 말한 바와 같이 앞부분은 정적이다.그러나 우리는 채팅방의 데이터를 방문해야 한다.이것은 가장 자주 사용하는 GraphQL 서버 Apollo에서 처리될 것입니다.
    // server/index.js
    // ...
    
    // Setup Apollo (GraphQL) server
    const { ApolloServer } = require("apollo-server-express");
    const { typeDefs, resolvers } = require("./schema.js");
    const graphqlServer = new ApolloServer({
      typeDefs,
      resolvers,
      context: async (arg) => {
        const conn = await getRethinkDB();
        return {
          conn: conn,
        };
      },
    });
    graphqlServer.applyMiddleware({ app });
    graphqlServer.installSubscriptionHandlers(http);
    
    이것은 패턴 파일 (다음 절) 에 정의된 형식 정의와 해석을 사용하여 Apollo 서버를 만들 것입니다.또한 RejectDB에 연결하여 GraphQL 상하문에 연결을 전달하여 모든 전송 요청에서 사용할 수 있도록 합니다.

    GraphQL 모드 만들기
    서버의 주요 논리는GraphQL 유형을 정의하고 이를 실현하는 해상도입니다.우리는 세 가지 다른 조작을 실행할 수 있어야 한다. 즉,
  • 채팅방의 채팅 정보 조회
  • 방에 채팅 메시지 보내기
  • 채팅방에서 새로운 채팅 정보 구독
  • 우선, GraphQL 유형을 만듭니다.이것은 Chat 메시지 유형과 상기 세 가지 조작, 즉 chats 조회, sendChat 변이와 chatAdded 구독을 포함한다.
    // server/schema.js
    
    // GraphQL type definitions
    const { gql } = require("apollo-server-express");
    exports.typeDefs = gql`
      type Chat {
        user: String
        msg: String
        roomId: String
        ts: Float
      }
    
      type Query {
        chats(room: String!): [Chat]
      }
    
      type Mutation {
        sendChat(user: String!, message: String!, room: String!): Chat
      }
    
      type Subscription {
        chatAdded(room: String!): Chat
      }
    `;
    
    // GraphQL resolvers
    // ...
    
    그 다음으로 우리는 이 조작, 즉 호출된 코드를 실현하는 것을 해결해야 한다.조회와 변이는 매우 간단하여 간단한 조회로 실현할 수 있다.그러나 구독은 비동기 교체기가 필요합니다.기본적으로 RejectionDB 마법을GraphQL 구독 마법으로 바꿀 수 있는 주문이다.더욱 현실적인 의미에서, 비동기 교체기는 데이터베이스 변경 요약을 포장하여GraphQL을 통해 구독할 수 있도록 한다.
    // server/schema.js
    
    // GraphQL type definitions
    // ...
    
    // GraphQL resolvers
    const r = require("rethinkdb");
    exports.resolvers = {
      Subscription: {
        chatAdded: {
          async subscribe(parent, args, context, info) {
            return new RethinkIterator(
              r.table("chats").filter({ roomId: args.room }),
              context.conn,
            );
          },
        },
      },
      Mutation: {
        async sendChat(root, args, context) {
          const chatMsg = {
            user: args.user,
            roomId: args.room,
            msg: args.message,
            ts: Date.now(),
          };
          await r.table("chats").insert(chatMsg).run(context.conn);
          return chatMsg;
        },
      },
      Query: {
        async chats(parent, args, context, info) {
          const cursor = await r
            .table("chats")
            .filter({ roomId: args.room })
            .orderBy(r.desc("ts"))
            .run(context.conn);
          return await cursor.toArray();
        },
      },
    };
    
    // Async iterator to access the RethinkDB change feed
    const { $$asyncIterator } = require("iterall");
    class RethinkIterator {
      constructor(query, conn) {
        this.cursor = query.changes().run(conn);
      }
    
      async next() {
        const val = await (await this.cursor).next();
        return { value: { chatAdded: val.new_val }, done: false };
      }
    
      async return() {
        await (await this.cursor).close();
        return { value: undefined, done: true };
      }
    
      async throw(error) {
        return Promise.reject(error);
      }
    
      [$$asyncIterator]() {
        return this;
      }
    }
    
    서버를 설정한 후, 우리는 앞쪽으로 돌아갑니다.

    프런트엔드 생성
    Vue를 만들었습니다.우리는 전방의 js 응용 프로그램 프레임워크에 사용할 것입니다.그러나, 저희 서버가 표준적인GraphQL 백엔드를 실현했기 때문에, 당신은React나 다른GraphQL을 지원하는 백엔드 프레임워크를 사용할 수 있습니다.
    우리의 앞부분은 두 개의 보기를 사용할 것이다. 하나는 홈페이지에 사용되고, 하나는 채팅방에 사용되며, 하나는 공유기가 둘 사이를 내비게이션할 것이다.이를 위해 Vue 프레임워크에 라우터를 추가하고 필요한 모든 종속성을 설치할 수 있습니다.Vue 애플리케이션에 라우터를 추가하면 변경 사항이 제한되지 않음을 경고하고 사용 내역 모드가 필요한지 여부를 묻습니다.
    $ vue add router
    $ npm install apollo-client apollo-link-http apollo-link-ws apollo-cache-inmemory vue-apollo
    $ npm install sass sass-loader --save-dev
    
    Google Vue 응용 프로그램은 src 폴더에 있습니다. 입구점은 main.js에 있고 graphql.js에서GraphQL 클라이언트 설정을 가져옵니다.우리의 메인 파일은 App.vue 마운트되어 있으며, 이 중 공유기가 router/index.js 에서 선택한 보기를 표시합니다.우리의 응용 프로그램은 두 개의 보기 views/Home.vueviews/ChatRoom.vue 를 포함한다.
    src
    ├── main.js
    ├── graphql.js
    ├── App.vue
    ├── router
    │   └── index.js
    └── views
        ├── Home.vue
        └── ChatRoom.vue
    

    기본 애플리케이션 및 라우터
    우선, skeleton Vue 프로그램에서 초기화된 메인 프로그램, 메인 보기, 공유기 파일을 수정합니다.main.js에서 Apollo GraphQL 클라이언트를 가져왔습니다. 이 클라이언트를 더 정의하고 Vue 응용 프로그램에 추가할 것입니다.이 밖에 우리는 사용자를 위해 무작위 채팅 사용자 이름을 만들 것이다.
    // src/main.js
    
    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import apolloProvider from "./graphql";
    
    Vue.config.productionTip = false;
    
    // Initialize random username
    window.username = Math.random().toString(36).substring(2, 8);
    
    // Create and mount Vue app
    new Vue({
      router,
      apolloProvider,
      render: (h) => h(App),
    }).$mount("#app");
    
    우리의 App.vue 는 심지어 골격보다 더 간단하다. 그것은 공유기의 보기만 표시하고 스타일도 있다.
    <!-- src/App.vue -->
    
    <template>
      <div id="app">
        <router-view />
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
    };
    </script>
    
    <style lang="scss">
    // See styles at https://github.com/mostlytyped/rethink-chat-graphql/blob/master/src/App.vue
    </style>
    
    우리router/index.js에서 우리는 기본적으로'방'노선으로'관'노선을 대체했다.
    // src/router/index.js
    
    import Vue from "vue";
    import VueRouter from "vue-router";
    import Home from "@/views/Home";
    import ChatRoom from "@/views/ChatRoom";
    
    Vue.use(VueRouter);
    
    const routes = [
      { path: "/", name: "Home", component: Home },
      { path: "/:roomId", name: "Room", component: ChatRoom },
    ];
    
    const router = new VueRouter({
      routes,
    });
    
    export default router;
    
    메인 보기에서 HelloWorld 구성 요소를 삭제하고 방에 가입할 수 있는 폼을 추가합니다.
    <!-- src/views/Home.vue -->
    
    <template>
      <div class="main">
        <form v-on:submit.prevent="gotoRoom">
          <label>
            Username:
            <input v-model="user" type="text" />
          </label>
          <label>
            Room:
            <input v-model="room" type="text" />
          </label>
          <button>Join</button>
        </form>
      </div>
    </template>
    
    <script>
    export default {
      name: "Home",
      data() {
        return {
          user: window.username,
          room: "lobby",
        };
      },
      methods: {
        gotoRoom() {
          window.username = this.user;
          this.$router.push({
            name: "Room",
            params: { roomId: this.room },
          });
        },
      },
    };
    </script>
    
    <style scoped lang="scss">
    // See styles at https://github.com/mostlytyped/rethink-chat-graphql/blob/master/src/views/Home.vue
    </style>
    
    이제 필요한 세션으로 프레임워크를 채워서, 전방,GraphQL 클라이언트, 채팅방 보기의 진정한 내용을 처리합니다.

    GraphQL 클라이언트
    현재 단말기가 불러올 때 GraphQL 클라이언트를 시작해야 합니다.우리의 예시에서, 우리는 Apollo를 사용합니다. 이것은 가장 자주 사용하는 GraphQL 클라이언트입니다. 이것은 좋은 Vue를 가지고 있습니다.js와 vue-apollo 패키지의 통합.
    // src/graphql.js
    
    import Vue from "vue";
    import VueApollo from "vue-apollo";
    import ApolloClient from "apollo-client";
    import { createHttpLink } from "apollo-link-http";
    import { InMemoryCache } from "apollo-cache-inmemory";
    import { split } from "apollo-link";
    import { WebSocketLink } from "apollo-link-ws";
    import { getMainDefinition } from "apollo-utilities";
    
    Vue.use(VueApollo);
    
    // HTTP connection to the API
    const httpLink = createHttpLink({
      // For production you should use an absolute URL here
      uri: `${window.location.origin}/graphql`,
    });
    
    // Create the subscription websocket link
    const wsLink = new WebSocketLink({
      uri: `wss://${window.location.host}/graphql`,
      options: {
        reconnect: true,
      },
    });
    
    // Split link based on operation type
    const link = split(
      ({ query }) => {
        const definition = getMainDefinition(query);
        return (
          definition.kind === "OperationDefinition" &&
          definition.operation === "subscription"
        );
      },
      wsLink, // Send subscription traffic to websocket link
      httpLink, // All other traffic to http link
    );
    
    // Create apollo client/provider with our link
    const apolloClient = new ApolloClient({
      cache: new InMemoryCache(),
      link: link,
    });
    
    const apolloProvider = new VueApollo({
      defaultClient: apolloClient,
    });
    
    export default apolloProvider;
    
    GraphQL 구독을 사용할 것이기 때문에, Apollo 설정은 평소보다 좀 복잡합니다.정상적인 GraphQL은 HTTP를 통해 실행해야 하지만 구독 업데이트는 웹소켓을 통해 전송되기 때문이다.

    채팅방 보기
    앞면의 마지막 부분은 ChatRoom 보기가 될 것이다.여기서 우리는 실제적으로 우리가 방금 초기화한 GraphQL 클라이언트를 사용할 수 있다.이 보기는 기본적으로 chats 변수의 모든 항목을 포함하고 백엔드에 채팅 정보를 보내는 목록을 보여 줍니다.
    <!-- src/views/ChatRoom.vue -->
    <template>
      <div class="chatroom">
        <ul id="chatlog">
          <li v-for="chat in chats" v-bind:key="chat.ts">
            <span class="timestamp">
              {{
                new Date(chat.ts).toLocaleString(undefined, {
                  dateStyle: "short",
                  timeStyle: "short",
                })
              }}
            </span>
            <span class="user">{{ chat.user }}:</span>
            <span class="msg">{{ chat.msg }}</span>
          </li>
        </ul>
        <label id="username"> Username: {{ user }} </label>
        <form v-on:submit.prevent="sendMessage">
          <input v-model="message" autocomplete="off" />
          <button>Send</button>
        </form>
      </div>
    </template>
    <script>
    import gql from "graphql-tag";
    
    export default {
      name: "ChatRoom",
      data() {
        return {
          chats: [],
          message: "",
          user: window.username,
          handle: null,
        };
      },
      methods: {
        sendMessage() {
          const msg = this.message;
          this.$apollo.mutate({
            mutation: gql`
              mutation($user: String!, $msg: String!, $room: String!) {
                sendChat(user: $user, room: $room, message: $msg) {
                  ts
                }
              }
            `,
            variables: {
              user: this.user,
              msg: msg,
              room: this.$route.params.roomId,
            },
          });
          this.message = "";
        },
      },
      apollo: {
        chats: {
          query: gql`
            query FetchChats($room: String!) {
              chats(room: $room) {
                msg
                user
                ts
              }
            }
          `,
          variables() {
            return {
              room: this.$route.params.roomId,
            };
          },
          subscribeToMore: {
            document: gql`
              subscription name($room: String!) {
                chatAdded(room: $room) {
                  msg
                  user
                  ts
                }
              }
            `,
            variables() {
              return {
                room: this.$route.params.roomId,
              };
            },
            // Mutate the previous result
            updateQuery: (previousResult, { subscriptionData }) => {
              previousResult.chats.unshift(subscriptionData.data.chatAdded);
            },
          },
        },
      },
    };
    </script>
    
    <style scoped lang="scss">
    // See styles at https://github.com/mostlytyped/rethink-chat-graphql/blob/master/src/views/ChatRoom.vue
    </style>
    
    sendMessage 방법은 sendChatGraphQL 돌연변이와 관련이 있다.chats 변수에 대해서는 귀속이 좀 복잡해야 한다.GraphQL chats 조회에 연결하고, 변수의 최신 상태를 유지하기 위해 chatAdded 구독을 사용합니다.
    현재 우리는 일하는 서버와 전방이 하나 있다.우리가 해야 할 마지막 일은 응용 프로그램을 실행할 때 chats표가 데이터베이스에 실제로 존재하는지 확인하는 것이다.

    데이터베이스 마이그레이션chats표가 없어서 프로그램을 실행할 수 없습니다.따라서, 우리는 표를 추가한 데이터베이스 이전이 필요하다.
    // server/migrate.js
    
    var r = require("rethinkdb");
    
    r.connect(
      {
        host: process.env.RETHINKDB_HOST || "localhost",
        port: process.env.RETHINKDB_PORT || 28015,
        username: process.env.RETHINKDB_USERNAME || "admin",
        password: process.env.RETHINKDB_PASSWORD || "",
        db: process.env.RETHINKDB_NAME || "test",
      },
      function (err, conn) {
        if (err) throw err;
    
        r.tableList().run(conn, (err, cursor) => {
          if (err) throw err;
          cursor.toArray((err, tables) => {
            if (err) throw err;
    
            // Check if table exists
            if (!tables.includes("chats")) {
              // Table missing --> create
              console.log("Creating chats table");
              r.tableCreate("chats").run(conn, (err, _) => {
                if (err) throw err;
                console.log("Creating chats table -- done");
                conn.close();
              });
            } else {
              // Table exists --> exit
              conn.close();
            }
          });
        });
      },
    );
    
    이 이전 검사 chats 표가 존재하는지 확인하고, 없으면 만듭니다.

    간단한 채팅 로봇
    보시다시피 DBS의 중요한 특징은 베이킹의 반응성입니다. 이것은 우리가 조회를 구독할 수 있도록 합니다.간단한 채팅 로봇을 만들 때 이 기능도 매우 편리하다.bot은 구독chats표의 변경 사항을 구독하고 적당한 시기에 반응하기만 하면 됩니다.
    우리 로펌 로봇은 @lorem의 프롬프트를 받으면 로펌 Ipsum에 무작위로 응답합니다.bot 구독 chats 표를 구독하고 메시지의 시작을 스캔합니다.@lorem로 시작하면 같은 방에서 답장을 보냅니다.
    // server/lorem-bot.js
    
    const LoremIpsum = require("lorem-ipsum").LoremIpsum;
    const lorem = new LoremIpsum({
      sentencesPerParagraph: {
        max: 8,
        min: 4,
      },
      wordsPerSentence: {
        max: 16,
        min: 4,
      },
    });
    
    // Run Lorem bot
    const runBot = function (conn) {
      console.log("Lorem bot started");
      r.table("chats")
        .changes()
        .run(conn, (err, cursor) => {
          if (err) throw err;
          cursor.each((err, row) => {
            const msg = row.new_val.msg.trim().split(/\s+/);
            // Is the message directed at me?
            if (msg[0] === "@lorem") {
              let num = 10;
              if (msg.length >= 1) {
                num = parseInt(msg[1]) || num;
              }
              r.table("chats")
                .insert({
                  user: "lorem",
                  msg: lorem.generateWords(num),
                  roomId: row.new_val.roomId,
                  ts: Date.now(),
                })
                .run(conn, function (err, res) {
                  if (err) throw err;
                });
            }
          });
        });
    };
    
    // Connect to RethinkDB
    const r = require("rethinkdb");
    const rdbConnect = async function () {
      try {
        const conn = await r.connect({
          host: process.env.RETHINKDB_HOST || "localhost",
          port: process.env.RETHINKDB_PORT || 28015,
          username: process.env.RETHINKDB_USERNAME || "admin",
          password: process.env.RETHINKDB_PASSWORD || "",
          db: process.env.RETHINKDB_NAME || "test",
        });
    
        // Handle close
        conn.on("close", function (e) {
          console.log("RDB connection closed: ", e);
          setTimeout(rdbConnect, 10 * 1000); // reconnect in 10s
        });
    
        // Start the lorem bot
        runBot(conn);
      } catch (err) {
        throw err;
      }
    };
    rdbConnect();
    

    Heroku에 응용 프로그램 배포
    Heroku에 작업 프로그램과bot을 배치하려면 Procfile을 만들어야 합니다.이 파일은 기본적으로 Heroku에게 어떤 프로세스를 실행해야 하는지 알려줍니다.
    // Procfile
    
    release: node server/migrate.js
    web: node server/index.js
    lorem-bot: node server/lorem-bot.js
    
    Heroku는 release 프로세스와 web 프로세스를 발표할 때 실행되는 명령과 메인 웹 프로그램으로 식별합니다.lorem-bot 프로세스는 작업 프로세스일 뿐 이름이 있을 수 있습니다.
    Heroku에 응용 프로그램 배포
    $ git add .
    $ git commit -m 'Working rethink-chat app'
    $ git push heroku master
    

    You will need to manually enable the lorem-bot process in your Heroku app. You can do so on the Resources tab.



    결론
    15분도 안 되는 시간에 우리는 간단한 로봇으로 채팅 프로그램을 만들고 배치했다.이것은 RejectDB의 강력한 기능과 사용 편의성을 보여 줍니다.구독 조회 능력은 반응식 응용 프로그램 구축을 쉽게 하고 GraphQL과 쉽게 통합할 수 있다.또한 Heroku는 배치를 쉽게 합니다. RejectionDB Cloud 추가 구성 요소가 있어서 데이터베이스 서버의 번거로운 작업을 직접 관리할 필요가 없습니다.

    좋은 웹페이지 즐겨찾기