<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>return.log</title>
        <link>https://velog.io/</link>
        <description>Team return 기술 블로그입니다.</description>
        <lastBuildDate>Sat, 14 Sep 2024 06:52:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>return.log</title>
            <url>https://velog.velcdn.com/images/team-return/profile/4fd6e569-e92e-4862-85f0-ffd3aadb6914/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. return.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/team-return" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[클릭 한 번으로 안드로이드 앱 배포시켜버리기]]></title>
            <link>https://velog.io/@team-return/%ED%81%B4%EB%A6%AD-%ED%95%9C-%EB%B2%88%EC%9C%BC%EB%A1%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%B1-%EB%B0%B0%ED%8F%AC%EC%8B%9C%EC%BC%9C%EB%B2%84%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@team-return/%ED%81%B4%EB%A6%AD-%ED%95%9C-%EB%B2%88%EC%9C%BC%EB%A1%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%B1-%EB%B0%B0%ED%8F%AC%EC%8B%9C%EC%BC%9C%EB%B2%84%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Sat, 14 Sep 2024 06:52:12 GMT</pubDate>
            <description><![CDATA[<p>오늘은 자동 배포를 위한 CI/CD 파이프라인 구축에 대해 이야기를 해보겠습니다. 저의 많은 애정이 담겨 있는 작업이니 잘 읽어주세요~~</p>
<h2 id="배경">배경</h2>
<p>배포하는 임무를 인수인계 받고 배포를 진행하려고 하니 너무 귀찮아졌습니다. 그래서 바로 자동 배포에 대해 알아보았습니다. (사실 iOS 배포되었다고 문자오는게 좀 부러웠습니다...ㅠ). 재미있는 경험이 될 거 같아 파이프라인을 구축하기로 했습니다.</p>
<p>아래는 수동 배포의 과정입니다.</p>
<blockquote>
<ol>
<li>배포할 내역들을 main 브렌치에 머지합니다.</li>
<li>릴리즈 모드로 .aab or .apk 버전을 생성합니다.</li>
<li>구글 플레이 스토어 콘솔로 들어갑니다.</li>
<li>배포할 앱을 선택합니다.</li>
<li>파일 업로드 및 변경사항을 적습니다.</li>
<li>검수 요청을 합니다.</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/team-return/post/598d9ae7-04fd-498e-a9c1-f8cfa9e17a28/image.png" alt=""></p>
<h2 id="환경">환경</h2>
<p>CI/CD 파이프라인 구축하는 환경을 알아본 결과 Github Action으로 하는게 가장 편할 거 같아 사용하게 되었습니다. <a href="https://github.com/Triple-T/gradle-play-publisher#quickstart-guide">Gradle Play Publisher</a>이라는 앱 배포를 도와주는 라이브러리를 사용했습니다. <code>Google Cloud Platform</code>에서 프로젝트를 생성하여 계정을 만든 다음 <code>Goole Play Console</code>과 연결해서 배포되도록 하였습니다. </p>
<h2 id="코드-뜯어보기">코드 뜯어보기</h2>
<h3 id="buildgradleapp">build.gradle(app)</h3>
<pre><code>android {
    signingConfigs {
        create(&quot;release&quot;) {
            storeFile = file(&quot;./keystore/jobis_v2_key.jks&quot;)
            storePassword = System.getenv(&quot;SIGNING_STORE_PASSWORD&quot;)
            keyAlias = System.getenv(&quot;SIGNING_KEY_ALIAS&quot;)
            keyPassword = System.getenv(&quot;SIGNING_KEY_PASSWORD&quot;)
        }
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName(&quot;release&quot;)
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile(&quot;proguard-android-optimize.txt&quot;),
                &quot;proguard-rules.pro&quot;,
            )
        }
    }
}</code></pre><p><code>signingConfigs</code> 파일을 생성하여 배포 파일을 만들 수 있게 세팅합니다. 그리고 release로 빌드 할 때 signingConfig를 등록하여 앱 서명을 진행하게 합니다. release 빌드는 <code>Github Action</code>에서 자동으로 되게끔 구축했습니다.</p>
<pre><code>play {
    serviceAccountCredentials.set(file(&quot;src/main/play/google-cloud-platform.json&quot;))
    defaultToAppBundles.set(true)
    releaseStatus.set(ReleaseStatus.IN_PROGRESS)
    track.set(&quot;production&quot;)
}

tasks.register(&quot;release&quot;) {
    dependsOn(tasks[&quot;clean&quot;])
    dependsOn(tasks[&quot;bundleRelease&quot;])
    mustRunAfter(tasks[&quot;clean&quot;])
}</code></pre><p><code>Google Play Publisher</code> 플러그인을 사용하여 <code>Google Play Store</code>에 배포할 때 사용하는 설정입니다.
<code>google-cloud-platform.json</code>에 있는 서비스 계정 인증 정보를 사용합니다. </p>
<blockquote>
<p><code>defaultToAppBundles.set(true)</code>: App Bundle 형식으로 배포하게 됩니다.
<strong>App Bundle</strong>: Google Play에서 권장하는 배포 방식으로, APK보다 더 작은 크기의 설치 파일을 제공합니다.
<code>releaseStatus.set(ReleaseStatus.IN_PROGRESS)</code>: 점진적으로 릴리즈하여 일정 비율로 배포됩니다.
<code>track.set(&quot;production&quot;)</code>: 실제 사용자에게 배포됩니다.</p>
</blockquote>
<blockquote>
<p><code>tasks.register(&quot;release&quot;)</code>: <code>./gradlew release</code>를 실행하면 태스크가 호출 됩니다.
<code>dependsOn(tasks[&quot;clean&quot;])</code>: 기존 빌드 파일들을 삭제 시켜줍니다. 
<code>dependsOn(tasks[&quot;bundleRelease&quot;])</code>: 앱 번들을 빌드합니다.
<code>mustRunAfter(tasks[&quot;clean&quot;])</code>: clean 태스크가 먼저 실행된 후에 release 태스크가 실행됩니다.</p>
</blockquote>
<h3 id="android-production-deploy-play-storeyml">android-production-deploy-play-store.yml</h3>
<pre><code>- name: Create google-services.json
  env:
      DATA: ${{ secrets.GOOGLE_SERVICES_JSON }}
  run: echo $DATA &gt; /home/runner/work/JOBIS-ANDROID-V2/JOBIS-ANDROID-V2/app/google-services.json

- name: Create google-cloud-platform.json
  env:
      DATA: ${{ secrets.GOOGLE_CLOUD_PLATFORM }}
  run: echo $DATA | base64 --decode &gt; /home/runner/work/JOBIS-ANDROID-V2/JOBIS-ANDROID-V2/app/src/main/play/google-cloud-platform.json</code></pre><p>파이어베이스에서 사용되는 <code>google-services.json</code> 파일과 배포할 때 사용되는 <code>google-cloud-platform.json</code> 파일을 가져오는 내용입니다. <code>google-cloud-platform.json</code>은 <code>Google Cloud Platform</code>에서 사용되는 api 정보를 담고 있고 <code>base64</code>로 디코딩하여 데이터를 가져옵니다.</p>
