Rails 아키텍처에서 데이터와 코드 분리

Root를 지원하는 백엔드 플랫폼은 Rails 애플리케이션입니다. 65k 줄의 Ruby 애플리케이션 코드와 37 개의 엔진과 22 개의 gem에 분산 된 135,000 줄의 테스트 코드로 구성됩니다. 3 년 동안 구축해 왔으며 현재 30 명의 엔지니어로 구성된 팀이 작업 중입니다. 애플리케이션이 커짐에 따라 빠른 전송 속도를 유지하는 데있어 두 가지 기술이 중요했습니다.

많은 웹 애플리케이션과 백엔드 플랫폼이 객체 지향 언어를 사용하여 구축되지만 데이터와 코드를 분리하는 것이 객체 지향 패러다임을 사용하는 것보다 더 효과적입니다. Root의 백엔드 플랫폼에 대한 코드를 살펴보면 놀랍게도 코드에 상태가 거의 없습니다. 거의 없음. 특히 Rails 백엔드의 요청에서 실행중인 프로세스의 상태를 유지해야하는 루트에서 구현 한 단일 기능이 없습니다. 다른 회사의 대부분의 웹 플랫폼과 유사하다고 생각합니다.

Ruby의 웹 백엔드는 임시 프로세스로 간주되는 수평 확장이 가능하도록 구축되었습니다. 더 많은 처리량이 필요하십니까? 더 많은 프로세스를 실행합니다. 너무 많은 메모리를 사용하는 프로세스가 있습니까? 죽여. 이 특성은 기능을 위해 애플리케이션 수준 상태를 활용하는 잠재적 인 이점을 약화시킵니다. 모든 프로세스는 클라이언트의 관점에서 일관된 상태를 가져야하고 프로세스가 고착되지 않을 수 있기 때문에 관계형 데이터베이스는 실제 애플리케이션 상태를위한 좋은 홈이됩니다. 이로 인해 일반적으로 모든 상태가 코드가 아닌 데이터베이스에 유지됩니다. 궁극적으로 단일 프로세스로 실행되는 애플리케이션을 빌드하는 것과 방대한 수의 독립적 인 프로세스를 사용하여 실행하는 애플리케이션을 빌드하는 것에는 근본적인 차이가 있습니다. 이는 최고의 소프트웨어 아키텍처와 사용할 패러다임에 영향을 미칩니다.

프로세스의 일시적인 특성은 우리가 Root에서 사용하는 메시지 대기열 라이브러리 인 Resque의 최고급 기능입니다. 모든 작업은 새로운 프로세스에서 실행됩니다.

Resque 작업자가 작업을 예약하면 즉시 하위 프로세스를 분기합니다. 자식은 작업을 처리 한 다음 종료합니다. 자녀가 성공적으로 퇴사하면 작업자는 다른 작업을 예약하고 프로세스를 반복합니다.

객체 지향 프로그래밍 포기

대형 웹 플랫폼은 코드에서 데이터를 분리하여보다 지속 가능하게 구축 할 수 있습니다. 이것은 본질적으로 객체 지향 프로그래밍이 아닙니다. 객체 지향 프로그래밍은 해당 데이터에서 작동하는 함수와 데이터를 번들링하도록 장려합니다. Tell Do n’t Ask 원칙이이를 강조합니다.

Tell-Don’t-Ask는 객체 지향이 해당 데이터에서 작동하는 함수와 데이터를 묶는 것임을 사람들이 기억하는 데 도움이되는 원칙입니다. 객체에게 데이터를 요청하고 그 데이터에 대해 조치를 취하는 대신 객체에게 무엇을해야하는지 알려 주어야 함을 상기시킵니다. 이것은 [우리]가 데이터와 함께 이동하는 객체로 행동을 옮기도록 장려합니다.

