<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>team.croco</title>
        <link>https://velog.io/</link>
        <description>세상을 바꾸는 사이드 프로젝트 팀, 크로코입니다.</description>
        <lastBuildDate>Sun, 28 Feb 2021 09:54:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>team.croco</title>
            <url>https://images.velog.io/images/croco_space/profile/69e3e5da-87c7-44cd-91ec-dea09eb39701/drive.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. team.croco. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/team_croco" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[프로덕션 서버에서 데이터 마이그레이션 하기 (with bulk insert)]]></title>
            <link>https://velog.io/@team_croco/production-data-migration</link>
            <guid>https://velog.io/@team_croco/production-data-migration</guid>
            <pubDate>Sun, 28 Feb 2021 09:54:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/team_croco/post/86fe3349-0a37-460a-8859-ffa58a5b518e/image.png" alt=""></p>
<blockquote>
<p>저기,, 배포가 안되는데요?</p>
</blockquote>
<h1 id="프롤로그">프롤로그</h1>
<p>안녕하세요. 저는 myWallets에서 백엔드 개발을 담당하고 있는 형주입니다.
얼마전 데이터 분석을 위해 <strong>통계</strong> 기능을 추가해달라는 요청을 받고 유저 가입 정보를 새로운 테이블에 추가하는 마이그레이션 코드를 만들었습니다. <em>(유저 외에도 패스 생성, 발급등 총 3개의 자료를 이런 방식으로 추가했습니다... )</em></p>
<pre><code class="language-tsx">for (const user of users) {
  await userRegistrationRepository.save({
    userId: user.id,
    registeredAt: user.createdAt,
  });
}</code></pre>
<p>로컬에서는 정말 잘 작동했지만 저는 1시간 후 어떤 일이 벌어질지 몰랐습니다.</p>
<hr>
<h1 id="내가-알지-못했던">내가 알지 못했던..</h1>
<p>마이 월렛에는 6,000명 이상의 유저와 15,000개가 넘는 멤버십 카드가 있습니다.</p>
<p>이런 상황에서 적어도 35,000번 이상의 Save 작업이 이루어졌을 것으로 생각할 수 있는데, save의 경우 한 번에 정말 많은 작업을 수행합니다.</p>
<p>데이터베이스에 엔티티가 존재하는지 여부를 확인하는 작업부터 데이터를 삽입할 때는 (매번!) 트랜잭션까지 사용합니다.</p>
<p>게다가 트랜잭션은 Insert 시간에 큰 영향을 준다고 합니다...</p>
<p>그래서 결국 35,000번의 반복문과 Save로 인해 배포되기까지 1시간 30분이 넘게 걸렸습니다.</p>
<p>조금 더 데이터가 많았다면 서비스가 터져버렸을지도 모릅니다.</p>
<p>생각만 해도 무서운 일이니 그만 알아보도록 합시다</p>
<h1 id="개선해보기">개선해보기</h1>
<p>구글에서 저와 같은 경험을 한 사람이 있지 않을까 싶어 찾아보았는데, 다행히 비슷한 사람이 있었습니다.</p>
<p><a href="https://prosaist0131.tistory.com/entry/insert%EC%99%80-bulk-insert-%EB%AC%B4%EC%97%87%EC%9D%84-%EC%8D%A8%EC%95%BC%ED%95%A0%EA%B9%8C%EC%9A%94">https://prosaist0131.tistory.com/entry/insert와-bulk-insert-무엇을-써야할까요</a></p>
<p>위 블로그 글을 참고해서 저도 save 대신 bulk insert를 사용하기로 했습니다.</p>
<p>기존에 사용되던 save와 배포 시간 지연의 주범인 반복문을 없애고, 쿼리 빌더를 사용해 벌크 인서트를 하도록 만들었습니다.</p>
<pre><code class="language-tsx">await userRegistrationRepository
      .createQueryBuilder()
      .insert()
      .into(UserRegistration)
      .values(
        users.map(user =&gt; ({
          userId: user.id,
          registeredAt: user.createdAt,
        })),
      )
      .execute();</code></pre>
<p>벌크 인서트는 매번 Insert를 할때 전후로 발생되는 작업들의 시간이 줄어듦으로 더 빨리 처리된다고 합니다.</p>
<p>아직 저희 데이터가 그렇게 크지는 않지만 조금 더 규모가 커진다면 더욱 더 체감이 될것입니다.</p>
<h1 id="결론">결론</h1>
<p>벌크 인서트가 뭔지 몰랐던 주니어 개발자는 새 지식을 얻었습니다.</p>
<p>달리는 차의 엔진을 바꾸는 데이터 마이그레이션 작업을 수행하던 1시간 30분이 걸리던 35,000개의 쿼리들은 3개로 대체했습니다.</p>
<p>정말 큰 일이 날 수도 있었지만 빨리 발견해 미래의 사건을 막을 수 있지 않았을까 생각해봅니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React mvvm 적용기]]></title>
            <link>https://velog.io/@team_croco/mywallet-frontend-mvvm</link>
            <guid>https://velog.io/@team_croco/mywallet-frontend-mvvm</guid>
            <pubDate>Mon, 18 Jan 2021 12:08:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/team_croco/post/c997ea9a-399f-4e05-8678-89dfd3809896/Frame%2011.png" alt="">안녕하세요! Team Croco에서 &quot;<a href="https://mywallets.xyz">마이월렛</a>&quot;이라는 프로젝트를 리딩하고 있는 오웬이라고 합니다. 팀 내에서 저의 역할은 프로젝트가 늘어지거나 문제가 생기지 않도록 프론트엔드와 백엔드에 적절한 도움과 압박(?)을 주는 역할입니다. 실제로 오픈 후 어드민 서비스의 경우 적절한 라이브러리들을 택해 3시간 만에 추가했던 일도 있었습니다 😃
