들어가며
국내 웹콘텐츠 시장은 코로나19 이후 급격한 성장을 이루었고, 이에 따라 웹툰/웹소설/일러스트 작가부터 성우, 애니메이터, 역/식자, 편집자 등 콘텐츠 창작에 관여하는 창작자들의 수도 크게 증가했습니다. 하지만 시장의 성장 속도에 비해 체계적인 작업 과정과 분업화된 직무에 맞는 인력을 찾고 소통할 수 있는 플랫폼은 부재한 상황이었습니다.
특히 창작자들의 소통은 주로 트위터나 카페 등에서 개별적으로 이루어지고 있었고, 작품이나 작업 과정을 아카이빙하기 위해서는 여러 플랫폼을 동시에 이용해야 하는 불편함이 존재했습니다. 게다가 구인구직을 위한 포트폴리오 제출 시에도 기존의 일반적인 포트폴리오 플랫폼들은 웹콘텐츠 창작자들의 특성을 제대로 반영하지 못했습니다.
라이트(LAITEU)는 이러한 문제를 해결하기 위해 시작된 프로젝트입니다. 단순한 작품 전시를 넘어, 작업 과정과 결과물을 효과적으로 공유하고 아카이빙할 수 있으며, 나아가 구인구직까지 이어질 수 있는 통합 플랫폼을 목표로 했습니다. 특히 작업 시간, 사용 도구, 작업 단계별 이미지 등 웹 콘텐츠 창작자만의 특별한 정보들을 잘 담아내고, 이를 통해 실제 작업 의뢰나 채용으로 이어질 수 있도록 하는 것이 핵심이었습니다.
기획팀과 디자인팀의 초기 요구사항을 분석하면서, 다음과 같은 핵심 기능들이 도출되었습니다:
- 작업 과정별 다중 이미지 업로드 및 관리
- 작품 타입별 맞춤 정보 구조 (웹툰/일러스트/웹소설)
- 작가의 구인구직 상태 표시
- 작품 공개 범위 관리 (전체 공개/링크 공개/비공개)
- 포트폴리오 폴더 관리 및 북마크 시스템
이러한 요구사항들을 충족시키면서도, 창업 프로젝트의 특성상 빠른 변화와 피봇에 대응할 수 있는 유연한 백엔드 시스템이 필요했습니다. 오늘은 이러한 요구사항들을 어떻게 기술적으로 구현했는지, 그 첫 단계였던 백엔드 아키텍처 설계 과정과 기술 스택 선정에 대한 이야기를 해보려 합니다.
기술 스택 선정 과정
Spring Boot 선택
Spring Boot는 기술 스택 경험을 기반으로 자연스럽게 선택되었습니다. 특히 타입 안정성과 객체지향적 설계의 용이성, 그리고 대규모 트래픽 처리에 대한 검증된 사례들이 있다는 점이 주요 이점이었습니다.
MongoDB 도입 결정
데이터베이스를 선정하는 과정에서 가장 중요하게 고려한 것은 '변화에 대한 유연성'이었습니다. 창작자들의 다양한 작업 방식과 포트폴리오 구성을 지원하기 위해서는 미리 정해진 형식에 데이터를 맞추는 것이 아니라 데이터에 맞춰 구조가 자연스럽게 변화할 수 있어야 했습니다.
예를 들어, 일러스트 작가의 경우 작업 시간과 사용한 도구 정보가 중요하지만, 웹소설 작가는 글자 수나 연재 플랫폼 정보가 더 중요할 수 있습니다. 또한 웹툰 작가는 스토리보드, 선화, 채색 등 작업 단계별 이미지를 모두 보여줄 필요가 있습니다. 이렇게 직군별로 다른 정보 구조를 유연하게 지원하기 위해 문서 기반 데이터베이스의 도입을 결정했고, 그 중에서도 MongoDB를 선택했습니다.
MongoDB를 선택한 결정적인 이유는 강력한 쿼리 기능과 확장성이었습니다. 특히 작품 검색이나 태그 기반 필터링과 같은 기능을 구현할 때, MongoDB의 집계 파이프라인과 인덱싱 기능이 큰 강점을 보였습니다. 또한 AWS DocumentDB를 통한 클라우드 마이그레이션 가능성도 고려했는데, 이는 향후 서비스 확장 시 중요한 요소가 될 것으로 판단했기 때문입니다.
MongoDB 스키마 설계와 데이터 모델링
MongoDB를 선택한 후, 가장 중요한 것은 데이터 모델링이었습니다. 웹 콘텐츠 창작자들의 다양한 작업 방식과 포트폴리오 구성을 효율적으로 지원하기 위해 세 가지 주요 컬렉션(작가, 작품, 작품 이미지)을 설계했습니다.
작가(Artist) 컬렉션은 기본적인 프로필 정보 외에도 구직 상태(employmentStatus)와 작가 유형(artistType)을 포함하도록 했습니다. 특히 북마크 기능을 위한 폴더 구조를 작가 문서 내에 임베딩하여 구현했는데, 이는 북마크 조회가 주로 특정 작가의 컨텍스트 내에서 이루어진다는 점을 고려한 선택이었습니다.
이러한 구조의 실제 구현은 다음과 같은 쿼리 패턴으로 이어졌습니다:
// 작가의 북마크 폴더 조회
@Query("{ 'firebaseUid': ?0, 'favoriteFolders': { $elemMatch: { 'folderName': ?1 } } }")
Optional<Artist> findByFirebaseUidAndFolderName(String firebaseUid, String folderName);
// 작품 즐겨찾기 추가
public void addFavorite(String firebaseUid, String artworkId) {
Artist artist = findByFirebaseUidOrThrow(firebaseUid);
FavoriteFolder defaultFolder = artist.getDefaultFolder();
defaultFolder.getArtworkIds().add(artworkId);
artistRepository.save(artist);
}
작품(Artwork) 컬렉션은 하나의 작품이 여러 이미지를 포함할 수 있도록 설계했습니다. 작품의 상태(공개/비공개/링크공개)를 관리하는 artworkStatus 필드와 함께, 작업 시간(duration)과 연령 제한(age) 정보도 포함했습니다. 특히 publicId 필드를 별도로 두어 외부 공유용 URL을 생성할 때 사용하도록 했는데, 이는 보안과 확장성을 모두 고려한 선택이었습니다.
작품 조회를 위한 주요 쿼리는 다음과 같습니다:
// 공개 상태인 작품만 조회
@Query("{ 'artistId': ?0, 'artworkStatus': 'PUBLIC' }")
Page<Artwork> findPublicArtworks(String artistId, Pageable pageable);
// 태그 기반 작품 검색
@Query("{ 'artworkTags': { $in: ?0 }, 'artworkStatus': 'PUBLIC' }")
List<Artwork> findByArtworkTags(List<String> tags);
작품 이미지(ArtworkImage) 컬렉션은 원본 이미지와 미리보기 이미지의 URL을 따로 관리하며, AI 기반의 태그 정보도 함께 저장합니다. 이미지 태그는 한글과 영문을 모두 지원하여 향후 검색 기능 확장을 고려했습니다.
이러한 세 가지 컬렉션의 설계는 다음과 같은 이점을 제공했습니다:
- 빠른 포트폴리오 조회 - 작가와 작품 정보를 효율적으로 조회
- 유연한 확장성 - 새로운 작가 유형이나 작품 형태 추가 용이
- 효율적인 검색 - 태그와 키워드 기반 검색 최적화
이미지 처리 전략
웹 콘텐츠 창작자들의 특성상 고해상도의 대용량 이미지 파일을 다루어야 했습니다. 한 작품당 여러 장의 이미지가 필요했고, 작업 과정을 보여주기 위해 단계별 이미지도 필요했습니다.
현재는 이미지 업로드 시 동기적으로 처리하고 있으나, 향후에는 다음과 같은 개선을 계획하고 있습니다:
- 비동기 이미지 처리
- AWS Lambda와 SQS를 활용한 이미지 처리
- 썸네일 생성과 AI 태그 생성을 비동기로 처리
- 이미지 최적화
- WebP 포맷 지원
- 해상도별 이미지 변형 자동 생성
API 설계 전략
프론트엔드 팀과의 원활한 협업을 위해 API 설계에 많은 공을 들였습니다.
리소스 중심의 URL 구조
// 작품 관련 엔드포인트
GET /api/v1/artworks/{artworkId} // 작품 상세 조회
POST /api/v1/artworks/{firebaseUid} // 새 작품 등록
PUT /api/v1/artworks/{artworkId} // 작품 정보 수정
DELETE /api/v1/artworks/{artworkId} // 작품 삭제
// 포트폴리오 관련 엔드포인트
GET /api/v1/artists/public?folioId={folioId} // 공개 포트폴리오 조회
GET /api/v1/artists/{firebaseUid}/profile // 작가 프로필 조회
상태 코드의 명확한 활용
@DeleteMapping("/{artworkId}")
public ResponseEntity<?> deleteArtist(@PathVariable("artworkId") String artworkId) {
try {
artworkService.deleteArtwork(artworkId);
return ResponseEntity.ok().build(); // 200: 성공
} catch (NoSuchElementException e) {
return ResponseEntity.notFound().build(); // 404: 리소스 없음
} catch (UnauthorizedException e) {
return ResponseEntity.status(403).build(); // 403: 권한 없음
} catch (Exception e) {
return ResponseEntity.internalServerError().build(); // 500: 서버 오류
}
}
데이터 조회 최적화
현재 이미지 URL 조회는 다음과 같이 구현되어 있습니다:
public List<String> fetchPreviewImageUrlsByImageIds(List<String> imageIds) {
return imageIds.stream()
.map(artworkImageRepository::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.map(ArtworkImage::getPreviewImageUri)
.collect(Collectors.toList());
}
이 코드는 N+1 문제가 발생할 수 있어, 다음과 같은 최적화를 계획하고 있습니다:
@Cacheable(value = "previewUrls", key = "#imageId")
public String fetchPreviewImageUrlByImageId(String imageId) {
return artworkImageRepository.findById(imageId)
.map(ArtworkImage::getPreviewImageUri)
.orElse(null);
}
문서화
API 문서화를 위해 Insomnia를 활용했습니다. Insomnia를 통해 API 스펙을 명세하고, 이를 프론트엔드 개발자와 공유하며 실시간으로 피드백을 주고받을 수 있었습니다.
배운 점과 앞으로의 과제
이번 프로젝트의 아키텍처를 설계하면서 가장 크게 배운 점은 "변화에 유연한 설계의 중요성"입니다. 창업 프로젝트의 특성상 기획과 디자인이 자주 변경되었고, 이에 빠르게 대응하기 위해서는 처음부터 유연한 구조를 갖추는 것이 중요했습니다.
특히 MongoDB를 선택한 것은 이러한 맥락에서 매우 중요한 결정이었습니다. 웹툰, 일러스트, 웹소설 등 다양한 창작자들의 특성을 고려할 때, 정형화된 관계형 데이터베이스보다는 유연한 문서 기반 데이터베이스가 더 적합했고, 실제로 새로운 기능을 추가하거나 기존 기능을 수정할 때 큰 도움이 되었습니다.
앞으로 해결해야 할 기술적 과제들은 다음과 같습니다:
- ElasticSearch 도입을 통한 검색 고도화
- 태그, 키워드 기반의 효율적인 작품 검색
- 연관 작품 추천 시스템 구현
- 성능 최적화
- 대용량 이미지 로딩 최적화
- MongoDB 인덱싱 전략 개선
- 캐싱 시스템 도입
- 데이터 분석 파이프라인 구축
- 사용자 행동 분석을 위한 로깅 시스템
- 작품 조회 통계 및 인사이트 제공
- 보안 강화
- 작품 저작권 보호 시스템
- 사용자 인증/인가 체계 개선
마치며
백엔드 아키텍처 설계는 단순히 기술 스택을 선택하는 것 이상의 의미가 있었습니다. 실제 서비스를 운영하면서 마주치게 될 다양한 상황들을 미리 고려하고, 확장 가능하고 유연한 구조를 만드는 것이 중요했습니다. 특히 웹 콘텐츠 창작자들을 위한 특화된 플랫폼이라는 특성을 고려할 때, 기술적인 선택들이 실제 사용자 경험과 직결된다는 점을 항상 염두에 두어야 했습니다.
다음 글에서는 이러한 아키텍처를 기반으로 구현한 작가 프로필 시스템에 대해 자세히 다루도록 하겠습니다.
'[프로젝트] > [백엔드] LAITEU' 카테고리의 다른 글
EC2에서 Multi-Version API 서버 구현하기 (1) | 2024.11.20 |
---|---|
작품 대표 이미지 저장 (1) | 2024.11.20 |
API 응답 형식 표준화: 즐겨찾기 서비스 개선 (0) | 2024.11.20 |
AWS 개발 환경 비용 최적화하기 (0) | 2024.11.20 |
[Spring] LAITEU 개발기 (2) - MongoDB를 활용한 데이터 모델링 (2) | 2024.11.20 |