[Project] NGNG 개발 회고

yooni·2022년 5월 1일
1
post-thumbnail

BEB 과정의 두 번째 프로젝트였다. 첫 프로젝트보다 더 긴 시간이 주어졌지만 전보다 더 많은 기능들을 구현해야 했기 때문에 훨씬 더 많은 노력을 기울여야 했다.




✏️ 역할 분담

  • Front-end (내가 담당한 🙋🏻‍♀️)
    클라이언트 웹사이트
  • Back-end
    서버, DB, web3, demon
  • Smart Contract
    ERC-20, ECT-721 컨트랙트 개발 및 발행




1. NGNG community service

NGNG 링크
github 레포지토리 링크

스팀잇, gm, 트위터를 간단하게 합친 형태의 web2 커뮤니티이다. 커뮤니티에 참여하는 유저들의 각 활동에 대해 NGT라는 ERC-20 토큰이 보상으로 부여된다. ERC-20, ERC-721을 이용하지만 사용자는 직접 지갑을 연결할 필요는 없다. 모든 컨트랙트 관련 작업은 서버단에서 처리된다.



1-1. 주요 기능 & 아이디어


💡 활동에 따른 보상 시스템

유저는 로그인, 포스트/댓글 작성, 작성한 포스트의 좋아요 갯수, 다른 유저의 도네이션 등을 통해 ERC-20으로 보상을 받을 수 있다.


💡 post 좋아요, 기부, 신고 기능

이 기능들은 (당연하지만) 모두 유저 본인의 포스트에 대해서는 실행할 수 없다.


  • Like
    일반적인 SNS의 좋아요 기능과 같다.

  • Donate (Transfer)
    원하는 만큼의 NGT(ERC-20)를 포스트 작성자에게 기부할 수 있다.

  • Report
    악성 포스트로 생각될 경우 해당 포스트를 운영자에게 신고할 수 있다. 운영자의 검토 후 해당 포스트의 보상이 깎일 수 있다. (아직은 신고 접수까지만 가능하고, 운영자의 검토 결과를 반영하는 것은 구현되어 있지 않다.)

💡 admin 계정에서 보상 일괄 지급하기

블록체인의 속도 문제로 인해, 보상은 실시간으로 지급되지 않고 운영자가 원하는 때에 일괄 반영된다. admin 계정으로 로그인했을 때만 보이는 보상 지급 버튼이 구현되어 있다. 운영자가 이 버튼을 누르면 지금까지 누적된 모든 행위들에 대한 보상 지급이 이루어진다.


💡 Hot Tags

모든 포스트에는 여느 SNS처럼 원하는 태그를 달 수 있다. 그리고 이 태그들은 집계되어 사이트 Home 상단에는 현재 기준 가장 많이 언급된 태그들이 출력된다. 각 태그를 클릭하면 해당 태그를 포함한 포스트들만 필터링한 결과를 볼 수 있다. 또한 헤더의 Search Bar를 이용해 원하는 태그를 포함한 포스트들을 직접 검색할 수도 있다.


💡 NFT Zone (구현 중)

NFT와 관련된 작업을 할 수 있는 영역을 독립적으로 배치해두었다. NFT는 일종의 회원 등급을 나타낸다. 등급이 높은 유저일 수록 보상 비율이 커진다. 각 유저는 최초 1회 NFT를 민팅할 수 있으며 민팅을 위해 일정량의 NGT 토큰을 지불해야 한다. 민팅받은 NFT에는 등급 레벨이 담겨져 있다. 등급 레벨은 랜덤하게 부여되며 레벨이 높을 수록 보상 비율이 더 커진다.

이 등급 레벨 NFT는 유저간 거래가 가능하다. 내가 랜덤으로 뽑은 NFT에 높은 레벨이 담겨있다면 높은 금액으로 팔 수 있고, 반대로 더 높은 보상을 위해 다른 유저의 NFT를 구입할 수도 있다.

현재 NFT Zone는 NFT를 발급하는 단계까지만 구현되어 있다.


