첫 개인 프로젝트 Refactoring

shinychan95·2020년 1월 24일
0
post-thumbnail

첫 번째 개인 프로젝트 - URL-Shortener
https://github.com/shinychan95/url-shortener

첫 주에 했던 프로젝트 refactoring을 이제서야 시작해 끝을 냈다. 설날동안 미뤘던 refactoring과 블로그 정리를 마치겠다는 포부를 이제 시작했는데, 월요일까지 끝을 보도록 하겠다🙋‍♂️


URL-Shortener

우선 이 기능은 사용해왔지만, 서비스로써 존재하는 것과 용어에 대해서는 처음 알게 되었다. 그래서 나름 새로웠고 흥미가 들었다. 또 간단하지만, 프론트 및 백 엔드 개발을 모두 해야 하는, 기본 능력을 한번 짚고 넘어가는 느낌의 프로젝트라 한번 제대로 하고 싶었다.

프로젝트의 필수 기능은 아래와 같았다.

  • URL 입력 폼 제공
  • 단축 후 결과 출력
  • 동일한 URL을 입력할 경우 항상 동일한 shortening 결과 값이 나와야 함
  • Shortening의 결과 값은 8문자 이내로 생성
  • 브라우저에서 shortening URL을 입력하면 원래 URL로 리다이렉트
  • 도메인은 localhost로 처리

Raw한 결과물

~보여주기 민망해 refactoring 후 결과만 공개해본다.~

~(사실 서버는 다른 사람의 글을 거의 참고 및 이해만 했었고, 나는 프론트 부분만 작업했다고 봐도 무방하다.)~

매번 그러했듯이 나는 참고할만한 사이트를 찾았고, 이를 바탕으로 만들어가기 시작했다.
참고 사이트

그리고 후하고 값진 피드백을 어마무시하게 들었다. 도움이 되는 만큼 나의 현 상태를 깊게 되돌아보는 순간이었다.

피드백 및 해결

~(자세한 부분은 길 것 같아서 깔끔하게 정리해본다)~

  • Shorten ID 변환에 있어서 난수 생성이 아닌 base 62 사용
    - 리다이렉트 시 데이터에 직접 접근을 위해 사용한다.
    • 기존에 shorten id 생성에 있어서 모듈을 사용하였는데, 모듈을 사용하지 않고 구현하였다. ~물론 base62 encode 및 decode 코드를 참고하여 구현)~
    • 물론 MongoDB를 사용하여 RDBMS처럼 직접 접근할 순 없지만, 최대한 직접적으로 하기 때문에 MongoDB indexing 기능 및 Number Type을 사용했다.
  • Default ObjectId generator가 아니라, 오름차순 숫자 Index
    - { "_id" : ObjectId("5e15dc1722d31d3c44b7a26a") } 의 경우 공식 문서에 따르면 하나의 값이라기 보다 클래스에 가까우며, 이 데이터를 추적할 때 쓰는 것 같다.
    - 아래는 공식 문서에서 ObejctId 에 대해서 나온 개념이다.
    - An ObjectId is a special type typically used for unique identifiers. Here's how you declare a schema with a path driver that is an ObjectId
    - ObjectId is a class, and ObjectIds are objects. However, they are often represented as strings. When you convert an ObjectId to a string using toString(), you get a 24-character hexadecimal string
    - 따라서, ObjectId 를 index로 바꿔주는 것이 아닌, 따로 unique index를 생성하였다.
    • mongoDB의 경우, NoSQL에 key-value 형태이기 때문에, RDBMS의 row index와 같은 것처럼 직접적으로 접근하는 것이 아니다.
    • 하지만, 색인 기능을 늘리기 위해 몽고디비에서 unique와 index 기능을 제공하는데, 이를 통해서 그나마 빠르게 찾는 구조를 설계해보았다.
    • 서버에서 처리되는데 가장 오래 걸릴 것으로 예상되는 것이 바로 original URL이 DB 내에 존재하는지 확인하는 것인데, 이 작업을 빠르게 하기 위해서 MongoDB 내에 original URL을 unique type으로 하여, 빠르게 색인하도록 하였다.
  1. DB: shortUrl 데이터 생성 시 localhost 제거 (굳이 필요 없음)
    • 그저 DB 내에 저장되는 데이터가 적을수록 좋다는 판단인데, 사실 퍼포먼스 및 리소스에 과연 큰 영향을 줄까 싶기도 한 부분이다.
  2. ID index를 결정할 때, base62 변환 시 8글자 되려면 100만부터 하면 됨
    • 사실 이것은 결과로써 나오는 shorten id의 형태가 8자리로 깔끔하게 나오게 하기 위해 처리하는 부분이다. 그래서 결국 document 수를 count한 뒤에 10000000000000을 더해주었다.
  3. (이전 프로젝트에서) MySQL 사용 시 TEXT형 데이터 절대 NO
    • 추가적인 피드백이었는데, var이나 char라는 데이터 타입으로 목적에 맞는 크기로 설계하는 것이 좋다는 피드백이다.
  4. 데이터 1억개 되었을 때도 효율적이려면: Encoding 된 데이터를 디코드해서 바로 key 접근
    • 사실 가장 크게 놓친 부분이 바로 이 부분인데, original URL은 중복 검사를 모두 해야 하지만, shorten id를 original URL로 변환하는 부분은 곧바로 할 수 있다는 사실을 망각했었다.
  5. 누가봐도 이해할 수 있는 코드에 대해서는 주석 생략