<pre><code>- name: Create local.properties
  run: |
      echo &quot;BASE_URL_PROD=\&quot;${{ secrets.BASE_URL_PROD }}\&quot;&quot; &gt;&gt; ${{ github.workspace }}/local.properties</code></pre><p><code>local.properties</code>파일에 <code>BASE URL</code>을 넣기 위해 깃허브 시크릿 파일에서 데이터를 가져오는 내용입니다.</p>
<pre><code>- name: Create keystore directory
  run: mkdir -p ${{ github.workspace }}/app/keystore

- name: Decode Keystore
  run: |
      echo &quot;$KEYSTORE&quot; &gt; ${{ github.workspace }}/app/keystore/keystore.b64
    base64 -d -i ${{ github.workspace }}/app/keystore/keystore.b64 &gt; ${{ github.workspace }}/app/keystore/jobis_v2_key.jks
  env:
      KEYSTORE: ${{ secrets.APP_RELEASE_KEY_STORE }}</code></pre><p>Github Action 가상 환경에서 keystore를 저장 할 수 있게 폴더를 만들어줍니다. base64로 디코딩한 keystore을 가져와 <code>jobis_v2_key.jks</code> 파일을 생성합니다. 이렇게 되면 배포 세팅이 끝나게 됩니다. </p>
<pre><code>- name: Build Release And Publish AAB
  run: ./gradlew publishReleaseBundle
  env:
      SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
    SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
    SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}</code></pre><p>AAB형식으로 빌드를 한 후, <code>Google Play Store</code>에 자동으로 업로드하게 됩니다. 
