[PROJECT] 1st Project - SoolDamHwa Modeling

먹보·2022년 12월 13일
0

MUK_BO's Projects

목록 보기
1/5
post-thumbnail

개요 및 기획 방향

술담화 : https://www.sooldamhwa.com/
기간 : 2022년 11월 14일 ~ 2022년 11월 24일
GIT HUB 주소 : https://github.com/MatheGoD/My_1st_project
기획 방향 : 첫 번째 프로젝트인 만큼 기본 개념에 충실하여 기본적인 기능 구현 및 필터링에 충실

간략한 팀원 소개 및 역할

프론트 : 4명
백엔드 : 3명
MY ROLE : 로그인/회원가입, 전체 상품리스트 (정렬 및 필터링), 상세 페이지

사용한 기술 스택

백엔드 (NODE.js + EXPRESS, JavaScript, MySQL , AWS EC2 AND RDS )

ERD 설계

구현 코드 설명 및 코드에 대한 회고

RDBMS - MySQL QUERY

SELECT 
        p.id,
        p.name, 
        ROUND(p.price,0) as price, 
        pi.image_url, 
    JSON_ARRAYAGG(
        JSON_OBJECT(
            "tags",t.name
            )) as tags, 
    (SELECT 
        COUNT(c.title) 
        FROM comments c 
        WHERE c.product_id = p.id 
        GROUP BY c.product_id) as reviews, 
    (SELECT 
        CAST(AVG(c.rating)*100/5 AS UNSIGNED) 
        FROM comments c 
        WHERE c.product_id = p.id 
        GROUP BY c.product_id) as ratings 
    FROM products p 
    LEFT JOIN product_images pi ON p.id = pi.product_id 
    LEFT JOIN tags t ON p.id = t.product_id
    ?
    GROUP BY p.id, pi.image_url
    ORDER BY ?
    ?;
    `,
    [whereCond, sortCategory, limitOffset]

REVIEW :

MySQL의 기본적인 문법들을 연습하기 위해 우리는 진행되는 프로젝트 동안 RAW QUERY를 사용하기로 했다. 물론 SQL INJECTION이라던가 코드 리뷰의 리소스 부하 등과 같은 이유로 RAW QUERY를 사용하기 보다는 ORM을 사용해보는 것이 좋을 수도 있으나 이건 추후에 따로 공부하면 충분히 할 수 있을 것 같다.

쿼리문으로 돌아와 이번 프로젝트의 메인 쿼리문이자 1차 프로젝트의 주 목적인 필터링 부분을 내가 담당하게 되었다. 우선 전체 리스트 불러오기 부터 쿼리문을 짜기 시작했지만 처음에는 난처하기만 했다. 정규화 작업이 완료된 ERD의 데이터 값들을 한 번에 가져와야 했는데 머릿속으로 들어오지 않았다.

그래서 쪼갰다.

결합집합이라던가 SUBQUERY가 단일 역할을 할 수 있다는 사실을 알기에 각 부분을 하나의 과제로 보고 작동이 될 수 있을 만한 쿼리로 만든 후 하나로 합쳐 버리기로 했다. 각 서브쿼리들을 하나로 합치는 과정이 컬럼이 중복되는 경우도 발생하고 조건이 맞지 않아 문법 에러도 나서 쉽지 않았지만 테이블 간의 관계를 파악한 뒤로는 조건 하나를 더 추가해주는 것으로 해결이 되었고 모든 로직을 짜는 것이 코딩 테스트와 같았기에 마무리 된 후의 희열은 수학에서 증명 문제 하나를 힘들게 푼 것과 같은 느낌이 들어 기분이 좋았다.


RDBMS - MySQL MY OWN QUERY BUILDER

const makeWhereList = (
    cate_id , sweetness , sourness , carbon , fruit , flower , grain , priceRange , alchol ) => {
    
    const startLine = "WHERE ";
    const filter = ["p.id IS NOT NULL"];
    
    if (typeof(cate_id) == 'object'){
      const orS = cate_id.map(el => `categories_id=${el}`).join(" or ")
        filter.push(orS);
      } else if (typeof(cate_id) == 'string'){
        filter.push(`categories_id=${cate_id}`)
      };
    if (typeof(sweetness) == 'object'){
      const orS = sweetness.map(el => `sweetness_id=${el}`).join(" or ")
        filter.push(orS);
      } else if (typeof(sweetness) == 'string'){
        filter.push(`sweetness_id=${sweetness}`)
      };
    if (typeof(sourness) == 'object'){
      const orS = sourness.map(el => `sourness_id=${el}`).join(" or ")
         filter.push(orS);
      } else if (typeof(sourness) == 'string'){
        filter.push(`sourness_id=${sourness}`)
      }
    if (typeof(carbon) == 'object'){
      const orS = carbon.map(el => `carbon_id=${el}`).join(" or ")
         filter.push(orS);
      } else if (typeof(carbon) == 'string'){
         filter.push(`carbon_id=${carbon}`)
      }
    if (typeof(fruit) == 'object'){
       const orS = fruit.map(el => `fruit_id=${el}`).join(" or ")
         filter.push(orS);
      } else if (typeof(fruit) == 'string'){
         filter.push(`fruit_id=${fruit}`)
      }
    if (typeof(flower) == 'object'){
       const orS = flower.map(el => `flower_id=${el}`).join(" or ")
         filter.push(orS);
      } else if (typeof(flower) == 'string'){
         filter.push(`flower_id=${flower}`)
      }
    if (typeof(grain) == 'object'){
      const orS = grain.map(el => `grain_id=${el}`).join(" or ")
        filter.push(orS);
    } else if (typeof(grain) == 'string'){
        filter.push(`grain_id=${grain}`)
    }
    if (priceRange) {
      filter.push(`price BETWEEN ${priceRange[0]} AND ${priceRange[1]}`);
    }
    if (alchol) {
      filter.push(`alchol BETWEEN ${alchol[0]} AND ${alchol[1]}`)
    }
    const body = filter.join(" AND ");
    const combined = startLine + body;
    return {
      toSqlString: function () {
        return combined;
      },
    };
  };

REVIEW :

내가 처음으로 만들어본 나만의 쿼리 빌더.

필터링 효과를 도대체 어떻게 구현해야하지를 머릿속으로 몇 날 며칠을 고민해보았고 그래서 낸 결론이 쿼리를 경우에 따라 설계해서 MYSQL 로직에 추가로 넣어주면 어떨까가 떠올라 MYSQL SYNTAX를 직접 작성하여 변수에 담아 넣어보기로 하였다.

추후 멘토님들께 여쭤본 결과 이런 거를 쿼리 빌더의 시작이라고 하였고 개념을 인지하지 못한 채로 만들었다는 사실에 희열을 느껴 다시 한 번 코딩에 재미를 느꼈다.

사실 코드 자체만으로 보자면 반복되는 패턴이 보여지는 것을 봐서는 어떤 식으로는 리팩토링이 가능할 것이라 느끼기에 자랑스럽고 자랑하고픈 코드는 아니지만 처음 내 손으로 뭔가를 해냈다, 뭔가를 완성해보았다는 성취감을 기록하기에는 이 만한 코드가 없다고 느껴지기에 내 블로그에 박제를 해놓는다. 물론 어느정도 정리된 후에는 다 뜯어 고쳐보고 싶은 마음은 굴뚝 같다.


ESCAPING / PLACEHOLDER / MySQL INJECTION

 return {
      toSqlString: function () {
        return combined;
      },

REVIEW :

쿼리 빌더에 사용한 ESCAPING

우선, 사용한 이유부터 설명을 하자면, 처음 쿼리를 변수에 담아 넣는 다는 아이디어를 냈을 때 너무 기분이 좋아 바로 적용해보기로 하였지만 이상하게 SQL SYNTAX 에러가 발생해 의문을 남겼다.

에러의 원인을 찾아 보자 MYSQL INJECTION을 피하기 위해 PREPARED STATMENT OR PLACEHOLDER의 개념을 사용해 우리는 MYSQL 문구 자체에 변수를 넣는 방식을 택했지만 이게 문제가 되었다. PLACEHOLDER안에 쿼리문이 할당된 변수가 들어갈 경우 ""가 포함되어있는 채로 SQL문에 들어가게 되는 데 그러면 ""을 제대로 스트링의 일부로 인지되어 SYNTAX에러가 나지만 TEMPLATE LITERAL을 사용해 ${변수} 형태로 바로 넣게 되면 이런 문제가 발생하지 않지만 MYSQL INJECTION을 피하기 위해 배운 PLACEHOLDER가 무의미해진다. 그래서 어떻게 하면 PLACEHOLDER를 유지한 채 넣을 수 있을 까를 구글링 해본 결과 ESCAPING을 생각하게 되었고 적용했더니 문제 해결이 되었다.

하.지.만. 추후 멘토님께 피드백을 받았을 때 들은 사실로는 ESCAPING을 쓰고 나서 PLACEHOLDER를 사용할 경우 PLACEHOLDER를 쓴 의미가 없어진다고 한다. 여기서부터는 추가적인 개념이 필요하기에 향후, MYSQL DRIVE를 살펴보게 될 때 한 번 공부해보려고 한다.


전반적인 회고

REVIEW :

처음으로 개발자로써 진행한 프로젝트이기도 했고 회고가 익숙하지 않기에 4L 방식으로 회고를 해보려고 한다.

4L: Liked, Learned, Lacked, Longed for
😍 좋았던 것(Liked)
📚 배운 것(Learned)
💦 부족했던 것(Lacked)
🕯 바라는 것(Longed for)

  1. 좋았던 것(LIKED) :
    처음이었기에 모든 게 재밌었다. 처음으로 프론트와 협업을 해본 것, 누군가와 같이 말을 맞춰 가며 일해 본 것 등 A-TO-Z 다 재밌었다. 특별히 재밌는 것을 뽑자면, 누군가한테는 TEXT로 밖에 보여지지 않을 법한 데이터들이 실제로 프론트와의 통신을 통해 누구나 다 읽어 볼 수 있도록 맵핑이 되었을 때 기분이 너무 좋았다.

  2. 배운 것(LEARNED) :
    프론트와의 협업을 배웠다. 물론 처음이기에 많이 미숙했지만, 어떻게 해야 프로젝트가 완성이 되어 가는 것인지 얼추 큰 그림은 그릴 수 있을 만큼 배운 것 같았다.

  3. 부족했던 것 (LACKED) :
    이 것 역시 2번과 동일하다 프론트와의 대화가 많이 부족했다 느꼈고 API 명세의 중요함을 깨달았다. 처음이어서 그런지 작업을 진행하면서 변수명에 있어 많은 수정이 있었고 RESTFUL API의 전달 방식에 있어 혼동이 와서 마지막에 시간을 많이 잡아 먹었다.

  4. 바라는 것 (LONGED FOR) :
    2차 프로젝트 진행 시에는 API 명세를 중요시 여기고 프론트와의 협업에 힘을 다해 쓸데 없는 시간을 낭비하는 일이 없도록 신경을 쓰겠다.

profile
🍖먹은 만큼 성장하는 개발자👩‍💻

0개의 댓글