포스팅이 순서대로 올라오지 않는 이유
넘버링은 글을 작성하기 시작할때 부여하고,
포스팅은 글을 끝맺을 때 하기 때문입니다.
프로젝트를 많이 해보지 않아서 그럴까? 항상 프로젝트 설계는 고민이 된다.
얼마전에 fastapi 프로젝트 구조를 깃허브 레포에서 본적이 있다.
https://github.com/zhanymkanov/fastapi-best-practices
이걸 참고 하려고 했지만, 이 구조는 유지보수 관점에서도, 테스트 관점에서도 어려운점이 있다.
모듈이 구현체를 직접 참조하기 때문에 ISP원칙이나 DIP원칙에 위배되는데,
이러면 기능을 추가할때 참조하는 모듈(상위 모듈)을 손봐야 할 수도 있고, 단위 테스트할때 목 객체를 만들기도 까다롭다.
(몽키 패치 방식을 써야 하는데, 타입힌트도 안먹혀서 IDE의 도움도 못 받고 테스트 코드가 기능의 내부 동작을 알아야 한다.)
tiangolo가 만든 예시 프로젝트도 모듈간 결합도가 있어서 단위 테스트를 하기가 엄청 까다롭다.
테스트 코드를 보면 뭔가 단위 테스트를 한 것 같지만 의존성을 분리하지 않고 그대로 한다.
A -> B -> C로 되어 있다면 A 테스트 할때 B, C도 같이 범위에 들어가고,
B 테스트 할때 C도 범위에 들어가는 느낌..
그래서 좀 찾다가.. 아래의 다른 예시를 보게 되었다.
라우터 부터 서비스, 레포지토리 모두를 클래스를 만들자는 것 이였다.
https://github.com/zhanymkanov/fastapi-best-practices/issues/4
한국인 개발자의 이런 예시도 볼 수 있었다.
여기선 라우터를 제외한 다른 모듈을 클래스로 하고, 다른 DI 프레임워크를 도입했는데 아주 인상깊었다.
작년인데 SQLModel을 도입한 것도 볼 수 있었는데, 그것도 신기했다.
하지만 repository부분은 차라리 처음 말했던 best-practice의 예제가 더 좋아보였다.
https://github.com/jujumilk3/fastapi-clean-architecture
몇 시간에 고민 끝에 그냥 개발을 시작했고, 하다보니 서비스 레이어의 함수들에서 모두 세션을 의존하고 있다는 사실을 알았다.
코드가 지저분해지니 서비스 클래스로 묶게 되었는데, 이는 라우터도 마찬가지였다.
서비스 객체든 세션이든 주입받아야 하는데, 이럴거면 그냥 클래스로 묶는게 더 나아보였다.
결국, 라우터 / 서비스 모두 클래스로 묶게 되었다~
아래 분의 예시 처럼 되는듯.
https://github.com/zhanymkanov/fastapi-best-practices/issues/4
기존의 fastapi-utils 패키지는 못쓰게 되었다. sqlalchemy와 충돌이 발생하는 것이 문제였다. (리드미에는 이런 말도 없었는데..)
이 패키지는 아마 버려진것 같다.
그래서 이를 계승한 fastapi-restful을 사용했다.
https://fastapi-restful.netlify.app/
현재는 아래와 같이 구성했다.
각 모듈의 생명주기는 dependency에서 관리한다. (여기에 있는 걸로 프레임워크가 알아서 주입해준다.)
아래 그래프에서 화살표는 의존관계(알고있다)를 표현한 것이고, 타입을 알고있다면 t, 객체를 알고 있다면 실선으로 표시했다. (객체는 모르는 경우엔 점선)
사실 이론적으로는 route service repository 모두가 분리가 가능하다. (fastapi-restful에서 Annotated지원을 안한다..)
아무튼 의존관계를 줄여서 결합도를 낮췄다. (dependency는 사실상 객체 생성자 및 주입자 역할)
설명을 안했는데 repository는 get_by_id, exists, create, update, delete 와 같은 기능을 mixin으로 한번에 만들어 재사용 이점을 높이기 위해 도입했다.
(마치 JPARepository처럼 하고 싶었다.)
이분의 프로젝트에서 영감을 얻었다.
https://github.com/jonra1993/fastapi-alembic-sqlmodel-async
허나 model 부분이 좀 문제다.
모든 곳에서 sqlmodel을 사용한다.
sqlmodel을 pydantic model로 사용하기도 하고, sql model로 사용하기도 한다.
파란색으로 표시한 것이 pydantic model로 사용하는 경우, (db에는 관심이 없고 도메인 객체의 값만 궁금함)
초록색으로 표시한 것이 sql model로 사용하는 경우다. (db에 관심이 많고 쿼리를 작성하기 위해 자주 쓰임)
게다가 비동기를 도입해서 service나 route에서 접근해선 안되는 필드들이 있다. (연관관계 필드들)
repository에서 selectin으로 연관관계를 같이 가져오면 괜찮지만, 여러 이유로 그렇지 않았다면 오류를 내뿜는다. (차라리 초기값이 None(Null)이 아닌 undefined였으면 좋겠다)
지금 당장은 방법이 생각나지는 않고 좀 코드를 작성하면서 고민해 볼 것이다.
+ 다시 생각해보면, 이렇게 사용하는 것이 sqlmodel이 바라는 바다.
+ sqlmodel은 식별자를 제외한 필드를 BaseXxxx 형태로 모델을 만들어 BaseXxxx를 상속하는 실제 테이블 객체 Xxxx를 사용하기를 원한다.
+ 그렇게 해서 재사용의 극대화를 얻을 수 있다나 뭐래나..
'프로그래밍 > 스팀 게임 퀴즈' 카테고리의 다른 글
#17 fastapi-users를 써서 OAuth2를 적용해보았다! (0) | 2024.01.18 |
---|---|
#16 Fastapi-users의 OAuth2 사용시 생기는 MissingGreenlet 오류 (0) | 2024.01.16 |
#14 프론트 엔드 구축 (0) | 2024.01.11 |
#13 비동기 SQLModel (0) | 2024.01.08 |
# 12 백엔드 코드를 비동기로 바꾸다. (0) | 2024.01.08 |