미래 지향적인 웹 앱 구축: The Codest의 전문가 팀이 제공하는 인사이트
The Codest가 최첨단 기술로 확장 가능한 대화형 웹 애플리케이션을 제작하고 모든 플랫폼에서 원활한 사용자 경험을 제공하는 데 탁월한 성능을 발휘하는 방법을 알아보세요. Adobe의 전문성이 어떻게 디지털 혁신과 비즈니스를 촉진하는지 알아보세요...
많은 사람들이 Rails 프레임워크로 시작하여 루비를 배우고 있지만, 안타깝게도 이것은 이 언어를 배우는 최악의 방법입니다. 오해하지 마세요: Rails는 훌륭하며 많은 기술적 세부 사항을 다루지 않고도 웹 애플리케이션을 빠르고 효율적으로 구축할 수 있도록 도와줍니다.
많은 사람들이 Rails 프레임워크로 시작하여 루비를 배우고 있지만, 안타깝게도 이것은 이 언어를 배우는 최악의 방법입니다. 오해하지 마세요: Rails는 훌륭하며 많은 기술적 세부 사항을 다루지 않고도 웹 애플리케이션을 빠르고 효율적으로 구축할 수 있도록 도와줍니다. 일을 간단하게 만들어주는 많은 "Rails 마법"을 제공합니다. 그리고 초보 프로그래머에게는 "살아있다!"라고 말할 수 있고 모든 부분이 서로 맞고 사람들이 앱을 사용하는 것을 볼 수 있을 때가 가장 즐거운 순간이기 때문에 정말 좋습니다. 하지만 좋은 프로그래머와 평균적인 프로그래머를 구별하는 한 가지가 있습니다. 좋은 프로그래머는 자신이 사용하는 도구가 어떻게 작동하는지 이해합니다. 여기서 "도구를 이해한다"는 것은 프레임워크에서 제공하는 모든 메서드와 모듈을 아는 것이 아니라 프레임워크가 어떻게 작동하는지, "Rails 마법"이 어떻게 일어나는지 이해하는 것을 의미합니다. 그래야만 Rails로 객체를 사용하고 프로그래밍하는 데 편안함을 느낄 수 있습니다. 객체 지향 프로그래밍의 기초이자 복잡한 Rails 애플리케이션을 더 쉽게 만드는 비밀 무기는 제목에서 이미 언급된 PORO, 즉 Plain Old Ruby Object입니다.
이 이름 뒤에 숨겨진 비밀은 무엇일까요? 이 위대한 비밀 무기는 무엇일까요? 그것은 아무것도 상속하지 않는 단순한 루비 클래스입니다. 네, 바로 그거죠.
어썸포로 클래스
end
사용자 수와 기대치가 증가함에 따라 애플리케이션을 지속적으로 개발하고 새로운 기능을 추가하고 있습니다. 그러다 보면 아무리 용감한 개발자라도 전염병처럼 피하고 싶어하는 극도로 뒤틀린 논리의 어두운 지점을 점점 더 많이 만나게 됩니다. 이러한 장소가 많을수록 애플리케이션을 관리하고 개발하기가 더 어려워집니다. 표준적인 예는 새 사용자를 등록하는 작업으로, 이 이벤트와 관련된 다른 작업 그룹 전체를 트리거합니다:
샘플 코드 사용자 등록을 담당할 수 있습니다:
등록 컨트롤러 클래스 < 애플리케이션 컨트롤러
def create
user = User.new(registration_params)
if user.valid? && ip_valid?(registration_ip)
user.save!
user.add_bonuses
user.synchronize_related_accounts
user.send_email
end
end
end
좋아, 코딩을 했고 모든 것이 작동하지만... 이 모든 코드가 정말 괜찮은 건가요? 더 잘 작성할 수 있을까요? 우선, 프로그래밍의 기본 원칙인 단일 책임 원칙을 위반하고 있으므로 더 잘 작성할 수 있을 것입니다. 하지만 어떻게? 이 부분에서 이미 언급한 PORO가 도움이 될 수 있습니다. 모든 관련 서비스를 알리는 한 가지 일만 담당하는 RegistrationService 클래스를 분리하는 것으로 충분합니다. 서비스별로 우리는 위에서 이미 선택한 개별 작업을 고려할 것입니다. 동일한 컨트롤러에서 등록 서비스 객체를 생성하고 "fire!" 메서드를 호출하기만 하면 됩니다. 코드가 훨씬 명확해지고 컨트롤러가 차지하는 공간이 줄어들었으며 새로 생성된 각 클래스는 이제 하나의 액션만 담당하므로 필요한 경우 쉽게 교체할 수 있습니다.
등록 서비스 클래스
def fire!(params)
user = User.new(params)
if user.valid? && ip_validator.valid?(registration_ip)
user.save!
after_registered_events(user)
end
user
end
private
def after_registered_events(user)
BonusesCreator.new.fire!(user)
AccountsSynchronizer.fire!(user)
EmailSender.fire!(user)
end
def ip_validator
@ip_validator ||= IpValidator.new
end
end
등록 컨트롤러 클래스 < 애플리케이션 컨트롤러
def create
user = RegistrationService.new.fire!(registration_params)
end
end
그러나 평범한 루비 객체는 컨트롤러에만 유용하지 않을 수 있습니다. 만들고 있는 애플리케이션이 월별 청구 시스템을 사용한다고 가정해 보세요. 이러한 청구서를 생성하는 정확한 날짜는 중요하지 않으며 특정 월과 연도와 관련이 있다는 것만 알면 됩니다. 물론 매월 1일로 날짜를 설정하고 이 정보를 "Date" 클래스의 객체에 저장할 수 있지만 이는 실제 정보도 아니며 애플리케이션에 필요하지도 않습니다. PORO를 사용하면 필요한 정확한 정보를 저장하는 객체 "MonthOfYear" 클래스를 만들 수 있습니다. 또한 "Comparable" 모듈을 적용하면 Date 클래스를 사용할 때와 마찬가지로 해당 객체를 반복하고 비교할 수 있습니다.
MonthOfYear 클래스
포함 비교 가능
ATTR_READER :년, :월
def initialize(month, year)
month.between?(1, 12) 아니면 ArgumentError를 발생시킵니다.
year, @month = year, month
end
def (other)
[year, month] [other.year, other.month]
end
end
Rails 세계에서는 각 클래스가 모델, 뷰 또는 컨트롤러라는 사실에 익숙합니다. 또한 디렉토리 구조에서 정확한 위치를 가지고 있으므로 우리의 작은 PORO 군대를 어디에 배치할 수 있을까요? 몇 가지 옵션을 고려해 보세요. 가장 먼저 떠오르는 생각은 생성된 클래스가 모델도 뷰도 컨트롤러도 아닌 경우 모두 "/lib" 디렉터리에 넣어야 한다는 것입니다. 이론적으로는 좋은 생각이지만, 모든 PORO 파일이 한 디렉터리에 있고 애플리케이션의 크기가 크다면 이 디렉터리는 금방 열기가 두려운 어두운 장소가 될 것입니다. 따라서 의심할 여지없이 좋은 생각이 아닙니다.
어썸프로젝트
├──앱
│ ├─컨트롤러
│ ├─모델
│ └─뷰
│
└─lib
└─서비스
1TP63톨 포로 여기
일부 클래스의 이름을 액티브 레코드 모델이 아닌 모델로 지정하여 "app/models" 디렉터리에 넣고, 다른 클래스의 처리를 담당하는 클래스의 이름을 서비스로 지정하여 "app/services" 디렉터리에 넣을 수도 있습니다. 이것은 꽤 좋은 해결책이지만 한 가지 단점이 있습니다. 새 PORO를 만들 때마다 모델인지 서비스인지 매번 결정해야 한다는 것입니다. 이렇게 하면 애플리케이션에 어두운 부분이 두 개가 있고 작은 부분만 있는 상황에 도달할 수 있습니다. 세 번째 접근 방식은 네임스페이스 클래스와 모듈을 사용하는 것입니다. 컨트롤러나 모델과 이름이 같은 디렉터리를 만들고 해당 컨트롤러나 모델에서 사용하는 모든 PORO 파일을 그 안에 넣기만 하면 됩니다.
어썸프로젝트
├──앱
│ ├─컨트롤러
│ │ ├─registration_controller
│ │ │ └─registration_service.rb
│ │ └─registration_controller.rb
│ ├─models
│ │ ├─settlement
│ │ │ └─month_of_year.rb
│ │ └─settlement.rb
│ └─views
│
└─lib
이 배열 덕분에 클래스 이름 앞에 네임스페이스를 붙일 필요가 없습니다. 코드가 더 짧아지고 논리적으로 정리된 디렉토리 구조를 갖게 됩니다.
PORO를 사용하면 애플리케이션의 단위 테스트를 더 빠르고 쉽게 작성할 수 있고 나중에 다른 사람이 이해할 가능성이 높아진다는 것은 놀라운 일입니다. 이제 각 클래스가 한 가지 일만 담당하므로 경계 조건을 더 빨리 인식하고 적절한 테스트 시나리오를 쉽게 추가할 수 있습니다.
설명 MonthOfYear do
subject { MonthOfYear.new(11, 2015) }
it { be_kind_of 비교 가능해야 함 }
설명 "새 인스턴스 생성" do
"올바른 연도와 월로 초기화" do
expect { described_class.new(10, 2015) }.to_not raise_error
end
"주어진 월이 잘못되었을 때 오류를 발생시킵니다" do
expect { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
end
end
end
앞서 제시한 예시를 통해 PORO를 사용하면 애플리케이션의 가독성이 향상되고 모듈화되어 결과적으로 관리와 확장이 쉬워진다는 것을 분명히 알 수 있습니다. 단일 책임 원칙을 수용하면 필요한 경우 특정 클래스를 다른 요소에 간섭하지 않고도 쉽게 교환할 수 있습니다. 또한 테스트도 더 간단하고 빠르게 진행할 수 있습니다. 또한 이렇게 하면 Rails 모델과 컨트롤러를 짧게 유지하는 것이 훨씬 쉬워지며, 개발 과정에서 불필요하게 커지는 경향이 있다는 것을 우리 모두 알고 있습니다.