💡 댓글 및 대댓글 기능

댓글과 대댓글은 로그인하지 않은 익명의 유저도 남길 수 있다. 익명으로 댓글을 남기기 위해서는 비밀번호를 입력해야 하며, 나중에 댓글을 지우고자 할 때 비밀번호를 입력해야 한다.



1-2. Pages 상세

📃 Home

Hot Tags Now
Post Creation Form
Posts

스팀잇처럼 컨텐츠를 게시하고 이에 따른 보상을 받을 수 있는 gm과 전체 구성이 비슷하게 구현되어 있다. 포스트는 트위터나 인스타그램처럼 단순화하여서 각 포스트의 상세 페이지는 따로 없고 메인 화면에 모든 내용이 출력된다.


📃 My Talk

Posts

로그인한 유저 본인이 작성한 포스트들만 따로 확인할 수 있다.


📃 My Wallet

NGT(ERC-20) Balance
Transaction List
(Admin user의 경우) 보상 토큰 지급 버튼


📃 Tag Search Result

Posts




2. Frontend 개발 회고

  • react
  • redux tool-kit (전역 상태 관리)
  • redux persist (새로고침 시에도 로그인 유지)
  • react query (서버에 데이트 요청 시)
  • MUI

🖥 redux took kit, react persist

RTK docs - Getting Started

RTK는 리덕스를 좀 더 쉽게 사용하기 위한 툴킷이다. redux를 잘 사용하기 위해서는 immer, reselect, thunk, saga 등의 추가 라이브러리를 설치해야 하는 경우가 많다(고 한다). RTK는 리덕스에서 공식적으로 제공하는 툴킷으로 추가 라이브러리 설치가 필요 없다.

하지만 페이지를 새로고침할 경우 redux storestate가 날아가버리는 것을 해결하기 위해서는 다른 라이브러리가 필요하다. redux persist를 사용하면 아주 간단하게 이 문제를 해결할 수 있다.

이곳에서 react toolkit 기본 설치 방법을 참고하였고, 이곳에서 redux toolkitredux persist를 적용하는 방법을 참고했다.


📎 store.js 구성

combiendReducerpersistConfig를 선언해주고, 이를 활용해 persistReducer를 생성 하고 store를 구성한다.

import { configureStore } from '@reduxjs/toolkit';
import userReducer from './user';
import tagReducer from './tag';
import selectedTagReducer from './selectedTag';
import storage from 'redux-persist/lib/storage';
import { combineReducers } from "redux"; 
import { persistReducer } from 'redux-persist'
import thunk from 'redux-thunk';

const reducers = combineReducers({
  user: userReducer,
  tag: tagReducer,
  selectedTag: selectedTagReducer,
});

