[SOPT] 30기 솝커톤 후기

공혁준·2022년 5월 22일
0

SOPT 활동

목록 보기
7/8
post-thumbnail

인트로

SOPT 회원이라면 누구나 참여하고 싶은 솝커톤!
밤새 솝커톤을 마치고 집에 돌아와 최대한 생생한 기억으로 후기를 작성해보려 한다.
코로나 거리두기 제한이 해제됨에 따라 이번 해커톤은 전체 오프라인으로 진행되었다.
인원 수 제한이 있어서 파트 별 선착순으로 신청을 받았는데 다행히도 신청에 성공했다.
다양한 팁 주신 OB 분들 감사합니다.. 🐷

주제

이번 30기 솝커톤은 주제는 일상이었다.

워낙 포괄적인 주제라 다양한 아이디어들이 나오겠다는 생각을 했고 우리 팀은 곧바로 아이데이션에 들어갔다.
우리 팀은 협업 툴인 Notion을 활용해서 아이데이션을 진행했는데, 브레인스토밍을 하듯이 자유롭게 아이디어를 적어나가며 발산하는 과정을 거치고, 아이디어를 추리는 수렴, 최종적인 아이디어 확정, 디벨롭 순서로 진행했다.

다양한 의견들 중 우리 팀은 일상 속 고맙고 미안한 마음을 자유롭게 기록할 수 있는 서비스를 구상하게 되었다.

서비스 소개

과일을 나타내는 단어인 fruit을 이용해 '푸릇푸릇한 나무를 열매로 꾸미는 서비스' 푸릇푸릇 서비스다.

서비스의 핵심 기능 4가지는 열매를 매달고, 매단 열매를 수확하고, 익명의 열매들을 통해 힐링하고, 공감되는 열매의 나무에 물을 주는 것이다. 푸릇푸릇 서비스에서 열매는 게시글을 의미하고 감사하는 글은 감 열매가 매달리고 사과하는 글은 사과 열매가 매달린다. 물을 주는 것은 글에 공감 표시를 한다는 의미이다.

서버 파트 작업

아이디어가 확정된 후 기획 파트 팀원들이 구체적인 와이어프레임 작업에 들어갔고 디자이너, 개발자와 소통을 통해 서비스의 flow를 확정지었다. 나를 포함한 서버파트는 곧바로 데이터베이스를 위한 컬렉션 설계에 들어갔다. 무박 2일 16시간 동안 진행되는 짧은 기간의 해커톤이었기 때문에 기능이 간소해서 컬렉션은 두 가지로 충분했다.

User Collection

import mongoose from "mongoose";
import { UserInfo } from "../interfaces/user/UserInfo";

const UserSchema = new mongoose.Schema({
    userNickname: {
        type: String,
        required: true
    },
    userProfileImageUrl: {
        type: String,
        required: true
    }
});

export default mongoose.model<UserInfo & mongoose.Document>("User", UserSchema);

Fruit Collection

import mongoose, { Mongoose } from "mongoose";
import { FruitInfo } from "../interfaces/fruit/FruitInfo";

const FruitSchema = new mongoose.Schema({
    type: {
        type: Number,
        required: true
    },
    contents: {
        type: String,
        required: true
    },
    wateringCount: {
        type: Number,
        required: true
    },
    userId: {
        type: mongoose.Schema.Types.ObjectId,
        required: true,
        ref: "User"
    },
    onTree: {
        type: Boolean,
        required: true
    }
},
{
    timestamps: true
});

export default mongoose.model<FruitInfo & mongoose.Document>("Fruit", FruitSchema);

컬렉션 설계를 마치고 필요한 API들을 파악하며 클라이언트 파트 팀원들의 원활한 작업을 위해 API 명세서를 작성하기 시작했다. 노션을 이용해서 작성하였고 명세서를 모두 작성한 후에는 클라이언트 팀원과 회의를 통해 이대로 진행해도 좋을지 확인을 마쳤다.

API 명세서는 Notion을 활용해서 작성했고 end point 에 작성한 내부 페이지에 들어가면 각 API 별로 필요한 Request Header, Request Param, Request Query, Request Body, Response Body, Response Code 등을 모두 정리해두었다.

API 명세서 작성을 마치고 본격적인 코딩에 들어가기 전, 서버 파트 팀원과 각자 담당할 API, 코드 컨벤션, 커밋 메시지 컨벤션, Git 브랜치 전략에 대한 회의를 진행했다. 협업 방식을 모두 정하고 나서야 마침내 코딩을 시작했다.

에러 발생과 해결

작업 총 소요 시간은 오후 8시부터 오전 6시 30분까지 10시간 30분 정도였다. 이 과정에서 골치 아픈 에러가 두 번 정도 발생했다. 첫 번째 에러로 서버 파트 팀원과 거의 1시간을 넘게 낭비했다.

GET /fruit/my API 요청을 보내면 발생하는 문제였는데 아래와 같은 에러 메시지가 나왔다.

CastError: Cast to ObjectId failed for value "my" (type string) at path "_id" for model "Fruit"
    at model.Query.exec (/Users/orijoon98/Desktop/Workspace/Nangman-Server/node_modules/mongoose/lib/query.js:4719:21)
    at model.Query.Query.then (/Users/orijoon98/Desktop/Workspace/Nangman-Server/node_modules/mongoose/lib/query.js:4818:15)

