김민태의 데브캠프의 첫 번째 토이 프로젝트로 ‘관리자용과 사용자용으로 구분된 인트라넷 서비스 설계 및 개발’이라는 미션이 주어졌다.
바닐라 자바스크립트로 SPA를 구현해야 했는데 라이브러리나 프레임워크의 도움 없이 기본적인 구조부터 데이터베이스, 라우팅 등 모든 걸 직접 구현해야 해서 처음에는 너무나도 막막했다.
하지만 팀원들과 회의를 진행하며 차근차근 하나씩 의견을 나누다 보니 조금씩 방향성을 잡아갈 수 있었다.
팀 소개
우리 조의 팀명은 (mbt)i-dle
이다. 팀원 모두 MBTI가 I로 시작해서 팀명을 아이들
로 지었다.
인트라넷 서비스의 회사 이름은 Cube.IT(큐브잇)
으로 정했는데 (아이돌) 아이들의 소속사인 Cube와 IT 회사라는 의미의 IT를 합친 이름이다. 😁
팀 목표
- 다양한 예외 사항을 고려해 필수 기능을 제대로 구현하기
- Git과 Github 잘 활용해 보기
- 사용자 친화적인 UI/UX 설계하기
- 보안 강화하기
우리는 필수 기능을 제대로 구현하는 걸 최우선 과제로 삼았다. 대부분 팀 프로젝트 경험이 없거나 적었기 때문에 깃과 깃허브를 잘 활용해 효율적인 협업 방식을 배워보는 것도 중요한 목표였다.
서비스 목표로는 실제 사용자를 고려한 UI/UX 설계와 인트라넷 서비스인 만큼 여러 개인 정보를 포함하고 있어 보안에도 신경 써보자는 목표를 정했다.
개인적인 목표는 익숙한 것보다는 새로운 도전 해보기와 이유 있는 선택하기였다.
기술 스택
Vite
기반으로 만들어진 템플릿이 주어졌다. 요구사항에 맞게 프론트엔드 파트는 HTML, CSS, JavaScript
를 사용해 구현했다.
템플릿에 Express
기반의 간단한 서버가 구현되어 있었고 SQLite3
패키지가 설치되어 있었다.
SQLite3는 선택사항이었지만, 김민태 강사님께서 SQL 문을 사용해 데이터베이스를 다뤄보면 좋은 경험이 될 거라고 하셔서 도전해 보기로 결정했다.
사용한 주요 라이브러리는 다음과 같다.
Axios
: API 통신을 위해 사용Bcrypt
: 비밀번호 암호화에 사용Cloudinary
: 사용자가 업로드한 이미지를 url로 변환해 저장하기 위해 사용Dayjs
: 현재 시각 표시 및 캘린더 구현 등에 사용JsonWebToken(JWT)
: 클라이언트와 서버 간 보안정보를 안전하게 전달하기 위해 사용
기획
주어진 필수 요구 사항을 바탕으로 다 같이 기획을 시작했다. 프로젝트 경험이 있으신 조장님이 아이스브레이킹, 페르소나 설정, 와이어프레임 설계 등 기획을 이끌어주셔서 수월하게 진행됐다.
피그잼은 처음 사용해 봤는데 기획에 활용하기 좋아서 앞으로도 사용할 계획이다.
기획을 바탕으로 기능 정의서, 컴포넌트 관계도 등 문서 작업을 진행했다.
초반에 컴포넌트 관계도는 문서에서 제외하자는 이야기가 나왔는데, 설계하는 데 도움이 될 것 같아 내가 만들겠다고 했다.
팀원들이 컴포넌트 관계도 덕분에 전반적인 구조를 파악하기 좋았다고 해주셔서 뿌듯했다!
디자인
김민태 강사님께서 프론트엔드 개발자에게 디자인 감각은 중요한 요소이니 디자인 리서치를 해보며 UI 요소들을 직접 만들어보는 경험을 가지면 좋을 것 같다고 하셨다.
사실 팀에 디자인할 줄 아시는 분이 두 분이나 계셔서 내가 하게 될 줄은 몰랐는데 어쩌다 보니 디자인을 맡아서 하게 됐다.ㅎㅎ
반응형으로 구현 예정이었기 때문에 PC와 모바일 두 가지 디자인을 완성했다. 오랜만에 해서 그런지 디자인 작업은 나름 재미있게 끝낸 것 같다.
디자인이 나와야 개발을 시작할 수 있어서 레퍼런스를 활용해 주말에 빠르게 작업했다.
조장님(진짜 디자이너)이 레퍼런스도 구해주시고 피드백도 빠르게 주셔서 좀 더 수월하게 작업할 수 있었다.
역할
디자인 외에도 프로젝트 세팅, 기본 구조 및 라우팅 설계, 프로필 페이지 등 다양한 역할을 맡았다.
프로젝트 세팅
깃허브 관리자
라는 역할을 맡아 프로젝트 세팅을 담당했다. 코딩 컨벤션을 정하고 이슈 템플릿, PR 템플릿 등도 알아봤다.
다양한 깃허브 프로젝트 저장소를 찾아보며 Github Wiki를 활용해 보면 좋겠다는 생각이 들어 제안하기도 했다.
기본 구조 및 라우팅 설계
Class
문법을 사용해 컴포넌트 단위로 개발했다.
각 컴포넌트는 html
메소드로 템플릿을 설정하고, render
와 addEventListeners
등의 메소드를 활용해 일관된 구조를 유지할 수 있도록 설계했다.
라우팅은 History API
를 활용했다. Hash 방식도 고려했지만 URL에 #
이 포함되어 가독성이 떨어지고 보통 같은 페이지 내의 요소를 가리키는데 사용되는 방식이라 좀 더 깔끔한 URL 구조를 만들 수 있는 History API
방식을 선택했다.
SPA 구현을 위해 popstate
이벤트와 document.body
의 클릭 이벤트로 페이지 전환을 처리했다. 링크 클릭 시 handleNavigatePage
메소드가 실행되어 페이지가 전환된다.
URL의 동적 파라미터를 처리하기 위해 정규식을 사용한 matchRoute
함수도 구현했다.
// /members/:id 같은 동적 경로 처리
export const matchRoute = (path, routes) => {
if (!routes) {
return null
}
let matchedRoute = null
Object.keys(routes).forEach((routePath) => {
if (matchedRoute) return
const route = routes[routePath]
const paramNames = []
const regexPath = routePath.replace(/:[^\s/]+/g, (match) => {
paramNames.push(match.slice(1))
return '([^\\/]+)'
})
const regex = new RegExp(`^${regexPath}$`)
const match = path.match(regex)
if (match) {
matchedRoute = route
}
})
return matchedRoute
}
js
프로필 페이지
Cube.IT 인트라넷 서비스는 크게 로그인, 홈, 프로필, 구성원, 근무/휴가 페이지로 구성되어 있다. 그중 내가 맡은 페이지는 프로필 페이지
였다.
프로필 페이지에서는 근무 시작/종료 및 본인 프로필 수정을 할 수 있다.
재사용할 수 있게 컴포넌트 단위로 만든 덕분에 구성원 상세 페이지에서도 해당 컴포넌트를 사용해 빠르게 구현할 수 있었다.
이미지 파일을 업로드하면 Cloudinary를 이용해 url로 변환한 다음, 데이터베이스에 저장한다.
그대로 데이터베이스에 저장할 수도 있었지만, 용량 최적화를 위해 Cloudinary 서비스를 이용했다.
기본 이미지 설정
을 클릭하면 DiceBear API의 Lorelei 스타일 아바타를 랜덤으로 설정해주는 이스터에그 같은 기능을 추가했다.
처음 만들었을 때 재미있어서 홀린 듯이 계속 눌러봤던 기억이 있다. 🤣
구성원 상세 페이지
위에서 언급한 것처럼 프로필 페이지에서 만든 컴포넌트를 조합해 구현했다.
구성원 상세 페이지에서는 권한에 따라 확인할 수 있는 정보가 다르다.
관리자
는 연봉, 주소, 고용 정보, 학력 및 경력 등 구성원의 모든 정보를 확인할 수 있고, 수정도 할 수 있다.
반면 일반 직원
은 다른 구성원의 제한된 정보만 확인할 수 있다.
더미 데이터 생성
ChatGPT
를 활용해 직원, 부서, 휴가 관리 등 여러 테이블의 연관 데이터를 생성했다.
실제 서비스처럼 보이게 하려고 더미 데이터 생성에 많은 공을 들였는데, 그 시간을 전체적인 구조 설계에 더 투자했다면 좋았을 것 같다는 아쉬움이 든다.
프로젝트를 마치며
데이터를 어떻게 구성할지, 어떤 UI/UX가 더 사용하기 편할지, 어떻게 하면 안전하게 로그인한 사용자 정보를 저장해둘 수 있을지, 어느 시점에 어떻게 렌더링할지 등 정말 많은 요소를 고민하고 답을 찾아가며 프로젝트를 진행했다.
많은 고민이 있었던 만큼 좋은 결과물이 나올 수 있었던 것 같고, 혼자 했으면 절대로 이 정도 퀄리티를 낼 수 없었을 것 같다.
팀원들이 각기 다른 강점을 가지고 있어서 서로에게 배울 점이 많았는데 이번 프로젝트 경험이 다음 프로젝트에 큰 도움이 될 거라는 생각이 든다.
좋았던 점
팀 분위기
매니저님도 인정하실 정도로 팀 분위기가 정말 좋아서 프로젝트를 진행하는 내내 즐거웠다.
슬랙 소통도 활발했고 (덕분에 짤 폴더도 생겼다) 다들 맡은 일을 열심히 해줬다.
이슈 및 PR 활용
이슈에 구현해야 할 기능들을 미리 추가해 두고, 구현할 사람이 해당 이슈에 본인을 할당하는 방식으로 프로젝트를 진행했다.
이슈 목록을 보면 누가 어떤 작업을 하고 있는지 쉽게 파악할 수 있어서 프로젝트 진행에 큰 도움이 됐다.
PR은 2명 이상 승인했을 때 머지할 수 있도록 설정했다. 코드 리뷰 문화를 도입해 수정이 필요한 부분이나 개선했으면 하는 부분에 대한 리뷰를 남기면 반영했고 덕분에 프로젝트의 퀄리티가 높아질 수 있었다.
중반쯤 마일스톤 기능에 대해 알게 돼서 마일스톤도 추가했는데, 프로젝트가 얼마나 진행됐는지 파악하기 좋아서 유용했다.
목표 달성!
프로젝트가 끝나고 돌아보니 팀에서 목표로 한 부분들을 대부분 이룬 것 같아 신기하고 뿌듯했다.
개인적인 목표도 나름대로 달성한 것 같다. 우선 api 설계, 데이터베이스, Cloudinary 연동 등 백엔드 코드에 도전해 볼 수 있었고, 바닐라 자바스크립트로 SPA를 만들기 위한 구조를 설계하고 클래스 문법으로 구현해 보는 등 낯설지만 도전 자체만으로도 의미 있는 경험을 할 수 있었다.
배포도 도전해 봤는데 다음 프로젝트에서 또 배포를 하게 된다면 이번 경험을 바탕으로 좀 더 수월하게 할 수 있을 것 같다.
기능을 추가하거나 라이브러리 등을 정할 때도 항상 근거를 들어 의견을 제시하려고 노력했다. 다른 팀원분들도 각자 근거를 들어 의견을 제시해 주셔서 큰 갈등 없이 프로젝트를 진행할 수 있었다.
아쉬웠던 점
깃허브 프로젝트 활용
깃허브의 기능을 최대한 활용해 보고 싶어서 추가했는데, 이번 프로젝트에서는 실효성이 없었다.
이슈를 만들 때 프로젝트에 연결하긴 했지만, 프로젝트 페이지에 들어가 확인하는 팀원도 없었고 그냥 연결할 일 하나만 더 추가된 느낌이라 아쉬웠다.
문서화
문서화를 하지 않은 건 아닌데 자료를 찾는 데 어려움이 있었다.
회의록을 날짜로 관리하고 있어서 일정표를 찾기 위해 여러 회의록을 열어봐야 하는 등 (6/26 회의록 안에 있었다…) 원하는 자료를 빠르게 찾기 어려웠다.
제출을 위한 문서가 아닌 프로젝트 진행에 도움이 되는 문서 정리를 하기 위한 방법을 고민해 봐야 할 것 같다.
맺으면서
처음엔 정말 막막했는데 모두 힘을 합쳐 좋은 결과물을 만들어낼 수 있어서 뿌듯하고 즐거운 시간이었다. 우리 조도 정말 잘했지만, 다른 조도 각자의 개성을 담은 멋진 결과물을 만들어내셔서 발표를 보면서 대단하다는 생각이 들었다.
열심히 한 만큼 후회는 없고 이번 프로젝트를 통해 배운 것들을 다음 프로젝트에서 잘 활용해 보고 싶다.
CubeIT의 전체 코드는 Github에서 확인할 수 있다.