Day 34

김정동·2021년 12월 16일
0

Graphql의 실체

graphql도 사실은 Rest-api다???

rest-api사용할때를 생각해보자

게시글 조회 : axios.get(API주소)
게시물 등록 : axios.past(API주소, {데이터})

그래서 api주소를 우리는 endpoint라고 불러왔다.
어디서 봤냐면

https://koreanjson.com/ 에서 연습할때도 이미 엔드포인트라고 써있었음

각각의 api 를 만들 때 결국 api 하나하나가 함수였던것을 백엔드 만들때 기억할것(디비버쓸때) (우웩)

그럼 사용자 등록 api = 사용자 등록 함수 라고 이해해도된다. 근데 여기서 발생하는 문제가 주소가 너무 많아진다는것 (엔드포인트가 넘 많다!)


문제점을 해결한사람
엔드포인트를 하나만 만들고 + 그리고 그걸 Post로 만듬 그럼 등록하기처럼 데이터를 보낼 수 있지 않나?? 그리고 api안에(함수) 거대하게 만들어놓고 그 안에 하나씩 맞는 함수 만들면 되지 않나?

/graphql{key : mutation createBoard{...}}

이렇게 하면 거대함수에서 알맞는 함수를 실행시켜줌

어 게시판 생성이네 -> api실행

mutation만 아니고 query도 같은방식으로 가능

여러개 있던 주소를 통합 + 그에 맞게 api도 하나 + 그 안에 다 넣어준것

근데 주소(엔드포인트)를 graphql 로 바꾸고 작동방식은 같은 rest-api인것

!! 1. Graphql은 endpoint가 1개인 rest-api인것!!
!! 2. 항상 post 방식

실제 연습해보기

postman에서 연습

그냥 조회

백엔드에 접근해보기
post고르기, 주소넣기, 방식에 raw-JSON고르기

{
	"query" : "query fetchBoards { fetchBoards {_id, writer, title, contents} }"
	
}

fetch 든 mutaion이든 데이터가 들어가야되서 무조건 Post인것


(너무 잘뜬다)

등록도 해보자

앞의 query는 그냥 키므로 우리가 알던 키가 아니다

vs코드로도 한번 보자

import axios from "axios";
//         axios.post("", {}, { headers: {}}) 헤더를 넣는다면 이렇게 넣을것

export default function GraphqlRestPage() {
  async function onClickSubmit() {
    const result = await axios.post(
      "http://backend04.codebootcamp.co.kr/graphql",
      {
        query: `
        mutation createBoard {
          createBoard(
              createBoardInput:{
                  writer: "철수",
                  password: "1234"
                  title: "제목"
                  contents: "내용"
              }
          ){
              _id
              writer
          }
         }
          `,
      }
    );
    console.log(result);
  }
  return (
    <>
      <button onClick={onClickSubmit}>등록하기</button>
    </>
  );
}

콘솔로도 실제로 올라간 것을 볼 수 있다.
(실제로 업데이트도 된것임)

뭘 하든 사실 POST기능, REST-API을 활용해서 쓰고있었던 것

Optimistic UI 낙관적인 UI


페이스북에서 좋아요 누르면 분명히 DB에 좋아요 될텐데 엄청 빨라보인다.
알고보면 성공할꺼라고 확신해서 state에 담아서 먼저 보여준것, 그리고 이후에 요청을 보낸다

대신 조건이 있다.
1. 작업량 적은 API
2. 99% 이상 성공 가능한 API
3. 실패하더라도 피해가 없는 API(좋아요 1 덜올라가도 괜춘, 실패하면 다시 전의 결과로 보여주게 요청)
-> 작업량이 적고 안정적인 API에만 쓰자

실제 코드로 구현해보기

import { gql, useMutation, useQuery } from "@apollo/client";
import {
  IMutation,
  IMutationLikeBoardArgs,
  IQuery,
  IQueryFetchBoardArgs,
} from "../../src/commons/types/generated/types";

