다이내믹 스크래핑과 전월세대출 이동제의 실행
토스뱅크는 기술로 은행산업의 변화를 만들어 내기 위해 끊임없는 도전을 이어왔어요. 출범 단 2년 반 만에 1,000만 은행이 될 수 있었던 것은 이러한 기술혁신을 위한 시도가 있었기 때문인데요.지난 8월 9일 토스뱅크의 첫번째 테크 밋업 Tech.nic에서 토스뱅크가 다른 은행과는 다른 기술 환경을 갖추기까지의 과정, 마주한 문제를 임팩트 있게 해결해 나간 노하우를 소개했습니다.
토스뱅크만의 일하는 방식과 성공 방정식, 그리고 앞으로의 도전 방향성까지 "은행을 바꾸는 은행"이 되기 위한 토스뱅크만의 과정이 궁금하시다면 앞으로 Tech.nic에서 소개드릴 이야기에 집중해주세요!
토스뱅크 전월세대출과 이동제를 개발하면서 마주했던 어려움과 해결방법, 그리고 토스뱅크만의 새로운 운영툴을 소개드릴게요.
우선 시작하기에 앞서, 전월세대출을 간단히 설명드릴게요. 전월세대출은 한국주택금융공사(HF), 서울보증보험(SGI) 등과 같은 대외기관과 연계하여 주택을 빌릴 때 필요한 자금을 대출받는 상품입니다.
전월세대출 흐름
먼저, 전월세대출을 실행하기까지의 흐름을 설명드릴게요. 고객은 가심사 결과를 확인하기 위해 체크리스트에 계약정보, 연소득, 혼인 여부, 주택 보유 수 등을 입력합니다. 모든 절차는 고객에게 ‘약관 및 개인(신용)정보 처리 동의서’ 를 받고 진행됩니다.
이후, 대외기관과 신용정보원에서 받아온 정보 등을 토대로 산출된 가심사 결과를 확인하고
산출된 상품 중 신청하고자 하는 상품을 선택하고, 서류 제출 과정을 거치게 돼요. 사용자의 인증이 필요한 경우는 인증을 완료한 후 진행됩니다.
이후, 이미지로 제출해야 하는 서류 제출까지 완료하면 운영 검토 및 처리 과정에 진입합니다.
운영 검토 및 처리 과정에는 제출된 서류 확인 및 추가 제출 요청 / 주택 수 확인 / 목적물 조사 / 소득 산출 / 보증 신청 등과 같은 많은 업무들이 있는데요.
운영 프로세스가 완료되면 고객은 본심사를 거쳐 마침내 전월세대출을 실행할 수 있게 됩니다.
하지만, 운영 검토 및 처리 과정에는 몇가지 어려움이 있었는데요. 모든 과정을 사람이 판단해야 하는 것이었어요.
운영 수기 프로세스
전월세대출은 고객의 소득구분(근로/사업/기타/무소득)에 따라 수집해야 하는 서류가 달라지고, 추가로 받아야 하는 서류가 있을 수 있어요. 예를 들면, 근로자의 경우는 재직증명서, 사업자의 경우는 연말정산용 사업소득 원천징수영수증과 같은 서류를 추가로 받아야 할 수도 있죠.
소득 산출 또한 수집된 서류에 따라 케이스가 나뉘고, 1명의 고객은 N개 이상의 소득 산출 케이스에 해당할 수 있어요.
많은 케이스를 사람이 수기로 판단하는 과정이 비효율적이고, 휴먼 에러가 발생할 가능성이 크다고 생각했어요. 필요한 자료를 수집하고, 고객에게 추가자료를 요청 후 소득을 산출하는 과정을 최대한 자동화하기로 했습니다.
자동화와 검증
자동화하기 위해 가장 먼저 한 일은 발생 가능한 케이스를 정의하고, 테스트 코드를 작성하는 것이었어요.
추가 제출이 필요한 서류를 판단하는 것과 소득 산출 케이스는 40가지가 넘었는데요. 비즈니스 로직을 구현하고, 검증을 위해 약 270개의 테스트 케이스를 작성했습니다.
하지만 전월세대출을 테스트하는 토스뱅크 팀원은 모두 직장인으로 분류되는데요 어떻게 사업자, 프리랜서, 기혼, 미혼, 무소득 청년과 같은 케이스를 검증할 수 있었을까요?
다이내믹 스크래핑
다이내믹 스크래핑을 활용했습니다. 다이내믹 스크래핑이란, PO/PM/QA/DA/디자이너/FE/BE 등 누구나 사업자, 프리랜서, 기혼, 미혼, 청년이 될 수 있도록 만든 기능이에요.
여신 트라이브에서 독립적으로 구현한 어드민으로 커스텀한 서류를 만들고 테스트 환경에서 적용할 서류를 선택할 수 있어요. 예를 들면, 사업자가 아니지만 사업자등록증명A(1개의 사업장), 사업자등록증명B(2개의 사업장)과 같은 서류를 정의해놓고 선택할 수 있죠.
적용하고 나면 고객이 앱에서 접하는 프로세스 그대로 재현할 수 있는데요.
몇가지 경우를 소개해드릴게요. 첫번째, 미혼인 사람이 기혼으로 서류를 커스텀한 경우에요. 기혼이라면 배우자의 주택 수 또는 서류 수집을 위해 동의가 필요한데요. 이런 과정을 재현할 수 있어요.
두번째, 근로소득자가 아닌데 근로소득을 선택했거나, 사업소득자가 아닌데 사업소득을 선택한 경우 서류 수집 과정에서 부결 처리되는 과정이에요.
이렇게 다이내믹 스크래핑으로 정상, 실패 그리고 소득 관련 데이터가 음수로 오는 경우, 데이터가 비정상적으로 들어오는 경우까지 검증할 수 있었어요.
구현
다이내믹 스크래핑까지 포함된 서류 수집 과정을 어떻게 구현할 수 있을까요? if-else로 구현해볼게요.
fun main(args: Array<String>) {
if (고객_근로소득) {
서류_스크래핑()
return 응답
}
if (고객_사업소득) {
서류_스크래핑()
return 응답
}
if (고객_기타소득) {
서류_스크래핑()
return 응답
}
if (고객_무소득) {
서류_스크래핑()
return 응답
}
if (배우자_근로소득) {
서류_스크래핑()
return 응답
}
if (배우자_사업소득) {
서류_스크래핑()
return 응답
}
if (배우자_기타소득) {
서류_스크래핑()
return 응답
}
if (배우자_무소득) {
서류_스크래핑()
return 응답
}
if (더미_스크래핑) {
서류_스크래핑()
return 응답
}
}
fun main(args: Array<String>) {
if (고객_근로소득) {
서류_스크래핑()
추가제출필요서류판단_근로소득()
return 응답
}
if (고객_사업소득) {
서류_스크래핑()
추가제출필요서류판단_사업소득()
return 응답
}
if (고객_기타소득) {
서류_스크래핑()
추가제출필요서류판단_기타소득()
return 응답
}
if (고객_무소득) {
서류_스크래핑()
추가제출필요서류판단_무소득()
return 응답
}
if (배우자_근로소득) {
서류_스크래핑()
추가제출필요서류판단_근로소득()
return 응답
}
if (배우자_사업소득) {
서류_스크래핑()
추가제출필요서류판단_사업소득()
return 응답
}
if (배우자_기타소득) {
서류_스크래핑()
추가제출필요서류판단_기타소득()
return 응답
}
if (배우자_무소득) {
서류_스크래핑()
추가제출필요서류판단_무소득()
return 응답
}
if (더미_스크래핑) {
서류_스크래핑()
추가제출필요서류판단_더미스크래핑()
return 응답
}
}
기능은 의도한대로 동작할거에요. 하지만, 새로운 케이스가 추가되거나 특정 케이스의 수정이 필요한 경우 유지보수가 힘들고, 코드가 굉장히 길어질 것 같아요.
하나의 구현체가 근로소득과 같은 자신의 케이스만을 담당하고, 알맞은 구현체로 라우팅 할 수 있다면 좋을 것 같은데요. 그래서 상속이 아닌 composition과 전략 패턴을 활용했습니다.
resolver로 해당 유저가 다이내믹 스크래핑, 근로소득, 사업소득 등 어떤 Service의 scraping() 로직을 타야하는지 라우팅하고, 알맞은 Service가 동작할 수 있도록 했어요. 새로운 케이스가 추가되면 그것에 맞는 새로운 Service 구현체만 만들면 되도록 만들었죠.
이렇게 수집된 서류들의 확인은 어디서 하도록 했을까요?
자동 운영 프로세스 LUMOS
전월세대출의 운영 검토 및 처리 과정에서 다루어야 하는 많은 업무들이 있다고 설명드렸었는데요. 여신을 밝힌다는 의미인 저희만의 새로운 어드민 LUMOS(Loan Universal Management Operating System)을 소개드릴게요.
LUMOS에서 처리하는 몇가지 화면을 같이 볼까요?
첫번째, 고객이 제출한 서류를 확인하는 화면이에요. 왼쪽 사이드 바에는 제출했거나 제출이 필요한 서류들이 동적으로 노출되고 체크 표시로 제출 여부 구분할 수 있어요. 운영 담당자가 확인했는지 여부를 직접 체크할 수도 있습니다.
두번째는 고객이 제출한 서류로 산출된 소득을 확인하는 화면이에요. 소득 산출 또한 모두 자동화했어요.
세번째, 알림 또는 LMS 발송 여부와 발송된 내용을 확인할 수 있는 화면이에요. 재발송도 가능하고, 필요하면 직접 작성한 내용을 고객에게 발송할 수도 있어요.
마지막, 이미지 조회입니다. 고객이 제출한 이미지를 조회할 수 있고, 개인정보를 마스킹하거나 필요없는 이미지를 삭제할 수도 있어요. 추가로 고객이 임대차계약서 카테고리에 계약금 영수증을 제출한 경우, 계약금 영수증을 계약금 영수증 카테고리로 이동시키는 기능도 제공해요.
이것의 성과로 내부와 외부에서 좋은 의견을 들을 수 있었습니다.
지금까지 전월세대출의 실행 전까지의 과정을 소개해드렸는데요, 다음으로 전월세대출 이동제의 실행 과정을 설명드릴게요.
전월세대출 이동제 실행
전월세대출 실행일에 대출이 잘 실행되는 것은 너무나도 중요한 일이에요.
하지만, 전월세대출 이동제 즉, 대환 과정에는 많은 종류의 자금 이체가 포함되어 있는데요. 다양한 자금 이체가 모두 성공하고 대환이 완료되면 좋겠지만, 실패할 가능성이 있기 때문에 롤백과 재시도 전략을 수립해야 했어요.
몇가지 케이스를 생각해볼까요?
- 보증료와 인지세를 납부하고, 상환총액 이체까지 성공해 대환이 완료되는 경우
- 보증료와 인지세를 납부하고, 상환총액을 보유기관에 이체, 고객이 부분상환을 해 돌려줘야 하는 돈이 있는 경우
첫번째 케이스를 조금 더 자세히 볼게요.
- 보증료 이체를 시도하다가 실패
- 보증료 이체는 성공했지만, 인지세를 이체하다가 실패
- 보증료와 인지세 이체는 성공했지만, 상환총액(대출잔금 + 중도상환수수료 + 대출이자 + 기타비용 등)을 이체하다가 실패
위와 같은 경우가 발생할 수 있을 것 같아요.
- 고객이 보유기관 대출을 부분 상환해서 고객에게 입금해줘야 하는 경우
- 고객이 증액을 해서 임대인에게 추가 이체해야하는 경우
조금 더 디테일하게 들어가보면 이런 경우까지 있을 수 있습니다. 고객이 부분상환도 하고, 증액도 했다면 2가지가 섞여있을 수도 있죠.
대환 과정에는 보유기관에 기존 대출이 잘 상환됐는지 확인해야 하는 절차도 포함되어 있는데요. 이때 보유기관이 정상 응답을 주지 않는 경우도 있을 수 있어요. 이 경우 보류 상태에 빠지게 됩니다.
고객의 액션으로 시작되는 전월세대출 이동제의 실행 파이프라인을 어떻게 재시도 가능하게 만들 수 있을까요?
구현
위에서 했던 것처럼 if-else로 한번 만들어볼게요.
fun 이체_요청() {
val 보증료이체여부 = false
if (보증료이체여부.not()) {
val result보증료이체 = 보증료이체()
if (result보증료이체.success) {
val result인지세이체 = 인지세이체()
if (result인지세이체.success) {
val result가상계좌이체 = 가상계좌이체()
if (result가상계좌이체) {
} else {
val result인지세반환 = 인지세반환()
if (result인지세반환.success) {
val result보증료반환 = 보증료반환()
if (result보증료반환.success) {
실행대기()
}
}
}
} else {
val result보증료반환 = 보증료반환()
if (result보증료반환.success) {
실행대기()
}
}
} else {
실행대기()
}
}
}
기능은 의도한대로 동작하게 만들 수는 있을 것 같아요. 하지만, 아까보다 훨씬 더 depth가 깊어지고, 수정이 필요하다면 side-effect가 발생할 가능성이 큰 것 같아요. 가독성도 떨어지죠.
보증료 이체, 인지세 이체, 상환총액 이체를 독립적인 구현체로 만들고, 저희가 정의한 순서대로 처리되도록 해서 다음 단계로 이어질 수 있도록 만들면 좋을 것 같은데요.
상태를 정의해보자면 위와 같은 여러 상태들이 있을 것 같아요.
이 상태들을 나름의 순서를 만들어 나열해보면 위와 같이 나열할 수 있는데요.
결론적으로 복잡한 if-else를 제거하고, 상태들이 순서대로 잘 진행되도록 구현하기 위해 전략 패턴과 상태 패턴 그리고 사가 패턴을 적용했습니다.
전월세대출의 대환 프로세스를 총괄하는 오케스트레이터가 있고, 오케스트레이터는 현재 대환 진행 상태가 자금 이체를 필요로 한다면 독립적으로 존재하는 송금 서버에게 자금 이체를 요청합니다.
송금 서버는 자금 이체 결과를 카프카 메시지로 발행하고, 그 메시지를 다시 오케스트레이터가 consume 해요.
consume한 오케스트레이터는 현재 대환 프로세스가 어디까지 진행되었는지 알고 있기 때문에 진행해야 하는 다음 상태를 처리하게 됩니다.
대환 프로세스에는 자금 이체가 필요하지 않은 단계도 있는데요. 자금 이체가 필요하지 않은 경우는 Orchestrator가 다른 마이크로 서버와의 통신 등과 같은 처리로 다음 단계로 진행해요.
위에서 상환 결과 조회 응답이 정상이 아니라면 보류 상태에 빠진다고 설명드렸었는데요. 이 경우 고객의 재시도로 대환 프로세스가 다시 시작될 수도 있을텐데요. 저희는 굳이 고객이 직접 재시도 하지 않고도 대환 프로세스가 완료될 수 있도록 배치로 보류 상태에 빠진 대상의 상환 결과 조회를 재시도하고, 정상 응답이 오면 대환 프로세스가 완료될 수 있도록 했어요.
오케스트레이터의 롤백과 재시도가 가능한 프로세스로 고객의 전월세대출 이동제 실행은 성공적으로 완료됩니다.
기술적 노력과 성과
고객의 대출을 안정성 있게 실행하고, 유지보수 및 변경에 빠르게 대응하기 위해 여러가지 디자인 패턴을 적용했습니다. 또한, 전월세대출 이외 상품은 소득 산출과 같은 로직이 계정계에 존재했기 때문에 수집된 서류는 모두 계정계로 전달되어 채널계와 계정계에 모두 존재했었는데요. 계정계 모놀리틱으로부터 독립하고 관리 포인트를 줄이기 위해 서류와 소득 관련된 로직을 마이크로 서비스로 구현했습니다. 결과적으로, 내외부적으로 긍정적인 반응을 얻을 수 있었습니다.
지금까지 토스뱅크에서 전월세대출과 대환 프로세스를 만들면서 겪은 어려움을 해결한 방법을 공유드렸는데요.
저희는 지금도 이전으로 돌아갈 수 없는 새로운 은행 경험을 제공하기 위해 노력하고 있습니다. 앞으로도 지켜봐주시고, 저희와 함께 은행의 혁신을 만들어 나갈 분들을 기다립니다. 감사합니다.