오늘은 마이월렛 프론트엔드에서 적용했던 아키텍쳐 패턴인 MVVM를 적용한 이야기를 하고자 합니다.</p>
<h3 id="왜-mvvm을-적용했을까">왜 MVVM을 적용했을까?</h3>
<p>model-view-viewModel로 이루어진 아키텍처 패턴입니다. 
사실 다양한 아키텍쳐 디자인이 많지만 view, viewModel, model간의 의존성이 없다는 것이 가장 매력적이었습니다. viewModel의 경우 ts 파일로 작성해 명시적으로 로직을 분리할 수 있었습니다.
약간의 차이는 viewController를 만들어 viewModel을 view에 바인딩하는 용도로 사용했습니다. 밑에서 더 이야기를 하겠지만 개인적으로 아쉬웠던 판단이었습니다.</p>
<h4 id="실제-적용-예">실제 적용 예</h4>
<pre><code class="language-TS">const ViewController = React.memo(() =&gt; {
    const viewModel = useViewModel();
    return &lt;View viewModel={viewModel}/&gt;;
});</code></pre>
<h3 id="엄격하게-적용하기">엄격하게 적용하기</h3>
<p>개인적인 경험으로 이러한 패턴의 경우 잘 적용할 경우 좋은 효과를 내지만 충분히 이해하지 못하거나 귀찮음 등으로 인해 적용되지 않는다면 오히려 개발 생산성을 떨어뜨리기만 합니다.
팀에서는 Typescript를 사용 중이었기 때문에 Type을 통한 약간의 강제성을 부여하기로 결정해 view와 viewModel의 타입을 정의해 사용했습니다.</p>
<h2 id="아쉬운-점">아쉬운 점</h2>
<h3 id="nextjs의-ssr">Nextjs의 ssr</h3>
<p>마이월렛의 프론트는 Nextjs를 통해 개발되었습니다. 그러다보니 SSR앱이지만 SSR을 하기 애매한 상황이 일어났습니다.
Next.js는 getServerSideProps라는 함수를 통해 컴포넌트 밖에서 props를 통해 값이 전달됩니다. 위에 보여드린 저희 팀의 mvvm 활용 방식은 이러한 구조를 고려하지 못한 채 적용되었습니다. api통신을 model을 통해서만 이뤄지도록 설계하였다보니 컴포넌트 외부에서 모델을 활용할 수가 없었습니다. model의 초기화가 클라이언트 사이드에서 이뤄지다보니 서버사이드에서 접근할 방법이 없었습니다.
자료를 찾아봐도 적당한 사례를 찾을 수 없었고 현재는 클라이언트 사이드 렌더링만을 사용하고 있습니다. (이럴거면 Next.js를 왜...)
조만간 이에 대해 고민해보고 적절한 타협책을 찾아볼 생각입니다. 기회가 된다면 블로그에 정리해서 업로드해보겠습니다 😉</p>
<h3 id="view-controller와-view의-명확한-분리">view controller와 view의 명확한 분리</h3>
<p>view와 view controller를 명확히 분리하지 않은채 view controller를 provider로써 활용했습니다.
그러다보니 view를 재활용하기가 가끔 까다로운 경우가 있었습니다. 조금 더 경험을 쌓고 다양한 시도들을 해본 후 다시 한번 공유해보겠습니다!</p>
<h2 id="마무리">마무리</h2>
<p>오늘은 마이월렛 프론트엔드 아키텍처에 mvvm을 적용한 이야기를 적어보았습니다.
글을 발행하는 지금 가입해주신 유저수가 4500명을 넘어가고 있네요!</p>
<p>가입해주신 분들께 감사드리며 글을 마치도록 하겠습니다.</p>
<p>PS) 현재 팀 크로코에서 프론트엔드 개발자를 모집중이니 많은 지원 바랍니다. <a href="https://www.team-croco.xyz/frontend">지원하러 가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nextjs로 프로덕션 배포하기]]></title>
            <link>https://velog.io/@team_croco/mywallets-frontend-story</link>
            <guid>https://velog.io/@team_croco/mywallets-frontend-story</guid>
            <pubDate>Wed, 13 Jan 2021 08:49:16 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! 👋 “<a href="https://mywallets.xyz/">myWallets</a>”이라는 프로젝트에서 프론트엔드와 디자인, 그리고 백엔드(살짝)를 담당하고 있는 도다입니다. (업무 비중은 프론트엔드 &gt; 디자인 &gt;&gt;&gt; 백엔드 정도)</p>
