Normalized data

중첩 구조의 데이터를 정규화하여 중첩구조를 제거하고, 중복된 데이터를 배제 할수 있게 됩니다.

1. 정규화 하기

서버측에서는 비지니스 로직의 처리 용이성을 위해 여러 데이터를 논리적 구조에 따라 중첩 구조로 사용하는 경우가 많습니다.

클라이언트에서 이렇게 중첩된 데이터 구조를 기반으로 애플리케이션을 구성하다 보면 복잡한 데이터 구조에 대해 변경을 시도할때 지나치게 많은 변경 처리가 발생하는 경우가 많습니다.

다음과 같은 데이터가 있습니다.

Article List
[
  {
    "id":123,
    "title":"article 1",
    "date":"2018-03-22",
    "author": {
      "id": "user123",
      "name": "John, Smith",
    },
    "comments":[
      {
        "id":234,
        "title": "Goods article",
        "content": "blah, blah"
      },
      // more comments
    ]
  },
  // more article
]

이 데이터를 Vuex에 넘겨주면 vuex는 루트 객체인 배열의 변경부터 말단 객체인 comment의 content의 변화까지 모두 모니터링하게 됩니다.

이런 데이터가 있을때 id가 234인 커멘트의 제목을 변경하려고 하면 어떻게 해야할까요? 모든 아티클의 목록을 순회하며, 중첩된 루프로 아티클의 커멘트를 순회하며 234인 커멘트를 찾고 해당 커멘트의 제목을 수정해야 합니다. 아마도 코딩 중복을 없애기 위해 findComment(userId, commentId) 함수가 만들어지게 될것입니다.

보통 화면에 렌더링 되는 시점에서 객체의 id가 주어지기 때문에 id는 언제나 알수 있습니다. 그럼에 불구하고 이런 중첩 객체는 그저 하위 객체를 찾기 온갖 종류의 findXXX 함수로 도배되기 쉽습니다.

이런 문제를 해결하기 위해 정규화된 데이터를 사용할수 있습니다.

Articles
{
  "articles": {
    123: {
      "title":"article 1",
      "date":"2018-03-22",
      "author": "user123",
      "comments": [234,245]
    },
    // More article
  },


  "articleList":[123,124,125], // 정렬을 유지할수 있음

  "authors":{
    "user123":{
      "name": "John, Smith",
    },
    // more authors
  },

  "comments":{
    234:{
      "title": "Goods article",
      "content": "blah, blah"
    },
    // more comments
  },
}

이런 데이터 타입이 있을때 data.comments[234] 로 우리가 원하는 커멘트를 바로 얻어올수 있습니다. 아티클도 마찬가지이며 저자(author)도 마찬가지입니다.

정렬된 값이 필요하다면 id를 원하는 순서로 정렬시킨 list 배열을 포함하면 됩니다. 또한 list 가 배열이기 때문에 filter, map, reduce등 함수형 명령을 사용할수도 있습니다.

또한 한명의 저자가 여러 아티클을 썼다고 해도 저자 데이터는 단 한번만 나오게 됩니다. (정규화 됩니다)

이런 구조에서

  • 만약 전체 아티클 목록을 순서대로 얻고 싶다면 다음처럼 간단하게 배열의 map 함수를 사용하면 됩니다.

    function articles(){
        return data.articleList.map( articleId => data.articles[articleId])
    }
  • 만약 특정 아티클의 커멘트 객체 목록을 얻고 싶다면

    function articleComments(article) {
      return article.comments.map( commentId => data.comments[commentId])
    }

이렇게 정규화된 데이터 구조를 이용해 중첩구조를 제거하고, 중복된 데이터를 배제 할수 있게 됩니다.

물론 이렇게 요청시마다 검색하는 방식은 데이터의 양이 많지 않을때 사용 해야 하며, 데이터의 양이 아주 많다면 애초에 이런 형태의 JSON을 사용할수 없습니다.

2. Vuex

vuex 를 사용하는 환경에는 보통 다음과 같이 구성됩니다.

getters
getters: {
  articleSet: (state) => state.articleList.map( articleId => state.articles[articleId]);
}
in component
computed: {
  articles: () => this.$store.getters.articleSet,
  comments: () => this.$store.getters.comments,

},
methods: {
  articleComments(article) {
    return article.comments.map( commentId => this.comments[commentId]);
  }
}

이런 함수들은 템플릿에서 다음과 같이 사용할수 있습니다.

<div v-for="article in articles">
  <ul>
    <li v-for="comment in articleComments(article)">{{comment.title}}</li>
  </ul>
</div>
Vue.js에서 computed는 원본이 바꾸지 않으면 캐싱된 값을 반환하기 때문에 성능 이슈도 해소될수 있습니다.

3. normalizr

데이터가 중첩구조일때 정규화를 직접 할수도 있습니다만 라이브러리를 이용해서 수행할수도 있습니다.

링크 normalizr

  • 다음과 같은 중첩 데이터가 존재할때

    blost posts
    {
      "id": "123",
      "author": {
        "id": "1",
        "name": "Paul"
      },
      "title": "My awesome blog post",
      "comments": [
        {
          "id": "324",
          "commenter": {
            "id": "2",
            "name": "Nicole"
          }
        }
      ]
    }
  • 다음과 같이 normalizr 라이브러리를 이용해 데이터 구조에 대한 scheme를 작성하면

    import { normalize, schema } from 'normalizr';
    
    // Define a users schema
    const user = new schema.Entity('users');
    
    // Define your comments schema
    const comment = new schema.Entity('comments', {
      commenter: user
    });
    
    // Define your article
    const article = new schema.Entity('articles', {
      author: user,
      comments: [ comment ]
    });
    
    const normalizedData = normalize(originalData, article);
  • 다음과 같이 정규화된 데이터 객체를 얻을수 있습니다.

    {
      result: "123",
      entities: {
        "articles": {
          "123": {
            id: "123",
            author: "1",
            title: "My awesome blog post",
            comments: [ "324" ]
          }
        },
        "users": {
          "1": { "id": "1", "name": "Paul" },
          "2": { "id": "2", "name": "Nicole" }
        },
        "comments": {
          "324": { id: "324", "commenter": "2" }
        }
      }
    }
Tags: JS  normalized data