Trade-Off에 관한 고찰

프로필

2024년 12월 21일

151 1

Trade-Off에 대하여

Trade-Off란 단순하게 설명하면 무엇을 포기하고 무엇을 얻는지를 결정하는 과정을 뜻한다.

소프트웨어 개발에서의 Trade-Off

(1) 성능 vs 유지보수성
- 고성능을 위해 복잡한 알고리즘이나 최적화를 적용하면 코드가 읽기 어려워지고 유지보수 비용이 증가함.
- 단순하고 명료한 코드는 성능에서 손해를 볼 수 있지만 장기적으로 안정성과 확장성이 높음.

(2) 시간 vs 품질
- 빠른 출시가 필요한 경우에는 기술 부채를 감수하더라도 빠른 개발을 선택해야 함.
- 품질을 우선시하는 경우에는 출시가 지연되거나 자원이 더 많이 소모될 수 있음.

(3) 모놀리식 vs 마이크로서비스
- 모놀리식은 단순하고 초기 구축이 빠르지만 확장성과 유연성에 한계가 존재함.
- 마이크로서비스는 확장성이 뛰어나지만 초기 복잡도가 높고 관리 비용이 큼.

FastAPI의 Trade-Off

나는 오랫동안 Flask로 개발을 진행하다가 이번 블로그 프로젝트에서 Django를 스킵하고 FastAPI로 넘어오게 되었다. Django가 아닌 FastAPI를 선택한 이유는 다음과 같다.
1. Flask와 구조, 특징의 유사성
2. Django의 엄격한 규칙이 싫어서
3. Django보다 빠른 초기 개발 속도

결과적으로 모든 이유가 양날의 검이자 Trade-Off 그 자체였다. 생각보다 Flask와 많이 유사한 구조덕에 그리 어렵지 않게 MVP를 구현할 수 있었지만, Flask에서 겪었던 Circular Import(순환참조)를 또 다시 겪게 되었다.

Circular Import(순환참조)란?
- 모듈 A가 B를 참조하고 B가 다시 A를 참조하는 경우

내가 실제로 겪었던 케이스를 첨부하자면

Traceback (most recent call last):
  File "/Users/cliche/Desktop/project/backend/app.py", line 4, in <module>
    from utils import main_logger as logger, schedular, check_data
  File "/Users/cliche/Desktop/project/backend/utils.py", line 4, in <module>
    from models import AppleMusicChart, YouTubeChart, SpotifyChart, db
  File "/Users/cliche/Desktop/project/backend/models.py", line 1, in <module>
    from app import db
  File "/Users/cliche/Desktop/project/backend/app.py", line 4, in <module>
    from utils import main_logger as logger, scheduler, check_data
ImportError: cannot import name 'main_logger' from partially initialized module 'utils' (most likely due to a circular import)
  1. app.py에서 utils.py를 참조하고
  2. utils.py에서 models.py를 참조하고
  3. models.py에서 app.py를 참조하고
  4. 다시 app.py에서 utils.py를 참조한다

지금봐도 거지같은 구조였고, MSA의 개념을 얄팍하게 이해한 채로 빠른 개발만을 위해 구조를 구축한 결과였다.
심지어 이 프로젝트는 SQLAlchemy가 아닌, Flask-SQLAlchemy를 사용했으므로 Flask와의 컨텍스트 의존성 같은 강결합 문제도 존재했기에 결국에는 대대적으로 리팩토링을 한 번 진행하고, 지금은 아예 프레임워크를 FastAPI로 전환한 상태이다.

이야기가 조금 새지만 저걸 해결한 방법도 정말 무식했는데, 3번의 db 선언부를 다시 models.py로 옮기고, 4번의 로거를 app.py의 시작부에서 import 하지 않고, 필요한 부분에서 lazy import를 사용함으로써 해결했는데, 이건 본질적인 해결 방법도 아니었고, 성능 오버헤드가 문제가 될 정도로 규모가 큰 프로젝트는 아니지만 일단 코드가 더러워진다는 건 확실히 보였다.