<p>이전에 백엔드 전반을 담당하고 계시는 형주님께서 서비스 구조나 협업 방식에 대한 글을 써주셨으니, 앞 글도 봐주세요! <a href="https://velog.io/@team_croco/%EC%B4%88%EB%B3%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-myWallets-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EA%B8%B0">🔗 velog</a></p>
<h2 id="읽으시기-전에-😓">읽으시기 전에! 😓</h2>
<p>이 글은 회고록...처럼 의식의 흐름대로 적어나간 글입니다.
맞춤법, 어투 등이 불편하시더라도 양해해 주시기 바랍니다... 🙏</p>
<h2 id="어떤-서비스인가요-🧐">어떤 서비스인가요? 🧐</h2>
<p><img src="https://static-edge-g.doda.dev/b/posts/6616456564.png" alt=""></p>
<p>저희가 제작하고 있는 “마이월렛&quot;은 아이폰이나 iPod touch에 존재하는 Wallet 앱에 Pass라고 불리는 카드를 간단하게 추가할 수 있도록 도와주는 사이트에요. PassKit에 관심이 있으신 분은 <a href="https://developer.apple.com/wallet/">Apple Developer - Wallet</a>을 참고해보세요.</p>
<h2 id="어떤-기술들을-썼나요-🍱">어떤 기술들을 썼나요? 🍱</h2>
<blockquote>
<p>💪 여기서 언급하는 기술들은 모두 <strong>프론트엔드만 해당</strong>해요!</p>
</blockquote>
<h3 id="구성">구성</h3>
<p>주요 기술들은 다음과 같아요 🤩</p>
<ul>
<li>Next.js (React)</li>
<li>Emotion (<code>@emotion/react</code>, <code>@emotion/styled</code>)</li>
<li>TypeScript</li>
</ul>
<p>이번 프로젝트는, <a href="https://nextjs.org/blog/next-10">Next.js 10</a>이 출시되고 얼마 되지 않아 시작한 프로젝트에요. 그래서 React 17을 도입하고, <code>next/image</code> 컴포넌트를 사용한 첫 프로젝트랍니다.</p>
<p>상태 관리는 모두 React의 기본 Context로 하고 있어요. 상태 관리 라이브러리를 사용하지 않은 이유는 Hook의 도입으로 Context를 직관적으로 관리할 수 있는 이유도 있었고, 관리해야 할 변수들이 많지 않았기 때문이에요.</p>
<h3 id="배포--ci">배포 &amp; CI</h3>
<p><img src="https://static-edge-g.doda.dev/b/posts/7544095850.png" alt=""></p>
<p>Production, Preview(beta) 모두 Vercel을 사용하고 있어요.</p>
<p>Vercel의 현재 가격 정책은 개인 사용자에게만 무료이지만, 가격 정책이 바뀌기 전의 Team들에게는 이전 가격 정책을 유지해주고 있어요! 덕분에 팀에서도 편하게 개발하고 있어요 🙇</p>
<p><img src="https://static-edge-g.doda.dev/b/posts/6089805449.png" alt=""></p>
<p><img src="https://static-edge-g.doda.dev/b/posts/8282138397.png" alt=""></p>
<p>또한 프로덕션에서도 Vercel을 유지하는 이유 중의 하나는, ICN(서울) 리전의 존재이기도 해요. <strong>국내 모바일 사용자</strong>들이 타겟이니 만큼, 로딩 속도가 중요하기 때문이에요.</p>
<p>하지만 Vercel도 트래픽 제한이 존재하기 때문에, 혹여나 발생하는 트래픽 초과 상황을 방지하기 위해 모니터링 중이랍니다.</p>
<h2 id="디자인-🛤">디자인 🛤</h2>
<p><img src="https://static-edge-g.doda.dev/b/posts/5695846634.png" alt="">
<img src="https://static-edge-g.doda.dev/b/posts/8995797215.png" alt=""></p>
<p>디자인에서는 Figma 툴을 이용하여 기초 베이스를 잡았어요. </p>
<p>Figma를 계속 사용하고 선호하는 이유라면, 커뮤니티가 커서 여러가지 목업이나 디자인 리소스를 얻을 수 있고, 다른 팀원들과 협업이 쉽고 간편하게 이루어진다는 점에서 좋다고 생각하여 사용하고 있어요.</p>
<p><img src="https://static-edge-g.doda.dev/b/posts/1855758669.png" alt="">
기본적인 레이아웃은 모두 피그마에서 구상하고, 디자인으로 옮기고 있답니다. 하지만 개발하는 시간을 위하여 디자인 작업물의 디테일까지는 신경 쓰지 않아요. (어짜피 제가 만든다구요… 😅)</p>
<h2 id="성능-⏳">성능 ⏳</h2>
<p>지난번 Croco에서 진행했을 때의 프로젝트는 성능을 전혀 신경 쓰지 않고 구현했었어요.</p>
<p>이번 프로젝트에서도 성능보다는 구현을 우선으로 하기로 했어요. 모든 기능을 모두 제작한 후에 성능에 대한 최적화나 리팩토링을 진행하기로 하여서, 기본적인 기능 구현이 끝난 현재도 계속 성능 최적화를 진행하고 있답니다.</p>
<p>추가적으로, React 렌더링 최적화에 대한 이해를 하기 쉬웠던 글을 남길게요. <a href="https://medium.com/vingle-tech-blog/react-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f255d6569849">React 렌더링 이해 및 최적화 (With Hook)</a></p>
<h3 id="memoization">Memoization</h3>
<p>함수 재사용의 중요성을 알게 된 이후로는 useCallback(혹은 useMemo)을 사용하고 있어요.</p>
<p>하지만 useCallback을 항상 붙이는 것은 아니에요!  <a href="https://dmitripavlutin.com/dont-overuse-react-usecallback/">Your Guide to React.useCallback()</a>이라는 글을 참고하여 중요하거나 반복 사용되는 함수, 계산이 많은 함수들에게만 useCallback을 사용하고 있어요.</p>
<p>또한 <code>React.memo()</code> 를 사용하는 경우도 종종 있는데요. 주로 props가 없거나 거의 변경되지 않는 레이아웃에만 사용하고 있어요. (<a href="https://dmitripavlutin.com/use-react-memo-wisely/">Use React.memo() wisely</a> 글을 읽어보고 도움을 받았답니다)</p>
<h3 id="불필요한-리렌더링">불필요한 리렌더링</h3>
<p>Virtual DOM 개념을 이용하는 React에게는 리렌더링이라는 과정이 존재해요. 당연하겠지만 불필요한 렌더링이 일어나는 경우엔 성능상으로 손해에요. 그래서 불필요한 리렌더링을 방지하기 위해 약간 삽질을 했답니다.
<img src="https://static-edge-g.doda.dev/b/posts/9386406895.png" alt=""></p>
<p>🔼 찾아보니 React Devtools 설정에서 저 체크박스를 치면 렌더링마다 하이라이팅이 된다하여 켰지만, 직관적으로 보이지 않더라구요. (제가 못쓰는 것일지도)</p>
<p><img src="https://static-edge-g.doda.dev/b/posts/0497875692.png" alt=""></p>
<p>🔼 그래서 Elements를 직접 보며, 어느 부분이 변경되는지 확인하면서 리렌더링을 확인했어요. (지금 확인해보니, React Devtools에 있는 Components에서도 확인할 수 있네요)</p>
<p>그리고 <strong>또 다른 방법</strong>은, useEffect를 이용하는 방법이에요.</p>
<pre><code class="language-js">useEffect(() =&gt; {
  console.log(&#39;Render: Header&#39;);
}, []);</code></pre>
<p>이렇게 콘솔에 로그를 찍으면서… 라우팅이 변경될 때 렌더링이 이루어지는지 확인하는 방법도 사용했어요!</p>
<h2 id="다크-모드-🎨">다크 모드 🎨</h2>
<p>이 문단을 작성하는 지금 시각, <strong>오전 3시 1분</strong>.</p>
<p>낮보단 밤이 더 좋은 평범한 개발자인 저는 가끔씩 방 불을 끄고 컴퓨터를 할 때가 있어요. 밝은 화면은 눈이 아파서 저는 다크 모드를 좋아해요. 디자인 초반부터 다크 모드를 기획했었답니다.</p>
<h3 id="구현-꼼수">구현... (꼼수)</h3>
<blockquote>
<p><strong>💬 참고!</strong> 이 문단은 다크 모드의 기술적 구현에 대해서만 다뤄요!</p>
</blockquote>
<p>라이트 모드(?)를 모두 스타일링 후에 서비스 도중에 다크 모드를 추가하는 작업이였기에 효율적인 방법을 선택하려고 했어요.</p>
<p>구현 초반에는 각 요소에 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS properties</a>를 지정하는 방법도 생각했었지만, 생각보다 광범위하여 금방 포기하게 됬어요.</p>
<p>그래서 선택한 방법은 Context(전역 상태) + CSS!</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  if (window.matchMedia &amp;&amp; window.matchMedia(&#39;(prefers-color-scheme: dark)&#39;).matches) {
    setDark(true);
  }
}, []);</code></pre>
<p>🔼 ContextProvider 컴포넌트가 로딩 될 때에 다크 모드인지 감지합니다</p>
<pre><code class="language-jsx">&lt;LayoutContext.Provider value={{ dark, setDark, changeDark }}&gt;
  &lt;div className={dark ? &#39;theme-dark&#39; : &#39;&#39;}&gt;{children}&lt;/div&gt;
&lt;/LayoutContext.Provider&gt;</code></pre>
<p>🔼 ContextProvider가 전역을 감싸는 div를 만듭니다</p>
<pre><code class="language-jsx">const styled = styled.div`
  .theme-dark &amp; {
    color: #7d86a0;
  }
`</code></pre>
<p>🔼 그 후, 요소를 구성하는 styled-components에서 .theme-dark를 부모 객체로 받아서 스타일링 하는 꼼수(?)를 쓰고 있답니다.</p>
<p>이 방법을 고안한 이유는... 귀찮아서에요. 스타일링마다 Context 받기도 귀찮고, CSS Properties를 만들기도 귀찮았기에 스타일링이 조금 늘어나더라도 이런 방법을 선택했어요. 😅</p>
<p>결과적으론 리렌더링의 부담도 없고 예상처럼 잘 작동해주는 것 같아 고맙네요.</p>
<blockquote>
<p><strong>🥲 단점</strong> 지금은 다크 모드 감지가 유동적이지 않아요. macOS나 다른 운영체제에서 제공하는 자동 색상 모드를 지원하지 못한다는 점.</p>
<p>코드 구현에 정답은 없다고 생각하기 때문에, 다른 방법이 있으면 제안해주셔도 좋아요 🙇</p>
</blockquote>
<h2 id="구조-🏗">구조 🏗</h2>
<p><img src="https://media.giphy.com/media/QVlhSp492723RtVu4V/giphy-downsized-large.gif" alt=""></p>
<p>유지보수가 힘든 코드를 관리하는 것은 어렵죠. 저도 제가 짠 코드들에 많이 당해본 기억이 있는데요.</p>
<p>이번 프로젝트는 프론트엔드에서 아키텍처를 처음으로 적용해 본 프로젝트에요. 지금 껏 백엔드에서는 여러가지 아키텍처를 많이 이용했었지만, 프론트엔드에서는 해봤자 Atomic Design 정도만 사용해봤었거든요.</p>
<p>팀 리더의 소개로, MVVM(Model-View-ViewModel) 패턴을 도입했어요. MVVM 패턴이 궁금하신 분들은 <a href="https://medium.cobeisfresh.com/level-up-your-react-architecture-with-mvvm-a471979e3f21">이 글</a>을 참고하세요!</p>
<p>첨부한 글과는 저희 프로젝트는 다른 점이 조금 있어요!
글에서는 class를 사용하고 있지만, 저희 프로젝트에는 Hooks처럼 도입했어요.</p>
<pre><code class="language-ts">// Model
const { passes, loading } = useMembershipPass(id);</code></pre>
<p>그 이외에도 여러 다른 점들이 존재하지만... 아직 저도 적응 단계라서 설명을 못하겠네요. 😓</p>
<h2 id="마치며-🙇">마치며 🙇</h2>
<p>설마 여기까지 봐주신건가요...?🤭 쓰다보니 글이 <del>너무</del> 길어졌지만, 부족한 글을 읽어주셔서 감사합니다.</p>
<p><img src="https://static-edge-g.doda.dev/b/posts/9615726951.png" alt=""></p>
<p>마이월렛은 지금도 계속 개선 중인 프로젝트에요. 구현 예정인 기능들과, 기획 중인 기능들도 있어요. 앞으로 계획이 어떻게 될지는 모르겠지만, 힘내볼 예정입니다 😋</p>
<p>이 글 뒤로도 팀에 유일한 실무 경력 개발진이자 저희 팀 리더 분께서 개발기를 적어주실 예정이니, 관심 가져주시면 감사하겠습니다 😎</p>
<p>팀 계정이라 댓글 확인이 힘들어요. 궁금하신 점이 있으시다면 <code>me@doda.dev</code>로 메일 부탁드릴게요 🧑‍🔧</p>
<ul>
<li>(광고) Apple Wallet에 관심 있으신 분들은 <a href="https://mywallets.xyz/">마이월렛</a> 써주세요…</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[초보 개발자의 백엔드 개발기]]></title>
            <link>https://velog.io/@team_croco/%EC%B4%88%EB%B3%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-myWallets-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@team_croco/%EC%B4%88%EB%B3%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-myWallets-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EA%B8%B0</guid>
            <pubDate>Fri, 01 Jan 2021 15:31:29 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 저는 myWallets에서 백엔드 개발을 담당하고 있는 형주입니다.</p>
