Notice
Recent Posts
Recent Comments
Link
관리 메뉴

쉽고 편한 공간

[2021 군장병 공개SW 온라인 해커톤] 참가 후기 - "본선(개발)" 편 본문

각종 후기

[2021 군장병 공개SW 온라인 해커톤] 참가 후기 - "본선(개발)" 편

evera_fter 2021. 10. 30. 20:20

이전 글 : https://everafter12.tistory.com/28


9월 30일 부터 10월 20일까지 약 3주간의 개발 레이스가 시작되었다. 각 팀원의 역할 분배는 다음과 같았다.

  • 본인 : Back-end 총괄, Front-end 보조
  • 팀장 : AI 개발, Front-end 보조, PM
  • C씨 : Front-end 총괄
  • P씨 : AI 개발

이야기가 길어지므로, 지금부터는 말머리를 나누어 글을 써보겠다.


제가 Back-end 개발이요?


초기 생각한 백엔드 스택은 Node.js(Express) - MongoDB(Mongoose) 였다. 내가 실제 개발해본 거의 유일한 스택이였기에 다른 선택지가 없었다. 그러던 와중 팀장이 Strapi란 것을 추천해주었다. Strapi(https://strapi.io) 는 Node.js 기반의 Headless CMS Open source이다. 한마디로, Content Managing이 가능한 온전한 서비스에서 FE부분만을 제거한 것으로 아주 쉽게 BE를 구축하고 데이터를 관리할 수 있는 인터페이스를 제공한다. 나는 바로 Strapi 사용을 결심했는데 이유는 다음과 같다.

1. Node.js로 처음부터 api를 짜는 데 드는 시간 소요를 획기적으로 줄일 수 있다. (strapi는 기본 restful api를 default로 제공)
2. DB 설계에도 압도적으로 용이하고, RDB 또한 직관적으로 설계 및 활용이 가능했다.
3. 커뮤니티 서비스를 만드는 데 있어 더미데이터 관리가 무엇보다 중요하기에, strapi와 같은 잘 짜여진 CMS는 무조건 환영이다.
4. Strapi 배포 시, /admin 페이지서 팀원들도 관리자 계정을 통해 데이터 추가 및 삭제가 가능하여 협업에도 용이하다.

Strapi - Open source Node.js Headless CMS 🚀

Strapi is the next-gen headless CMS, open-source, javascript, enabling content-rich experiences to be created, managed and exposed to any digital device.

strapi.io

다만 default로 제공되는 api 외에 어느정도까지 커스텀이 가능한지가 고려사항이었는데, 다행히 Backend customization에 대한 문서도 마련돼있어 어렵지 않게 커스텀 api 추가가 가능했다. MySQL, MongoDB 등의 여타 DB와도 연동이 가능했기에 사용 경험이 있는 MongoDB와 연결하여 사용키로 결정했다. 초기 세팅에는 document 숙지까지 하루 정도 걸렸으나 이후 생산되는 효용을 따져보면 Strapi를 사용하는 선택은 '신의 한수'였다. 여력이 되면 GraphQL까지 연동하는 옵션을 고려했으나, 거기까지 학습하기엔 불확실성이 있어 다음 기회로 미루게되었다.

제가 DB 설계를요?


백엔드의 묘미, DB 설계또한 내게 피할 수 없는 과제였다. 이전에 '소다란' 사이트를 제작하면서 커뮤니티 구현에 필요한 기본적인 DB 구조는 이해하고 있었다. 무엇보다 첫 DB 설계를 잘 해야 뒤탈이 적다는 점도 절감하고 있었다. 이부분에서만큼은 소다란에서의 내 삽질 경험이 도움이되었다. 다만 전과 다른 점은 관계형 DB를 설계하게 되었다는 점이다. MongoDB의 NoSQL형 DB와 연동하면서 관계형 구조를 짰다는 것이 이상하게 들릴 수 있다. 그러나 Strapi에서는 기본적으로 DB 테이블 추가 시 테이블 간의 관계를 정의할 수 있도록 한다. MongoDB와 연동해도 마찬가지의 DB Schema를 설계할 수 있다. 따라서 User, Post, Comment 간의 관계를 정의함으로써 여러 효용을 이끌어낼 수 있었다. 완성된 DB Schema는 다음과 같다.

DB Schema

크게 User, Post, Pool의 Table이 존재하고, 하위 속성 Table이 존재한다. UserType에 따라 soldierData, companyData가 주어지고, PostType에 따라 jobInfo가 주어진다. Pool은 members와 posts를 가진다. 일반 사용자 계정은 군인 신분을 가정하기에 소속 부대, 보직, 계급 등을 정보로 가지고, 기업 사용자 계정은 기업 규모 등의 기업 정보를 가진다. Post는 일반 포스트와 채용 공고, Pool 게시글로 종류가 나뉘며, type에 따라 다른 display를 갖도록 한다.

soldierData, companyData 등을 따로 분리한 것은 기존 내 개발 경험에 따른 결정이다. 소다란 사이트를 개발할 적에 '고등학생/학부모' 계정과 '컨설턴트' 계정을 구분했었는데, User Table에 모든 속성을 열거하고, UserType에 따라 해당하는 속성만 값을 갖도록 하고 나머진 null 또는 undefined로 지정했었다. 그러다 보니 Table이 너무 길어지고 개발 경험도 딱히 좋지 않았다. 이번 Strapi의 Table 간 relationship 지정 기능을 보고 떠오른 직감으로, 차라리 세부 속성들을 Table로 묶고, 부모 prop에 1:1 relation을 주는 것이다. 예를 들어 User > soldierData > { skill, spec, experience } 로 Table을 분리하면 schema 상에서도 가독성이 훨씬 좋고, 실제 admin 페이지서 더미데이터를 관리할 때에도 작업이 수월해진다.
그러나 세부 Table이 많아질 경우 한 가지 문제가 생겼는데, 바로 서버단에서 데이터 쿼리 시 하위 prop object 전체가 넘어오지 않고 id값만 넘어온다는 것이다. 정확한 작동 방식은 다음과 같았다.

// strapi default query const post1 = await Strapi.query("post").findOne({id: _id}); console.log(post1); /* { username: "user", soldierData: { id: "1234", specs: { id: "4321" }, ... }, ... } */ // query using mongoose plugin const post2 = await Strapi.query("post").model.findOne({id: _id}); console.log(post2); /* { username: "user", soldierData: { id: "1234", }, ... } */

기본 Strapi query에서는 Post를 쿼리할 경우 soldierData까지는 하위 prop 전부가 불러와 지고, soldierData의 하위 prop인 spec 객체에 대해서는 id만 불러와졌다. mongoose query를 이용할 경우 아예 soldierData부터 id만 불러와졌다. 이러한 처리의 이유인 즉슨 순환 참조의 에러를 방지하기 위해서다. 단적인 예로 User의 prop으로 Posts가 있고, Post에는 Author prop이 있다. 즉 RDB 구조에서는 객체가 순환참조하는 경우가 자주 발생한다. 따라서 prop을 전부 쿼리하게 되면 무한 loop이 발생하므로 1차 하위 prop까지만 불러오고, 더 하위의 객체는 objectID값 만을 가져오는 것이다.
프론트 단에서는 한번의 백엔드 콜로 필요한 모든 정보를 가져오고 싶어하므로, 이러한 부분에 대한 처리가 백엔드 단에서 필요해졌다. 가령, user 데이터를 쿼리 할 경우 posts prop의 jobInfo prop은 objectID만 넘어오게 된다. 따라서 다음 작업이 백엔드 단에서 요구된다.

const user = await Strapi.query("user").findOne({ id: _id }); const posts = await Promise.all(user.posts.map(async post => { const jobInfo = await Strapi.query("jobInfo").findOne({ id: post.jobInfo }); return { ...post, jobInfo }; })); return posts;

또, 위 스키마에서는 Tag 조차 Table로서 분리했으나 프론트에서는 굳이 object로 이를 받아올 필요가 없다. 따라서 다음과 같은 작업을 백엔드 단에서 기대하게 된다.

const user = await Strapi.query("user").findOne({ id: _id }); const tags = await Promise.all(user.tags.map(async tag => tag.content)); return { ...user, tags }

object 배열을 넘겨주기 보다, content만을 담은 string 배열을 넘겨주는게 프론트 단에선 용이하다. 물론 관점에 따라 이런 작업은 오히려 프론트에서 처리하는게 맞다고 볼 수 도 있다. 그러나 후술하겠지만, 프론트 단에서 TypeScript를 사용하여 data interface를 미리 짜 놓는 경우 data model이 간단할 수록 작업이 수월해진다.

제가 Vue.js 요?


서술했지만, 나는 React.js를 주 스택으로 삼아왔고 Vue.js는 말로만 들었었다. 러닝커브가 적고 쉽기에 웹 프레임워크 입문 용으로 많이 배운다고들 들었다. 내게 팀장으로부터 컨택이 올 적에 웹 개발시 웹 프레임워크는 Vue.js를 사용키로 합의를 미리 봤었다. 때문에 휴가 가기 전 미리 실습을 몇 번 진행해봤었는데, 이미 React.js에 대한 이해가 있었기 때문인지는 모르겠으나 학습이 확실히 매우 빨랐다. 가장 먼저 눈에 띈 특징은 코드 일관성이다. 정확한 표현인지는 모르겠으나 리액트와 달리 뷰는 특정 로직을 구현하고자 할때 작성되는 코드가 매우 일관된다. v-if, v-for, v-key 등 미리 웹 프레임워크에 필요한 요소들을 아주 잘 정돈해 놓아 사용자로 하여금 코드 작성에 있어 헤맬 일이 매우 적었다. 나아가 data, computed, watch, created 등 기능에 따라 필요한 메서드 또한 잘 분리되어있어 편리하다.
https://kr.vuejs.org/v2/guide/instance.html

Vue 인스턴스 — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

이러한 차이점은 리액트와 뷰의 본질에 따른 것이다. 많은 사람들이 헷갈려 하나, React.js는 "라이브러리"이고 Vue.js는 "웹 프레임워크"이다. Vue.js로 구성된 프로젝트 파일들은 .vue의 확장자 명을 가진다. 즉, Vue 프로젝트 내에서만 제 기능을 하는 파일이란 뜻이다. 그러나 React.js는 라이브러리로서 모든 js 파일에서 자유롭게 작동한다. jsx문법을 통해 React component를 만들어내는 react 모듈과, 이를 render하여 html을 출력해 내는 react-dom 모듈은 그 어느 프로젝트에서도 자유롭게 적용이 가능하다. 더불어, jsx문법이 주는 표현의 다양성까지 더해져서 React 프로젝트의 코드는 정말 사람마다 스타일이 다양하다. 그러나 뷰는 필요한 모든 기능을 미리 지정해주는 "프레임 워크"이므로 폴더 구조조차 그렇게 크게 달라지지 않는다. 그런 점이 리액트의 매운맛을 보아온 내게는 큰 장점으로 보였다. 자세한 내용은 따로 기회가 되면 다루어보도록 하겠다.

제가 TypeScript를요?


Vue.js 자체는 큰 문제 될 것이 없었다. 그러나 우리 팀이 사용한 스택은 Vue.js + TS 기반의 Vue Class Component였다. 기존 Vue 인스턴스와 lifeCycle hook을 이용한 코드 대신 class 형태를 사용한 구조인데, React와 꽤 닮아 있는 구조였다. 뷰 강의들이 대부분 기본 인스턴스를 이용한 코딩 스타일로 가르쳤기에 당황할 수 밖에 없었다. 더군다나 TS와 ESLint가 시도때도 없이 걸어오는 태클은 입문자인 내게 너무 당혹스러웠다. 그동안 너무 제멋대로의 코드를 짜온 내 잘못도 있겠으나, 적응하기 까지 꽤 오랜 시간이 걸렸다. Vue Class Component에 대한 자세한 Overview는 다음 링크를 참고하면 되겠다.
https://class-component.vuejs.org/

Overview | Vue Class Component

Overview Vue Class Component is a library that lets you make your Vue components in class-style syntax. For example, below is a simple counter component written with Vue Class Component: As the example shows, you can define component data and methods in th

class-component.vuejs.org

TypeScript의 주된 기능은 미리 모든 변수의 데이터 타입을 명시케 하고 그에 다른 타입체킹을 해준다는 것이다. 타입스크립트 공식 홈페이지의 설명란에는 다음과 같이 언급한다.

TypeScript adds additional syntax to JavaScript to support a tighter integration with your editor.
Catch errors early in your editor.
TypeScript understands JavaScript and uses type inference to give you great tooling without additional code.

백엔드 단에서 데이터를 불러올 경우 customed type or interface를 선언하게 된다.
https://www.typescriptlang.org/docs/handbook/2/objects.html

Documentation - Object Types

How TypeScript describes the shapes of JavaScript objects.

www.typescriptlang.org

이부분에서 또 애로사항이 발생했었는데, 바로 순환 참조 구성을 가지는 데이터 모델에 대해 interface를 구성하기 애매해진다는 것이다. 어찌저찌 해결하긴 했으나 굳이 interface를 미리 지정해야 한다는 점이 조금 불편했던 것 같다. (물론 서비스 규모가 커지면 필수적인 기능임에는 의심할 여지가 없다)

제가 Vuetify를요?

https://vuetifyjs.com/en/

Vuetify — A Material Design Framework for Vue.js

Vuetify is a Material Design component framework for Vue.js. It aims to provide all the tools necessary to create be...

vuetifyjs.com

Vuetify는 Vue.js 용으로 개발된 Material Design Framework이다. 프론트 총괄을 맡으신 C씨가 Vuetify를 애용하셨기에 시간 절약을 위해 사용을 결정했다. 쌩 CSS를 자주 짜던 나지만, 이번 기회에 style framework도 사용해보고 싶었다. 다른 프레임워크 선택지도 많았으나, 너무 신생 프로젝트이거나 뷰 버전에 맞지 않는 경우가 있었고, 무엇보다 한명이라도 사용해본 경험이 있는 프레임워크가 낫다고 생각했다. Vuetify는 사용자도 많고 문서도 잘 되있어 사용에 어려움은 없었다. 공식 문서 예제와 C씨가 짠 코드를 참고해가며 기능을 익혔다. 내가 자주 사용한 component는 Cards, Chips, Groups 정도다.
https://vuetifyjs.com/en/components/cards/

Card component

The v-card component is a versatile component that can be used for anything from a panel to a static image.

vuetifyjs.com

처음에는 필요한 컴포넌트를 찾고 적당한 컴포넌트 조합을 만드는데 헤맸으나, 익숙해지니 작업 효율이 매우 향상되는 것을 느꼈다. 하나부터 열까지 CSS와 JS로만 짜던 과거의 내가 조금 무안해졌으나, 그런 경험이 있기에 style framework의 효용을 제대로 이해할 수 있다고 정신승리키로 했다.

제가 Git을요?

이번 개발에 있어 코드 퀄리티 만큼이나 신경썼던 것이 바로 DevOps(Development Operations)다. 특히 CI/CD의 구현을 목표로 두었고, 이 과정에서 Git을 보다 다방면으로 이용하게 되었다. DevOps의 정의는 처음 접할때 추상적이고 잘 와닫지 않는다. 실제로 구글에 검색 시 여러 정의를 찾아볼 수 있다.
https://aws.amazon.com/ko/devops/what-is-devops/

DevOps란 무엇입니까? – Amazon Web Services(AWS)

소프트웨어와 인터넷은 쇼핑에서 엔터테인먼트 그리고 뱅킹에 이르기까지 전 세계와 산업을 변화시켰습니다. 이제 소프트웨어는 비즈니스를 지원하는 것에 그치지 않고, 비즈니스의 모든 부분

aws.amazon.com

내가 이해한 바에 따르면, DevOps는 결국 원활한 개발을 위한 하나의 문화 지향점으로 요약할 수 있다. 그 중 CI/CD는 지속적 통합 및 지속적 제공이란 구체적인 과제를 제시한다. 지속적 통합이란, 다수의 개발자가 협업을 통해 개발을 진행하고 하나의 저장소(대부분 Git repository)에 모든 변경사항이 반영되는 것을 의미한다. 구체적으로, Git을 이용할 경우 여러 브랜치에서 독립적으로 기능 개발을 진행하고, 한번에 merge를 함으로써 통합이 이루어진다는 것이다. 이때 기능 빌드 및 테스트가 수반된다. 지속적 제공이란, 이러한 지속적 통합 과정 이후에 반영된 변경사항이 자동으로 배포된다는 것이다. 이때 코드 저장소와 배포될 장소가 사전에 연결되는 등 개발 파이프라인에 CI가 사전에 구축되어있어야 한다.
https://www.redhat.com/ko/topics/devops/what-is-ci-cd

CI/CD(지속적 통합/지속적 제공): 개념, 방법, 장점, 구현 과정

CI/CD는 애플리케이션의 통합 및 테스트부터 제공 및 배포까지 전체 라이프사이클에서 지속적인 자동화와 모니터링을 제공합니다. 개념, 차이점, 학습방법(인강)을 보세요.

www.redhat.com

우리 팀의 경우 CI는 기본적으로 git과 slack을 활용했다. github을 활용한 개발 프로세스는 다음의 링크를 참조하기로 했다.
GitHub - agis/git-style-guide: A Git Style Guide

GitHub - agis/git-style-guide: A Git Style Guide

A Git Style Guide. Contribute to agis/git-style-guide development by creating an account on GitHub.

github.com

Simplified Git Flow

Simplified Git Flow

There are a few popular, published patterns for using git on small teams. GitFlow is famous for scaring off new git users with an avalanche…

medium.com

개발에서 생기는 이슈는 모두 slack을 통해 커뮤니케이션을 진행했고, slack에 github을 연동하여 commit, merge 등에 대한 알람을 받았다. CD의 경우 github actions를 사용해 배포 자동화를 구현했다. 배포는 github pages를 이용했다. 아주 기초적인 수준의 CI/CD이지만, 그럼에도 개발 생산성이 매우 향상됨을 체감할 수 있었다. 별개로, 이번 기회에 git을 제대로 사용하게 되면서 브랜치를 다루는 데 나름(?) 능숙해지게 되었다. 올바른 커밋 날리는 법과 github 이용법도 팀원들 덕에 여럿 알게 되었다. 참고로 최종적으로 마무리 된 프로젝트의 git commit 수는 349개, PR 수는 58개 이다.

번외) 제가 Figma를요?


사실 이부분은 기획편에서 다루었어야 했으나, 개발 일정상 코딩과 동시에 프로토타이핑을 진행했기에 개발편의 번외로 다루기로했다. '소다란' 건이나 여타 프로젝트에선 'Zeplin'과 'AdobeXD' 조합을 프로토타이핑 도구로 많이 사용했었다. 그러나 Figma가 웹에서 사용가능한 점 때문에 다른 선택의 여지가 없었다. 아무리 개발이 급하더라도 프로토타이핑의 중요성을 몇 번 맛본 터라 그냥 넘어갈 수 없었다. 격리가 풀리고 가장 먼저 한 것이 페이지 프로토타이핑이었다.
결과물은 다음과 같다.

초기 기획 단계서 제작한 와이어프레임
와이어프레임2
discover page 프로토타입
pool page 프로토타입

이 정도가 내가 이번 프로젝트에서 다룬 개발의 범위이다. 더욱 자세한 개발 및 협업 과정은 팀원들 모두의 재산이므로 생략하도록 하겠다. 다음 글은 "본선(마무리)" 편이 될 것 같다.

Comments