const LIKE_BOARD = gql`
  mutation likeBoard($boardId: ID!) {
    likeBoard(boardId: $boardId)
  }
`;
const FETCH_BOARD = gql`
  query fetchBoard($boardId: ID!) {
    fetchBoard(boardId: $boardId) {
      _id
      likeCount
    }
  }
`;

export default function OptimisticUIPage() {
  const [likeBoard] = useMutation<
    Pick<IMutation, "likeBoard">,
    IMutationLikeBoardArgs
  >(LIKE_BOARD);
  const { data } = useQuery<Pick<IQuery, "fetchBoard">, IQueryFetchBoardArgs>(
    FETCH_BOARD,
    {
      variables: { boardId: "61bab569717be5002baa75a8" },
    }
  );
  function onClickOptimisticUi() {
    // 여기서 좋아요 증가시키는 mutation
    likeBoard({
      variables: { boardId: "61bab569717be5002baa75a8" },
    });
  }
  return (
    <>
      <div>좋아요 갯수 : {data?.fetchBoard.likeCount}</div>
      <button onClick={onClickOptimisticUi}>좋아요 올리기</button>
    </>
  );
}

좋아요 갯수를 불러오기위해 FetchBoard를 했고 아이디는 하드코딩으로 채움
갯수는 data안의 fetchBoard안의 likeCount인것

이전에는 이제 좋아요갯수를 올리고 refetch를 수정해서 다시 불러오기를 했다.

  function onClickOptimisticUi() {
    // 여기서 좋아요 증가시키는 mutation
    likeBoard({
      variables: { boardId: "61bab569717be5002baa75a8" },
      refetchQueries: [
        {
          query: FETCH_BOARD,
          variables: { boardId: "61bab569717be5002baa75a8" },
        },
      ],
    });

이렇게 해야했다

이러면 다시 fetch를 할때까지 기다려야한다.
근데 좋아요처럼 가볍고, 중요하지않고, 빨리빨리가 필요한 것들은 DB에 업데이트할때까지 기다릴 필요가없다.

그러면 어떻게 적용??

optimisticResponse로 설정,
update를 써서 cache를 수정하겠다, 대상은 data,
cahe.wrtieQuery를 해서 캐쉬수정
query는 FETCH_BOARD,
variables로 어떤 게시물인지 구별하는 아이디,
그리고 data에 바꿀 정보를 입력

다시정리하면
likeBoard api 요청, variables에 맞는걸 가지고옴
그러면 optimisticResponse로 보내기전에 data 넣음(likeCount),
그 바뀐 data를 cache에 넣고, 그 바뀐걸 업데이트를 보냄
가짜응답수정 + 진짜 수정으로 캐쉬는 두번 업데이트되는 셈

?? 업데이트가 두번이면 비효율적인거아닌가?
-> 우리눈에는 훨씬 빠르게보임.


인터넷 속도를 인위적으로 조절할 수 있다.
개발자도구 - 네트워크 - 가면 조절할 수 있다.
느린 3G하면 개느리다. (불러올때는 제한없음으로 하고 카운트를 확인해볼 때 속도제한을 걸자)

그럼 속도체감이 훨씬 될것

한번 제출하는 사이트를 만들어보자

import { gql, useMutation } from "@apollo/client";
import { Modal } from "antd";
import { ChangeEvent, useCallback, useState } from "react";

const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
      writer
      title
      contents
    }
  }