<p><a href="https://mywallets.xyz/">myWallets</a>는 Apple Wallet에 다양한 멤버십 카드들을 추가할 수 있는 서비스입니다.</p>
<p><img src="https://images.velog.io/images/croco_space/post/b7f6681c-2416-4661-8e23-51aa7965052b/image.png" alt=""></p>
<p>멤버십 리스트에서 멤버십을 선택해 패스를 추가하면 휴대폰의 Apple Wallet 앱에 등록해 멤버십을 빠르게 사용할 수 있습니다.</p>
<p>이 글에서는 초보 개발자인 제가 myWallets 서비스의 백엔드 개발을 진행한 과정에 대해 소개해 드리고자 합니다.</p>
<blockquote>
<p>myWallets의 프론트엔드에 관심이 있으시다면 다음 글을 봐주세요!  <a href="https://velog.io/@team_croco/mywallets-frontend-story">마이월렛 프론트 엔드 이야기</a></p>
</blockquote>
<hr>
<h2 id="기술-스택">기술 스택</h2>
<ul>
<li>NestJS</li>
<li>TypeScript</li>
<li>MySQL, TypeORM</li>
</ul>
<p>백엔드 개발 경험이라고는 라라벨을 비롯한 PHP 프레임워크들 뿐이었던 저는 이번 프로젝트가 첫 TypeScript 프로젝트이자 Nest 프로젝트였습니다.</p>
<p>평소였다면 접해보지 못했을 새로운 프레임워크와 언어를 이 프로젝트를 통해 배우게 되어 정말 뜻깊은 작업이 되었던것 같습니다 ☺️</p>
<h2 id="mywallets-서비스-구조에-대해">myWallets 서비스 구조에 대해</h2>
<p><img src="https://images.velog.io/images/croco_space/post/59cb9679-cf1c-4735-8730-e8d2774f796e/image.png" alt=""></p>
<p>myWallets은 위와 같은 구조로 패스를 생성합니다.</p>
<p>애플 월렛에서 지원하는 패스 타입은 <strong>보딩 패스, 쿠폰, 이벤트 티켓, 매장 카드, 제네릭, 매장 카드</strong>로 총 5개의 타입이 존재합니다.</p>
<p>이 모든 패스 타입을 지원하기 위해 PassTemplate과 Pass는 타입에 따라 다른 필드를 가지는 객체로 구성되어야 했습니다.</p>
<p><img src="https://images.velog.io/images/croco_space/post/982a0547-79bb-478c-8373-09515aa5d093/image.png" alt=""></p>
<blockquote>
<p>팀의 최고 존엄 개발자분께서 전달해 주신 귀중한 링크</p>
</blockquote>
<h3 id="테이블-상속-전략에-대한-고민">테이블 상속 전략에 대한 고민</h3>
<p><img src="https://images.velog.io/images/croco_space/post/676236f7-335f-4c6d-af31-63f80e4ecdce/image.png" alt=""></p>
<p>myWallets에서 사용하고 있는 ORM인 TypeORM에서는 <strong>싱글 테이블 전략</strong>과 <strong>멀티 테이블 전략 (Concrete 테이블 전략)</strong>을 제공합니다.</p>
<p>싱글 테이블 전략은 모든 자식 엔티티의 칼럼을 <strong>한 테이블에</strong> 저장하고 <em>Discriminator(구분자)</em>를 사용해 테이블에 어떤 자식 엔티티가 저장되어 있는지 구분합니다.</p>
<p><strong>싱글 테이블 전략</strong>을 사용하면 테이블이 커질 수 있다는 단점이 있지만 다른 전략에 비해 쿼리 속도는 비교적 빠릅니다.</p>
<p>TypeORM에서 제공하는 또 다른 전략인 <strong>멀티 테이블 전략</strong>은 자식 엔티티를 위한 모든 속성을 가지는 테이블을 각각 생성합니다.</p>
<p>이 전략은 여러 종류의 엔티티를 쿼리할때 여러 테이블에 접근 해야하므로 성능이 떨어지는 단점이 있습니다.</p>
<p>위와 일맥상통하는 점으로 테이블이 너무 많아지면 심미적으로 불-편 하기에 이번 프로젝트에서는 싱글 테이블 전략을 사용하기로 결정했습니다.</p>
<pre><code class="language-tsx">import { ChildEntity, Column } from &#39;typeorm&#39;;
import { PassTemplate, PassTemplateType } from &#39;./pass-template.entity&#39;;