keystore에서 필요한 정보를 환경 변수로 설정된 GitHub Secrets에서 가져옵니다.</p>
<pre><code>- name: Get version
  id: get_version
  run: |
      echo &quot;::set-output name=code::$(grep VERSION_CODE buildSrc/src/main/kotlin/ProjectProperties.kt | awk &#39;{print $5}&#39;)&quot;
      echo &quot;::set-output name=name::$(grep VERSION_NAME buildSrc/src/main/kotlin/ProjectProperties.kt | awk &#39;{print $5}&#39; | tr -d &#39;&quot;&#39; )&quot;

- name: Get tag name
  id: get_tag
  run: echo &quot;::set-output name=name::v${{ steps.get_version.outputs.name }}&quot;

- name: Generate Release
  uses: actions/create-release@latest
  env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
      release_name: &quot;🚀 :: ${{ steps.get_tag.outputs.name }}&quot;
      tag_name: ${{ steps.get_tag.outputs.name }}
      draft: false
      prerelease: false</code></pre><p>깃허브에서 릴리즈 태그를 자동으로 만들기 위해 추가하였습니다. 출시되는 앱 버전을 가져와 태그를 만들어 생성합니다.
<img src="https://velog.velcdn.com/images/team-return/post/df044baf-2dd6-42cf-96ab-6b903291fa9b/image.png" alt=""></p>
<pre><code>- name: Read and format release notes
  id: read_release_note
  run: |
      RELEASE_NOTE=$(cat ./app/src/main/play/release-notes/ko-KR/default.txt | sed &#39;:a;N;$!ba;s/\n/\\n/g&#39; | sed &#39;s/&quot;/\\&quot;/g&#39;)
      echo &quot;RELEASE_NOTE=$RELEASE_NOTE&quot; &gt;&gt; $GITHUB_ENV

- name: Notify Slack on Success
  if: ${{ success() }}
  id: slack-success
  uses: slackapi/slack-github-action@v1.24.0
  with: 
      payload: | 
        {
          &quot;channel&quot;: &quot;${{ secrets.SLACK_DEPLOY_CHANNEL_ID }}&quot;,
          &quot;attachments&quot;: 
          [
            {
              &quot;color&quot;: &quot;#36a64f&quot;, 
              &quot;title&quot;: &quot;${{ github.repository }}&quot;,
              &quot;title_link&quot;: &quot;https://github.com/${{github.repository}}&quot;,
              &quot;text&quot;: &quot;🚀 앱이 배포되었습니다.&quot;,
              &quot;fields&quot;: 
              [
                {
                  &quot;title&quot;: &quot;Repository&quot;,
                  &quot;value&quot;: &quot;${{ github.repository }}&quot;,
                   &quot;short&quot;: true
                },
                {
                  &quot;title&quot;: &quot;Tag&quot;,
                  &quot;value&quot;: &quot;${{ github.ref_name }}&quot;,
                  &quot;short&quot;: true
                },
                {
                  &quot;title&quot;: &quot;Version&quot;,
                  &quot;value&quot;: &quot;${{ steps.get_tag.outputs.name }}&quot;,
                  &quot;short&quot;: true
                },
                {
                  &quot;title&quot;: &quot;Release Note&quot;,
                  &quot;value&quot;: &quot;${{ env.RELEASE_NOTE }}&quot;,
                  &quot;short&quot;: false
                }
              ]
            }
          ]
        }
    env: 
         SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
</code></pre><p>CD가 성공적으로 실행되었을 슬랙으로 알림이 가는 작업을 추가하였습니디. slack api를 사용하여 문자를 커스텀하여 보낼 수 있습니다. 배포에 어떤 내용이 추가되었는지 한눈에 알기 위해 릴리즈 노트도 같이 보내게 만들었습니다.
<img src="https://velog.velcdn.com/images/team-return/post/69674c7e-99a5-4b9f-8cd6-8ecbac7bf4e0/image.png" alt=""></p>
<pre><code>- name: Notify Slack on Failure
  if: ${{ failure() }}
  id: slack-failure
  uses: slackapi/slack-github-action@v1.24.0
  with: 
      payload: | 
        {
          &quot;channel&quot;: &quot;${{ secrets.SLACK_DEPLOY_CHANNEL_ID }}&quot;,
          &quot;attachments&quot;: 
          [
            {
              &quot;color&quot;: &quot;#ff0000&quot;, 
              &quot;title&quot;: &quot;${{ github.repository }}&quot;,
              &quot;title_link&quot;: &quot;https://github.com/${{github.repository}}&quot;,
              &quot;text&quot;: &quot;💣 앱 배포에 실패했어요 ㅠㅠ&quot;,
              &quot;fields&quot;: 
              [
                {
                  &quot;title&quot;: &quot;Repository&quot;,
                  &quot;value&quot;: &quot;${{ github.repository }}&quot;,
                   &quot;short&quot;: true
                },
                {
                  &quot;title&quot;: &quot;Tag&quot;,
                  &quot;value&quot;: &quot;${{ github.ref_name }}&quot;,
                  &quot;short&quot;: true
                },
                {
                  &quot;title&quot;: &quot;Version&quot;,
                  &quot;value&quot;: &quot;${{ steps.get_tag.outputs.name }}&quot;,
                  &quot;short&quot;: true
                }
              ]
            }
          ]
        }
    env: 
         SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK</code></pre><p>CD가 실패하였을 때 슬랙으로 알림이 가게 됩니다.
<img src="https://velog.velcdn.com/images/team-return/post/18d5192c-7e48-44aa-843f-add84846c79f/image.png" alt=""></p>
<hr>
<p>아래는 깃 시크릿에 저장된 값들입니다.
<img src="https://velog.velcdn.com/images/team-return/post/71609d22-0880-447c-8d8d-94e72f846650/image.png" alt=""></p>
<h2 id="느낀-점">느낀 점</h2>
<p><img src="https://velog.velcdn.com/images/team-return/post/fbaec084-f354-442e-a9b5-70d22a18cf3e/image.png" alt="">
이 작업을 하면서 기도를 정말 많이 했던거 같습니다. 일주일 동안 CI/CD만 돌리면서 돌아가는거 보고 많이 쫄렸었는데 결국 해낼 수 있어서 정말 좋았습니다. 이상한 오류도 많이 만나고 답답함도 있었지만 이런걸 해쳐나가니 더 성장할 수 있다는걸 느낄 수 있었습니다.
<img src="https://velog.velcdn.com/images/team-return/post/f39db69c-c6a6-42c2-ac98-642379ca7ad3/image.png" alt="">
위 사진은 CD 고치고 rebase 받아서 실행하는거 반복하니 9780커밋이라는 살면서 처음보는 커밋수를 찍게 되었습니다. 커밋수가 너무 많아 pr은 결국 닫고 새로하였습니다...ㅠㅠ
<img src="https://velog.velcdn.com/images/team-return/post/c7ccc0fd-c3ef-4732-a258-f5805507a8d0/image.png" alt=""></p>
<h3 id="추가-할-점">추가 할 점</h3>
<p>자비스 서비스가 커지면 내부에서 테스트를 돌릴 수 있도록 파이어베이스를 사용하여 내부 테스트 배포 자동화를 만들어보려고 합니다. 저의 로망이긴 하지만..ㅎㅎ</p>
<blockquote>
<p>나는 결코 성공에 대해 꿈꾸지 않았다, 나는 꿈을 위해 행동했다. -에스티 로더</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mendable을 적용하여 Compose를 더 Stable하게 만들기]]></title>
            <link>https://velog.io/@team-return/Mendable%EC%9D%84-%EC%A0%81%EC%9A%A9%ED%95%98%EC%97%AC-Compose%EB%A5%BC-%EB%8D%94-Stable%ED%95%98%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@team-return/Mendable%EC%9D%84-%EC%A0%81%EC%9A%A9%ED%95%98%EC%97%AC-Compose%EB%A5%BC-%EB%8D%94-Stable%ED%95%98%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 28 Aug 2024 14:09:27 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 자비스 2기 부장을 맡고 있는 안드로이드 개발자 <a href="https://github.com/parkuiery">박의엘</a>입니다. <a href="https://github.com/Team-return/JOBIS-ANDROID-V2">자비스 안드로이드</a>에서 Compose로 개발되어 있는 코드들을 최적화한 내용에 대해 소개합니다. </p>
<h2 id="stable이란">Stable이란?</h2>
<p>우선 Compose로 만들어진 코드에서 Stable이 얼마나 중요한지 알아야합니다. </p>
<blockquote>
<p>Recomposition이 발생했을 때 Parameter가 Stable 하다면 Compose Runtime에서 Recomposition을 건너뛰는 것(Skippable)이 가능한 상태.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/team-return/post/c11f5b8d-8f4c-4eb2-9e74-1efac42c30e3/image.webp" alt=""></p>
<p>Stable이 Compose에서 중요하게 작용하는 이유는 Smart Recomposition 때문입니다. </p>
<p>@Composable 어노테이션이 달린 함수는 Compose 컴파일러 플러그인에 의해 일반 함수와 다르게 Compose 생명주기에 따라 재실행 합니다. 상태가 바뀔때마다 실행되는 불필요한 recompostion을 막기 위해 Smart Recomposition을 사용합니다.
Smart Recomposition은 변경된 상태가 이전상태와 동일한지 비교하여 변경된 부분만 다시 그리고 나머지는 재사용하여 효율적으로 UI를 업데이트 합니다. 
Smart Recomposition을 만들기 위해서는 객체를 Stable로 만들어야합니다. </p>
<h2 id="what-is-mendable">What is Mendable??</h2>
<p><a href="https://github.com/jayasuryat/mendable">Mendable</a>은 Jetpack Compose compiler metrics의 지표를 HTML로 볼 수 있게 해주는 CLI 툴입니다. </p>
<h2 id="적용하기">적용하기</h2>
<h3 id="1gradle-설정하기">1.gradle 설정하기</h3>
<pre><code>allprojects {
    tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinCompile::class.java).configureEach {
        kotlinOptions {
            // Trigger this with:
            // ./gradlew assembleRelease -PenableMultiModuleComposeReports=true --rerun-tasks
            if (project.findProperty(&quot;enableMultiModuleComposeReports&quot;) == &quot;true&quot;) {
                freeCompilerArgs += listOf(&quot;-P&quot;, &quot;plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=&quot; + rootProject.buildDir.absolutePath + &quot;/compose_metrics/&quot;)
                freeCompilerArgs += listOf(&quot;-P&quot;, &quot;plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=&quot; + rootProject.buildDir.absolutePath + &quot;/compose_metrics/&quot;)
            }
        }
    }
}</code></pre><p>루트 모듈의 <code>build.gradle</code> 파일에 컴파일 옵션을 추가합니다.</p>
<h3 id="2터미널에서-명령어-실행하기">2.터미널에서 명령어 실행하기</h3>
<pre><code>./gradlew assembleRelease -PenableMultiModuleComposeReports=true --rerun-tasks</code></pre><p><img src="https://velog.velcdn.com/images/team-return/post/5a3059b8-c1d5-4a06-964f-57f2e4e63ce9/image.png" alt="">
<del>저는 11분동안 돌아가서 노트북 터지는 줄 알았습니다.</del></p>
<h3 id="3-mendablejar-저장">3. mendable.jar 저장</h3>
<p><code>rootProject/build/compose_metrics</code> 경로에 <code>mendable.jar</code>를 저장합니다.
<code>mendable.jar</code>는 <a href="https://github.com/jayasuryat/mendable/releases">여기</a>에서 다운로드 받습니다.</p>
<h3 id="4-mendable-실행">4. mendable 실행</h3>
<pre><code>java -jar mendable.jar</code></pre><p><code>mendable.jar</code>가 저장된 경로에서 gradle 명령어를 실행합니다.
실행이 완료되면 <code>index.html</code> 파일이 생성됩니다.</p>
<h2 id="stable하게-만들기">Stable하게 만들기</h2>
<p><img src="https://velog.velcdn.com/images/team-return/post/dba98f6e-4e4a-477b-9942-326c2fb417ae/image.png" alt=""></p>
<p>자비스 프로젝트에 적용을 했는데 <code>70% stable</code> 이라는 결과가 나왔습니다. 
아래 결과를 보고 수정하는 작업을 하면 됩니다. 
<img src="https://velog.velcdn.com/images/team-return/post/46d36412-8e09-468e-a543-5026e26d182f/image.png" alt=""></p>
<h3 id="1list">1.List</h3>
<pre><code>companyInfo: List&lt;CompanyInfoData&gt; // Not stable</code></pre><p>list는 MutableList를 넣을 수 있기 때문에 Unstable한 함수로 확인이 됩니다. <a href="https://github.com/Kotlin/kotlinx.collections.immutable">immutable collection</a>을 사용해서 ImmutableList로 변경하였습니다.</p>
<pre><code>companyInfo: ImmutableList&lt;CompanyInfoData&gt;</code></pre><pre><code>companyInfo = companyInfo.toPersistentList()</code></pre><h3 id="2-data-class">2. Data Class</h3>
<pre><code>state: SetProfileState // Not stable</code></pre><p>StableMaker의 @Immutable 어노테이션을 사용하여 stable하게 수정하였습니다.</p>
<pre><code>@Immutable
internal data class SetProfileState(
    val image: File?,
    val imageUrl: String,
    val uri: Uri?,
    val buttonEnabled: Boolean,
)</code></pre><p><img src="https://velog.velcdn.com/images/team-return/post/85ae679a-16cc-47d1-809e-240c3afe64df/image.png" alt=""></p>
<p>하지만 여기서 DataClass가 compose와 의존성이 없는 모듈에 있다면 문제가 발생합니다. 자비스에서는 domain 모듈에 있는 entity를 참조하는 과정에서 문제가 발생했습니다. 찾아본 결과 skydoves님이 만드신 <a href="https://github.com/skydoves/compose-stable-marker?tab=readme-ov-file">stableMarker</a> 라이브러리를 사용하여 해결할 수 있었습니다.</p>
<h3 id="3painter">3.Painter</h3>
<pre><code>painter: Painter // Not stable</code></pre><p><code>Painter</code> 클래스는 <a href="https://developer.android.com/develop/ui/compose/graphics/images/optimization?hl=ko#pass-url">공식문서</a>에 따르면 이미지 처리의 복잡성으로 인해 stable 클래스로 명시하지 않다고 나와있습니다. 따라서 <code>painter</code> 클래스 대신 Int 타입의 리소스 아이디를 매개변수로 전달하는 새로운 컴포저블을 만들면 문제를 해결할 수 있습니다.</p>
<pre><code>@DrawableRes drawableResId: Int</code></pre><h3 id="4uri">4.Uri</h3>
<pre><code>uri: Uri? // Not stable</code></pre><p>이미지를 사용하는곳에서 uri를 참조하는 부분이 있어 stable한 String으로 수정하였습니다.</p>
<pre><code>uri: String?</code></pre><h3 id="5-navcontroller">5. NavController</h3>
<pre><code>navController: NavController // Not stable</code></pre><p>navcontroller에 default값을 추가하여 stable로 변경하였습니다.</p>
<pre><code>navController: NavController = rememberNavController()</code></pre><h2 id="최종-결과">최종 결과</h2>
<p><img src="https://velog.velcdn.com/images/team-return/post/7c1e7a1f-7606-4347-9f77-edc0519dad65/image.png" alt=""></p>
<p>100%를 달성하였습니다!!</p>
<h2 id="후기">후기</h2>
<p>Mendable을 적용하고 수정하면서 recomposition에 대해 더 많이 알게 되었습니다. Stable로 변경하고 찾아보면서 좋은 경험이 되었습니다. 앞으로도 100%를 유지할 수 있도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vercel로 Nx 모노레포 배포하기]]></title>
            <link>https://velog.io/@team-return/JOBIS-Nx-Vercel</link>
            <guid>https://velog.io/@team-return/JOBIS-Nx-Vercel</guid>
            <pubDate>Thu, 04 Apr 2024 18:10:31 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 JOBIS에서 프론트엔드 개발하고 있는 <a href="https://github.com/jikwan0327">정지관</a>입니다.
