2 분 소요

Spring과 FastAPI의 아키텍처 관리 방법이 또 다른것 같아 정리하면 좋을 것 같다.

Spring

Spring 프레임워크의 경우, 철학이 “객체의 생명주기를 개발자가 아니라 프레임워크가 관리한다”의 방식이다. 이른 제어권이 거꾸로 되었다고 해서 Ioc(Inversion of Control)이라고 부르고, IoC Container를 통해 객체의 생성과 소멸을 관리한다.

나아가 코드에서 @Service, @Component 등의 어노테이션을 붙인 것을 볼 수 있는데, 이는 해당 클래스를 Bean으로 관리하겠다는 것이다.

Bean이란 Spring이 관리해주는 객체로, Ioc Container에 담기는 객체가 이것이다. Bean의 장점은, 해당 객체를 단 한번만 만든 후에 계속 재사용하는 것이며(Singleton pattern이 기본 옵션, 다른 옵션으로 빈 요청시마다 새로운 인스턴스 생성하는 등의 다른 옵션도 가능은 함), 다른 곳에서 해당 객체를 필요로 하면 주입해주는 패턴을 사용한다는 것이다.

일반적으로 객체를 new 키워드를 통해 계속 만들게 되면 heap 메모리 사용량이 늘고, 호출마다 객체가 새로 생기므로 상태 관리또한 어려우며 Garbage collector가 회수해야 하는 양 또한 증가한다. 따라서, @Component, @Service, @Controller 등의 어노테이션을 통해 Bean으로 지정하면 ApplicationContext가 해당 어노테이션이 달린 클래스들을 찾아 Bean으로 등록하여 재사용하는 것이다.

이렇게 Bean을 만들어 다른 클래스에게 주입(ex: Service 클래스가 Repository 객체를 필요로 함)해서 의존성을 추가해주는 것을 DI(Dependency Injection, 의존성 주입)라고 한다.

그러나, 순환참조를 만들거나 존재하지 않는 Bean을 주입받으려 하면 에러가 나므로 주의해야 한다.

객체의 소멸 또한 마찬가지이다. 애플리케이션이 종료될 때 컨테이너가 Bean을 파괴하는 함수를 호출하며 객체가 소멸된다. 명시적으로 소멸되지 않고, 생성 또한 개발자가 Bean 등록만 해주면 프레임워크가 알아서 객체의 생성과 소멸을 관리한다.

그래서 FastAPI 코드를 처음 작성할 때, Bean도 없고, 컨테이너도 없고, 이게 정녕 DI가 맞는지 의문이었다.

FastAPI

컨테이너와 같은 전역 관리자가 객체를 관리해주는 개념이 아니라, Depends(function name) 형태의 함수를 호출하면 즉 요청이 들어올 때마다 응답으로 객체를 생성해주고 사용이 종료되면 메모리가 정리되는 그런 형태이다. 좀 더 자세하게는, 서로 다른 HTTP 요청의 경우 의존성 객체가 공유되지 않고 매번 새로 생성된다(전역 singleton pattern을 구현하고 싶으면 별도의 클래스나 이벤트를 주입해야 한다고 하던데, 이 부분은 본 프로젝트에서 다룬 부분이 아니라 짧게 기록만 하고 패스).

  • 앞 문단에서 일반적인 이야기만 한 것 같긴 하지만 두 프레임워크 차이의 핵심은 (외부에서 생성한 세션을 그대로 주입받을 수 있기도 하지만) 객체를 누가 어떻게 어떤 패턴으로 주로 관리해주느냐가 큰 것 같다.

Spring과 비교하면, 요청마다 객체를 생성하는 오버헤드가 존재하고(본 프로젝트에서 그렇게 큰 수준의 성능 저하는 아닌듯 하다) 객체의 생성과 소멸 책임이 프레임워크 보다는 개발자 쪽도 포함하는 느낌이 있다. 또한 Spring은 호출 전에 미리 컨테이너 안에서 객체를 만들어놓고 주입만 하지만, FastAPI 쪽에서는 호출 시점에 객체를 만들고 주입한다.

즉 Spring에서는 프레임워크의 설계 철학에 따라 계층 구조가 자연스러운 선택이지만, FastAPI에서는 명시적인 것을 선호함에 따라 별도의 컨테이너 등의 개념 없이 함수 호출과 기초적인 타입만을 가지고 계층구조를 구현해야 했다.

Depends(…)라는 것이, Spring의 Bean과 동일한 개념이 아니기 때문에, 객체의 의존도를 언제/어디서/얼마동안 사용해야 하는지를 기준으로 구조를 좀 더 새로운 시각으로 바라보며 코드를 짜게 된 것 같다.

카테고리:

업데이트: