깊고 넓은 삽질

웹서비스 기획부터 운영까지 | 대학생 짝선배 짝후배 매칭 웹 개발 회고 본문

Programming/개발 회고록

웹서비스 기획부터 운영까지 | 대학생 짝선배 짝후배 매칭 웹 개발 회고

ggsno 2023. 3. 5. 13:08

  웹 서비스를 기획부터 배포, 운영까지 할 기회가 생겨 그 과정을 기록하고자 한다. 좋은 기회를 준 친구에게 감사의 마음을 전한다.

1. 기획 | 짝선배 짝후배 문화 이어가기

  친구의 대학교에는 짝선배 짝후배 문화가 있다. 신입생의 학교 적응을 위해 선배와 짝을 지어주는 문화라고 한다. 학생회에서 주최하는 프로그램이었는데 문제는 학생회가 사라지면서 이 문화도 흐지부지 사라질 위기에 처했다. 좋은 문화의 명맥을 이어가기 위해 나와 내 친구 둘이서 웹사이트를 운영해보기로 했다.

 

  웹사이트의 기능은 간단하다. 사용자가 본인의 정보와 원하는 탐색 범위를 제출하면 해당 범위에 적합한 상대를 이어주는 것이다. 개발 일정은 2월 초에 시작해 3월 개학 이전까지 배포를 목표로 잡았다.

원하는 탐색 범위 받기

  사용자가 원하는 짝과 이어지기 위해 탐색 범위를 선택할 수 있게 기획했다. 탐색 범위로는 성별, 학년 차이, 학번 차이, 학과범위가 있다. 다만 여기서 성별은 ‘동성’과 ‘상관 없음’으로 범위 옵션을 제한했다. ‘이성’은 의도적으로 옵션에서 제외했다. 성별 옵션은 이성의 선후배 관계가 불편할 이용자를 위해 동성 옵션을 넣은 것일 뿐, 이성의 선후배 관계를 원하는 이용자는 사용 목적이 이성 관계 발전에 있다고 판단했다. 이성과의 매칭을 위한 이용자는 학교생활 적응을 위한 짝선배 짝후배 문화와는 맞지 않아 배제하기로 했다. (이 때 우려했던 이런 부류의 이용자는 배포 이후에 작은 참사를 일으켰다... 후술할 운영 부문에 자세한 내용을 담았다.)

 

  정보 입력 방법으로 일반적인 form 형식보다 간편한 UX를 만들고 싶었다. 여기에 적합한 것으로 이전에 했던 프로젝트에서 만들었던 컴포넌트가 있어서 재사용했다.

마시는 차 플랫폼 사이트 개발 당시 만들었던 차 취향 찾기 컴포넌트

문제는 이 컴포넌트의 버튼에는 선택되는 옵션의 이미지가 들어갔었는데 우리의 옵션은 이미지화 하기엔 애매한 면이 있었다. 사실 이미지를 넣지 말고 그냥 글자를 넣으면 됐는데 뭔가 귀여운 요소를 넣고 싶었다. 그래서 버튼 안에 들어갈 귀여운 캐릭터를 그려줄 사람을 구했다.

회화과 학우분이 그려주신 귀여운 캐릭터. 카와이~

  캐릭터를 받고 보니 버튼이 의도한 값과 캐릭터가 아무런 상관이 없어서 버튼은 그냥 글씨만 있고 캐릭터는 질문지 위에 귀엽게 박아뒀다.

  추가로 버튼 안에 내가 찾고자 하는 범위에서 기다리는 사람이 몇명인지 실시간으로 볼 수 있게 만들기로 했다.

캐릭터 활용 최최종

본인의 정보 받기

본인의 정보는 회원가입을 통해 받는 방법이 있었지만 더 좋은 방법으로 학사시스템을 이용할 수 있었다. 서비스하는 대학에 재학중이라면 학사 시스템에 필수적으로 등록되어있기 때문에 학사 시스템 아이디 비밀번호를 입력하면 학사 시스템api를 이용해 사용자의 정보를 받아오도록 기획했다. 우리의 DB에 사용자의 아이디 비밀번호가 저장되지 않아 간편하기도 했다. (그런데 로그인 시스템을 외부 api로 맡겼을 때의 문제점이 있었다… 배포 후 가장 땀을 많이 싼 이슈였다... 후술할 운영 부문에 자세한 내용을 담았다.)

짝 이어주기