const persistConfig = {
  key: 'root',
  storage,
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = configureStore({
  reducer: persistedReducer,
  devTools: process.env.NODE_ENV !== 'production',
  middleware: [thunk]
})

export default store;



🖥 react-query

react-query docs

react-query는 client-server 사이의 비동기 로직을 간편하게 다룰 수 있게 해준다. get 요청에는 useQuerypost/put/delete 요청에는 useMutation을 쓰는 것이 기본적인 내용이다. react-query 자체가 통신을 처리하지는 않고 axiosfetch를 감싸는 형태로 구성되며 아래처럼 매우 다양한 반환값을 자체적으로 제공하기 때문에 원하는 대로 적절히 사용할 수 있다.

📎 useQuery의 다양한 반환값

 const {
   data,
   dataUpdatedAt,
   error,
   errorUpdatedAt,
   failureCount,
   isError,
   isFetched,
   isFetchedAfterMount,
   isFetching,
   isIdle,
   isLoading,
   isLoadingError,
   isPlaceholderData,
   isPreviousData,
   isRefetchError,
   isRefetching,
   isStale,
   isSuccess,
   refetch,
   remove,
   status,
 } = useQuery(queryKey, queryFn?, {
   onError,
   onSettled,
   onSuccess,
   placeholderData,
   queryKeyHashFn,
   refetchInterval,
   refetchIntervalInBackground,
   refetchOnMount,
   refetchOnReconnect,
   refetchOnWindowFocus,
   retry,
   retryOnMount,
   retryDelay,
   select,
   staleTime,
   structuralSharing,
   suspense,
   useErrorBoundary,
 })

비동기 로직 성공시 반환되는 데이터 혹은 실패시 발생하는 에러에 대한 처리를 간단히 처리해줄 수 있고 isFetching, isLoading 등을 활용하여 데이터가 페칭/로딩되는 동안의 로직까지 간편하게 구성해줄 수 있다.

react-query는 아직 많이 쓰이고 있지는 않은 듯 하다. 구글링했을 때 여러 자료가 검색되지만, 다양한 사례를 다룬 내용은 잘 없는 것 같다. react-query docs를 참고하는 편이 낫다. docs가 꽤나 친절하게 구성되어 있다. 앞으로 점점 유저가 늘어나지 않을까 생각된다.


📎 useQuery 적용

아래의 코드는 각 포스트를 불러오기 위한 useQuery문이다. 메인 화면에서는 지금까지 작성된 모든 포스트들을 한꺼번에 불러와야 한다. 따라서 작성된 포스트의 개수만큼 useQuery가 실행되는데 이 때 에러가 발생해서 한참을 고생했다. 각 포스트마다 queryKey를 고유한 값으로 변경해주니 잘 동작했다 😭 페이지나 컴포넌트 안에서 한번만 실행되는 useQuery문의 경우 상관 없지만 이렇게 반복적으로 데이터를 불러들일 때는 꼭 queryKeyunique하게 만들어줘야 한다!

  // 🔥 useQuery Key.... unique하게 작성!!!
  const {data, status} = useQuery(`getPost_${uuid}`, () => {
    return axios.get(`/api/post/getPost?postUuid=${uuid}`)
    .then((res) => {
      return res.data.data.post;
    })
  })

📎 useMutation 적용

새로운 댓글을 작성하는 useMutation 구문이다. 코드가 매우 직관적으로 구성됨을 알 수 있다.

  const newCommentMutation = useMutation(((comment) => {
    return axios.post('/api/comment/sendMemberComment', comment, {
      headers: {
        "Authorization": `bearer ${accessToken}`
      }
    })
  }), {
    onSuccess: () => {
      alert('😄 The comment has been created successfully');
      commentRef.current.value = '';
    },
    onError: (error) => {
      alert(`
      ❗️ Something Wrong! Please try again

      (${error})
      `);
    }
  });

이렇게 선언된 useMutation은, 원하는 곳에서 아래와 같이 실행시킬 수 있다.

newCommentMutation.mutate(comment);

📎 useMutation (2)

또한 useMutation이 반환하는 data를 아래와 같이 활용하면, 리액트 컴포넌트의 리턴문 안에서 바로 사용이 가능하기 때문에 매우 편리하다.

const HotTags = (props) => {

  const {data} = useQuery('getHotTags', () => {
    return axios.get('/api/hashtag/topHashtag')
    .then((res) => {
      return res.data.data.hashTag;
    })
  })
  
  return (
    <Card sx={{mb: 2, p: 2}}>
      <Typography variant="body2" color="primary" fontSize={'28px'}>
        🔥 Hot Tags Now!
      </Typography>
      {
        data &&
        data.map((tag, idx) => <Tag color="primary" keyword={tag.tag} key={idx} />)
      }
    </Card>
  )
}



🖥 MUI

지난 프로젝트에서는 순수 CSS만을 활용했었는데, 이번에는 MUI를 처음으로 사용해보았다. 사용해보고 느낀 것이 CSS 툴이나 템플릿은 정말 편리하고 빠르게 내가 원하는 UI를 만들어낼 수 있지만 편리한 만큼 자유도가 떨어진다는 점이다. 미세한 수정을 위해 필요 이상의 에너지를 써야할 때가 있다. 처음엔 아예 미리 개발되어 있는 템플릿을 그대로 가져다가 쓰려다가 이런 자유도 문제에 답답함을 크게 느껴 MUI로 넘어갔다. MUI는 작은 기능 단위의 모듈들을 제공하기 때문에 디자인 수정에 대한 자유도가 그래도 비교적 높은 편이다.

또한 이곳에서 custom theme를 직접 만들어 적용할 수 있었다. 원하는 색상 조합과 사용하고자 하는 폰트 몇가지만 구성해주면 알아서 테마 코드가 만들어지고 이를 app.js에 넣어주면 사이트 전체에 반영된다.

const customTheme = createTheme({
  palette: {
    type: 'dark',
    primary: {
      main: '#dba531',
    },
    secondary: {
      main: '#007849',
    },
    error: {
      main: '#ff7605',
    },
    success: {
      main: '#f7b92a',
    },
    background: {
      default: '#292b33',
      paper: '#3a3a3f',
    },
    info: {
      main: '#00c4b5',
    },
  },
  typography: {
    fontFamily: 'Montserrat',
    fontSize: 14,
    fontWeightRegular: 400,
    fontWeightMedium: 600,
    fontWeightLight: 200,
    fontWeightBold: 700,
    h1: {
      fontWeight: 400,
      fontFamily: 'Permanent Marker',
      fontSize: '2.9rem',
    },
  },
});


function App() {
  return (
    <ThemeProvider theme={customTheme}>
		// ...
    </ThemeProvider>
  );
}




3. 이번 협업을 통해 배운 점


  • 다양한 툴의 활용
    개발자는 깊게가 아니라 넓게 알아야 한다는 말을 실감했다. 물론 react와 기본 css 만으로도 비슷한 결과물을 만들어낼 수 있을 것이다. 하지만 훨씬 오랜 시간이 걸렸겠지.. 모든 툴과 기술 스택의 사용법을 알 필요는 없지만 '이럴 때 활용할 수 있는 이런 툴이 있다~'는 정도는 알고 있어야 효율적인 개발을 할 수 있을 것이다. 나는 사실 리덕스나 MUI 정도만 적용해볼 생각이었는데, 팀원분들이 이런 것들이 있다고 알려주셔서 다양한 시도를 해볼 수 있었다.

  • frontEnd - backEnd 간의 워크플로우
    프론트엔드와 백엔드는 API로 소통을 하기 때문에 깔끔하게 의사 전달 및 요청이 가능한 것 같다. API 문서가 잘 짜여져 있어야 한다고 배워왔는데 어떤 의미인지 알 수 있었다. 우리 사이트에는 매우 다양한 기능들이 적용되어 있는데 이런 내용을 그냥 말로 소통했다면 아마 너무 어려웠을 것 같다. 백엔드 담당자가 개발한 내용을 API로 깔끔하게 정리해서 보내주니 프론트 입장에서도 개발하기가 훨씬 수월했다.




4. 보완해야 할 사항들

bare minimum 과제로 정해둔 기능까지는 모두 구현했다. 하지만 advanced까지 진행했다면 더 재밌었을텐데.. 하는 짧은 기간에 대한 아쉬움이 있었다. 프론트 개발에서 가장 아쉬웠던 부분은 무한 스크롤을 구현하지 못한 것과, 이미지 로딩 인디케이터 구현에 실패한 것이다.


  • NFT 발급에 따른 등급 변화가 구현되지 않음
    구현되면 재밌을것 같다고 생각한 아이디어였는데, 아쉽게도 이번 기간동안에는 완성이 되지 않았다. 랜덤하게 부여되는 등급 레벨에 따라서 보상도 천차만별로 된다는 점도 참 좋은 아이디어였던 것 같다.

  • NFT Minting 여부를 확인할 수 없음
    원래 구상한 아이디어대로라면 최초 1회만 NFT 민팅을 할 수 있어야 하는데, 민팅 여부가 확인되지 않아 NFT가 무한정 발급되고 있다.

  • 무한 스크롤을 구현하지 못함
    이 부분은 나도 매우 아쉽다고 생각한 부분이고 팀원분도 아쉬운 부분으로 지적하셨다. 사이트 성격상 페이지네이션을 하는 것 보다는 무한 스크롤로 구현하는 것이 훨씬 어울릴 것 같았다. 클라이언트가 어느 정도 완성이 되어갈 즈음에 무한 스크롤을 구현해보려고 계속 시도해보았지만 결과적으로는 실패했다. react-query에서 제공하는 무한 스크롤 예시를 그대로 참고했는데 뭐가 문젠지 잘 안돼서 너무 아쉽다 😓 프로젝트는 마무리 되었지만 이 부분은 개인적으로 꼭 다시 도전해보고 싶다.

  • memo, useCallback 등을 통한 성능 최적화를 하지 않음
    프로젝트1에서도 성능 최적화를 거의 해주지 않아(사실 해줄 필요가 없을 정도로 작은 사이트이긴 했다) 아쉬웠고 프로젝트2에서는 꼭 적용해보고 싶었지만 이번에도 적용을 못했다. 아직 memo와 useCallback에 대한 나의 이해도가 부족한 것도 있고 이번 앱도 성능이 문제가 되지는 않았던 것 같아 더 신경쓰지 못했다. 만약 실제로 서비스하는 앱이라면 꼭 필요하겠지.

  • 파일 구조 관리가 되어있지 않음
    지금 components 폴더에 28개의 컴포넌트를 냅다 때려박아 두었다. 서비스 로직도 구분하지 못했고 그야말로 구조 관리가 엉망이다. 코드를 짠 내가 아니면 원하는 코드를 찾아서 읽기 어려울 것 같다 😅 신경써야겠다 싶었을 때에는 이미 손쓰기 어려워서 그냥 반성만 하고 '다음엔 잘해야지...'로 정리했다.

  • 파일이 제대로 첨부되었는지 확인하기 어려움
    포스트를 작성하거나 회원가입을 할 때, 이미지를 첨부할 수 있는데 원하는 이미지를 선택하고 이 이미지가 제대로 첨부되기 전에 submit 버튼을 눌러버리면 이미지가 제대로 전송되지 않는 것 같다. 이를 위해 state를 활용한 인디케이터를 추가하는 등의 다양한 방법을 시도해보았지만 제대로 작동하는 방법이 하나도 없었다 😭 여기에 시간을 정말 많이 들인 것 같은데 대체 왜 안되는 걸까?!! 임시방편으로 그냥 이미지를 선택한 직후 로딩스피너를 약 4초간 돌아가게 해두었는데 사실 눈속임과 같은 방법이라 개선이 꼭 필요하다. 사실 이미지 첨부가 완료되면 파일의 이름을 출력시키는 등의 방법도 있지만, 이 경우에도 혹시 유저가 파일 이름이 뜨기 전에 submit을 눌러버린다면? 결국 같은 문제가 발생하기 때문에 '이미지 파일이 잘 첨부되고 있는 중이다~'를 나타내는 방법이어야 할 것 같다.

  • accessToken 만료시 자동 로그아웃 되지 않음
    이 부분도 맘에 걸렸는데, 로그아웃 페이지만 따로 만들면 서버단에서 처리가 가능하다고 하셔서 오늘 중으로 개선해볼 예정이다!!




5. 아쉬웠던 점

프로젝트 1,2에서 연달아 프론트엔드를 담당했다. 내가 프론트를 희망했기 때문에 즐겁게 참여했고 프론트에 대해서는 정말 많이 배울 수 있는 기회였지만, 백엔드와 컨트랙트에는 참여하지 못해 아쉬움이 크다. 아마 프로젝트3에서는 프론트/web3/DB/컨트랙트 모두를 조금씩은 다뤄볼 수 있을 것 같아서 기대가 된다.



profile
멋쟁이 코린이

0개의 댓글