02. 레이어 맹신주의의 함정
0. Controller/Service/Model로 레이어를 분리하고, 각각의 레이어들이 담당할 일에 대해서는 나름 명확하게 정의하고 개발을 하려했다. 간략하게 정리하자면 Controller는 RestAPI 인터페이스를 제공하고, Service는 비즈니스 로직을 담당하며. Model은 Service에서 필요로 하는 DB와의 데이터 교환을 처리하게 할려고 했다.
.....의도는 좋았다. 의도는.
1. SqlAlchemy를 이용해 DB를 사용하는 방식은 ORM방식과 Core(raw sql) 방식으로 나뉠수 있다. 이건 다른 DB 인터페이스에도 다 존재하는 것인데 둘중 뭐가 좋냐에 대해서는 예송논쟁 수준의 토론이 벌어진다.
양쪽다 일장일단이 있겠지만, 나는 가급적이면 ORM을 사용하는게 좋다 보는 쪽이다. 일단 가장 크게 SQL Injection 공격을 손쉽게 방어할수 있고, DB를 직접적으로 들여다 보지 않아도 테이블 구조를 알수 있다는 장점이 있다. 물론 단점도 여러가지 있겠지만 간단한 CRUD기능을 구현하는데에도 ORM 방식은 큰 도움이 된다.
......그러니까, 간단할때에는 도움이 된다고.
2. 실제 비즈니스 로직을 구현하다 보면, 단일 테이블의 데이터를 조회할때도 있지만 여러 테이블을 join하여 데이터를 구하는 경우도 많다. 특히 테이블간의 연관관계를 잘 만들어 놓으면 굳이 여러번의 쿼리를 보낼필요 없이 한번의 쿼리로 데이터를 가져올수 있다.
여튼간 ORM을 쓸때도 이런 쿼리를 필요로 하는경우가 많은데, 내 초기 코드에는 데이터 조회를 하는 코드를 전부 ORM 객체 안에 밀어 넣겠다는 야심찬 삽질(....)로 Model 레이어의 ORM 객체를 만드는 코드들은 전부 아래와 같은 식이었다.
class A_Model(......):
__tablename__ = "a_tbl"
id = Column(Integer, nullable=True)
name = Column(String(50), nullable=False)
def find_by_id(....):
....
def find_by_name(...):
....
이런 구조에서, ORM을 이용해 세 테이블을 Join하는 쿼리를 만든다고 하면 그 코드는 아래와 같이 만들어진다.
import B_Model
import C_Model
query = db_session.query(A_Model, B_Model, C_Model)
query = query.join(
B_Model,
and_(A_Model.id == B_Model.A_id)
.join(
C_Model,
and_(A_Model.id == C_Model.A_id)
)
3. 문제는 B_Model 클래스 안에서 B_Model과 C_Model을 join하고 C_Model 클래스 안에서 A_Model과 C_Model을 join하고 있다면 어떤일이 발생할까.
그래서, 결국 모든 데이터 조회 코드를 빼내기 위해 만든게 models 패키지 내에 handler 패키지인것이다. 필요한 모델들을 모아서 DB와의 CRUD를 처리하는 코드를 전부 빼냈다.
그리고 만들다 보니 handler안에 있는 코드들은 굳이 클래스를 만들지 않게 되더라. 다른데는 거의 다 클래스로 감싸는 코드로 작성했는데 말이다.
그렇다. 여기까지 오고 나니 현타와 함께 내가 바보짓을 했다는것을 깨달은 것이다...