데이터를 서빙하는 API가 하나 있는데 해당 API는 Java Spring으로 개발되어 있었고 현재 우리가 속해있는 데이터 팀에서 관리하고 있는 상태였습니다. Spring으로 만들었던 이유는 빠르게 만들어야 했고 인력도 부족했기에 일단 그렇게 만들었고 현재는 팀원들의 기술 스택이 Python을 주로 사용하고 있고 Java에 대한 경험이 많이 없기 때문에 추후 기능 추가나 이런 부분에서 유지 보수가 쉽게 하기 위해 Python으로 마이그레이션 하는 것이 필요했고 그 일을 제가 맡게 되어서 그 과정에 대해 포스팅해보려고 합니다.
현재 Serving API 파악
일단 해당 Serving API의 기능을 파악해야 했는데 API 주소, Response 스키마 등 API를 호출하는 쪽에서 필요한 부분들에 대해서는 영향이 없게 해야 했고 기능적인 부분만 python 코드로 구현해야 했습니다. 전체적으로 데이터 서빙을 위해 DW에서 주기적으로 DM을 생성하여 DB에 있는 데이터를 가져와서 서빙하고 있기 때문에 API는 DB에 쿼리를 통해 데이터를 가져와서 그대로 보여주는 기능이기에 API의 비즈니스 로직적인 부분에 대해서 복잡한 부분은 없었습니다.
그렇다고 API에서 아예 비즈니스 로직이 필요 없는 건 아니었고 해당 로직을 이해하기 위해서는 가져오는 데이터의 구조와 제공하는 서비스에 대해 파악이 어느 정도 필요했기 때문에 쿼리 부분과 실제 데이터를 서빙하여 제공되는 서비스의 부분을 함께 살펴봐야 했습니다.
그리고 어떻게 보면 잘 돌아가고 있는 API를 굳이 바꿀 필요는 없지만 성능 같은 부분에 대해서 좀 더 개선사항이 있으면 마이그레이션 하는 중에 개선하는 것이 좋기 때문에 그러한 개선 포인트가 있을지 고민도 해보았습니다.
Java로 만들어진 API를 파악하기 위해서는 Java의 기본 문법 정도와 Spring의 프로젝트 구조를 이해해야 했기 때문에 Java는 예전 전공 수업 때 들어서 그래도 이해는 할 수 있었지만 Spring의 구조에 대해서는 아예 몰랐기 때문에 그 부분에 대해서 좀 더 리서치가 필요했습니다. 그나마 복잡한 로직을 수행하지는 않았고 개발자분과 의논하면서 했기 때문에 이 부분에 대해서는 다행히 큰 어려움 없이 API를 파악하게 되었습니다.
API 개발을 위한 프레임워크 선정
사실 이 부분은 사전에 FastAPI를 생각하고 있었고 선정한 이유에 대해서는 NodeJS와 Go랑 대등할 정도로 빠르다고 알려져 있고 Serving API는 CRUD의 역할보다는 Read 즉, 읽어와서 API를 호출하는 입장에서 편하게 사용할 수 있게 만드는 느낌이 강하기 때문에 웹 개발 프레임워크로 유명한 Django나 Flask보다는 굳이 필요하지 않은 기능보다 최소한의 기능을 가지고 있고 쉽고 빠른 FastAPI를 선정하게 되었습니다.
Python Project Architecture
FastAPI가 생각보다 공식 문서가 잘 되어 있어서 처음에는 데이터 서빙의 기능만 잘 수행하면 되겠지 하고 하나의 파일에서 필요한 데이터를 가져와서 Json 형태로 반환해주는 구조로 간단하게 API를 구축하였고 해당 API에 대해서 팀원들과 리뷰를 했고 Spring으로 API를 개발하신 팀장님의 조언을 받았는데 Python도 OOP가 가능한 걸로 알고 있는데 OOP로 구축한 듯한 느낌이 없었다고 하셨고 Java도 OOP 언어로써 Spring을 예로 Dto, Service, Dependency Injection 등 이러한 구조와 이런 식의 프로그래밍 기술은 추후 개발자로서의 실력에도 도움이 될 것이라고 판단되어 프로젝트를 OOP 방식으로 하기 위해 공식 문서와 소프트웨어 아키텍처 등을 참고하며 프로젝트 구조를 다시 구성하게 되었습니다.
해당 과정에서 성능적인 부분에 대해 기존 API는 한 번의 호출에 여러 번의 DB 접근이 있는 상태이고 해당 DB 접근에 대해 동기식 접근이 이루어졌습니다.. 물론 동기식으로 해도 충분히 빠른 성능을 보여주기 때문에 굳이 변경할 필요는 없지만 그래도 비동기로 바꾸고 싶어서 해당 부분에 대해 수정을 하였고 아래와 같은 구조를 가지게 되었습니다.
전체적인 흐름은 User가 API를 파라미터와 함께 Request를 하면 RouterParameter 부분에서 해당 파라미터 정보를 받게 되고 Router에서 해당 파라미터를 가지고 비즈니스 로직을 수행하는 Service를 호출하게 됩니다. Service는 비즈니스 로직을 수행하기 위해 DB에 접근하는 Repository를 호출하게 되고 Repository는 SqlAlchemy를 통해 DB에 쿼리를 날려서 해당 결과를 받아서 Model 객체로 Service에게 전달해주고 Service는 해당 Model 객체를 가지고 비즈니스 로직을 수행한 후 나온 결과를 Schema 객체로 만들어서 Router에게 넘겨줍니다. 그리고 Router는 해당 Schema 객체를 jsonable_encoder()를 통해 Json형태로 변환하여 User에게 Response를 날리게 됩니다.
- Router : API의 URL과 파라미터에 대한 정보를 받는 부분. Client와 직접 소통.
- RouterParameter : 파라미터를 처리하기 위한 부분.
- jsonable_encoder() : FastAPI에서 제공하는 Schema 객체를 json으로 변환함.
- Service : 비즈니스 로직을 처리하는 부분. Router와 의존 관계에 위치.
- Schema : Pydactic을 통해 만든 반환 데이터에 대한 스키마를 가지고 있는 부분. 데이터 검증이 가능.
- Repository : DB와 접근하는 부분. Service와 의존 관계에 위치.
- Model : SqlAlchemy를 통해 만든 DB 데이터에 대한 ORM.
- SqlAlchemy : DB에 직접 쿼리를 날리는 부분. 라이브러리로 직접 제공됨.
FastAPI는 이러한 설계에 대한 문서는 많이 없어서 참고할 만한 게 많이 없어서 최대한 오픈 소스들을 참조하면서 만들었습니다. 이러한 아키텍처는 어떻게 보면 개발자들마다 제 각각이라서 뭔가 저만의 아키텍처가 필요하지 않을까 해서 여러 개의 아키텍처들을 참조했고 위의 아키텍처가 무조건 정답이라고는 할 수 없지만 점점 경험하면서 조금씩 바뀌지 않을까 생각합니다.
'Backend' 카테고리의 다른 글
Locust 동작 방식 (1) | 2023.04.15 |
---|---|
EFK 스택을 통한 Airflow 로그 모니터링 구축 (0) | 2023.03.17 |
Fluentd 기본 개념 (1) | 2023.03.14 |