객체 지향 프로그래밍은 종종 응용 프로그램이 핵심 도메인 엔터티로 끝나고 신 클래스로 변합니다. 이는 대부분의 비즈니스가 동일한 핵심 데이터에 의존하는 매우 많은 기능을 가지고 있기 때문에 발생합니다. 대부분의 애플리케이션에서 ‘사용자’모델이 부풀어 오른 것은 놀라운 일이 아닙니다. 구현되는 비즈니스 로직의 대부분은 사용자와 관련됩니다. 이 로직을 User 클래스에 배치한다고해서 관리가 용이 ​​해지지는 않습니다. 대신 사용자 데이터에 대한 의존성이 유일한 공통점과 함께 관련없는 비즈니스 절차의 혼합을 단일 클래스로 결합합니다. 이는 도메인의 중심 인 다른 데이터 모델에서 발생하기 쉽습니다. Root에서는 InsurancePolicy 모델을 통해 이러한 일이 발생하기 쉽습니다.

시간이 지남에 따라 애플리케이션이 커짐에 따라 특히 데이터를 읽을 때 기능 대 데이터 비율이 증가합니다. Root의 백엔드 플랫폼에있는 기능의 많은 부분이 보험 정책 데이터에 액세스해야합니다. 이러한 모든 이질적인 기능에 대한 모든 로직을 InsurancePolicy 클래스 자체에 넣는 것은 관리 할 수 ​​없습니다.

활성 레코드 패턴 회피

비즈니스 로직을 모델에 넣는 것은 액티브 레코드 패턴 정의의 문자 그대로 일부이지만이를 피합니다. PoEAA에서 Active Record는 다음과 같이 정의됩니다.

데이터베이스 테이블 또는 뷰에서 행을 래핑하고 데이터베이스 액세스를 캡슐화하며 해당 데이터에 도메인 논리를 추가하는 개체입니다.

도메인 로직 추가 부분에 유의하세요. 패턴의 중심입니다. 이 작업은 소규모 응용 프로그램에서는 잘 작동하지만 대규모 응용 프로그램에서는 다루기 어려워집니다. 대규모 애플리케이션에서는 패턴의 “도메인 논리 추가”부분을 제거하고 데이터베이스 테이블에서 행을 래핑하고 이에 대한 데이터베이스 액세스를 캡슐화하는 개체를 갖는 방법으로 ActiveRecord 라이브러리 만 사용하는 것이 더 좋습니다.

거래 스크립트

Root의 Rails 백엔드를 구축하는 데 사용하는 패턴은 PoEAA의 트랜잭션 스크립트 패턴과 가장 유사합니다.

각 절차가 프레젠테이션의 단일 요청을 처리하는 절차별로 비즈니스 논리를 구성합니다.

많은 절차에서 보험 가입과 같은 프레젠테이션의 단일 요청을 처리하지만 이러한 요청의 여러 측면을 처리하기 위해 하위 절차도 추출했습니다.

우리는 절차를 서비스 계층으로 구성하지만 객체 지향 서비스 계층이 아닙니다. 모듈의 정적 함수로 정의합니다. 예 :

이 접근 방식에서 서비스는 실제로 서비스 클래스가 아닙니다. 이는 코드를 체계적으로 유지하는 데 도움이되는 절차를위한 더 많은 네임 스페이스입니다. PolicyService.create 는 실제로 글로벌 절차입니다.

엔터프라이즈 애플리케이션 아키텍처 패턴에서 :

Transaction Script의 장점은 단순성입니다. 이러한 방식으로 로직을 구성하는 것은 로직이 적은 애플리케이션에서는 당연한 일이며 성능이나 이해 측면에서 오버 헤드가 거의 발생하지 않습니다.

그 영광은 참으로 단순하고 이해하는 데 부담이 거의 없다는 점에 있습니다.하지만 논리가 적은 애플리케이션에만 사용할 수있는 것은 아닙니다. 팩토링이 좋으면이 패턴을 확장 할 수 있습니다.

상태 비 저장 코드