@ChildEntity(PassTemplateType.Coupon)
export class CouponPassTemplate extends PassTemplate {
  @Column()
  itemName!: string;

  @Column()
  itemImage!: string;
}</code></pre>
<pre><code class="language-tsx">import { ChildEntity } from &#39;typeorm&#39;;
import { PassTemplate, PassTemplateType } from &#39;./pass-template.entity&#39;;

@ChildEntity(PassTemplateType.StoreCard)
export class StoreCardPassTemplate extends PassTemplate {}</code></pre>
<p>위 코드에서 CouponPassTemplate과 StoreCardTemplate이 공통적으로 PassTemplate을 상속하는 모습을 확인할 수 있습니다.</p>
<hr>
<h2 id="우리의-협업-방식">우리의 협업 방식</h2>
<p>myWallets 에서는 GitLab Flow에 따라 협업을 진행합니다.</p>
<p>기능 단위별로 브랜치를 열고 작업한 후 PR을 생성합니다.</p>
<p>이 과정에서 myWallets의 모든 코드는 리뷰를 받고 develop 브랜치로 머지됩니다.</p>
<p>그리고 일정한 간격을 두고 Frontend와 함께 릴리즈하고 master(production)에 최종적으로 배포하고 있습니다. </p>
<p><img src="https://images.velog.io/images/croco_space/post/720d925e-252f-40e5-bddf-8fa22cd193d7/image.png" alt=""></p>
<blockquote>
<p>리뷰의 흔적</p>
</blockquote>
<p>보다 나은 코드를 위해 myWallets 프로젝트에는 <strong>모든 코드는 리뷰를 받아야 한다</strong>는 원칙이 있습니다.</p>
<p>이런 리뷰 분화는 익숙해 지는데 시간이 다소 걸리고 힘들기도 했지만 새로운 언어와 프레임워크에 적응하는데 정말 많은 도움이 되었습니다.</p>
<p>TypeScript와 Nest 생태계에 처음 발을 들인 입장으로 팀 내에서 Nest 경험이 풍부한 다른 개발자로부터 리뷰를 받으면서 프레임워크와 언어 특성에 맞는 코드를 작성하는 법을 배울 수 있었습니다.</p>
<p>또한 프로젝트 초기에 정한 코드 컨벤션과 린팅 스타일에 대해서도 코드 리뷰를 통해 유지할 수 있게 되었습니다.</p>
<p>다만 개발 후기글을 쓰면서 알아차린 부분이지만 PR 템플릿이 정해져 있지 않고 내용이 없어 리뷰어께서 고생을 많이 하셨을것 같은 생각이 드네요 ㅋㅋㅋ..</p>
<p>저는 리뷰를 받는 입장이라 크게 생각을 못했지만,, 이 글을 빌어 감사를 전합니다 🙏 </p>
<p><img src="https://images.velog.io/images/croco_space/post/3df38a45-dcef-456c-8137-394dc6b266d5/image.png" alt=""></p>
<blockquote>
<p>개발을 하면서 쌓인 46개의 머지된 PR</p>
</blockquote>
<h2 id="앞으로의-계획">앞으로의 계획</h2>
<p>위 이미지의 PR에서도 볼 수 있듯이 각 모듈을 DDD를 도입해 리팩토링을 하고 있습니다.</p>
<p>지금은 다른 모듈 의존성이 없는 멤버십 모듈부터 작업을 하고 있지만 앞으로는 Pass 생성을 포함한 모든 모듈을 <strong>Domain Driven</strong> 으로 개발하는 것이 저의 목표입니다.</p>
<hr>
<p>myWallets 백엔드를 간략하게 소개했습니다.</p>
<p>블로그에 글로써 개발 후기를 적는것이 아직 어색해 제가 이야기하고 싶은 내용을 모두 다 담지 못해 너무 아쉽습니다.</p>
<p>myWallets의 다른 글들도 많은 기대 부탁드립니다 🙏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코로나인포 개발기 : 백엔드 편]]></title>
            <link>https://velog.io/@team_croco/making-coronas-info-backend-1</link>
            <guid>https://velog.io/@team_croco/making-coronas-info-backend-1</guid>
            <pubDate>Thu, 20 Feb 2020 14:48:13 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! Croco에서 코로나인포를 만들고 있는 Heewon이라고 합니다!</p>