구글링도 많이 해보고 관련된 코드를 전체적으로 살피며 문제점을 찾아보려 했지만 실패했고 결국 서버 파트장님에게 도움을 요청했다. 서버 파트장님의 도움으로 원인을 알게 되었는데 라우터가 원인이라는 것을 알게 되었다.

에러가 났던 라우터 코드

import { Router } from "express";
import { body } from "express-validator/check";
import { FruitController } from "../controllers";

const router: Router = Router();

router.get("/", FruitController.getFruits);
router.post(
    "/",
    [body("type").notEmpty(), body("contents").notEmpty()],
    FruitController.createFruit
);
router.get("/my/tree", FruitController.getMyFruitsOnTree);
router.get("/:fruitId", FruitController.findFruitById);
router.get("/my", FruitController.getMyFruits);
router.put("/:fruitId/water", FruitController.putWateringCount);

export default router;

여기서 router.get("/my", FruitController.getMyFruits);
이 부분으로 요청이 넘어가기 전에 라우터는 위에서 아래로 확인하며 동작하기 때문에 /fruit/my 로 보낸 요청이
router.get("/:fruitId", FruitController.findFruitById); 라우터로 라우팅 되어
fruitId Param에 my가 들어가서 발생한 에러였다.

해결한 코드

import { Router } from "express";
import { body } from "express-validator/check";
import { FruitController } from "../controllers";

const router: Router = Router();

router.get("/", FruitController.getFruits);
router.post(
    "/",
    [body("type").notEmpty(), body("contents").notEmpty()],
    FruitController.createFruit
);
router.get("/my/tree", FruitController.getMyFruitsOnTree);
router.get("/my", FruitController.getMyFruits);
router.get("/:fruitId", FruitController.findFruitById);
router.put("/:fruitId/water", FruitController.putWateringCount);

export default router;

두 라우터의 순서를 바꿔주는 것 만으로 에러를 해결할 수 있었다. 라우터의 동작 방식을 제대로 이해하고 있지 않아서 이런 문제를 파악하지 못했었다는 생각이 들었고 기술에 대한 이해는 역시 중요하다는 걸 다시 한번 되새기게 되었다.

두 번째 문제는 사소한 실수로 발생한 에러였다. 작업을 모두 마친 후 AWS EC2 에 배포하여 테스트를 진행했는데 로컬 호스트에선 정상적으로 작동하던 API들이 작동하지 않았다. npm, yarn 등의 버전을 로컬과 비교해보고 데이터베이스를 확인해보고 빌드도 다시 해보는 등 다양한 시도를 해보았는데 문제는 해결되지 않았다. 주변 사람들에게 도움을 요청했지만 모두들 원인을 찾지는 못했다. 망연자실하던 와중 AWS EC2에 작성했던 .env 파일에 데이터베이스 접근 url을 다른 값을 복사했다는 것을 발견하게 되었다. 너무나 사소한 실수였지만 그 실수 하나로 1시간을 넘게 애먹은 걸 생각하면 ...😥 정신차리고 코딩해야겠다.

소감

무박 2일 동안 진행되는 해커톤은 이번이 세 번째 경험이었다. 지난 두 번의 해커톤 때, 밤 새 팀원들과 함께 고생하며 서비스를 만들어가는 과정이 즐거워서 이번 해커톤도 신청하게 되었는데 역시나 즐거웠다. SOPT 동아리 내에서 진행한 해커톤이었기에 친한 사람들도 많아서 더욱 더 즐겁게 행사를 즐긴 것 같다.

디자인 파트, 웹, 앱 개발자 분들과 협업을 진행해 본 경험이 있지만, 기획 파트와의 협업 경험은 이번이 처음이었다. 기획 파트 팀원들의 아이데이션 주도, 와이어프레임 작업, 팀원, 스케줄을 관리하고 마무리 발표까지하는 과정을 보며 기획 파트의 역할에 대해서도 배울 수 있었다.

또한 지난 해커톤, 공모전 경험들 모두 서버 파트를 혼자 담당하는 경우가 전부였는데 이번 해커톤을 통해 다른 서버 팀원분과 협업하는 경험을 할 수 있었다. 코드 컨벤션, 깃 컨벤션, 브랜치 전략, 이슈 관리, 프로젝트 관리, PR 충돌 해결 등 혼자 작업할 때는 경험해보지 못한 수 많은 협업 프로세스를 경험해 볼 수 있었고 이후에 다른 서버 파트 팀원과의 협업도 더 잘 할 수 있겠다는 자신감을 얻게 된 계기가 되었다.

그리고 모든 협업이 그렇듯 언제나 가장 중요한 것은 소통과 배려였다. 서로의 의견에 귀를 기울이고 작업 상황을 공유하며 배려하는 마음으로 모두가 함께 해야 작업이 즐겁다. 내가 속한 팀의 팀원들은 모두 소통과 배려를 잘 해주었고 그 덕분에 이번 30기 솝커톤은 좋은 추억으로 남을 것 같다. 😊

profile
몰입을 즐기는 개발자입니다.

0개의 댓글