결론

  • 리소스 및 퍼포먼스 향상을 위해 고민은 끝없이 하자
  • DB의 장점 및 특성에 대해서 정확히 탐구하자
  • 나는 아직 멀었다

상세한 Refactoring Code

Base62 Encoding 및 기타 처리

// 기존 코드

// 그저 모듈을 이용해서 shorten id를 생성하여 사용
app.post("/api/urlshortner", async (req, res) => {
    const { originalUrl, shortBaseUrl } = req.body;
    if (validUrl.isUri(shortBaseUrl)) {
    } else {
      return res
        .status(401)
        .json(
          "Invalid Base Url"
        );
    }
    const urlCode = shortid.generate();
    const updatedAt = new Date();
    if (validUrl.isUri(originalUrl)) {
      try {
        const item = await UrlShorten.findOne({ originalUrl: originalUrl });
        if (item) {
          res.status(200).json(item);
        } else {
          shortUrl = shortBaseUrl + "/" + urlCode;
          const item = new UrlShorten({
            originalUrl,
            shortUrl,
            urlCode,
            updatedAt
          });
          await item.save();
          res.status(200).json(item);
        }
      } catch (err) {
        res.status(401).json("Invalid User Id");
      }
    } else {
      return res
        .status(401)
        .json(
          "Invalid Original Url"
        );
    }
  });

~(물론 base62 변환하는 코드는 외부 자료를 참고했다.)~

// Refactoring 후
app.post("/api/urlshortner", async (req, res) => {
    const { originalUrl } = req.body;
    if (validUrl.isUri(originalUrl)) {
      try {
        const item = await UrlShorten.findOne({ originalUrl: originalUrl });
        if (item) {
          res.status(200).json(item);
        } else { // 이후 원본 URL을 redirect 할 때 빠르게 찾기 위해서 idx
          const idx = await UrlShorten.count({});
          const shortened = base62.encode(idx + 10000000000000)
          const item = new UrlShorten({
            idx,
            originalUrl,
            shortened,
          });
          await item.save();
          res.status(200).json(item);
        }
      } catch (err) {
        res.status(401).json("Invalid User Id");
      }
    } else {
      return res
        .status(401)
        .json(
          "Invalid Original Url"
        );
    }
  });

MongoDB Schema

// 기존 코드
const urlShortenSchema = new Schema({
  originalUrl: String,
  urlCode: String,
  shortUrl: String,
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

아 추가로 MongoDB는 MySQL과 다르게 String 한 종류의 데이터가 있더라

// Refactoring 후
const urlShortenSchema = new Schema({
  idx: { type: Number, index: true },
  originalUrl: { type: String, unique: true },
  shortened: { type: String, index: true },
  createdAt: { type: Date, default: Date.now },
});

플러스 알파

MongoDB Data Types

  • String − This is the most commonly used datatype to store the data. String in MongoDB must be UTF-8 valid. ~(String 뿐이라니)~

  • Integer − This type is used to store a numerical value. Integer can be 32 bit or 64 bit depending upon your server.

  • Boolean − This type is used to store a boolean (true/ false) value.

  • Double − This type is used to store floating point values.

  • Min/ Max keys − This type is used to compare a value against the lowest and highest BSON elements.

  • Arrays − This type is used to store arrays or list or multiple values into one key.

  • Timestamp − ctimestamp. This can be handy for recording when a document has been modified or added.

  • Object − This datatype is used for embedded documents.

  • Null − This type is used to store a Null value.

  • Symbol − This datatype is used identically to a string; however, it's generally reserved for languages that use a specific symbol type.

  • Date − This datatype is used to store the current date or time in UNIX time format. You can specify your own date time by creating object of Date and passing day, month, year into it.

  • Object ID − This datatype is used to store the document’s ID.

  • Binary data − This datatype is used to store binary data.

  • Code − This datatype is used to store JavaScript code into the document.

  • Regular expression − This datatype is used to store regular expression.

profile
개발자로 일하는 김찬영입니다.

0개의 댓글