<p>지난 글에 이어 오늘은 백엔드에 대해 다뤄보고자 해요.</p>
<blockquote>
<p>✅ 해당 글에서는 백엔드 ( 와 약간의 기획 ) 만 다뤄요!</p>
</blockquote>
<h2 id="기술-스택">기술 스택</h2>
<ul>
<li>Django<ul>
<li>Django Rest Framework</li>
</ul>
</li>
<li>Postgresql</li>
</ul>
<h2 id="처음-접한-장고-🧐">처음 접한 장고 🧐</h2>
<p>Node.js, PHP 등 익숙한 언어들을 던지고 생뚱맞은 장고를 택한 이유는 단 하나였습니다. 장고를 사용하시는 분들이 가장 많이 이야기하는, <strong>생산성</strong>입니다. 물론 장고가 아예 처음인지라 러닝커브가 높다면 오히려 생산성을 저하시킬 수도 있었을 겁니다.</p>
<p>그래서 저는 가장 기본이 되는 모듈인 patients(확진자)를 만들어본 후 앞으로의 방향을 결정하기로 했습니다. 단순히 CRUD와 권한 체크만 있는 모듈이었기 때문에 Node.js로 전환하는데도 시간이 얼마 걸리지 않을거라고 생각했습니다.</p>
<p>장고를 어느정도 사용해보고 내린 결론은 코로나 인포와 같은 프로젝트에 사용하기 매우 적합하다였습니다. 기본적인 틀들이 잡혀있고, 권한, Api 문서, 어드민, router등 간단하고 작은 프로젝트에 적합한 프레임워크였습니다.</p>
<p>따라서 핵심 기능들을 만들고 린 방법론을 적용하려는 우리에게 장고는 최고였습니다.</p>
<h2 id="1차-배포-😆---확진자-확진자-이동경로-실시간-피드업데이트-내역-통계">1차 배포 😆 - 확진자, 확진자 이동경로, 실시간 피드(업데이트 내역), 통계</h2>
<p>일단은 현재 사람들이 가장 불안해하는 부분과 궁금해하는 부분인 확진자 현황과 이동경로에 집중하기로 했습니다.빠르게 Patients모듈을 만들고 Patients와 1:N관계의 Movements모듈을 만들었습니다. 어드민 계정일 경우에 두 모듈에 Push, Patch, Put 권한을 부여해주었고 정상적으로 동작했습니다. Serializer를 사용하니 인풋과 아웃풋의 validate까지 간편하게 할 수 있었습니다.</p>
<p>문제는 확진자 통계를 내는 report 모듈 이였습니다. Patients와 Movements 모두 Model이 있어 ModelViewSet, ModelSerializer를 활용했습니다. 모델이 없는데 어떤 식으로 구현을 해야할지 혼란스러웠지만 다행히 ModelViewSet과 ModelSerializer는 각각 ViewSet과 Serializer를 상속하고 있었습니다. 공식 도큐멘트를 통해 내용을 확인하고 Serializer에 내포낼 6개의 수치를 만들어주고, Viewset에서 데이터를 만들어 내보내줬습니다.</p>
<p>이렇게 모델이 없는 모듈까지 개발을 마칠 수 있었습니다.</p>
<h2 id="추가-기능은-어떤게-좋을까-어떻게-알지-🤔">추가 기능은 어떤게 좋을까? 어떻게 알지? 🤔</h2>
<p>사용자들이 점차 유입되면서 너무 기능이 없다는 피드백을 생기기 시작했습니다. 우리는 새로운 기능을 추가하고자 했지만 해당 기능이 정말 유저들이 환영할 기능인지 알 방법이 없었습니다. 이 검증 및 구현 또한 아주 빠르게 이루어져야했구요. 우리는 결국 극단적인 선택을 하게 되었습니다. 프론트엔드 코드에 스태틱 데이터를 넣어버렸습니다.</p>
<p><img src="https://images.velog.io/images/croco_space/post/98ab821b-bd96-4bb5-9236-e9d24d526dcf/image.png" alt="https://images.velog.io/images/croco_space/post/98ab821b-bd96-4bb5-9236-e9d24d526dcf/image.png"></p>
<p>🔼<em>애증의 커밋...</em></p>
<h2 id="실시간-뉴스-😊">실시간 뉴스 😊</h2>
<p>관련 영상 기능을 간단하게 테스트하고 받은 피드백을 종합해본다면 &#39;<strong>실시간</strong>으로 정보를 볼 수 있으면 좋겠고 보다 <strong>신뢰성</strong> 있는 정보였으면 좋겠다&#39;였습니다. 유튜브 영상은 신뢰성이 너무 떨어지고 실시간 영상을 보여줄 방법도 찾기가 어렵다고 생각했습니다. </p>
<p>우리가 내린 결론은 &#39;실시간 뉴스&#39;였습니다. 뉴스는 유튜브 영상들에 비해 신뢰도가 있는 글들인 경우가 많다고 판단했습니다. 하지만 문제는 &#39;실시간&#39; 이었습니다. API 요청이 들어올때마다 크롤링을 해야하나? 미리 크롤링을 해두고 보여줘야 하나? 하는 고민들이 있었지만 네이버 뉴스 검색 API를 활용하기로 결정했습니다. 키워드를 활용해 검색하면 실시간으로 데이터를 받아볼 수 있었고 일일 API한도가 2만5천건으로 충분하다고 판단했습니다.</p>
<p>(옛날에 만들어뒀던 네이버 뉴스 크롤러를 활용해 DB에 담아두는 작업을 하기는 했으나 &#39;실시간&#39;의 목적에 벗어난다고 생각해 API로 전환했답니다😵)</p>
<h2 id="마무리">마무리</h2>
<p><a href="https://coronas.info/">https://coronas.info/</a></p>
<p>코로나인포를 백엔드 개발을 맡은 Heewon이었습니다.</p>
<p>많은 기대 부탁드려요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코로나인포 개발기: 프론트엔드 편]]></title>
            <link>https://velog.io/@team_croco/making-coronas-info-frontend</link>
            <guid>https://velog.io/@team_croco/making-coronas-info-frontend</guid>
            <pubDate>Fri, 14 Feb 2020 07:46:45 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! Croco에서 코로나인포의 프론트엔드단 개발을 담당하게 된 도다라고 해요! 😋</p>
