새로운 프로젝트를 시작해서 마감 기한에 쫓기다 보면, 가장 먼저 실행 파일인 main.py 하나에 모든 코드를 때려 넣는 유혹에 빠지기 쉽다. 데이터 규격 검증부터 AI 모델 로딩, 이미지 전처리, 벡터 데이터베이스 검색, 그리고 API 통신까지 전부 한 곳에 모아두면 당장 테스트하기엔 편할지 모른다. 하지만 코드가 수백 줄을 넘어가기 시작하면 어디서 에러가 났는지 찾기도 힘들고, 기능 하나를 고치려다 전체 서버가 뻗어버리는 대참사가 발생한다.
무엇보다 파이썬은 여러 파일이 서로를 꼬리 물기 하듯 참조하게 되면 순환 참조라는 악명 높은 에러를 뿜어낸다. 이를 원천 차단하고 유지보수하기 좋은 단단한 백엔드를 만들기 위해, 모든 로직이 뭉쳐있던 main.py를 역할에 맞게 4개의 파일로 쪼개는 리팩토링을 진행했다. 핵심은 의존성이 한 방향으로만 예쁘게 흐르도록 설계하는 것이다.
models/schemas.py
가장 먼저 분리한 것은 클라이언트와 주고받을 데이터의 형태를 정의하는 스키마 파일이다. Pydantic을 활용해 응답해야 할 JSON 규격(예: FraudResponse)을 이곳에 묶어둔다. 이 파일은 다른 어떤 내부 로직 파일도 쳐다보지 않는 가장 독립적인 구역이다. 덕분에 다른 파일들에서 응답 형태가 필요할 때마다 맘 편히 이 파일을 가져다 쓸 수 있다.
core/vision.py
메모리를 엄청 잡아먹는 이미지 처리와 OCR 모델 추론 로직을 한 곳으로 격리했다. 무거운 ONNX AI 모델을 이 파일 최상단에서 전역으로 딱 한 번만 띄워두어 메모리 파편화를 막는다. 그리고 클라이언트가 보낸 사진을 8GB 램 환경에 맞게 압축하고, 글자를 뽑아내는 복잡한 전처리 과정들을 extract_text_from_buffer라는 하나의 함수로 포장해 두었다.
core/rag.py
이미지에서 뽑아낸 단서들을 바탕으로 과거의 사기 수법을 검색하는 벡터 데이터베이스 로직도 따로 빼냈다. 경량화된 임베딩 모델과 인메모리 ChromaDB 세팅을 전역으로 묶어두고, find_similar_case라는 검색 전용 함수를 만들었다. 이제 검색이 필요하면 이 함수에 텍스트를 던져주기만 하면, 복잡한 내부 연산은 숨긴 채 가장 비슷한 과거 판례 딱 1건만 안전하게 꺼내어 돌려준다.
main.py
복잡한 비즈니스 로직들을 core 폴더로 전부 내보내고 나니, 서버의 진입점인 main.py는 본연의 '컨트롤러' 역할에만 집중할 수 있게 되었다. 외부 클라이언트의 요청을 받아들이고, 램이 터지지 않게 동시성 제어 락(Semaphore)으로 트래픽을 통제하는 문지기 역할만 한다. 데이터가 무사히 들어오면 앞서 만들어둔 vision.py와 rag.py의 함수들을 차례대로 호출해 조립하고, 그 결과를 schemas.py의 규격에 담아 반환하기만 하면 된다. 코드가 물 흐르듯 읽히고 확장이 압도적으로 쉬워졌다.