📌 프로젝트 개요
- Scope : 2024.12 ~ 2025.02 (약 3달)
- Team : PM(1) , Design(2), Front(4), Backend(4)
⚡ Frontend 기술 스택 ( Github Repository : 🔗)
언어: Swift
프레임워크: UIKit
레이아웃: SnapKit, Then
네트워크: Moya
이미지 캐싱: Kingfisher
온보딩 & 스플래시 애니메이션: Lottie
⚡ Backend 기술 스택 (Github Repository : 🔗)
언어 & 프레임워크: Java, Spring Boot
데이터베이스: MySQL, JPA
검색: Elasticsearch
알림: FCM (Firebase Cloud Messaging)
인증 및 보안: 카카오 & 애플 로그인, JWT, Spring Security
배포 및 인프라: Docker, Nginx, AWS GitHub Actions를 활용한 블루-그린 무중단 배포 Redis (컨테이너 기반 운영)
👕 프로젝트 주제 설명
Clokey는 당신의 옷장을 스마트하게 정리하고, 실시간 날씨에 따라 옷을 추천받으며, 다른 사용자들과 스타일을 공유할 수 있는 패션 기록 앱입니다. Clokey는 옷장의 디지털화를 통해 보유한 옷을 한눈에 파악하고, 착용 기록을 바탕으로 패션 소비 패턴을 가시적으로 확인할 수 있도록 도와줍니다.
❗주요 기능
내 옷장을 한눈에! 스마트한 옷 관리
카테고리별로 옷을 정리하고, 착용 횟수와 마지막 착용일이 코디 기록을 통해 자동 반영돼요. 옷장 정리 알림과 함께 스마트 요약 기능으로 활용도를 높일 수 있습니다.
날씨에 딱 맞는 AI 추천 코디와 나만을 위한 트렌드
현재 위치의 날씨 데이터를 기반으로, 옷장 속 아이템 중 최적의 스타일을 추천해 줍니다. 추운 날엔 부츠와 목도리, 더운 날엔 시원한 코디까지!
친구나 다른 사용자들의 옷장을 탐색하면서 새로운 스타일링 아이디어를 얻어보세요. 마음에 드는 스타일에 좋아요를 남기고, 댓글로 소통할 수도 있습니다.
매일의 코디를 기록하고 ‘출근룩’, ‘데이트룩’ 같은 태그로 저장하면, 나만의 스타일 히스토리가 완성됩니다. 과거 코디를 보며 비슷한 상황에서 참고할 수 있어요.
좋아요와 댓글을 통해 서로의 스타일을 칭찬하고 공유해 보세요. 트렌드를 함께 만들어가는 공간에서 소통할 수 있습니다.
🤝🏻 협업
TOOLS
🤔 팀원이 11명이나 되다 보니, 협업을 하는 것에도 체계가 필요했습니다.
팀 전체적으로 소통을 하기 위한 도구로 Notion과 Discord를 이용했습니다. Notion에는 파트별로 페이지를 만들어서 문서화 작업을 하면서 진행을 했고, 다른 파트의 진행도가 어느 정도 되는지 Notion 페이지를 통해서 확인할 수 있었습니다. 또한 팀원 모두가 모이는 대면 회의는 자주 진행하기 어려웠기 때문에 모이기 어려울 경우 Discord를 이용해서 비대면 회의를 진행했습니다.
프로젝트의 규모가 크고 분업이 되어 있다 보니 의견과 진척도를 공유하고 서로 같은 목표를 잡고 있는지 지속적으로 점검할 필요가 있었습니다. 따라서, 일주일에 한 번 정도 정기 회의를 진행을 했고 추가적으로 논의 사항이 필요할 경우에도 전체 회의를 했습니다.
이외에도 짧은 Scope 동안 많은 기능들을 만들어야 했기 때문에 Spring Boot 파트원들과는 주에 4번은 짧은 회의를 하거나, 카카오톡으로 작업 내용을 공유하거나, 아예 모여서 함께 코딩을 진행했습니다. 다들 적극적으로 참여를 해주었기에 더욱 열심히 할 수 있었습니다. 🔥
API 명세서
처음으로 API 명세서를 Notion에 문서화해보았는데, 이를 통해서 프론트와 원활하게 협업을 할 수 있었습니다.
DDD 구조로 설계를 했기 때문에 도메인 분류에 따라서 프론트에게 필요한 정보를 모두 정리했습니다. 추가적으로 진행도를 명시하여 프로젝트가 진행되고 있는 정도를 한눈에 확인할 수 있었고, 담당자를 기록해 두었기 때문에 해당 기능과 관련해서 궁금한 점이 있다면 누구에게 문의해야 하는지 확인할 수 있도록 정리했습니다.
다음과 같은 템플릿을 사용하여 API 명세서를 정리했습니다. 프론트에게도 구체적인 정보를 제공하지만 백엔드 팀원들끼리도 서로 기능을 이해하고 코드 리뷰를 할 때 코드를 이해하기 조금 더 쉬웠던 것 같습니다.
+ 프론트 분들이 Swagger 보다 잘 정리된 API 명세서를 선호하시는 것 같았습니다.
여기에 변수명을 잘못 작성해 주게 된다면... 프론트에서 에러가 계속 발생했는데 그래서 더 꼼꼼히 봐야겠다고 생각하고 있습니다.
AWS 서버에 Swagger를 띄워놓고 프론트 팀원들이 테스트할 수 있도록 공유했습니다.
구체적인 PR Template 도입과 꼼꼼한 리뷰
- 연관 이슈
- PR 요약
- 작업 내용
- 스크린 샷
- 리뷰어들에게
다음과 같은 내용으로 작성하도록 PR Template을 만들었고 새로운 기능이 만들어지거나 규모가 있는 버그가 수정된 경우, 꼼꼼히 작성하여 편하게 리뷰를 할 수 있고 모르는 기능에 대해서 학습할 수 있도록 팀원들끼리 공유했습니다.
⭐ 프로젝트에서의 역할
저는 백엔드 팀원 중 한 명으로 프로젝트에 참여를 했습니다.
프로젝트에서 구현된 기능들을 요약하면 다음과 같습니다.
- 앱의 기능과 관련된 API 77개
- 회원 가입
- Elastic Search를 통한 검색 기능
- 배포
- Chat gpt 연동
- 알림 구현
저는 배포와 알림 구현을 담당했으며, API의 기여도는 (23/77) 30% 정도입니다.
FCM을 이용한 알람 구현
저희는 ios 어플리케이션을 개발했기 때문에 결과적으로 APNs를 통해서만 핸드폰으로 푸쉬 알림을 보낼 수 있었습니다.
1️⃣ 서버에서 Firebase Cloud Messaging(FCM)으로 푸시 알림 요청
2️⃣ FCM 서버가 APNs(Apple Push Notification Service)로 요청 전달
3️⃣ APNs가 iOS 기기로 푸시 알림 전송
따라서, 다음과 같은 방법을 사용했고 어플리케이션에서는 FCM으로 푸시 알림을 요청하도록 구현했습니다.
private void sendNotifications(String content, String imageUrl, String deviceToken) {
Notification notification = Notification.builder()
.setBody(content)
.setImage(imageUrl)
.build();
Message message = Message.builder()
.setToken(deviceToken)
.setNotification(notification)
.build();
try {
firebaseMessaging.send(message);
} catch (FirebaseMessagingException e) {
throw new NotificationException(ErrorStatus.NOTIFICATION_FIREBASE_ERROR);
}
}
- 알림의 내용, 알림에 첨부될 사진의 url, 그리고 전송이 되어야 하는 기기의 deviceToken을 FCM으로 보냈고, 보내는 동시에 알림 객체를 생성해서 보관하는 방식으로 구현했습니다.
배포
서비스 아키텍쳐는 다음과 같습니다.
- 도메인을 구매하고 Route 53을 통해서 도메인과 연결했습니다.
- AWS 로드 밸런서+ACM을 통해서 Https를 구현했습니다.
- DB는 RDS로 EC2 인스턴스 외부에서 관리를 했고 사진은 S3를 통해서 관리했습니다.
- Nginx와 Docker를 이용해서 Blue-Green 무중단 배포를 구현했고 Github Action을 통한 CI/CD로 이를 자동화했습니다.
- Redis 또한 컨테이너로 띄웠습니다.
😢 첫 개발 프로젝트에서 배포를 맡게 되었는데 사실 걱정이 많이 있었습니다.
뭔가 잘 못 만지면 돈이 많이 나오게 될 것이라는 불안감..? 그리고 또 Devops 분야가 이렇게 다룰 것이 많다는 것을 처음 알게 되었습니다... 그래서 결과적으로 우선은 굴러가도록 만드는 것에 집중을 한 것 같습니다.
그 이후에 여유롭게 시간을 가지고 공부를 하면서 블로그에 모두 정리를 해보았습니다!!
🤭 결과적으로 지금은 가장 자신이 있는 기술 스택 중에 하나가 되었습니다.
다양한 종류의 API 개발
생성, 수정, 삭제, 그리고 조회등 다양한 API를 개발하게 되면서 API 개발에 굉장히 익숙해질 수 있었습니다.
나만의 코딩 원칙이 생기고 어떻게 하면 더 깔끔한 코드를 작성할 수 있는지?? 많은 고민들을 해결해 가며 개발을 할 수 있었습니다.
서로 코드를 리뷰해 주면서 어떤 코드가 다른 사람들에게 더 가독성이 좋은 코드가 될 수 있는지 많이 배울 수 있었고 DDD구조에 대해서
많이 배울 수 있었습니다.
⭐ Trouble 슈팅 기록
1️⃣ Validator는 DDD 구조에서 Presentation Layer일까요? 아니면 Service Layer일까요?
프로젝트에서 커스텀 Exception을 만들어서 사용하기도 했지만 커스텀 Annotation을 만들어서 에러를 처리하기도 했습니다. 이때 커스텀 어노테이션의 구현체가 Presentation Layer에 속하는지 Service Layer에 속하게 되는지 어려운 부분이 있었습니다.
그래서 프로젝트 당시에는 Presentation Layer에 속한다고 판단을 했고, Repository를 직접 의존할 수 없다고 생각을 했기 때문에 Repository를 직접 의존하지 않고 Repository에 접근하는 서비스에 의존을 하도록 구현을 했습니다.
@Component
@RequiredArgsConstructor
public class ClothExistValidator implements ConstraintValidator<ClothExist, Long> {
private final ClothRepositoryService clothRepositoryService;
@Override
public boolean isValid(Long clothId, ConstraintValidatorContext context) {
boolean isValid = clothRepositoryService.existsById(clothId);
if (!isValid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(ErrorStatus.NO_SUCH_CLOTH.toString()).addConstraintViolation();
}
return isValid;
}
}
- Validator 구현체에서는 Repository를 직접 의존하지 않았고...
@Override
public History findById(Long historyId) {
return historyRepository.findById(historyId).orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_HISTORY));
}
- 또한 findBy 매서드들의 Optional을 사용할 때마다 매번 확인해야 하는 중복 코드를 줄일 수 있어서 좋은 설계라고 생각했습니다.
하지만, 결과적으로 실무자 지인에게 질문을 해본 결과 이렇게 하면 유지 보수성이 너무 떨어질 수 있고 매번 Optional 객체를 확인하는 것 자체도 서비스 로직의 일부라고 말씀해 주셨습니다.
그리고, Repository에 접근해야 하는 유효성 검증은 Service에 조금 더 가깝다는 결론을 스스로도 내릴 수 있었습니다. 결과적으로 다음에는 Repository Serivce를 따로 만들지 않기로 결정했습니다.
2️⃣ Docker Container와 변수 전달 문제
Github Action으로 CI/CD 파이프라인을 구축하면서 Github Secrets로 yml에 필요한 변수들을 env 파일로 만들어서 컨테이너로 넘겨주었는데 도커 컨테이너에서 계속 인식이 되지 않아서 몇 번이고 Github Action이 실패했습니다.
분명 EC2로 env파일이 전달이 되고 있음에도 불구하고 docker container에서 이를 인식하지 못했습니다.
지금 생각해 보면 Container 내부의 env로 인식되지 않았던 것 같습니다.
이 문제는 yml을 통째로 Github Secret로 관리하고 JAR 파일 빌드시 넣어버리는 방법으로 해결할 수 있었습니다.
spring:
config:
import: application-secret.yml
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
jdbc:
time_zone: Asia/Seoul
show_sql: true
highlight_sql : true
logging:
level:
org.springframework.web: DEBUG
org.springframework.web.client.DefaultRestClient: OFF
- 다음과 같이 application.yml은 application-secret.yml을 import하도록 구현하고 application-secret.yml은 로컬에서는 gitIgnore로 관리하고 CI/CD를 할 때에는 github secrets에서 관리하면서 포함시켜서 빌드하도록 했습니다
3️⃣ Custom Annotation vs Custom Exception
에러 처리를 두 가지를 사용하다 보니, 어떤 경우에 어떤 것을 사용해야 하는지 애매한 부분이 있었습니다.
결과적으로 다음과 같은 결론을 내렸습니다.
Custom Annotation은 DTO의 필드 또는 DTO 자체에 붙여서 검증을 하거나 입력값 (query parameter 또는 path variable)를 검증하기에 용이했습니다. 간단한 유효성 검증과 어플리케이션의 다양한 곳에서 쓰이는 검증은 Annotation을 사용하면 편리했습니다.
하지만, DTO의 값 + member Token의 정보를 조합해서 검증을 하는 경우와 같이 여러 값을 조합하거나 Service 내부에서 일어나는 검증의 경우에는 Annotation을 사용하기 어려웠고 이럴 경우 Custom Exception을 통해서 검증했습니다.
📌 완성된 프로젝트
알림 기능 (알림 대상으로 이동)
푸쉬 알림이 전송되고 푸쉬 알림을 클릭할 경우 알림과 관련된 대상으로 이동됩니다.
캘린더 조회 및 기록 등록
캘린더는 달력 모양으로 표시됩니다. 인스타그램의 기록처럼 보관되며 OOTD를 기록하고 남들과 공유할 수 있습니다.
옷장 조회
나의 옷장을 디지털화 해서 저장할 수 있습니다! 한눈에 옷장의 옷에 대한 정보를 확인할 수 있습니다.
옷 등록
옷 장에 옷을 등록하는 과정입니다. Chat GPT API를 활용해서 이름에 따라서 자동으로 카테고리를 추천해 줍니다.
홈
홈에서는 다양한 정보를 알고리즘에 따라서 노출시켜 줍니다.
검색
Elastic Search를 이용한 검색 기능.
최종적으로!! 저희 팀은 UMC 동아리의 Demo Day 행사에서 프로젝트를 많은 분들께 성공적으로 소개할 수 있었습니다.
양재 AT 센터에서 동아리 Demo Day 행사를 진행했습니다!
😊 부스에서 간단히 Clokey를 시연하고 굿즈도 만들어서 나누어 드렸습니다.
🙏🏻 소감
정말 너무 좋은 팀원들과 함께 규모가 큰 프로젝트를 진행하면서 과정 자체가 즐거웠고, 많은 것을 배울 수 있어 뜻깊은 경험이었습니다.
🏃🏻 백엔드 개발자로서 첫걸음을 내딛으며, 프로젝트 세팅과 API 개발 같은 기본적인 작업부터 DevOps까지 깊이 있게 경험할 수 있었습니다. 배운 내용들을 아직도 소화하고 있을 정도로 많은 것을 얻어 갔고, 효과적인 학습 방법과 오픈 소스 및 공식 문서를 찾아보는 법을 익히면서 새로운 기능에도 주저 없이 도전할 수 있는 자신감이 생겼습니다.
또한, 개발자로서 가장 중요한 역량 중 하나인 협업의 중요성을 크게 깨달았습니다. 백엔드 개발자들 간의 소통뿐만 아니라, 다른 파트와 원활하게 협력하는 방법, 그리고 고려해야 할 요소들에 대해 깊이 고민할 수 있었습니다.
이번 경험을 바탕으로 앞으로도 계속 성장하는 개발자가 되고 싶습니다!!
👊🏻 프로젝트 그 이후..
현재 Clokey는 앱 스토어에 올리기 위한 심사를 준비하고 있습니다. 코드를 리펙토링 하고, 심사에 필요한 신고 기능들을 추가적으로 구현하면서 디벨롭시키고 있습니다. 앞으로도 계속 코드를 돌아보고 과거의 프로젝트로 남기는 것이 아닌 지속적으로 발전시키며 그 과정에서 새로운 것들을 배울 예정입니다.
다음과 같은 것들을 디벨롭할 예정입니다.
✅ Test Code
- 테스트 코드를 꼼꼼하게 작성하고 Test Coverage를 점검할 예정입니다.
✅ JPA 최적화 및 성능 모니터링
- 이 부분에 대해서는 프로젝트 이후에 계속 공부를 하고 있었습니다. DTO를 통한 Projection과 JPQL을 활용하여 조회 성능을 높이고 Redis를 활용하여 성능을 개선할 예정입니다.
😊 긴 글 읽어 주셔서 감사합니다!!
'ETC' 카테고리의 다른 글
우테코 7기 프리코스 회고 (1) | 2025.02.10 |
---|