본문 바로가기
Architecture/Clean Architecture

[Clean Architecture] 3. 설계원칙 - (2) SRP (단일 책임 원칙)

by 잭피 2021. 9. 12.

역사적으로 SRP는 아래와 같이 기술되어 왔습니다

단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다

 

SRP를 정의는 아래처럼 말할 수 있습니다

하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다

 

(액터 : 해당 변경을 요청하는 한 명 이상의 사람들)

(모듈: 소스파일)

 

징후 1 : 우발적 중복

Employee 클래스에 calculatePay(), reportHours(), save() 메소드를 가집니다

CFO, COO, CTO가 모두 이 클래스의 메소드를 사용합니다

 

현재 이 클래스는 SRP를 위반합니다

이 3가지 메서드가 서로 매우 다른 세 명의 액터를 책임지기 때문입니다

 

calculatePay()는 회계팀에서 기능을 정의하며, CFO 보고를 위해 사용합니다

reportHours()는 인사팀에서 기능을 정의하고 사용하며, COO 보고를 위해 사용합니다

save()는 DBA가 기능을 정의하고, CTO 보고를 위해 사용합니다

 

이렇게 세 액터가 결합되어 CFO 팀에서 결정한 조치가 COO 팀이 의존하는 무언가에 영향을 줄 수 있습니다

예를 들어 calculatePay()와 reportHours()가 초과 근무를 제외한 업무 시간을 계산하는 알고리즘을 공유한다고 해봅시다

regularHours()에 해당 로직이 들어가 있고, 공유하여 같이 사용합니다

[calculatePay()] → regularHours() ← [reportHours()]

 

CFO 팀에서 regularHours() 로직을 약간 수정하기로 결심했고, 반면 인사를 담당하는 COO 팀에서는 변경을 원하지 않는다고 가정해봅시다

CFO 팀 개발자는 reportHours() 의존성을 확인하지 못한채 수정하고 CFO 팀에서 원하는 방식대로 동작하는지 검증하고 배포할 것입니다

그리고 나중에 COO 팀에서 수치에 문제가 있다는 점을 발견하여 이슈가 될 것입니다

이런 문제는 서로 다른 액터가 의존하는 코드를 너무 가까이 배치했기 때문입니다

SRP는 서로 다른 액터가 의존하는 코드를 서로 분리하라고 말합니다

 

징후 2 : 병합

서로 다른 액터를 책임진다면 병합이 발생할 가능성이 높습니다

예를 들어 Employee 테이블을 서로 다른 팀에서 각각 테이블 스키마를 원하는 방향으로 변경한다면 서로 충돌합니다. 결과적으로 병합이 발생합니다

 

병합은 많은 사람이 서로 다른 목적으로 동일한 소스 파일을 변경하는 경우에 발생합니다

이 문제를 벗어나는 방법은 서로 다른 액터를 뒷받침하는 코드를 서로 분리하는 것입니다

 

해결책

여러 해결책 중 하나는 메서드를 각기 다른 클래스로 이동시키는 방식입니다

가장 확실한 해결책은 데이터와 메서드를 분리하는 방식입니다

즉, 아무런 메서드가 없는 간단한 데이터 구조인 EmployeeData 클래스를 만들어, 세 개의 클래스가 공유하도록 합니다

각 클래스는 자신의 메서드에 반드시 필요한 소스 코드만을 포함합니다

세 클래스는 서로의 존재를 몰라야 합니다

반면 이 해결책은 개발자가 세 가지 클래스를 인스턴스화하고 추적해야 한다는 게 단점입니다

 

이런 난관을 빠져나올 때 흔히 쓰는 기법으로 퍼사드(Facade) 패턴이 있습니다

EmployFacade에 코드는 거의 없습니다

이 클래스는 세 클래스의 객체를 생성하고, 요청된 메서드를 가지는 객체로 위힘하는 일을 책입집니다

 

만약 데이터와 가깝게 배치하는 방식을 선호한다면, 가장 중요한 메서드는 기존의 Employee 클래스에 그대로 유지하되, Employee 클래스를 덜 중요한 나머지 메서드들에 대한 퍼사드로 사용할 수 있습니다

 

가장 중요한 메서드는 기존의 Employee 클래스에 그대로 유지합니다

Employee 클래스를 덜 중요한 나머지 메서드들에 대한 퍼사드로 사용합니다

 

모든 클래스는 반드시 단 하나의 메서드를 가져야 한다느 주장에 근거하여 위와 같은 해결책에 반대할 수도 있습니다

하지만 이 주장은 현실과 전혀 다릅니다

각 클래스에서 지불, 보고서 생성, 데이터 저장 기능을 구현하는 데 필요한 메서드의 개수는 실제로 훨씬 더 많을 것입니다

이들 클래스는 모두 다수의 private 메서드를 포함할 것입니다

결론

단일 책임 원칙은 메서드와 클래스 수준의 원칙입니다

컴포넌트 수준에서는 공통 폐쇄 원칙이 됩니다

아키텍처 수준에서는 아키텍처 경계의 생성을 책임지는 변경의 축이 됩니다

 

 

 

이 글은 학습을 위해 “클린 아키텍처" 책 내용을 정리한 글입니다.

저작권 관련 문제가 있다면 바로 삭제하도록 하겠습니다

 

댓글