FastAPI와 Flask의 특징이 유사하다는 것은 Django 같은 풀스택 프레임워크가 아닌 마이크로 프레임워크로써, 유연성과 자유도를 제공한다는 점인데 그 말은 반대로 말하면 Django는 엄격한 규칙이 존재하는 대신 적절한 구조를 짜주지만, FastAPI의 경우는 내가 구조를 설계하고 모든 책임을 내가 져야한다는 뜻이다.

내가 모놀리식이 아닌 MSA 구조를 채용하게 된 것은 멘토님의 가르침이 컸는데, 처음에는 이유를 몰랐다.

그냥 한 파일안에 모든 코드가 있는게 더 편한 거 아닌가? 이거 나누는 것도 일인데? 귀찮은데?

왜냐면 그 당시에는 코드가 길어봤자 모든 기능을 다 해서 300~400줄 안짝이었고, 뭐 하나를 나눌 때마다 오류가 발생해서 불만이 더 컸었던 거 같다.

하지만 점점 프로젝트가 커지고, 다양한 라이브러리들의 다채로운 기능들을 사용하면서 코드 길이가 말도 안되게 길어지기 시작했다. 이 블로그 같은 경우에도 모듈화 진행 전에는 MVP만 구현한 거였는데도 2000줄이 넘을 정도 였으니까. 실제로 위의 예시로 든 크롤러 프로젝트를 진행할 때 MSA의 이점을 가장 크게 체감했는데, 현재는 아니지만 위의 코드를 보면 알 수 있듯이 각 사이트 별로 크롤링 코드가 나눠져 있었고, 유틸들은 따로 유틸에 보관을 하면서 어디 한군데가 터졌을 때 수정이 너무 쉽다는 걸 깨달았다.

하지만 지금도 마찬가지로 아직 MSA나 디자인 패턴에 대한 이해도가 너무 부족해서, 공부를 진행하려고 찾아보던 와중에 갑자기 그런 생각이 들었다.

FastAPI의 장점인 자유도와 유연성이 디자인 패턴이나 보일러 플레이트를 도입하여 정형화를 한다면 Django와 차별화되는 장점이 희석되어 버리는 건 아닐까

이 생각을 여러 AI들(o1, Claude, Perplexity)에게 똑같이 질문해봤다.
거의 다 대답이 비슷비슷했는데

FastAPI 개발자들 사이에서 자주 논의되는 Trade-Off이며, 가장 중요한 것은 균형점을 찾는 것이다.

그리고 가장 와닿는 대답은 내 정신적 지주 Claude의

제가 보기엔 이것은 "자유도의 상실"이라기보다는 "자유도의 체계화"에 가깝습니다.
디자인 패턴을 적용하더라도 선택의 자유는 여전히 존재합니다, 또한 패턴의 유연한 적용도 가능합니다.
코드 구조화의 자유도도 여전히 존재합니다.
즉, 디자인 패턴이나 보일러 플레이트의 도입은 "제약"이라기보다는 "가이드라인"에 가깝습니다. 이는 마치 개발자가 코딩 컨벤션을 따르는 것과 비슷합니다. 컨벤션이 있다고 해서 프로그래밍의 자유도가 없어지는 것이 아니라, 오히려 더 체계적이고 깔끔한 코드를 작성할 수 있게 돕는 것처럼요.
결국 중요한 것은 균형점을 찾는 것입니다. 프로젝트의 규모와 팀의 상황에 따라
- 작은 프로젝트 : FastAPI의 유연성을 최대한 활용하고, 최소한의 패턴만 적용
- 중간 규모 : 필요한 부분에만 선택적으로 패턴 적용
- 대규모 프로젝트 : 체계적인 패턴 적용
이런 식으로 유연하게 적용하는 것이 바람직해 보입니다. 어떻게 생각하시나요?

사실 실무자도 아닌 AI가 찌끄린 소리에 불과할지도 모르지만 이 말이 가장 와닿았고, 내가 너무 쓸데없는 고민을 하고 있었다는 생각이 듦과 동시에, 지금도 그렇겠지만 앞으로 몇년만 더 지나면 주니어 개발자가 설 자리는 없을 거 같다는 생각이 들었다.

#Trade-Off #FastAPI #디자인 패턴

댓글 개

댓글을 작성하려면 로그인이 필요합니다

2024-12-23 20:59
레포좀 보여주시면 안돼요?