`;

const inputsInit = {
  writer: "",
  password: "",
  title: "",
  contents: "",
};

export default function IsSubmittingPage() {
  const [inputs, setInputs] = useState(inputsInit);
  const [createBoard] = useMutation(CREATE_BOARD);
  const onChangeInputs = useCallback(
    (name: string) => (event: ChangeEvent<HTMLInputElement>) => {
      setInputs((prev) => ({
        ...prev,
        [name]: event.target.value,
      }));
    },
    []
  );

  async function onClickSubmit() {
    try {
      const result = await createBoard({
        variables: {
          createBoardInput: { ...inputs },
        },
      });
      console.log(result);
      Modal.confirm({ content: "등록에 성공하였습니다." });
    } catch (error) {
      Modal.error({ content: error.message });
    }
  }

  return (
    <>
      작성자 <input type="text" onChange={onChangeInputs("writer")} />
      <br />
      비밀번호 <input type="password" onChange={onChangeInputs("password")} />
      <br />
      제목 <input type="text" onChange={onChangeInputs("title")} />
      <br />
      내용 <input type="text" onChange={onChangeInputs("contents")} />
      <br />
      <button onClick={onClickSubmit}>등록하기</button>
    </>
  );
}

UseCallback까지 사용해서 좀 복잡해졌다


여러번 누르면 여러번 요청이 나가게된다

수정하면?

issubmitting state를 만들어서 기본값 false로 설정, 제출할때 true, 그리고 이후 false로 바뀌게한다. 그리고 버튼에도 disabled를 달아서

센트리?

https://sentry.io/welcome/

docs - next.js를 들어간다

그럼 설치명령어를 볼 수 있음

yarn add @sentry/nextjs

이후 app.tsx를 설정해야한다.

import + SENTRY_DSN을 설정해야함
DSN은 프로젝트 - 클라이언트 키

코드에도 적용한다. 적용할 index로 가서
import, 에러를 내기위해 throw new Error("에러 강제로 발생시킴");
sentry.captureException(error)

import { gql, useMutation } from "@apollo/client";
import { Modal } from "antd";
import { formatStrategyKeys } from "rc-tree-select/lib/utils/strategyUtil";
import { ChangeEvent, useCallback, useState } from "react";
import { useForm } from "react-hook-form";
import * as Sentry from "@sentry/nextjs";

const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
      writer
      title
      contents
    }
  }
`;

const inputsInit = {
  writer: "",
  password: "",
  title: "",
  contents: "",
};

export default function IsSubmittingPage() {
  // const { formStage } = useForm()
  // formatStrategyKeys.isSubmitting // 리액트 훅폼의 모습
  const [inputs, setInputs] = useState(inputsInit);
  const [createBoard] = useMutation(CREATE_BOARD);
  const [isSubmitting, SetIsSubmitting] = useState(false);
  const onChangeInputs = useCallback(
    (name: string) => (event: ChangeEvent<HTMLInputElement>) => {
      setInputs((prev) => ({
        ...prev,
        [name]: event.target.value,
      }));
    },
    []
  );

  async function onClickSubmit() {
    SetIsSubmitting(true);
    try {
      throw new Error("에러 강제로 발생시킴");
      // const result = await createBoard({
      //   variables: {
      //     createBoardInput: { ...inputs },
      //   },
      // });
      // console.log(result);
      // Modal.confirm({ content: "등록에 성공하였습니다." });
      // SetIsSubmitting(false);
    } catch (error) {
      Modal.error({ content: error.message });
      Sentry.captureException(error);
    }
  }

  return (
    <>
      작성자 <input type="text" onChange={onChangeInputs("writer")} />
      <br />
      비밀번호 <input type="password" onChange={onChangeInputs("password")} />
      <br />
      제목 <input type="text" onChange={onChangeInputs("title")} />
      <br />
      내용 <input type="text" onChange={onChangeInputs("contents")} />
      <br />
      <button onClick={onClickSubmit} disabled={isSubmitting}>
        등록하기
      </button>
    </>
  );
}


에러가 났고

이슈를 볼 수 있다.

요약
graphql 도 사실 post를 이용하는 엔드포인트가 하나인 rest-api임

그리고 옵티미스틱 ui를 적용해봄

issubmtting/센트리로 안정성증가
한번만 클릭하게 하기 / 오류 자동추적하기
만약 서비스가 커지면 자체 sentry를 만들겠지만 그 전에는 이 서비스를 쓰는게 죠음

profile
개발자 새싹🌱 The only constant is change.

0개의 댓글