등록된 정보들을 기반으로 조건이 맞는 사용자 쌍에게 SMS문자를 전송해 서로의 연락처를 준다. 대안으로 채팅 채널 개설이나 카카오톡 알림이 있었는데 채팅 채널은 시간이 부족해 만들지 못했고 카카오톡 알림 서비스는 비용적인 문제가 있어 SMS문자 발송을 택했다.

두근두근 매칭 완료

 

2. 개발 | 빠른 개발과 확장 가능한 구조

  프론트 한 명에 백엔드 한 명, 총 두 명의 인력으로 개발을 하다보니 의견 조율이나 협업에 대한 공수가 크게 들어가진 않았다. 주로 오프라인으로 만나서 개발을 했고 노션에 회의록과 할 일, api 명세 등을 정리했다.

 

  내가 맡은 프론트엔드에서 개발할 때의 주안점은 빠른 개발과 확장 가능성이었다. 작은 프로젝트이기도 하고 개학까지 남은 기간이 촉박했기 때문에 과한 고민은 하지 않기로 했다.

atomic design + flat한 구조

  이전에 프로젝트를 할 때 많은 시간을 들였던 것 중 하나로 구조에 대한 고민이 있었다. 이를 줄이기 위해 최대한 단순한 구조로 설계하고자 했다.

 

  atomic design이란 컴포넌트를 원자, 분자, 결합체, 유기물, 페이지와 같이 단위 별로 나누는 디자인 방법론이다. 여기서 atom과 molecules만을 가져와 사용했다. atom에는 재사용될 가능성이 많은 단위의 컴포넌트를 넣고 molecules에는 특정 목적을 가진 컴포넌트를 모두 넣었다.

 

  flat한 구조란 디렉토리들로 중첩되지 않은 구조를 말한다. 디렉토리를 세세하게 많이 나누면 구조가 정교해지지만, 단점으로 구조가 복잡해지고 나누는 기준을 한 번 더 생각해야 하기 때문에 시간이 더 오래 걸린다. 디렉토리 중첩 구조를 배제하면 하나의 파일이 비대해질 수 있다. 이를 방지하기 위해 충분히 큰 파일은 파일을 나누되 닷(.) 이후 세부적인 사항을 명시하는 형식으로 응집도를 지켰다.

 

 

Atomic Design | Atomic Design by Brad Frost

Learn how to create and maintain digital design systems, allowing your team to roll out higher quality, more consistent UIs faster than ever before.

atomicdesign.bradfrost.com

 

File Structure – React

A JavaScript library for building user interfaces

reactjs.org

관심사 분리와 응집도

  관심사를 너무 잘게 나눈다면 응집도가 떨어질 수 있다. 그래서 최대한 단순하게 눈에 보이는 부분(이하 ‘뷰’로 통칭)과 데이터의 로직을 실행하는 부분(이하 ‘로직’으로 통칭)을 나누기로 했다. 여기서 로직은 데이터 관리, 상태 관리를 처리하는 부분이다. 비지니스 로직은 백엔드에서 처리된다고 가정했다. 하나의 컴포넌트는 뷰를 담당하는 뷰 파일과 로직을 담당하는 로직 파일로 나뉜다. 뷰 파일에는 스타일링, 레이아웃 등이 있고 로직 파일에는 상태를 관리하는 훅이 있다. 뷰 파일에 스타일링과 레이아웃을 함께 보여주기 위해 styled-components를 사용하다가 tailwindcss로 바꿨다. 처음 styled-components를 사용했던 이유는 컴포넌트와 컴포넌트명에 인라인으로 스타일이 들어가는 방식이 복잡해 보일 수 있고 컴포넌트의 이름을 짓는 행위가 재사용에 도움을 준다고 생각했기 때문이다.

// styled-components 사용시 뷰파일 예시
export default function MyComponent() {
  const { title, content, somedata, handleData, ... } = useMyHook(); // 상태관리 로직을 묶은 훅

  return (<>
            <StyledContainer>
              <StyledWrapper>
                <OtherComponent somedata={somedata}/>
                <h2>{title}</h2>
                <p>{content}</p>
                <input onClick={handleData} />
                ...
              </StyledWrapper>
            </StyledContainer>
         </>)
}

// 스타일링을 보기 위해서는 시선을 아래로 이동해야함
const StyledContainer = styled.div`
  display: flex;
  ...
`