<p>오늘은 코로나인포의 총 접속자가 1k를 넘은 기념으로...!
개발할 때 어떠한 스택을 사용하였고, 왜 그 기술을 사용하게 되었는지에 대해 글을 작성해볼까 해요!!</p>
<blockquote>
<p>✅ 해당 글에서는 프론트엔드만 다뤄요!</p>
</blockquote>
<h2 id="코로나인포를-기획하게-된-이유">코로나인포를 기획하게 된 이유!</h2>
<p>2020년 새해에, &#39;신종 코로나바이러스&#39;라고 불리는 전염병이 돌기 시작했어요.
뉴스, 온라인 커뮤니티 등지에서 계속 이야기가 나오는 것을 보고 기획하기 시작하였지만...</p>
<p>특유의 게으름으로 인하여... 개발은 2월 3일부터 시작했어요.</p>
<h2 id="적용되게-된-기술들">적용되게 된 기술들?</h2>
<ul>
<li>React.js<ul>
<li>Next.js - 검색 엔진 최적화를 위해 사용하는게 좋다고 판단하여 사용하게 되었어요!</li>
</ul>
</li>
<li>TypeScript</li>
<li>@emotion - <code>styled-component</code>와 매우 비슷한 라이브러리에요.</li>
<li>ZEIT (now.sh) - 배포를 위해 사용하고 있어요!</li>
</ul>
<p>개발의 편의를 위해 적용한 기술들!</p>
<ul>
<li>ESLint</li>
<li>Prettier - 설치 후, VSCode 플러그인과 함께 사용했어요!</li>
</ul>
<h2 id="개발의-시작-디자인">개발의 시작, 디자인</h2>
<p>React 등의 프론트-엔드 프레임워크를 사용하지 않고 정적 페이지로 구성을 할 때에는 디자인 구상 없이 뚝딱뚝딱 만들었는데, 지금은 컴포넌트 구조도 생각을 해야되서 디자인 없이는 구조를 짜는데에 오랜 시간이 걸려요.</p>
<p>그래서 초기 레이아웃 디자인을 미숙한 실력으로 직접 만들어봤는데...</p>
<p><img src="https://images.velog.io/images/croco_space/post/a8eae288-7434-4445-b5a2-54c4b7fd75b5/image.png" alt="코로나인포_초기_디자인"></p>
<p>모바일 사용성을 살리겠다고 요즘 디자인의 트렌디함을 살리지 못한 것 같아요. 나중에 기회가 된다면 디자인을 개선해보고 싶답니다 🙄</p>
<p>어찌되었든, 이 간단한 레이아웃 스케치를 가지고 우선 구조 작업을 시작했답니다.</p>
<h2 id="디자인을-가지고-코드로-옮기기">디자인을 가지고 코드로 옮기기!</h2>
<p>스케치 된 구조를 유심히 생각해보니, 모바일과 데스크톱 레이아웃이 꽤 많은 부분에서 다르더라구요.</p>
<p><code>@media</code> 태그로만 대응하기엔 너무 일이 커질 것 같은 직감<del>과 귀찮음</del> 때문에 레이아웃에서 width로 구분해서 레이아웃 구조를 변경해요. resize 이벤트가 발생할 때마다 변경하는 방식을 사용했어요.</p>
<p><img src="https://images.velog.io/images/croco_space/post/da3c5ecd-3a29-4b7f-9498-5d2dfef3af08/image.png" alt=""></p>
<p>🔼 현재 코로나인포 디자인 상태.</p>
<h2 id="고생했던-점-😭">고생했던 점 😭</h2>
<h3 id="네이버-지도-로딩-react-naver-maps">네이버 지도 로딩, <code>react-naver-maps</code></h3>
<p>네이버 지도를 처음에 띄우는 데에서 삽질을 많이 했습니다.
최근 네이버 지도가 제공하는 v3 API가 종료되고, Naver Cloud Platform 으로 넘어가게 되었는데요.</p>
<p>props에 넣을 key를 <code>clientId</code>만 보고 고생하다가 <code>ncpClientId</code>로 바꾸니까 되는 것 보고 머쓱... 😓</p>
<h2 id="반성할-점-😦">반성할 점 😦</h2>
<h3 id="정형화되지-못한-구조-컴포넌트">정형화되지 못한 구조, 컴포넌트</h3>
<p>React를 쓰는 장점 중에 하나는 <code>Component</code>를 사용하여 요소를 재사용할 수 있다는 점이죠.</p>
<p>하지만 개발이 급했던 나머지, 페이지에 styled-component 등까지 복붙이라는 재사용을 해버렸어요. 앞으로는 급하더라도 export와 import를 애용하기로 했답니다.</p>
<p>다음 스타일 수정 시에 크게 고생했거든요... 😭
현재는 리팩토링으로 대부분의 재사용되는 컴포넌트를 컴포넌트로 분리시켰어요.</p>
<h3 id="왜-나는-styled-component를-도입했지-😷">왜 나는 <code>styled-component</code>를 도입했지? 😷</h3>
<p><code>@emotion</code> 으로 CSS in JS를 사용하였는데, 왜 사용한지 이해가 되지 않는 구조로 사용할 때가 있어요.
<code>styled-component</code>도 그렇고 <code>@emotion</code>도 모두 Scss 방식의 하위 클래스를 지원하는데요!</p>
<p>제가 사용하는 방식은 <code>styled-component</code>로 크게 페이지를 <code>Layout</code>? 쯤으로 감싸고 클래스로 처리하는 버릇이 있어요. (물론 저에겐 이 방식이 편하긴 해요.)</p>
<p>그래서 이 방식을 버리려고 노력중이에요. 다음부턴 이렇게 사용하지 말아야지...</p>
<h2 id="개선할-점-🤐">개선할 점 🤐</h2>
<p>아직 서비스가 매우 초기 단계이기도 하고 개선할 점이 너무 많아요. 그래도 눈에 띄는 개선점을 적어보려구요!</p>
<h3 id="컴포넌트-비동기-로딩-시에-로딩-상태-표시">컴포넌트 비동기 로딩 시에 로딩 상태 표시</h3>
<p>일부 컴포넌트 (특히 지도!!)는 Dynamic Import라는 Next의 기능을 사용하여, 비동기로 늦게 처리하고 있어요. 근데 공식 문서를 보니, loading시에 커스텀 컴포넌트를 설정할 수 있더라구요!</p>
<p>그래서 해당 기능도 나중에 도입해보려고 합니다.</p>
<h3 id="지도-기능-추가">지도 기능 추가</h3>
<p>이동경로 지도가 선으로만 표시되고, 추가적인 기능은 없어요. 이 부분도 개선이 필요할 것 같더라구요.</p>
<p>제일 중요한 부분인 만큼, 어떻게 하면 좋을지 고민중이에요</p>
<h3 id="커뮤니티-기능-추가">커뮤니티 기능 추가</h3>
<p>초기부터 기획했지만 개발 단계에서 빠진 기능인데요, 커뮤니티 기능이에요. 사용자들이 자유롭게 토론할 수 있는? 🤔</p>
<h2 id="마치며">마치며</h2>
<p><img src="https://images.velog.io/images/croco_space/post/f3c9a5b0-33d9-4ed5-9f15-0c6497a84b6c/image.png" alt=""></p>
<p><a href="https://coronas.info/">https://coronas.info/</a></p>
<p>코로나인포를 프론트엔드 개발을 맡은 도다였습니다.
다음 편엔 백엔드 개발자분께서 백엔드 후기를 작성해주실 예정이니, 관심이 있으시다면 하트 부탁드려요!</p>
]]></description>
        </item>
    </channel>
</rss>