인스턴스 메소드가 아닌 정적 메소드를 사용하여 서비스 클래스를 지속적으로 구현합니다. 인스턴스 메서드를 사용하면보다 유창한 인터페이스를 구축 할 수 있지만 정적 메서드는 코드 수준 상태가 함수에 포함되지 않는 몇 가지 중요한 컨텍스트를 전달합니다.

Rails의 우아한 API 디자인은 호출자의 관점에서보기 좋은 유창한 인터페이스를 만든 엔지니어들 사이에서 감사를 표했습니다. 이 코드는 다음과 같습니다.

이 코드보다 훨씬 멋지게 보입니다 :

더 유창한 인터페이스를 만드는 방식으로 서비스를 구현하고 싶을 수 있습니다. 일부 인스턴스 변수를 설정 한 다음 해당 인스턴스 변수를 사용하는 함수를 정의하는 클래스를 만들면됩니다. 효과적으로 함수는 클로저처럼되어 생성자에 설정된 상태를 닫습니다. 이러한 수업은 다음과 같습니다.

그러나 이러한 유창한 인터페이스는 상태가 어떻게 관련되는지 이해하려는 엔지니어에게 명확성을 희생해야합니다. 이 코드를 작업하는 엔지니어가 다른 곳에서 정책으로 다른 작업을 수행해야하는 경우 동일한 서비스 인스턴스를 사용해야합니까, 아니면 새로 만들 수 있습니까? 그것이 중요합니까? 또한 Fluent 인터페이스는 논리가 user 개체에 정의 된 위의 예와 같이 클래스 디자인이 좋지 않은 경우가 많습니다.

정적 메서드를 사용한 구현을 고려하면 분명합니다. 인스턴스는 관련되지 않습니다. 국가는 관련이 없습니다. 궁극적으로 클래스가 초기화 된 후 어떤 상태도 업데이트하지 않을 경우 인터페이스를보다 유창하게 만드는 것 외에는 객체 지향 방식으로 작성할 이유가 없습니다. 이러한 경우에는 유창한 인터페이스 대신 정적 함수를 사용하는 것이 좋습니다.

폐쇄를위한 클래스 접근 방식은 생성자가 설정 한 인스턴스 변수의 상태가 변경되는지 확인하여 식별 할 수 있습니다. 다른 함수에서 상태를 업데이트하는 것은 진정한 객체 지향 프로그래밍입니다. 상태가 변경되지 않으면 생성자를 제거하고 함수를 정적으로 만들 수 있습니다. 클래스는 다음과 같이 리팩토링 될 수 있습니다.

산문처럼 읽을 수있는 코드를 갖는 것이 좋지만, 유창한 인터페이스를 갖는 것보다 코드가 상태 비 저장이라는 암시 적 지식을 갖는 것이 좋습니다.

절차 적 프로그래밍이 좋습니다

대규모 Rails 백엔드를 구축하는 이상적인 방법은 대부분의 도메인 로직을 데이터베이스에서 상태를 수정하는 정적 메서드로 구현하는 것입니다. 이것이 우리가 루트에서 백엔드 플랫폼을 구축하기 위해 취한 접근 방식입니다. 코드 수준 상태가 거의 없습니다. Controllers, Jobs 또는 ActiveRecord 클래스에 비즈니스 로직을 추가하지 않습니다. ActiveRecord를 통해 데이터베이스의 데이터에 액세스 한 다음 전체 절차를 구현합니다. 비즈니스의 복잡성이 증가하더라도 코드 기반을 확장 할 수있는 매우 효과적인 단순한 아키텍처입니다. 요약 :

일반적으로 Root에서는 팀이 커지고 코드 기반이 커지고 비즈니스 도메인이 복잡 해지더라도 빠른 전달 속도를 유지할 수있는 간단한 접근 방식을 활용했습니다. 이것이 바로 이러한 접근 방식 중 하나입니다.

Root의 Rails 아키텍처 및 소프트웨어 엔지니어링에 대한 자세한 내용은 Modular Monolith 구축에 대한 게시물 및 Root Engineering 웹 사이트를 참조하세요.