Introduction
이번 포스팅에서는 개인 프로젝트에서 MSA를 도입하게 된 배경과 서비스 모듈 분리에 대한 고민과 결정 사항을 기록하려고 합니다.
도입 배경
소규모 프로젝트로 정산 시스템을 구현하고자 했기 때문에, 처음에는 모놀리식 구조의 레이어드 아키텍처를 사용해 프로젝트를 설계했습니다. 기존의 구조는 하나의 어플리케이션 안에 서비스의 모든 로직이 포함된 전통적인 소프트웨어 개발 방식으로, 초기 개발 단계에서 빠르게 기능을 구현하기에 적합합니다. 실제로 지금까지는 해당 구조로 개발을 진행하며 프로젝트를 진행해왔습니다.
하지만 개인 프로젝트에서도 더 나은 확장성과 안정성을 갖춘 설계를 학습하는 것이 필요하다고 판단했습니다. 특히, 정산 시스템은 데이터가 점점 증가하고 기능이 복잡해지는 특성이 있기 때문에, 기존의 모놀리식 구조로는 한계가 있다고 생각했습니다. 이러한 이유로 MSA 로 전환하여 서비스의 독립성을 확보하고, 운영 효율성을 높이는 방식으로 학습하고자 했습니다.
- 서비스 간 독립성 확보 : MSA 구조는 각 서비스가 독립적으로 동작하기 때문에, 개별 서비스의 기능 추가나 수정, 장애가 다른 서비스에 영향에 미치지 않도록 할 수 있습니다.
- 실무에 가까운 설계 학습 : 많은 실제 시스템에서 MSA를 채택하고 있어서 실무에서 요구되는 아키텍처 설계 역량을 강화할 수 있을 것입니다.
서비스 모듈 분리
먼저, 서비스를 어떻게 분리할지 고민해보았습니다. 서비스 모듈 분리에는 도메인 주도 설계의 개념을 적용한다는 것을 알고 있습니다. 하지만 개인 프로젝트의 제한된 리소스와 시간으로 인해 도메인 주도 설계를 완벽히 적용하는 것은 어렵다고 판단했습니다. 그래서 실제 비즈니스 프로세스 흐름을 중심으로 도메인과 기능을 구분하는데 집중했습니다.
개인 프로젝트에서 MSA를 완벽히 구현하는 것은 쉽지 않지만, 이를 설계하고 구축하는 과정을 직접 경험하며 학습하는 것만으로도 의미가 있다고 생각했습니다. 프로젝트에서는 비즈니스 주요 흐름을 기반으로 도메인과 기능을 식별하여 아래와 같이 서비스 모듈을 분리했습니다.
서비스 모듈 구성
1. common 모듈
- 프로젝트에서 전역으로 공유하는 코드 및 구성 요소를 관리
- 공통 설정 파일, 전역 예외처리 등으로 중복 코드와 설정을 줄일 수 있습니다.
2. user-service 모듈
- 사용자와 관련된 모든 도메인 로직을 관리
- 로그인, 로그아웃, 회원가입 등
3. streaming-service 모듈
- 동영상 스트리밍과 관련된 도메인을 담당
- 동영상 재생 내역 관리, 광고 등록 및 관리 등
4. revenue-service 모듈
- 정산과 통계와 관련된 모든 로직을 처리
- 일별 정산, 통계 생성, 배치 작업 등
- 통계 데이터를 기반으로 정산을 진행하기 때문에, 데이터의 일관성과 무결성 유지를 위해 정산과 통계를 같은 모듈에 포함시켰습니다.
서비스간의 의존성을 최소화 하자
1. 데이터베이스 의존성 최소화
레이어드 아키텍처로 구현된 모놀리식 구조에서는 서비스 내 구현체 간의 결합도가 높아집니다. 특히, JPA를 사용할 경우 @JoinColumn을 사용하여 테이블 간 의존성이 강한 구조를 가지게 됩니다.
이러한 결합도는 개별 서비스의 독립성을 낮출 수 있습니다. @JoinColumn을 사용하여 직접적인 Foreign Key 제약 조건을 설정하는 대신, Foreign Key 값만 저장하는 방식으로 테이블 간의 결합도를 낮췄습니다.
2. 데이터 의존성 최소화
// Feign Client를 사용해 Streaming Service의 데이터를 조회
@FeignClient(name = "streamingServiceClient", url = "http://localhost:9003/api/streaming")
public interface StreamingServiceClient {
@GetMapping("/videos/all")
List<VideoDto> getAllVideos();
@GetMapping("/videos/lastId")
List<VideoDto> getVideosAfterId(@RequestParam("lastId") long lastId, @RequestParam("pageSize") int pageSize);
}
각 서비스는 독립적으로 자체 데이터베이스를 가지고 있어, 서비스 간 데이터를 직접적으로 공유하지 않습니다. 하지만 특정 서비스가 다른 서비스의 데이터에 접근하는 경우, API 호출을 통해 데이터를 가져오는 방식으로 설계했습니다.
다른 서비스의 데이터를 자주 요청하면 네트워크 I/O 비용이 증가할 수도 있겠지만, 데이터베이스 의존성을 최소화함으로써 DB 독립성을 유지하고, 서비스 간의 확장성과 장애 격리를 확보하는 것이 더 중요한 목표라고 판단했습니다.
3. DTO를 사용한 데이터 전달
서비스 간 의존성을 줄이고, 데이터를 일관성 있게 처리할 수 있도록 DTO를 활용했습니다. 서비스 간 데이터를 교환할 때, 다른 서비스의 엔티티를 직접 사용하는 대신 DTO를 정의하여 사용했습니다.
스트리밍 서비스의 동영상 엔티티를 정산 서비스에서 사용할 때 해당 엔티티를 그대로 복사하여 사용하면 DB 관리가 복잡해질 수 있습니다. 그렇기 때문에 동영상 엔티티의 필요한 정보만 담은 DTO를 정의하고, API를 통해 데이터를 주고 받는 방식으로 결합도를 낮췄습니다.
마무리
이번 프로젝트를 통해 MSA를 설계하고 구축하는 과정을 직접 경험하며, 단순히 아키텍처 적용하는 것을 넘어 서비스 간 의존성 최소화, 데이터 무결성 확보, 확장성과 안정성 보장의 중요성을 학습할 수 있었습니다.
개인 프로젝트에서 완벽한 MSA를 구현하는 것은 현실적으로 어렵지만, 실제 비즈니스 프로세스 기반으로 한 설계와 분리, 문제 해결 방식을 적용하며 많은 것을 배울 수 있었습니다.
'프로젝트' 카테고리의 다른 글
[트러블 슈팅] Redis에서 List<Dto> 역직렬화 문제 해결 과정 (1) | 2024.11.15 |
---|---|
[성능 최적화] 300만 건의 배치 작업을 성능 개선해보자 (82.35% 개선) (0) | 2024.11.14 |