From ff5d66d03155549834f91b0523c1e7581df4f357 Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 15:33:39 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=8E=A8=20=EB=B2=A0=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=8B=B5=EB=B3=80=EC=97=90=20private=20=EB=8B=B5=EB=B3=80?= =?UTF-8?q?=20=EC=A0=9C=EC=99=B8=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/techQuestion/repository/AnswerRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adevspoon-domain/src/main/kotlin/com/adevspoon/domain/techQuestion/repository/AnswerRepository.kt b/adevspoon-domain/src/main/kotlin/com/adevspoon/domain/techQuestion/repository/AnswerRepository.kt index c06737f1..1070add4 100644 --- a/adevspoon-domain/src/main/kotlin/com/adevspoon/domain/techQuestion/repository/AnswerRepository.kt +++ b/adevspoon-domain/src/main/kotlin/com/adevspoon/domain/techQuestion/repository/AnswerRepository.kt @@ -15,7 +15,7 @@ interface AnswerRepository: JpaRepository, AnswerRepositoryC @Query("SELECT a FROM AnswerEntity a " + "LEFT JOIN FETCH a.question " + "LEFT JOIN FETCH a.user " + - "WHERE a.question.id = :questionId " + + "WHERE a.question.id = :questionId and a.status != com.adevspoon.domain.techQuestion.domain.enums.AnswerStatus.PRIVATE " + "ORDER BY a.likeCnt DESC " + "LIMIT 1") fun findBestAnswerListByQuestionId(questionId: Long): List From 213b86dc1b129edcd0b274df5c3c636be75f9fbe Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 16:19:18 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=8E=A8=20=EC=8B=A4=ED=96=89=EC=8B=9C?= =?UTF-8?q?=20tiemezone=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adevspoon-api/scripts/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adevspoon-api/scripts/deploy.sh b/adevspoon-api/scripts/deploy.sh index edc25371..74bdec24 100644 --- a/adevspoon-api/scripts/deploy.sh +++ b/adevspoon-api/scripts/deploy.sh @@ -8,7 +8,7 @@ echo "[$NOW] $JAR 복사" cp $ROOT_PATH/build/libs/adevspoon-api-0.0.1-SNAPSHOT.jar $JAR echo "[$NOW] > $JAR 실행" -nohup java -jar -Dspring.profiles.active=prod $JAR > /dev/null 2> /dev/null < /dev/null & +nohup java -Duser.timezone=Asia/Seoul -Dspring.profiles.active=prod -jar $JAR > /dev/null 2> /dev/null < /dev/null & SERVICE_PID=$(pgrep -f $JAR) From fc2cac63c23263c72f77c92bba9ad101763332d0 Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 16:19:37 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=8E=A8=20submodule=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adevspoon-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adevspoon-config b/adevspoon-config index c524fcb1..96ee0c6f 160000 --- a/adevspoon-config +++ b/adevspoon-config @@ -1 +1 @@ -Subproject commit c524fcb1def20d0064dfd865d6269511ec332d4f +Subproject commit 96ee0c6ff2b7c28919c381eb0bf4f3dc8db224dc From fb6b84c11cb754526cf1d6237a6b6182d2806d46 Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 16:21:17 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=A8=20=EB=B8=8C=EB=9E=9C=EC=B9=98?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=9E=90=EB=8F=99=ED=99=94=20script=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/create-hotfix-branch.yml | 61 ++++++++++++++++++ .github/workflows/create-release-branch.yml | 69 +++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 .github/workflows/create-hotfix-branch.yml create mode 100644 .github/workflows/create-release-branch.yml diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml new file mode 100644 index 00000000..ba420c84 --- /dev/null +++ b/.github/workflows/create-hotfix-branch.yml @@ -0,0 +1,61 @@ +# 최신 Tag 기준으로 Hotfix Branch 생성 +name: Create Hotfix Branch + +on: + workflow_dispatch: + inputs: + artifact: + type: choice + description: 'artifact type' + required: true + default: 'Api' + options: + - 'Api' + - 'Batch' + +jobs: + create-branch: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.MY_TOKEN }} + fetch-depth: 0 + + # 최신 Tag 가져오기 + - name: Get latest tag + id: get-latest-tag + run: | + latest_tag=$(git describe --tags --abbrev=0 --match "${{ github.event.inputs.artifact }}-v*") + echo "LATEST_TAG=$latest_tag" >> $GITHUB_OUTPUT + + # Hotfix 용 버전 생성 + - name: Create new hotfix version + id: create-new-version + run: | + tag=${{ steps.get-latest-tag.outputs.LATEST_TAG }} + + IFS='+' read -ra version_parts <<< "$tag" + version=${version_parts[0]} + fix_number=${version_parts[1]} + + # 픽스넘버가 비어 있는 경우 0으로 설정 + if [ -z "$fix_number" ]; then + fix_number=0 + fi + + # 픽스넘버 증가 + fix_number=$((fix_number + 1)) + + # hotfix 버전 + new_version="${version}+${fix_number}" + echo "NEW_VERSION=$new_version" >> $GITHUB_OUTPUT + + # tag 기반 Hotfix Branch 생성 + - name: Create and push hotfix branch + run: | + tag=${{ steps.get-latest-tag.outputs.LATEST_TAG }} + + git checkout -b hotfix/${{ steps.create-new-version.outputs.NEW_VERSION }} tags/$tag + git push origin hotfix/${{ steps.create-new-version.outputs.NEW_VERSION }} diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml new file mode 100644 index 00000000..3186cc31 --- /dev/null +++ b/.github/workflows/create-release-branch.yml @@ -0,0 +1,69 @@ +# 새로운 버전의 Release Branch 생성 +name: Create Release Branch + +on: + workflow_dispatch: + inputs: + artifact: + type: choice + description: 'artifact type' + required: true + default: 'Api' + options: + - 'Api' + - 'Batch' + version-up: + type: choice + description: 'version up type' + required: true + default: 'patch' + options: + - 'major' + - 'minor' + - 'patch' + +jobs: + create-branch: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.MY_TOKEN }} + fetch-depth: 0 + + # 최신 Tag 가져오기 + - name: Get latest tag + id: get-latest-tag + run: | + latest_tag=$(git describe --tags --abbrev=0 --match "${{ github.event.inputs.artifact }}-v*" 2>/dev/null || echo "") + echo "LATEST_TAG=$latest_tag" >> $GITHUB_OUTPUT + + # 최신 Tag 기준으로 버전 업데이트 + - name: Version up + id: version-up + run: | + latest_tag=${{ steps.get-latest-tag.outputs.LATEST_TAG }} + + if [ -z "$latest_tag" ]; then + new_version="1.0.0" + else + version=$(echo $latest_tag | egrep -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}') + + if [ ${{ github.event.inputs.version-up }} == "major" ]; then + new_version=$(echo $version | awk -F'.' '{printf "%d.%d.%d", $1 + 1, 0, 0}') + elif [ ${{ github.event.inputs.version-up }} == "minor" ]; then + new_version=$(echo $version | awk -F'.' '{printf "%d.%d.%d", $1, $2 + 1, 0}') + else + new_version=$(echo $version | awk -F'.' '{printf "%d.%d.%d", $1, $2, $3 + 1}') + fi + fi + + echo "NEW_VERSION=$new_version" >> $GITHUB_OUTPUT + + # 새 버전 Release Branch 생성 + - name: Create and push release branch + run: | + new_version=${{ steps.version-up.outputs.NEW_VERSION }} + git checkout -b release/${{ github.event.inputs.artifact}}-v$new_version + git push origin release/${{ github.event.inputs.artifact}}-v$new_version \ No newline at end of file From 901dd84b9cdb49f3caaf75eb845960fc178f57d7 Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 16:21:50 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=A8=20API=20dev/prod=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=9E=90=EB=8F=99=ED=99=94=20script=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-dev-api.yml | 64 +++++++++++++++++++ ...pi-deploy-prod.yml => deploy-prod-api.yml} | 16 ++--- 2 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/deploy-dev-api.yml rename .github/workflows/{api-deploy-prod.yml => deploy-prod-api.yml} (87%) diff --git a/.github/workflows/deploy-dev-api.yml b/.github/workflows/deploy-dev-api.yml new file mode 100644 index 00000000..751fba78 --- /dev/null +++ b/.github/workflows/deploy-dev-api.yml @@ -0,0 +1,64 @@ +# API - Dev Server에 배포 +name: API - Deploy Development Environment + +on: + create: + push: + branches: + - release/Api-v* + - hotfix/Api-v* + +jobs: + check-branch: + # Branch 이름 가져오기 + if: ${{ github.event_name != 'create' || github.ref_type != 'tag' }} + runs-on: ubuntu-latest + outputs: + current_branch: ${{ steps.branch-name.outputs.current_branch }} + steps: + - name: Get branch name + id: branch-name + uses: tj-actions/branch-names@v8 + + deploy-development-environment: + needs: check-branch + # release, hotfix 일때만 실행 + if: startsWith(${{ needs.check-branch.outputs.current_branch}}, 'release/Api-v') || startsWith(${{ needs.check-branch.outputs.current_branch}}, 'hotfix/Api-v') + runs-on: 'ubuntu-latest' + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.MY_TOKEN }} + submodules: true + + # JDK 환경 셋팅 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + cache: gradle + + # Gradle Permission + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # Build + - name: Gradle build + run: | + ./gradlew globalCopyConfig + ./gradlew adevspoon-api:build -x test + + # AWS Config + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-region: ${{ env.AWS_REGION }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + # Deploy to Lambda + - name: Deploy to lambda + run: | +# aws lambda update-function-code --function-name adevspoon-api --zip-file fileb://adevspoon-api/build/libs/adevspoon-api-*.jar \ No newline at end of file diff --git a/.github/workflows/api-deploy-prod.yml b/.github/workflows/deploy-prod-api.yml similarity index 87% rename from .github/workflows/api-deploy-prod.yml rename to .github/workflows/deploy-prod-api.yml index 03fa0b45..130df206 100644 --- a/.github/workflows/api-deploy-prod.yml +++ b/.github/workflows/deploy-prod-api.yml @@ -1,8 +1,8 @@ -name: API Server Deploy to Production +name: API - Deploy Production Environment on: push: - branches: - - main + tags: + - Api-v* workflow_dispatch: env: @@ -17,9 +17,9 @@ jobs: steps: # Repo checkout - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: - token: ${{ secrets.TOKEN_GITHUB }} + token: ${{ secrets.MY_TOKEN }} submodules: true # JDK 환경 셋팅 @@ -35,7 +35,7 @@ jobs: run: chmod +x gradlew # Build App - - name: Gradle Build + - name: Gradle build run: | ./gradlew globalCopyConfig ./gradlew adevspoon-api:build -x test @@ -49,7 +49,7 @@ jobs: aws-region: ${{ env.AWS_REGION }} # S3 Upload - - name: S3 Upload + - name: S3 upload run: | aws deploy push \ --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ @@ -57,7 +57,7 @@ jobs: --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \ --source ./adevspoon-api - # Code Deploy 실행 (블루-그린 배포) + # Code Deploy 실행 (블루/그린 무중단 배포) - name: CodeDeploy - Blue-Green Deployment run: | aws deploy create-deployment \ From 36e74f720eb4b3be3a976951308272e67dc0c8d3 Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 16:22:12 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=94=A8=20tag/release=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=83=9D=EC=84=B1=20script=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tagging-and-release.yml | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/tagging-and-release.yml diff --git a/.github/workflows/tagging-and-release.yml b/.github/workflows/tagging-and-release.yml new file mode 100644 index 00000000..ecd88131 --- /dev/null +++ b/.github/workflows/tagging-and-release.yml @@ -0,0 +1,47 @@ +# Tag 생성 및 Release 생성 +name: Versioning - Tagging and Release + +on: + pull_request: + branches: + - develop + types: + - closed + +jobs: + check-branch: + runs-on: ubuntu-latest + outputs: + current_branch: ${{ steps.branch-name.outputs.current_branch }} + steps: + - name: Get branch names + id: branch-name + uses: tj-actions/branch-names@v8 + + # Release 또는 Hotfix 브랜치가 merge 될때 실행 + tagging-and-release: + needs: check-branch + runs-on: 'ubuntu-latest' + if: github.event.pull_request.merged == true && (startsWith(${{ needs.check-branch.outputs.current_branch }}, 'release/') || startsWith(${{ needs.check-branch.outputs.current_branch }}, 'hotfix/')) + steps: + - name: Get tag + id: get-tag + run: | + branch=${{ needs.check-branch.outputs.current_branch }} + new_tag="${branch#release/}" + new_tag="${new_tag#hotfix/}" + echo "NEW_TAG=$new_tag" >> $GITHUB_OUTPUT + + - name: Check tag + run: | + echo ${{ steps.get-tag.outputs.NEW_TAG }} + + - name: Create Release & Tag + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.MY_TOKEN }} + with: + tag_name: ${{ steps.get-tag.outputs.NEW_TAG }} + release_name: ${{ steps.get-tag.outputs.NEW_TAG }} + body: | + ${{ github.event.pull_request.body }} \ No newline at end of file From 802eb936ce810ab973a70aef380713379f84cc62 Mon Sep 17 00:00:00 2001 From: RokwonK Date: Sun, 12 May 2024 21:37:40 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=93=9D=20README=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 145 ++++++++++++++---------------------------------------- 1 file changed, 38 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 02628649..54f9fc1f 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,51 @@ # Adevspoon Backend -문답을 통해 쉽고 간편하게 CS(Computer Science) 공부를 도와주는 모바일 어플리케이션 '개발 한 스푼'의 서버 어플리케이션입니다. +문답을 통해 쉽고 간편하게 CS(Computer Science) 공부를 도와주는 모바일 어플리케이션 '개발 한 스푼'의 백엔드 레포지토리 +*(앱 다운로드 수 1만+, 최대 하루 이용자 수 300+)* -(총 1만 다운로드 이상, 최대 하루 이용자 수 300명) +1. [기술스택](#기술스택) +2. [인프라](#인프라) +3. [CI/CD & 자동화](#ci/cd-&-자동화) +4. [History](#history) -### Product -- [iOS 앱스토어](https://apps.apple.com/kr/app/%EA%B0%9C%EB%B0%9C-%ED%95%9C-%EC%8A%A4%ED%91%BC/id1638716398) -- [Android 플레이스토어](https://play.google.com/store/apps/details?id=com.adevspoon.adevspoon&hl=ko&gl=US) - -### Dev Log -- [AWS 서비스를 로컬, 테스트 환경에서 사용하기(ft. LocalStack)]() -- [ControllerAdvice로 공통 응답 형식 다루기]() -- [세밀하게 예외 다루기(ft. ResponseEntityExceptionHandler)]() -- [어노테이션만으로 Security 설정 off 시키기(Swagger에도 표시하기)]() -- [요청, 응답, Entity에 포함된 Legacy Enum 효율적으로 관리하기]() - -
- -## 개발환경 -### 🔨 Tech Stack -*기존 서버리스 아키텍쳐(AWS APIGateway, Lambda)에서 Spring Boot로 마이그레이션* -- Spring Boot, Kotlin, Docker, JPA, MySQL, AWS -- 로컬&테스트 한정 - JUnit, Mockk, TestContainer, LocalStack - -
- -### ⚖️ Project Structure -멀티 모듈 구조로 외부 시스템과의 느슨한 연결에 집중하여 패키지를 분리. -환경설정 정보는 git submodule로 pivate 레포지토리에서 따로 관리하고 gradle build시 fetch하도록 설정. -```bash -├── adevspoon-api ### API 모듈 ### -│ ├── common # 전역적으로 사용되는 컴포넌트 -│ ├── config # 전역적으로 적용되는 설정 -│ └── -│ ├── controller -│ ├── dto -│ └── service -│ -├── adevspoon-common ### 공통 모듈 ### (exception, enum,dto) -├── adevspoon-domain ### 도메인 모듈 ### -│ ├── common # 도메인 전역적으로 사용되는 컴포넌트 -│ ├── config # 도메인 전역적으로 적용되는 설정 -│ └── <도메인> -│ ├── domain -│ ├── dto -│ ├── exception -│ ├── repository -│ └── service -│ -└── adevspoon-infrastructure ### 외부 시스템 모듈 ### - ├── common # 인프라 전역적으로 사용되는 컴포넌트 - ├── config # 인프라 전역적으로 적용되는 설정 - └── <외부 시스템> # Oauth, Storage 등 - ├── dto - ├── exception - ├── service # interface와 구현체를 분리하여 외부 시스템의 변경에 유연하게 대응 - └── # client, config, util 등 필요에따라 패키지 분리 -``` -
+## 기술스택 +*서버리스 아키텍쳐(AWS API Gateway + Lambda) -> Spring Boot 마이그레이션* +Spring Boot, Kotlin, JPA, MySQL, LocalStack, AWS, Terraform, Github Actions -### 🪜 Infra Structure -AWS & Terraform 기반으로 배포. 프리티어 내에서 최대한 비용 효율적으로 구성 -
-(과거) Serverless 기반 -
+## 인프라 +AWS & Terraform 기반 배포. 비용 효율적으로 구성 +- 구조 : ALB -> EC2 -> RDS (3티어 아키텍쳐) +- 배포 : Github Actions + CodeDeploy + ALB를 통한 무중단(블루/그린) 배포 -![Serverless](https://github.com/kids-ground/adevspoon-backend/assets/52196792/0a0a9e95-64c0-4280-b552-3a1017d80d5c) -
-
+![adevspoon-v1](https://github.com/kids-ground/adevspoon-backend/assets/52196792/c3c5eef5-f6b9-4352-bcb3-6454397fc193) -
-(현재) EC2 기반 -
+## CI/CD & 자동화 +브랜치 & PR 기반 자동화. 무중단 배포 파이프라인 구축 -(작성중) +![adevspoon-automation](https://github.com/kids-ground/adevspoon-backend/assets/52196792/957ffd4d-9daa-499f-91e7-d68e2be5dca6) -
-
+1. 최신 Tag 기반으로 자동 버전업된 release 브랜치 생성 +2. 최신 Tag 기반 hotfix 브랜치 생성 +3. release & hotfix 브랜치 내 커밋 시 dev 환경 자동배포 +4. release & hotfix -> develop으로 PR 머지 시 Tag/Release 생성 +5. Tag 생성 시 prod 환경 자동배포 -
-CI/CD -
- -1. Github Push -2. Github Actions 동작 -> ECR Push -3. EventBridge로 CodePipeline 트리거 -4. CodeDeploy로 블루/그린 배포 - -
-
- -
- -## Rule -### 📎 Issue & Branch & Commit -- **Issue** - - `feature` : 새로운 기능 추가 - - `bugfix` : 버그 수정용 - - `hotfix` : 긴급 수정용 -- **Branch** - - `main` : 배포 가능한 상태의 코드만 merge - - `develop` : 기준 브랜치 - - `feat/#{iusse}` : (기능 개발) 이슈번호 기준으로 생성 - - `bugfix/#{issue}` : (버그 수정) - - `hotfix/#{issue}` : (긴급 수정) main 브랜치에서 생성 -- **Commit** - - `{깃모지} 메세지` : Gitmoji 기반으로 메시지 작성 - -
+## History +**Product** +- [iOS 앱스토어](https://apps.apple.com/kr/app/%EA%B0%9C%EB%B0%9C-%ED%95%9C-%EC%8A%A4%ED%91%BC/id1638716398) +- [Android 플레이스토어](https://play.google.com/store/apps/details?id=com.adevspoon.adevspoon&hl=ko&gl=US) -### 🖇️ Pull Request -PR Template 기반으로 작성 -- PR Title - `Feat#{issue}. 내용` -- 본문 - - Issue : `close #issue번호`로 이슈 연결시키기 - - Summary : 기능/버그 요약 - - Description : 설명이 필요한 부분 작성 \ No newline at end of file +**Tech Log** +- [AOP를 이용해 이벤트 발행, 비동기로 이벤트 처리하기](https://theliar.tistory.com/11) +- [MySQL로 분산락 처리하기, 확장성/비용 효율적으로 동시성 관리하기](https://theliar.tistory.com/10) +- [늘어나는 예외 정보 확장성/가독성 좋게 관리하기](https://theliar.tistory.com/8) +- [어노테이션으로 API별 인증 해제시키고 가독성/개발생산성 높이기](https://theliar.tistory.com/6) +- [동일한 API 응답형식 공통처리하여 개발 생산성 높이기](https://theliar.tistory.com/5) +- [서버 마이그레이션, 요청 및 응답에 포함된 Legacy Enum 효율적으로 관리하기](https://theliar.tistory.com/1) + +**Repository** +- Mobile Repository +- Infra Repository +- [Wiki](https://github.com/kids-ground/adevspoon-backend/wiki) \ No newline at end of file