안녕하세요. 주문서비스팀 김헌기입니다.
회사에서는 Darion으로 불리고 있습니다.
이번에는 엔터프라이즈 환경에서의 레거시 서비스 구조개선에 대한 현실적인 이야기를 적어보려고 합니다.
MSA에 대해 고민하고 계시거나 관심이 있는 분들께 도움이 되었으면 합니다.
1.주문 구조개선은 왜 필요했는가 ...
먼저 주문 구조개선을 왜 해야 했는지에 대해 기존 레거시 상황에 대한 이해가 필요합니다.
위의 그림을 보면
고객이 주문하면 각 주문인입 채널이 있는데 채널계에서 시작하여
핵심 비니즈니스 로직이 있는 기간계를 거쳐
대외서비스를 연계하는 대외계까지 데이터가 흘러가는 구조를 볼 수 있습니다.
채널계 -> 기간계 -> 대외계 중에
여기서 중점적으로 봐야 하는 부분이 가운데에 있는 기간계인데요.
기간계 시스템의 구조적인 특성을 이야기 해보면
1) 업무 도메인은 단일 svn 프로젝트로 형상관리 (고객, 상품, 방송, 주문, 물류, 정산)
Controller-Process-Entity-DAO의 레이어에 대해서 이야기하면
클라이언트의 요청을 검증하고 핵심 로직을 호출하고 응답을 제공하는 Controller 레이어
핵심 로직이 있는 Process 레이어
이기종 DB 트랙잭션을 관리하는 Entity 레이어
DB 접근을 하는 DAO레이어로 연결되어 있고
각 레이이어 안에 고객, 상품, 방송, 주문, 물류, 정산 업무 도메인이 있고
Java API로 서로를 호출하고 있고 SQL JOIN으로 데이터를 연동하고 있습니다.
그중 하나의 업무 도메인에서 장애가 발생하면 바로 다른 업무 도메인으로 장애 전파가 되어
서비스에 영향이 발생할 수 있습니다.
2) 단일 svn 프로젝트 소스를 여러 개의 채널연계 서비스에 배포
주문 인입채널의 용도에 맞는 각각의 채널연계 서비스가 있는데 단일 svn 프로젝트 소스로 배포되고 있어
소스 관리하기는 용이하나 주요 소스가 변경될 때 다른 채널 연계서버들도 전체 배포를 해야 하기에
배포에 대해 자유롭지 못한 상황입니다.
결론적으로 각각의 채널연계서비스에 대해 독립적인 배포방식이 아니라서
유관부서와 항상 비효율적인 소통의 비용이 발생하게 됩니다.
소통이 안 되면 기다려야 하는 것이지요.
3)각각의 채널연계 서비스는 단일 DB에 연결되어 있습니다.
단일 DB에 문제가 발생하면 모든 채널연계 서비스에 영향이 발생하게 됩니다.
가장 무서운 부분이기도 합니다.
2.주문 구조개선의 전략은 ...
주문서비스에 대해 장애 발생 시 장애 격리, 배포 시 배포 의존성 제거를 목적으로 구조개선하는 전략을 수립했습니다.
전략을 이야기하기에 앞서 주문 업무 도메인이 다른 업무 도메인과 어떤 관계로 연결되었는지 이해가 필요합니다.
주문 업무 도메인은 고객이 상품을 구매하기 위해 주문한 트랜잭션 이력입니다.
그래서 주문 데이터가 생성되려면
먼저 고객 상품 또는 상품이 편성된 방송 데이터가 필요하고요.
이러한 메타 데이터 기반으로 고객이 주문한 시점에 주문 데이터 생성되게 됩니다.
고객은 자산을 가지고 있으며 주문 시 결제로 활용할 수 있습니다.
주문 데이터는 생성만 되고 그냥 끝나는 것이 아니라
주문접수 - 입금확인 - 출하지시 - 출고완료 - 배송중 - 배송완료 상태를 가지고 있고
물류 - 정산 - BI와 함께 바라보고 상태 변경을 하고 있기에
연관 관계가 매우 높은 데이터라 이야기할 수 있습니다.
참고로 주문 업무 도메인은 이커머스 업무 도메인 중에 복잡도가 가장 높기 때문에
이커머스의 꽃이라 이야기를 많이들 하고 구조개선의 난이도 또한 매우 높습니다.
앞서 언급한 바와 같이 데이터 생성 순서를 고려하여
주문서비스만의 독립적인 구조개선 전략을 다음과 같이 수립하였습니다.
먼저 분리되어야 하는 고객 상품 방송 업무 도메인과의 분리를 위해
1) 새로운 주문 업무 영역을 클라우드로 정하고 이주하기 시작했습니다.
2) 결합도가 높은 기존 레가시 서비스에서는 Sidecar 레이어를 구성하여
Java API로 연동했던 부분은 RESTful API로 변경하여 연동하기 시작했습니다.
이 의미는 단일 서비스에서 단일 DB커넥션을 사용하던 것을 각각의 서비스의 각각의 DB커넥션으로 사용한다는 의미입니다.
3) 기술스택도 변경했는데요. 기존에 java8 + spring3.x대의 기술스택을 java11 + springboot2 변경하였습니다.
새로운 전략 기반으로 일하는 방식도 변하기 시작했습니다.
3.주문 구조개선 액티비티들 ...
개발자가 소스코드를 작성하고 단위테스트 종단테스트를 거쳐 빌드를 하고 개발 테스트 운영 환경으로 배포하고
배포된 것을 모니터링하면서 운영하고 이 모든 것들에 대해 자동화를 시도하여 개발자의 일하는 방식의 변화를 꾀하였습니다.
일하는 방식의 변화를 위해 다음의 주요 액티비티를 실행하였습니다.
1) 배포에 독립적인 DevOps 환경 구축
기존 사용하던 배포 파이프라인은 불편한 점이 많았습니다.
일주일에 한 번 모여서 배포할 때 각각의 배포 파일에 대해서 단계별로 승인받고 같은 시간에 배포 버튼을 함께 눌러야 했습니다.
지금은 당연한 이야기지만 코드를 컨테이너 이미지로 만들고
배포하기 위해 bitbucket으로 형상관리를 하고 bamboo로 배포 계획을 작성하여
AWS EKS Cluster에 배포하였습니다.
차이점도 느낄 수 있었는데 기존 레거시에서는 배포하고 문제가 생기면 에러 상태로 기동 되는데
현재의 환경에서는 배포하고 문제가 생기면 기동되지 않고
이전 컨테이너 이미지 버전으로 유지되어 새로운 버전이 배포되지 않는 것 빼고는
장애 대응에 용이하였습니다.
2) 환경변수 주입식의 Cloud Native Appication 구조 설계
어느 클라우드 환경에서라도 설치 될 수 있게 구조를 만들었습니다.
주요 컨셉은 환경에 관련된 것은 전부 외부에서 주입된다입니다.
Oracle DB 계정 정보 또한 암호화되어 외부에서 주입하여 기동됩니다.
레거시 구조와 신) 주문서비스 구조를 나누어 한시적으로 사용 할 것과 새롭게 확장할 것을 확실하게 구분하여
모호함으로 인한 잘못된 코드 작성을 미연에 방지하려고 노력하였습니다.
애플리케이션 아키텍처도 새로 정의하였습니다.
DB연동 API연동 Event 연동이 가능한 웹애플리케이션을 개발할 수 있는 구조를 만들려고 했고요.
Java Spring 개발자면 누구나 개발할 수 있는 구조를 채택했습니다.
(네이밍에 욕심이 있었지만, 혼자만 멋있게 짓는 네이밍은 독만 되더라고요.)
새로운 아키텍처 기반으로 디자인 패턴도 완성 중인데요.
패턴별 고유의 단일 책임을 부여하고 확장은 하되 자유롭게 변형은 하지 못하게 제약하는 개념을 적용했습니다.
지금도 10년 넘은 레거시 패턴을 유지보수하고 있는데요.
처음 설계는 훌륭했다고 생각하는데 이를 확장하는 사람들이 개념을 이해하지 못해서
전혀 다른 돌연변이가 탄생했습니다.
누구나 쉽게 역할을 이해하고 실행하고 확장 할 수 있게 흐름을 연결하였습니다.
우리만의 그라운드가 아니라 모두가 함께 참여 할 수 있는 그라운드를 지속적으로 진화시키고 싶은 욕심이 있습니다.
3) 클라이언트에 친화적인 도메인 업무 API 서비스 개발
또 다시 기존 레거시 방식과 비교하지 않을 수 없는데요.
기존 레거시에서는 서비스 제공은 ESB를 통해 제공되고 엑셀에 인터페이스 정의서를 작성하여 관리하였습니다.
코드하고 연결점이 없어서 최신화에 대한 어려움이 있었고
채널 기준의 ESB 인터페이스가 대량으로 추가되는 구조라 관리도 어려웠고
클라이언트에서 참고하기도 너무 복잡했습니다.
이러한 부분을 주문 업무 도메인 행위로 영역을 구분지어 주문, 반품, 교환 업무 도메인의 서비스로 올렸으며
각각의 서비스는 서로 참조하여 호출하지를 않고 완전히 격리되어 있습니다.
격리된 서비스로 만들어보니 장애격리와 배포 의존성 제거를 위해서는 필수라고 다시금 깨닫게 되네요.
4.시행착오 ...
1) DB Row Lock 현상
기존에는 단일 서비스에서 하나의 DB 커넥션으로 트랙잭션을 처리하였는데
서비스가 분리되면서 서로 다른 서비스에서 각각의 DB 커넥션으로 같은 Row를 바라보게 되는 경우 시
이전 서비스의 트랜잭션이 종료하지 않으면 대기가 걸려서 에러가 발생하였습니다.
또한 조회 건에 대해서는 트랙잭션이 완료되지 않으면 다른 DB 커넥션으로 조회하는 건에 대해서는
데이터가 조회되지 않은 현상도 발생하였습니다.
해결책으로
1)트랜잭션 처리를 제거하고 예외 발생시 보상 처리한다.
2)하나의 트랜잭션으로 처리하기 위해 하나의 서비스로 코드를 합친다.
3) 서비스를 요청할 때 식별자만 보내지 않고 이전 DB 커넥션에서 데이터를 조회하여 함께 보낸다.
1)번 같은 경우는 생각보다 작업량이나 영향도가 커서
주로 2) 3) 위주로 작업하여 문제를 해결하였습니다.
2)Sidecar + Remote 레이어의 단점
하나의 서비스에서 Java API로 호출하는 것을 RESTful API로 전환 함에 따른 통신 부하 증가로 다음과 같은 정책을 수립하였습니다.
1) 다른 업무 도메인에 대해서는 무조건 RESTful API로 호출한다.
2) 주문 업무 도메인 내에서는 최대한 RESTful API 호출을 지양한다.
3) 쉽게 변하지 않는 상수는 서비스로 가지고 내려온다.
2)의 경우는 주문, 반품, 교환에 대해서 서로 상호 간 호출을 금지하고 업무 도메인 경계를 구분하는 것으로 설계하였습니다.
3)API 테스트의 어려움
기존 ESB가 API Docs가 아닌 인터페이스 기준으로 정의서를 보고 테스트를 해야 했기에 조회는 가능했으나 입력 수정 생성에 대한 테스트가 어려워 기존 ESB 클라이언트의 도움을 받기 위해 개발 환경 ESB에서 라우팅하여 테스트하게 처리 하였습니다.
항상 그렇지만 테스트 데이터를 확보하는 데 어려움이 있었고 기존 레거시 환경에서 단위 테스트 적용에 대한 제약사항도 많았기 때문에
개발환경에서 ESB에서 라우팅하여 테스트하는 것은 개인적으로 신의 한 수였다고 생각합니다.
5.앞으로 남은 것들 ...
1) 분리된 주문서비스 API로 전환 및 신)주문서비스 API 제공
신) 주문서비스 API 서비스에는 2가지 패턴이 있는데
A. 분리된 주문서비스 API
B. 신) 주문서비스 API
로 구분하고 있으며
A.의 경우 기준에 레거시에서 주문서비스를 분리한 것이기 때문에
ESB에서 APIGateway로 변경 된 것 뿐이지 동일한 서비스를 제공하기에
주문 인입 채널 클라이언트에서 전환하는 작업을 진행하면 됩니다.
B.의 경우 완전히 새로운 API서비스를 API Docs로 제공하지만
굳이 잘돌아가는 A.를 사용하지 않고 B.로 전환하지 않을 것입니다.
저희의 전환 전략은 주문서비스가 신규 공통처리 API는 서비스 제공자가 제공하는 API를 사용해야 하기에
클라언트에 친화적은 API 신규 개발이 추가되면 자연적으로 전환되리라 생각합니다.
2) 주문DB 복제 및 주문DB 분리
주문서비스만 분리된다고 장애에 대한 격리가 완벽히 되지는 않습니다.
각 업무 도메인과 함께 바라보는 DB에서 분리되어져야 합니다.
현재 레거시 구조가 개발자에게는 빌런 같은 구조인데요.
몇년 후에 더 좋아진 미래를 위해서는 분리하는 방향성이 맞고
미리 미리 준비해야 된다고 생각합니다.
유관부서와의 충분한 소통과 협업이 중요하다고 생각하고
근래의 보기 드문 사례라고 이야기할 수 있을 것 같습니다.
처음에는 주문DB 복제만하고 주문DB 분리에 대한 자신감을 키워 나가려고 했는데
한치 앞도 못보는 세상에서는 언제 주문DB를 분리해야 되는 상황이 올지도 모른다는 생각이 드네요.
6.마무리 ...
개발자라면 누구나 최신의 기술과 완벽한 기술을 사용하고 싶어합니다.
저도 당연히 그런 맘은 굴뚝 같습니다.
하지만 규모가 있는 운영에서의 구조개선은
전체의 패턴을 작게 실행해보고 방향성을 확인하면서 민첩하게 대응해야지 결과를 만들 수 있습니다.
매번 새로운 퍼즐을 찾아서 맞추려 하지 말고
목표 이미지를 그리고 반복적으로 코드를 작성하고 배포하면서 구체화를 시켜보세요.
단계가 거듭 될수록 자신감도 배가 됩니다.
읽어주셔서 감사합니다.
김헌기 Darion | 디지털서비스본부 > 홈쇼핑서비스부문 > 주문서비스팀
Backend Office 주문 / 결제 / 마케팅프로모션 / 연계 / 혁신과제 서비스를 담당하고 있습니다.
동료들과 함께 기술 문화 만들기와 낯선 기술자와의 인연의 시작에 관심이 많습니다.
'APP' 카테고리의 다른 글
우리동네GS BFF 구현기 Step 1 - 도입 배경과 설계 (1) | 2023.07.28 |
---|---|
Flutter App 실시간 CDN 이미지 변경 상태 적용 방안 (1) | 2023.06.20 |
엔터프라이즈 MSA 이야기 2탄-RateLimit 적용으로 시스템장애 예방하기 (0) | 2023.06.20 |
GS SHOP App의 메모리 확보 (2) | 2023.05.12 |
안드로이드 포그라운드 서비스를 활용한 메모리부족으로 앱 종료되는 현상 개선 (4) | 2023.04.17 |