Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

ActiveRecord 모델은 언제 '너무 뚱뚱해진' 걸까?

Rails 관련 블로그나 책을 읽거나 컨퍼런스 발표를 보다 보면, 모델을 더 가볍게(skinny) 만드는 방법에 대한 이야기를 자주 접하게 됩니다.

이런 기법들은 분명 훌륭합니다. 모델은 언젠가 감당하기 어려울 만큼 커지고 복잡해지기 때문입니다. 하지만 정말로 모델의 역할을 영속성(persistence), 연관관계(association), 유효성 검증(validation)만으로 제한하고 싶으신가요? 대체 어느 정도의 로직까지 ActiveRecord 모델에 남겨두어야 하는 걸까요?

가벼워야 하지만, 너무 가볍게는 말고

Active Record는 모델이 데이터베이스 스키마와 밀접하게 일치할 때 가장 잘 작동하도록 설계된 패턴입니다. 그렇다면 이것이 실제로 무엇을 의미할까요?

  • ActiveRecord 모델의 관점에서 수행되는 작업을 하는 코드라면, 모델 안에 들어갈 수 있습니다.

  • 여러 테이블/객체에 걸쳐 동작하면서 명확한 소유자가 없는 코드라면, 서비스 객체(Service Object)에 넣는 것이 좋습니다.

  • 속성(attribute)과 유사한 것들, 예를 들어 연관관계나 다른 속성으로부터 계산되는 속성이라면 ActiveRecord 모델에 두어야 합니다.

  • 여러 모델의 저장이나 업데이트를 한 번에 조율해야 하는 로직이 있다면, ActiveModel 폼 객체(Form Object)에 넣어야 합니다.

  • 뷰(view)에서 모델을 표시하거나 포맷하는 용도의 코드라면, Rails 헬퍼(helper)나 프레젠터(Presenter)에 넣어야 합니다.

이러한 가이드라인을 벗어난 부분에 대해서는, 지나치게 커진 어떤 클래스를 리팩토링할 때 적용하던 것과 같은 원칙을 그대로 사용하면 됩니다. 다만 일반적으로 ActiveRecord 모델에 어느 정도의 로직을 남겨두는 것에 대해 죄책감을 느낄 필요는 없습니다. 애초에 모델은 로직을 담도록 만들어진 것이니까요!

결국 Rails는 Active Record 패턴을 선택했으므로, 모델에 어느 정도의 로직이 존재하는 것은 자연스러운 일입니다. 그렇다면 Rails는 왜 더 깔끔한 다른 패턴 대신 이 패턴을 선택한 걸까요?

루비라면 어떻게 했을까? (What Would Ruby Do?)

Martin Fowler의 『엔터프라이즈 애플리케이션 아키텍처 패턴(Patterns of Enterprise Application Architecture)』에서 Active Record 패턴은 Row Data Gateway와 Data Mapper 사이의 중간 지점에 위치합니다. Row Data Gateway는 테이블의 행(row)을 감싸는 단순한 객체 지향 래퍼로, 가능한 모든 모델 중 가장 가벼운 형태라고 할 수 있습니다. Data Mapper는 Active Record보다 복잡하며, 주로 비즈니스 로직만 담고 있는 객체와 영속성 로직만 담고 있는 객체 사이를 변환하는 데 사용됩니다.

따라서 루비의 맥락에서 보면, Active Record는 확실히 올바른 기본(default) 패턴입니다.

왜 그렇게 말할 수 있을까요?

루비는 프로그래머를 행복하게 만들기 위해 설계된 언어입니다. 깔끔함과 편리함 사이에서 트레이드오프를 강요받을 때, 루비는 거의 항상 편리함을 선택합니다.

실제로 Array 클래스의 공개 API에는 100개가 넘는 메서드가 있습니다. 서로 별칭(alias) 관계인 메서드도 엄청나게 많죠. 어떤 개발자들은 .find보다 .detect를 쓰는 것을 선호하기 때문입니다.

그런 맥락에서 보면, Rails가 더 유연하거나 객체 지향적으로 더 순수한 패턴 대신 편리한 패턴을 기본값으로 선택한 것은 충분히 이해됩니다. 그것이 바로 '루비다운 방식(The Ruby Way)'이니까요. 그리고 저는 그 점이 정말 마음에 듭니다.

필요해지는 시점에는 언제든 더 유연한 구조로 리팩토링할 수 있습니다. 하지만 다시 생각해보면… 결국 YAGNI(You Aren't Gonna Need It, '어차피 필요 없을 거다')겠죠.