저번 글에서는 저희 팀이 왜 모노레포와 Nx를 도입하였는지에 대하여 알아보았습니다.
이번 시간에는 어떻게 모노레포를 적용하고 배포까지 하였는지에 대하여 소개해드리겠습니다.</p>
<h2 id="구축하기">구축하기</h2>
<p>그럼 본격적으로 JOBIS에서 어떻게 Nx를 사용하였고 모노레포를 구축하였는지 알아봅시다.</p>
<h3 id="세팅">세팅</h3>
<p>일단 nx의 가이드 문서를 따라 다음 명령어를 입력해 nx 프로젝트를 생성 및 설치 해줍니다
<a href="https://nx.dev/getting-started/installation">https://nx.dev/getting-started/installation</a></p>
<pre><code>npx nx@latest init</code></pre><p>프로젝트가 생성된 뒤 package.json에서 스크립트와 workspace 구조 설정을 해주었습니다.</p>
<pre><code>{
    &quot;name&quot;: &quot;jobis&quot;,
    &quot;scripts&quot;: {
        &quot;build&quot;: &quot;nx build&quot;,
        &quot;build:many&quot;: &quot;nx run-many -t build&quot;,
        &quot;build:affected&quot;: &quot;npx nx affected -t build&quot;,
        &quot;graph&quot;: &quot;nx run-many --target=build --graph&quot;,
        ...
    },
    &quot;workspaces&quot;: [
        &quot;apps/*&quot;,
        &quot;packages/*&quot;
    ],
    ...
}</code></pre><p><strong>스크립트</strong></p>
<ul>
<li><code>build</code> 명령어는 최상단에서 특정 프로젝트를 빌드할 수 있습니다.<pre><code>  nx build &lt;패키지명&gt;</code></pre></li>
<li><code>build:many</code> 명령어는 프로젝트 전체의 빌드 명령어를 수행하는 명령어입니다</li>
<li><code>build:affected</code>는 저번 글에서  말했던 nx의 affected 기능을 이용하여 변경사항과 관련된 부분만 빌드를 진행하는 명령어입니다.</li>
<li><code>graph</code>: nx의 기능인 프로젝트 및 패키지 의존성 관계를 시각화 하는 그래프를 생성합니다.</li>
</ul>
<p><strong>workspaces</strong></p>
<ul>
<li><code>apps</code>: jobis의 여러 웹 서비스들을 포함하는 workspace 입니다.</li>
<li><code>packages</code>:  jobis 개발에 필요한 공통된 모듈을 포함하는 workspace입니다.</li>
</ul>
<h3 id="구조">구조</h3>
<p>디렉토리의 구조는 다음과 같이 가져 갔습니다.</p>
<pre><code>// 디렉토리 구조
┗━ apps
┃ ┗━ @jobis/company
┃ ┗━ @jobis/storybook
┃ ┗━ @jobis/policy
┗━ packages
┃ ┗━ @jobis/design-token
┃ ┗━ @jobis/esbuild-config
┃ ┗━ @jobis/icons
┃ ┗━ @jobis/ui
┗━ package.json</code></pre><p><strong>packages</strong>
설정이 끝난 뒤 jobis에서 필요한 공통 모듈을 생성해보았습니다.</p>
<p>추가할 모듈은 다음과 같습니다.</p>
<ul>
<li><code>@jobis/esbuild-config</code>: 모듈을 생성할 때마다 번거롭게 번들러 설정하지 않도록 재사용할 수 있는 esbuild-config입니다.</li>
<li><code>@jobis/design-token</code>: jobis에 사용되는 color와 font를 관리하는 모듈입니다.</li>
<li><code>@jobis/icons</code>: jobis에 사용되는 icon들을 관리합니다.</li>
<li><code>@jobis/ui</code>: jobis에 사용되는 UI 컴포넌트를 재사용할 수 있도록 제작한 패키지입니다.</li>
</ul>
<p><strong>apps</strong>
모듈을 분리한 뒤 서비스를 마이그레이션 하였습니다. 
생성한 apps 디렉토리에 프로젝트들을 추가해줍니다.</p>
<pre><code>┗━ apps
┃ ┗━ company
┃    ┗━ package.json
┃ ┗━ storybook
┃    ┗━ package.json
┃ ┗━ policy
┃    ┗━ package.json
┗━ package.json</code></pre><p>추가한 프로젝트 목록</p>
<ul>
<li><code>@jobis/company</code>: 학교로 채용 의뢰를 할 수 있는 기업용 웹입니다.</li>
<li><code>@jobis/policy</code>: 앱에서 개인정보 처리 방침 조회를 위한 웹뷰입니다.</li>
<li><code>@jobis/storybook</code>: storybook을 이용하여 UI 컴포넌트와 아이콘을 시각적으로 정리하고 문서화하는 웹입니다.</li>
</ul>
<p>이렇게 옮긴 company와 storybook 프로젝트에서는 위에서 만든 UI컴포넌트와 icon 패키지를 필요로 합니다. 이러한 경우 패키지 매니저를 이용해 해당 프로젝트에서 add 명령어를 통해 내부의 패키지를 추가할 수 있습니다.</p>
<pre><code>// 해당 패키지 경로에 있는 경우
yarn add &lt;패키지명&gt;
// 루트 경로에 있는 경우
yarn workspace &lt;패키지를 추가할 패키지명&gt; add &lt;패키지명&gt;</code></pre><p>설치가 되면 다음과 같은 형태를 지니게 되고 의존 관계를 지니게 됩니다.</p>
<pre><code>// ex)
// company/package.json
{
  &quot;name&quot;: &quot;@jobis/company&quot;,
  &quot;dependencies&quot;: {
    &quot;@jobis/design-token&quot;: &quot;workspace:^&quot;,
  }
}</code></pre><h3 id="의존성-그래프">의존성 그래프</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/e8175413-0798-46b9-8d90-88cfcd51fdea/image.png" alt="">
위 과정들을 통해 구축된 현재 저희 레포지토리의 의존성 구조는 위 사진과 같습니다.</p>
<h2 id="배포">배포</h2>
<p>이제 구축된 모노레포에 있는 각각의 서비스들을 배포해야합니다.</p>
<h3 id="cloudflare-pages에서-vercel로">Cloudflare pages에서 vercel로</h3>
<p>기존에 저희가 멀티레포 구조를 다룰 때에는 cloudflare pages를 이용하여 각각의 레포지토리를 배포하였습니다. 
그래서 기존 방식 그대로 cloudflare pages를 이용하여 배포를 하려고 하였지만 한가지 문제점이 존재하였습니다.
<img src="https://velog.velcdn.com/images/team-return/post/f6075101-1342-401f-97ff-32870d034ead/image.png" alt="">
위 내용 간단히 해석하자면 cloudflare pages에서는 하나의 레포에는 하나의 프로젝트만 배포할 수 있고 만약 여러 프로젝트를 배포하고 싶은 경우 멀티레포 방식을 사용하거나 수동으로 배포를 하라는 것이었습니다.
저희 팀은 일일히 수동으로 배포하는 방식은 배포가 꼬일 수 있고 매번 번거로운 방식이라고 생각하였습니다.
또 cloudflare pages에서 next13은 정적인 웹만 배포할 수 있었기에 동적인 부분을 필요로하는 저희 서비스와 적합하지 않은 부분이 존재하였습니다.</p>
<p>따라서 저희는 다른 플랫폼을 모색하였고 그렇게 하나의 레포에서 여러 개의 프로젝트를 배포할 수 있으며 nextjs 동적 배포를 모두 다 만족할 수 있는 vercel을 선택하게 되었습니다.</p>
<h3 id="vercel로-배포하기">vercel로 배포하기</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/64ec6251-fd46-43da-8d28-ea5e83b09dc0/image.png" alt="">
vercel 계정을 생성한 뒤 github 연결을 통해 새 프로젝트를 생성해주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/team-return/post/ddbdc934-df57-4364-a18e-130b32dbd779/image.png" alt="">
연결을 하고 나면 프로젝트 이름과 배포할 프로젝트의 프레임워크를 선택해줍니다.</p>
<p>다음은 빌드 명령어와 환경 변수 설정을 해주면 됩니다. 예시로 apps 디렉토리에 있는 company를 배포해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/team-return/post/bbbef32e-33a0-4053-9f69-6b28f3f58a9d/image.png" alt=""></p>
<p>다음과 같이 프로젝트 빌드 명령어와 output 디렉토리, 설치 명령어를 입력해주고 배포해주었습니다.</p>
<h3 id="yarn-version">yarn version</h3>
<p>위와 같은 명령어를 입력하였지만 바로 배포가 되지는 않았습니다. 저희 팀은 첫 배포에 yarn version으로 인하여 한동안 배포를 하는데에 어려움을 겪고 있었습니다.
<img src="https://velog.velcdn.com/images/team-return/post/842331b2-893d-498b-bd28-86ea788da0c6/image.png" alt="">
배포를 할 때마다 다음과 같은 오류가 계속해서 떴는데요.
<img src="https://velog.velcdn.com/images/team-return/post/eb126a6e-134b-4e38-a597-28a58d362ff9/image.gif" alt="">
이 오류를 고치기 위해 3일동안 삽질만 하고 있던 저는 한가지 이상한 점을 찾아낼 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/team-return/post/37671ba6-6424-46aa-94f1-ad107c47df79/image.png" alt=""></p>
<p>바로 제 로컬에서 yarn 버전에 대한 문제였는데요. 해당 프로젝트에서는 yarnPnP를 사용하고 있고 버전은 4.1.0입니다. 그러나 제 로컬에서의 yarn 버전을 확인해보니 4.0.2라는 것을 확인해볼 수 있었습니다. 그래서 .yarn폴더에 들어가 있던 파일들이 배포할 때 사용되는 환경과 다르게 설치되었기 때문에 계속해서 오류가 발생하는 것이었습니다.</p>
<p>하지만 <code>yarn set version 4.1.0</code> 명령어를 통해서 버전을 바꿨음에도 yarn 버전은 바뀌지 않았습니다. 그러다 제 pc에 있는 상위 폴더에서 yarn이 4.0.2 버전의 형태로 존재하는 것을 확인하였고 모든 yarn 관련 파일을 삭제해준 뒤 재설치를 하니 .yarn폴더 안의 파일들이 업데이트 되는 것을 볼 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/team-return/post/4e8a0da1-0034-419f-96f2-32ab8ee1c9b0/image.png" alt="">
이렇게 업데이트된 파일을 다시 커밋해주니 배포가 성공되는 것을 확인할 수 있었습니다.</p>
<h2 id="배포-시간-50-단축">배포 시간 50% 단축</h2>
<p>vercel의 <strong>remote caching</strong> 기능을 이용하면 배포 시간을 단축할 수 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/team-return/post/0b871b08-07cb-4ef7-b893-0ce5b83d90d8/image.png" alt="">
기존의 배포에 소요되는 시간입니다. 커밋을 할 때마다 새로 빌드를 하기 때문에 변경사항이 많이 없는 경우에도 똑같이 배포시간이 걸리는 것을 확인할 수 있습니다.</p>
<p>하지만 remote caching 기능을 사용하면 원격으로 캐싱을 해주어 다음 동작에서 반복되는 부분의 동작을 수행하지 않고 배포할 수 있습니다.</p>
<h3 id="적용하기">적용하기</h3>
<pre><code>yarn add -D @vercel/remote-nx</code></pre><p>위 명령어를 통해 @vercel/remote-nx를 설치해줍니다.</p>
<pre><code>{
  &quot;tasksRunnerOptions&quot;: {
    &quot;default&quot;: {
      &quot;runner&quot;: &quot;@vercel/remote-nx&quot;,
      &quot;options&quot;: {
        &quot;cacheableOperations&quot;: [&quot;build&quot;, &quot;test&quot;, &quot;lint&quot;, &quot;e2e&quot;],
        &quot;token&quot;: &quot;&lt;token&gt;&quot;,
        &quot;teamId&quot;: &quot;&lt;teamId&gt;&quot;
      }
    }
  }
}</code></pre><p>설치가 완료되었다면 nx.json에서 runner를 @vercel/remote-nx로 변경해주고 캐싱을 적용할 명령어를 cacheableOperations에 추가해줍니다. token과 teamId에는 vercel에서 제공하는 access token과 teamId를 넣어주면 적용이 완료됩니다.</p>
<p><img src="https://velog.velcdn.com/images/team-return/post/d5aa5d66-46e8-4ddb-baf9-004a8fa2504e/image.png" alt="">
위 과정을 통해 적용되었을 때 배포시간입니다. 캐시 히트를 통해 배포 시간이 이전보다 단축되는 것을 확인할 수 있습니다.</p>
<h2 id="모노레포-도입으로-개선된-점">모노레포 도입으로 개선된 점</h2>
<ol>
<li>코드리뷰 활성화<ul>
<li>각 프로젝트의 작업 사항을 하나의 레포지토리에서 한눈에 확인할 수 있게되면서 다른 프로젝트의 코드를 들여다보며 코드 리뷰에 적극 참여할 수 있는 계기가 만들어졌습니다. </li>
</ul>
</li>
<li>팀 컨벤션 통일<ul>
<li>레포지토리 별로 달랐던 eslint와 prettier의 설정들이 통합되면서 동일한 컨벤션을 가져가면서 새로운 프로젝트를 생성할 때마다 린트 환경설정 등과 같은 리소스 낭비를 줄일 수 있었습니다.</li>
</ul>
</li>
<li>최신화 상태 유지<ul>
<li>기존에 웹뷰 서비스의 경우 첫 배포 이후 수정사항이 거의 없어 프로젝트 관리가 거의 관리되지 않고 있었습니다. 하지만 모노레포를 통해 모든 프로젝트가 하나의 레포지토리에 관리되면서 팀원 모두가 모든 프로젝트에 관심을 가지고 최신화 상태를 보존할 수 있게 되었습니다.</li>
</ul>
</li>
</ol>
<h2 id="마무리">마무리</h2>
<p>모노레포를 적용하면서 팀의 생산성이나 유지보수적인 측면에서 개선해 나갈 수 있다는 부분이 인상적이었습니다.
nx와 vercel에서 지원하는 기능들이 정말 많았고 아직 미흡하게 적용해본 부분도 있고 연구해볼 내용들이 많은 것 같습니다. 앞으로도 계속 공부하고 적용하면서 다음에는 더 개선된 모습의 글로 찾아뵙겠습니다. 감사합니다.</p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://nx.dev/">https://nx.dev/</a></li>
<li><a href="https://d2.naver.com/helloworld/7553804">https://d2.naver.com/helloworld/7553804</a></li>
<li><a href="https://vercel.com/docs/monorepos/nx">https://vercel.com/docs/monorepos/nx</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 JOBIS는 Nx를 도입하게 되었는가?]]></title>
            <link>https://velog.io/@team-return/JOBIS-Nx</link>
            <guid>https://velog.io/@team-return/JOBIS-Nx</guid>
            <pubDate>Thu, 04 Apr 2024 12:37:27 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 JOBIS에서 프론트엔드 개발을 담당하고 있는 <a href="https://github.com/jikwan0327">정지관</a>입니다.
오늘은 제가 JOBIS를 개발하면서 저희 서비스에 모노레포를 도입하게 된 배경과 왜 Nx를 도입하게 되었는지 소개해드리려고 합니다.</p>
<h2 id="기존-jobis의-프론트엔드-구조">기존 <a href="https://notion.team-return.com/projects/jobis">JOBIS</a>의 프론트엔드 구조</h2>
<p>현재 JOBIS는 총 3개의 웹사이트로 구성되어 있습니다. </p>
<ul>
<li>모집의뢰서를 관리하고 학생 지원을 관리하는 <strong>선생님용</strong> 웹</li>
<li>학교로 학생들을 채용하기 위한 모집의뢰서를 작성할 수 있는 <strong>기업용</strong> 웹</li>
<li>학생들이 의뢰가 들어온 기업으로 지원할 수 있는 <strong>학생용</strong> 웹</li>
</ul>
<p><img src="https://velog.velcdn.com/images/team-return/post/9b843ca7-4586-4360-9794-f67cb350ea5e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/team-return/post/923e314a-5fff-4332-9633-13ebb65c5bd5/image.png" alt=""></p>
<p>기존의 JOBIS 프론트엔드는 3개 웹사이트를 각각의 레포지토리로 관리하는 멀티레포 구조를 사용하고 있었습니다. 또한 저희는 팀 생산성을 향상하고 디자인을 통일하기 위한 디자인 시스템 레포지토리를 따로 운영하고 있었고 웹뷰 레포지토리 또한 분리하고 있어 사실상 jobis 프론트엔드는 총 5개의 레포지토리로 관리되고 있었습니다. (디자인 시스템에 관한 글은 따로 쓸 예정입니다.)</p>
<p>멀티레포의 장점은 각각의 프로젝트가 독립적으로 운영되면서 다른 프로젝트와 의존성을 가지지 않아 빠른 개발이 가능하고 비교적 크기가 가볍워 프로젝트 관리가 수월하다는 점이었습니다.</p>
<h2 id="기존-구조의-문제점">기존 구조의 문제점</h2>
<p>하지만 이렇게 운영 및 개발을 하다보니 기존에 저희가 사용하는 멀티레포 구조에서는 몇가지 불편함이 존재한다는 것을 느끼기 시작하였습니다.</p>
<ol>
<li>프로젝트마다 서로 다른 컨벤션<ul>
<li>여러 레포지토리로 프로젝트가 관리되다 보니 eslint, prettier 등의 설정이 달라 코드 스타일이나 포매팅과 관련해서 레포지토리 별로 통합되지 못하는 부분이 있었습니다.</li>
</ul>
</li>
<li>중복되는 코드 증가<ul>
<li>각 레포지토리마다 동일한 중복코드가 많이 발생하는 경우가 잦았습니다. 또한 저희 프로젝트는 변경사항도 잦은 팀이었기에 중복되는 코드의 수정사항이 생길 경우 수정해야하는 저장소가 많아지므로 유지보수성을 해칠 수 있습니다.</li>
</ul>
</li>
<li>저장소의 수가 많아질수록 관리가 복잡<ul>
<li>저장소 수가 계속 증가함에 따라 버전, 의존성 관리가 복잡졌습니다. 또한 코드 리뷰에 있어서도 여러 레포지토리에 신경을 써야해 팀원들이 불편함을 느꼈습니다.</li>
</ul>
</li>
</ol>
<h2 id="모노레포">모노레포</h2>
<p>따라서 저희는 모노레포 도입을 통해 이러한 문제를 해결하고자 하였습니다. 시작에 앞서 모노레포란 무엇인지에 대하여 먼저 알아봅시다. </p>
<h3 id="모노레포란"><a href="https://monorepo.tools/">모노레포</a>란?</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/406cb992-4062-4496-a301-cf30676c562c/image.png" alt="">
모노레포란 버전 관리 시스템에서 두 개 이상의 프로젝트 코드가 동일한 저장소에 저장되는 소프트웨어 개발 전략을 말합니다. 따라서 하나의 레포지토리에서 여러개의 프로젝트를 관리할 수 있습니다.</p>
<h3 id="모노레포의-장점">모노레포의 장점</h3>
<ul>
<li>전체 코드의 일관성: 하나의 저장소에서 코드를 관리하기 때문에 eslint, preiiter와 같은 설정을 통합하여 모든 팀원이 동일한 코드 컨벤션을 가질 수 있게 해줍니다.</li>
<li>공유 라이브러리: 여러 프로젝트에서 공유되는 라이브러리를 관리하는데 효율적입니다.</li>
<li>단일 버전 관리: 모든 코드가 하나의 버전 관리 체계에서 관리되기 때문에, 버전 충돌 문제를 최소화할 수 있습니다.<h3 id="모노레포의-단점">모노레포의 단점</h3>
</li>
<li>복잡성 증가: 여러 프로젝트를 하나의 저장소에서 관리하므로 코드 충돌 등의 문제가 발생할 가능성이 높아집니다.</li>
<li>빌드 시간 증가: 모든 프로젝트를 함께 빌드해야 하므로 빌드시간이 증가할 수 있습니다.</li>
<li>변경 사항에 민감: 하나의 프로젝트에서 발생한 변경 사항이 전체 시스템에 영향을 미칠 수 있습니다.</li>
</ul>
<h2 id="왜-nx인가">왜 Nx인가?</h2>
<p>그렇다면 저희 팀은 왜 Nx를 선택하게 되었을까요? </p>
<h3 id="많은-사용자-수">많은 사용자 수</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/fa214835-1bc8-467c-8bff-617c002cc619/image.png" alt="">
위 사진은 모노레포 도구들에 대한 다운로드 수 현황을 나타내는 그래프입니다.
모노레포 툴로 대표적인 nx, lerna, turbo를 비교해 보았는데요. nx가 다른 툴들에 비하여 2배이상 높은 다운로드 수를 기록하고 있는 것을 볼 수 있었습니다. 또한 지속적인 업데이트를 통한 프로젝트를 관리하고 있는 것을 볼 수 있었는데요.
이는 nx의 커뮤니티 규모가 크며 도움 받을 수 있고 공유할 수 있는 환경이 크다는 것을 의미한다고 생각하였습니다.</p>
<h3 id="많은-기능-지원">많은 기능 지원</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/463ba853-798e-45f9-8ed2-39e11cf327e3/image.png" alt="">
위 사진과 같이 모노레포의 대표적인 툴들의 기능을 비교해 보았을 때 많은 기능을 지원하는 것을 확인할 수 있었습니다. 무조건 기능이 많다고 좋은 것은 아니지만 저희 팀은 속도와 구조 측면에서 강점이 있는 것에 집중하였습니다. 그 결과 yarn과 lerna는 저희의 선택 안에서 제외하는 결정을 하게 되었습니다. 또한 결정적으로 서비스들은 이미 독립적인 레포 환경에서 배포 관리되며 실 서비스에 문제가 되는 부분이 없어 저희는 이러한 기능들에 대해서 더 알아보고 적용해보며 공부할 환경이 되었기에 Nx를 적용해보기로 하였습니다. 기능에 대한 더 자세한 내용은 아래에서 확인해봅시다.</p>
<h3 id="의존성-그래프">의존성 그래프</h3>
<p>Nx는 현재 패키지 간의 의존성 그래프를 확인할 수 있는 기능을 지원하고 있습니다. 
<img src="https://velog.velcdn.com/images/team-return/post/39e58dc4-8370-4c69-bf01-b41d58a44b4a/image.png" alt=""></p>
<p>저희 서비스는 유지보수를 계속해서 해 나가야하기 때문에 처음 들어온 팀원들도 현재 서비스의 구조에 대한 이해를 쉽게 할 수 있도록 하기 위해 선택하였습니다. </p>
<h3 id="캐싱">캐싱</h3>
<p>Nx는 캐싱 기능을 제공합니다. 앞서 말씀드렸다시피 저희 팀은 속도와 관리 측면에서 강점이 있는 툴을 선택하려고 하였습니다. Nx에서는 동일한 명령어를 실행하면 캐싱된 부분을 통해 동일한 것을 두 번 빌드하거나 테스트하지 않도록 합니다. 따라서 이러한 기능을 활용한다면 빌드나 테스트 시간을 줄일 수 있을 것이라고 생각하였습니다.</p>
<h3 id="affected">affected</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/53ab5925-091b-4d25-ad1a-6ffb7f409999/image.png" alt="">
Nx에서는 affected라는 스크립트를 지원합니다. 이를 통해서 변경사항에 대해 의존하고 있는 빌드 및 테스트 작업만 수행할 수 있습니다. 앞서 나왔던 모노레포의 단점 중 하나의 수정사항에 대하여 모든 프로젝트가 빌드가 되면서 빌드 시간이 증가할 수 있다는 치명적인 단점이 존재하였으나 affected를 이용하면 수정사항에 대하여 관련이 있는 필요한 부분만 빌드 하게 되기 때문에 모노레포의 단점을 해소할 수 있다고 생각하였습니다.</p>
<h3 id="scaffold">Scaffold</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/62da4f33-6e9d-4378-a274-3269f134ae44/image.png" alt="">
스케폴딩이란 새로운 프로젝트를 생성할 때 초기 코드를 쉽게 생성하는 것을 말합니다. Nx는 다양한 플러그인 혹은 로컬 Generator를 통해 새로운 프로젝트를 생성할 때 초기설정을 팀에 쉽게 생성할 수 있습니다. </p>
<h3 id="nx-console">Nx console</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/693c7fdb-5844-414b-a023-1b68833d297b/image.png" alt="">
다른 모노레포 툴과 비교하였을 때 Nx는 Nx console이라는 VS Code에서 Nx를 쉽게 사용하기 위한 GUI 툴을 제공합니다. Nx console을 사용하여 새로운 프로젝트 또는 내부 패키지를 작성, 빌드나 테스트를 세팅하고 실행 작업도 손쉽게 할 수 있을 것 같아 보였습니다. 이 부분은 개발자 경험을 향상 시킬 수 있는 부분이라고 생각되었습니다.</p>
<h2 id="마무리">마무리</h2>
<p>이번 글에서는 저희 팀이 모노레포를 도입하게된 배경에 대하여 알아보았습니다.
다음 글에서는 저희가 이렇게 도입한 모노레포를 어떻게 구축 및 배포를 하였는지에 대해 다뤄보도록 하겠습니다.</p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://monorepo.tools/">https://monorepo.tools/</a></li>
<li><a href="https://www.seonest.net/posts/javascript/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-monorepo">https://www.seonest.net/posts/javascript/모노레포-monorepo</a></li>
<li><a href="https://d2.naver.com/helloworld/0923884">https://d2.naver.com/helloworld/0923884</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[채용 프로세스 만들고도 취업 못한 망자들의 velog]]></title>
            <link>https://velog.io/@team-return/velog-start</link>
            <guid>https://velog.io/@team-return/velog-start</guid>
            <pubDate>Fri, 22 Mar 2024 03:21:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/team-return/post/2601490c-375e-454d-a575-632b45f3d525/image.png" alt=""></p>
<h3 id="글쓴이">글쓴이</h3>
<p>안녕하세요! 저는 현재 대덕소프트웨어마이스터고등학교에 재학중이고, Team return 1기 리더인 길근우입니다.</p>
<h2 id="이게-무슨-velog임">이게 무슨 velog임?</h2>
<p>고등학교 학생들끼리 결성한 개발 팀 <a href="https://www.team-return.com">Team return</a>의 기술 블로그입니다.
저희 팀은 현재 대덕소프트웨어마이스터고등학교 채용 관리 서비스 JOBIS를 개발하고있습니다.</p>
<p>앞으로 저희가 프로젝트를 진행하며 이용한 여러가지 기술들에 대해 소개하고 이야기 하고싶어 이렇게 velog를 만들게 되었습니다.</p>
<h3 id="return에-대해">return에 대해</h3>
<p>현재 return은 1기, 2기 총 두 기수로 이루어져 JOBIS를 유지보수하고 있습니다. 앞으로는 JOBIS뿐만 아니라 여러 프로젝트를 진행하고 대회도 나갈 예정입니다.
<del>지금 2명빼고 전부 취업 못함</del></p>
<h3 id="jobis는"><a href="https://notion.team-return.com/projects/jobis">JOBIS</a>는~</h3>
<p><img src="https://velog.velcdn.com/images/team-return/post/90408ffe-db59-41d5-9396-f29d541403c3/image.gif" alt=""></p>
<p>JOBIS는 대덕소프트웨어마이스터고등학교 채용 관리 서비스입니다. 
제 고등학교 인생을 갈아넣어 만든 서비스로, 무려 <strong>현재 실사용 중</strong>이고 거의 개발만 1년정도 진행한 프로젝트입니다. 팀원들과 함께 열심히 개발했었고, 팀원들도 여러가지 느낀점들이 있었다고 생각이 되네요.</p>
<blockquote>
<p>팀원들이 쓴 JOBIS 회고글
<a href="https://velog.io/@tmdhoon2/1%EB%85%84-%EA%B0%84-%EA%B0%9C%EB%B0%9C%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0">Android</a>
<a href="https://velog.io/@gtw030488/%EA%B3%A02%EA%B0%80-%EA%B5%90%EB%82%B4-%EC%B7%A8%EC%97%85-%EA%B4%80%EB%A6%AC-%EC%84%9C%EB%B9%84%EC%8A%A4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0%EA%B9%8C%EC%A7%80JOBIS-%ED%9A%8C%EA%B3%A0">iOS</a></p>
</blockquote>
<h2 id="앞으로">앞으로</h2>
<p>저희 팀이 개발하면서 겪었던, 그리고 겪을 다양한 기술들에 대해 글을 작성할 예정입니다. 많은 관심 부탁드려요!</p>
]]></description>
        </item>
    </channel>
</rss>