...

  styled-components를 사용한 이유가 뷰 파일에 css를 같이 두기 위함이었는데 styled-components 변수 선언과 사용이 떨어져 있어서 불편했다.

  컴포넌트가 충분히 작다면 컴포넌트 명에 인라인으로 스타일링을 보여주는 편이 가독성이 더 좋았고 역할에 따라 지어지는 컴포넌트 명은 html태그와 파일명으로 충분히 유추할 수 있기도 했다. 또한 필요하다면 그 때 재사용을 위한 변수를 만들면 되기 때문에 tailwindcss로 변경했다.

// tailwindcss 사용시 뷰파일 예시
export default function MyComponent() {
  const { title, content, somedata, handleData, ... } = useMyHook(); // 상태관리 로직을 묶은 훅

  return (<>
            <div className="flex ...">
              <div className="text-white ...">
                <OtherComponent somedata={somedata} />
                <h2 className="text-white ...">{title}</h2>
                <p className="text-white ...">{content}</p>
                <input onClick={handleData} className="text-white ..."/>
                ...
              </StyledWrapper>
            </StyledContainer>
         </>)
}

기타 코드에 대한 사항은 해당 프로젝트의 깃허브 레포지토리에서 확인 가능하다.

 

GitHub - no-dam/sejong-peer-client: 짝선배 짝후배 매칭 웹앱 클라이언트

짝선배 짝후배 매칭 웹앱 클라이언트. Contribute to no-dam/sejong-peer-client development by creating an account on GitHub.

github.com

3. 배포 | AWS와 Github Actions로 배포 자동화하기

프론트엔드 배포는 다음과 같은 과정으로 이루어졌다.

  1. AWS S3 버킷 생성 후 build한 html과 정적 파일들을 넣고 정적 웹 사이트 호스팅 설정. 이 때 필요한 html파일은 github action으로 깃허브 레파지토리에 push시build를 실행하고 이상이 없다면 AWS S3 버킷에 있는 파일 갱신하도록 자동화
  2. 구매한 도메인의 네임 서버를 Route 53에 등록, S3 버킷과 CloudFront 배포를 가리키는 DNS 레코드를 생성
  3. HTTPS 보안 연결을 위해 AWS Certificate Manager (ACM)를 사용하여 SSL/TLS 인증서 발급
  4. 생성한 ACM 인증서를 사용하여 CloudFront 배포에 대한 SSL/TLS 설정을 구성

  배포에 많은 도움을 받은 글이 있어서 공유한다.

 

S3, ACM, CloudFront, Route53으로 서버리스 페이지 https 배포하기 [Intro]

아직 AWS에 익숙하지 않은 프론트엔드 개발자가처음부터 차근차근 따라해서자신이 소유한 도메인에 https를 붙여 배포하는과정을 상세히 작성한 시리즈입니다.

velog.io

  배포는 자동화 했지만 업데이트시 CDN의 캐싱을 무효화하는 것까지 자동화하진 못했다. 그래서 가끔 이전 버전의 정적 파일이 제공될 때가 있었는데CloudFront에서 변경된 파일을 직접 무효하는 방법으로 해결했다.

 

4. 운영 | 휴먼 에러로 식은땀 줄줄 싸기

  생각보다 많은 사람들이 우리 사이를 이용해 당황했다. 많아야 80명 내외로 생각했었는데 오픈 첫 날 무려 600명이 매칭 풀에 등록했다. 사이트 방문 수는 첫 날 30000번을 넘어섰다. 버그 제보와 개선요구도 있었다.

왜케 만이 옴???

로그인이 안 돼요

  기획 단에서 언급했듯이 우리 사이트는 회원가입 없이 학사시스템 정보로 로그인한다. 그런데 학사시스템 아이디와 비밀번호를 정확히 입력했는데도 로그인이 안되는 사용자가 생겼다. 외부 api를 사용해 인증하는 방식이라 뭐가 문제인지 감이 잡히질 않았다. 감사하게도 해당 버그를 유발하는 사용자들이 로그인 시도를 많이 해줘서 로그인 실패 로그에서 특이점을 찾을 수 있었다. 이런 종류의 인증 오류가 나는 이용자는 학과가 특이하다는 점을 발견할 수 있었다. 알고 보니 학사 api에서 제공하는 학과명과 대학교 공식 홈페이지의 학과명이 다른 경우가 있었다.

 

  학사 api에서 제공하는 정보는 학과명 밖에 없었는데 우리 서비스의 탐색 범위 옵션으로 ‘같은 단과대 내에서만 찾기’을 넣고싶었다. 그래서 대학교 공식 홈페이지에 나온 학과들을 크롤링해 학과의 단과대를 매핑시켜 사용자의 단과대를 알아냈다. 이 과정에서 공식 홈페이지에 없는 학과는 크롤링되지 않아 인증 오류를 발생시켰던 것이다. 오류 분기를 더 세세하게 짰으면(api 인증 실패로 뭉뚱그렸던 것을 api 인증 성공했지만 해당 학과는 없음 오류로 나누기) 더 빠르게 해결할 수 있었을 것 같고 외부 api에 너무 의존했던 것 같다.

