디자인 패턴은 특정 맥락에서 반복적으로 발생하는 문제에 대한 검증된 해결책을 의미합니다. 이는 단순히 코드를 재사용하는 것을 넘어, 문제 해결 방식 자체를 표준화하여 개발 효율성과 코드 품질을 향상시킵니다. “문제”, “맥락”, “해결책”의 세 가지 요소로 구성되며, 이들은 경험을 통해 정형화되고 이름이 붙여져 다른 개발자들에게 재활용됩니다.
하지만 모든 패턴이 항상 좋은 것은 아니며, 때로는 직관적으로 보이지만 부정적인 결과를 초래하는 ‘안티 패턴(Anti-Patterns)’도 존재합니다. ‘God Objects’, ‘React Mixins’, ‘Rails Fat Controllers’ 등이 대표적인 안티 패턴입니다. 팀 내에서는 일반적인 디자인 패턴을 강요하기보다 프로젝트의 특성에 맞는 패턴을 육성하여 개념적 무결성을 유지하는 것이 중요합니다.
고전적인 디자인 패턴은 언어 중립적이지만, Ruby on Rails와 같은 프레임워크는 자체 생태계에 더 적합한 커뮤니티 컨벤션으로 패턴을 발전시켰습니다. 예를 들어, Service Objects는 Command Pattern의 변형이며, Form Objects는 Adapter Pattern과 유사합니다. 다음은 Rails 백엔드에서 일반적으로 사용되는 커뮤니티 디자인 패턴들입니다.
1. Serializer (직렬 변환기)
- 맥락: 데이터가 저장되거나 전송될 때 변환이 필요할 때
- 문제: API 버전이나 XML, JSON과 같은 다양한 형식에 따라 데이터 변환 방식이 달라져 일관성이 부족해질 수 있습니다.
- 해결책: 원본 데이터를 필요한 형식이나 사양으로 변환하는 객체를 생성하여 데이터와 형식/사양을 분리합니다.
- 특징:
ProductSerializer
와 같이 객체 이름을 지정하고,to_json
과 같은to_FORMAT
패턴의 공개 메서드를 사용하여 의미론적 이점을 얻습니다. 컨트롤러에서 복잡한 데이터 변환 로직을 제거하고 직렬 변환기에 위임하여 깔끔한 코드를 유지할 수 있습니다.
2. Result Objects (결과 객체)
- 맥락: 작업의 반환 값을 표준화하여 후속 조치를 취해야 할 때
- 문제: 작업 결과가 통일되지 않아
nil
체크나 임시 오류 처리와 같은 모호한 상황이 발생할 수 있습니다. - 해결책: 성공 또는 실패를 명시적으로 나타내고 관련 데이터와 메시지를 포함하는 불변(immutable) 결과 객체를 도입하여 제어 흐름을 간소화합니다.
- 특징:
Result
기본 클래스와SuccessResult
,FailureResult
서브클래스를 사용하여 결과를 구분합니다.success?
또는failure?
와 같은 명확한 불리언 체크를 제공하여 일관된 오류 처리와 테스트 용이성을 높입니다.
3. Service Objects (서비스 객체)
- 맥락: 복잡하고 긴 비즈니스 로직이나 액션을 캡슐화하고 재사용 가능한 객체에 담아야 할 때
- 문제: 모델 클래스에 넣기에는 적합하지 않지만 핵심적인 동작이나 비즈니스 로직이 특정 위치에 자연스럽게 맞지 않을 때 발생합니다. 또한 호출자로부터 동작을 분리하여 SOLID 원칙을 유지해야 합니다.
- 해결책: 전체 동작이나 행위를 나타내는 단일 호출 가능한 객체를 생성합니다.
- 특징:
CreateProduct
와 같은VerbSubstantive
패턴 또는ProductCreator
와 같은SubstantiveSubject
패턴으로 객체 이름을 지정합니다.call
이라는 단일 공개 메서드를 가지며, 이는 Result Object를 반환하여 작업의 결과를 명확히 합니다.ApplicationService
와 같은 기본 클래스를 사용하여 구현할 수 있습니다.
4. Query Objects (쿼리 객체)
- 맥락: 인덱스에서 특정 객체에 대한 쿼리 및 필터가 있을 때
- 문제: 복잡한 쿼리와 필터 로직이 REST 액션이나 API 컨트롤러를 비대하게 만들 수 있습니다.
- 해결책: 쿼리 및 필터 로직을 나타내는 객체를 생성하여 이를 분리합니다.
- 특징:
FindProducts
또는FindProductsQuery
와 같이VerbSubstantive
또는VerbSubstantiveQuery
패턴으로 객체 이름을 지정합니다.call
,execute
,fetch
와 같은 단일 공개 메서드를 사용하여 쿼리를 실행하며, 추가 매개변수로 쿼리를 확장할 수 있습니다.