식은땀 한 바가지 싼 이슈였따

범위를 다르게 했는데 왜 같은 사람이 매칭되나요

  사용자가 짝 탐색 범위를 더 좁게 설정하고 매칭을 돌렸는데 이전과 같은 결과가 나온다는 제보가 들어왔다. 처음에는 백엔드 알고리즘을 의심했다. 그런데 백엔드 로그를 확인해보니 모든 사용자의 탐색 범위가 모두 ‘상관 없음’으로 가장 넓은 범위만 제출되고 있었다.

 

  코드를 확인해보니 프론트 문제였다. 탐색 범위를 상수 값들로 관리하고 있었는데, 주어진 상수 범위 밖의 값이 들어왔을 때 오류를 발생 시키지 않고 제일 큰 범위인 “상관 없음”으로 설정해서 벌어진 일이었다.

 

  주어진 상수 범위 밖으로 나간 이유는 탐색 범위 옵션 상수 값이 개발 중간에 바뀌었었는데 그것을 단일 포인트에서 관리하지 않아 서로 값이 달라 발생했다. 테스트 코드를 만들지 않아서 발생한 일이기도 하다.

 

  프론트엔드 테스트는 개발서버에 모킹 데이터를 이용해 일일이 손으로 확인했었는데, 자동화가 안되어있다 보니 배포 기한이 다가올 수록 테스트를 꼼꼼히 하지 않았고, 명시적인 에러가 나오지 않아 놓친 부분이었다. 클린코드, 리팩터링 책을 백날 봐도 이런 기본적인 분기 방법이나 에러 처리가 습관이 들지 않으면 무용하다는 것을 여실히 느꼈다.

매칭을 취소하고 다시 찾는 주기가 매우 짧은 이용자 발생

  짝과의 성향이 맞지 않아 매칭을 취소할 수도 있다. 하지만 사람이 서로 맞지 않는다는 것을 알기 위해선 최소한 서로 대화를 통해 알아가는 시간이 필요하다. 그런데 로그 상에서 매칭 즉시 취소 후 재등록을 반복하는 이용자가 발견되었다. 추측컨대 성별 옵션을 ‘상관 없음’으로 설정하고 동성의 짝이 매칭되면 취소하고 이성의 짝과 매칭될 때까지 매칭 시도를 반복하는 것이리라. 이에 계속 매칭이 취소되는 사용자들이 차라리 짝 성별 선택 옵션 중 ‘이성’을 추가해 달라는 피드백이 있었다. 하지만 이는 기획단에서 언급했듯 기획 의도와 맞지 않는 의도의 사용자다.

 

  이런 상황을 방지하기 위해 매칭 후 일정 기간 내에 매칭 취소 시 패널티를 추가했다. 그런데 여기에 파생된 문제로, 패널티는 매칭 취소를 신청한 이용자에게만 가기 때문에 의도적으로 상대의 매칭 취소를 유도하는 경우(잠수타기 등)가 생길 수 있었다. 하지만 딱히 좋은 대안이 떠오르지 않아 그런 경우가 있다면 제보를 통해 해결하기로 했다. 귀찮더라도 이게 확실한 방법이라고 판단했다.

5. 마치며..

  지금은 퇴사한, 전 직장에서 일할 때 개발에 대한 흥미가 점점 떨어지고 있었다. 많은 이유가 있었지만 가장 큰 문제는 내가 뭘 하고 있는지 모른다는 것이었다. 내가 만들어낸 작업이 가시적인 결과물로 보인다는 점에서 프론트엔드를 선택했지만 전 직장에서 내게 주어진 일은 그와는 거리가 있었다. 이번 프로젝트에서 겪은 경험이 개발에 대한 열정을 다시 살려줬다. 동료와의 긴밀한 협업과 모든 일을 주도적으로 결정하는 경험이 열정을 되살리는 데에 많은 도움을 줬다. 나에게 좋은 기회를 준, 행운을 몰고 다니는 친구에게 다시 한번 감사의 마음을 전한다.

Comments