Joe birch header

클린 아키텍처를 안드로이드에 도입하는 방법

클린 아키텍처가 무엇인지와 Buffer의 안드로이드 앱에 도입한 경험담을 소개합니다.


저는 Buffer의 안드로이드 개발자, Joe입니다. Buffer는 소셜 미디어에 예약으로 포스팅할 수 있는 도구를 개발하는 회사로, 포스트를 작성해서 한 번에 여러 SNS에 원하는 시간에 맞게 포스팅할 수 있습니다.

아키텍처가 없는 경우

Buffer에서는 시장에 테스트하기 위한 기능을 서둘러 만들어야 했습니다. 이처럼 아키텍처를 계획할 충분한 시간이 없으면 잘못된 결정을 내리기 쉽습니다. 저희 경우에는 다음과 같은 문제를 맞닥뜨렸죠.

  • 높은 기술 부채. 즉, 과거의 잘못된 선택 때문에 문제를 해결하고 새 기능을 제공하는 데 더 많은 시간이 걸립니다.

  • 클래스의 과다한 커플링

  • 유닛이나 UI 테스트 부재

  • 버그의 출처를 알 수 없음

만약 프로젝트가 제대로 구조화되지 않았다면 팀이 성장함에 따라 여러분의 팀원이 작업하기 더욱 복잡해질 겁니다.

안드로이드용 Buffer

Buffer에 합류한 후 어떤 것이 어디서 벌어지는지 파악하는 것이 상당히 어려웠습니다. 새로운 팀원이 더 들어오면서 다들 프로젝트 이해에 어려움을 겪었습니다. 이미 잘못된 것 위에 새로운 것을 추가하면서 기술 부채는 점점 커져만 갔습니다.

구조라고 할 것이 거의 없었고 액티비티는 여러 가지 책임을 추가하면서 점점 거대해졌습니다. 커스텀 뷰와 로직에 코드 중복도 많았습니다. 이미 고쳤던 버그가 몇 주 후에 예상치 못한 다른 곳에서 자주 발견되곤 했죠.

MVP(Model View Present)로 전환

너무 많이 변경되는 것을 우려하면서, 제가 합류한 시점에 저희 팀은 MVP(모델-뷰-프리젠트)를 도입하기로 했습니다. 간결함을 유지하면서도 너무 많은 것이 한꺼번에 변하지 않도록 MVP로 전환하기 시작했습니다. 결과적으로 좀 더 간결하게 프레임워커와 프리젠테이션 로직을 분리할 수 있었습니다. 모든 데이터 작업도 데이터 매니저 클래스로 옮겼습니다. 나아가 이 작업은 테스트 커버리지도 증가시킬 수 있었죠.

하지만 MVP만으로는 부족했습니다.

하지만 이 데이터 관리자는 이미 여러 책임으로 거대해진 이후였습니다. API에 접근하고, 캐시에도 접근하며, 흠, 데이터 작업이 있으니 데이터 매니저에 던져둬야지. 하는 식이었습니다. 결국 코드가 엉망이 되었죠.

이런 개발 뉴스를 더 만나보세요

이 데이터 매니저 클래스를 컴포저와 사용자 데이터 매니저로 분리했지만, 여전히 조직적이지 않은 상태로 비대해졌습니다.

데이터 매니저의 경우 향후 앱을 오프라인에서도 동작하게 하려면 지금 설명한 상태에서는 어떻게 할 수 있을까요? 이 의문에 대한 해법은 다시 아키텍처를 변경하는 것이었습니다.

아키텍처

우리에게는 분명 유연성이 필요했고, 앱의 유지 관리도 잘 되고 테스트 커버리지도 넓어야 했습니다. 안드로이드의 좋은 아키텍처에는 직관성이 필요합니다. 예를 들어 패키지 구조를 보면 그 구성 방법을 이해할 수 있어야 합니다. 저는 이런 관점으로 오픈 소스 코드를 작성하곤 합니다. 제 코드를 저도 이해하지 못한다면 누가 이해할 수 있을까요?

클린 아키텍처

좋은 아키텍처를 구현하는 특정한 방법이란 존재하지 않습니다. 그렇지만 클린 아키텍처에 대한 유명한 다이어그램에서는 네 가지 섹션을 제시합니다.

  • 도메인 로직: 엔티티, 데이터, 모델
  • 비즈니스 규칙: 유스 케이스
  • 인터페이스 어댑터: 프리젠터, 프리젠테이션 로직
  • 프레임워크/드라이버: UI, 네트워크, 데이터베이스

클린 아키텍처에서는 상위 레이어로만 종속성이 있어야 합니다. 예를 들어 프리젠터는 데이터베이스를, 엔티티는 유스 케이스를 알 수 없어야 합니다.

관심의 분리

각각의 관심사에 특화된 독립적인 테스트 클래스를 만들 수도 있습니다. 저희 안드로이드 앱에서는 UI 레이어만이 안드로이드 프레임워크를 알고 있습니다. 프리젠터와 엔티티는 안드로이드 프레임워크를 모르죠. 이렇게 리팩토링한 결과는 다음과 같습니다.

  • 코드의 테스트 가능성이 증대되고 클래스가 집중화되고 유지 보수가 쉬워졌습니다.

  • 내부 레이어가 사용자 인터페이스로부터 독립됐습니다. UI는 안드로이드 개발에서 가장 민감한 부분이므로 이런 레이어의 커플링을 제거해서 큰 변화가 일어났습니다.

  • 또한 내부 레이어가 다른 외부 관련 요소로부터 독립됐습니다.

  • 모든 것이 계층화되고 각각 책임이 명확하므로 버그를 파악하기 쉬워졌습니다.

레이어 모델

엔터프라이즈 비즈니스 규칙(Enterprise Business Rules)

첫 번째 레이어는 엔터프라이즈 비즈니스 규칙으로, 애플리케이션의 핵심 비즈니스 규칙입니다. 예를 들어 Twitter의 규칙은 프로필이나 트윗입니다. 비즈니스 요구 사항이 바뀌지 않는 한 한 번 생성한 이후 고칠 필요가 없습니다. UI를 작성하기 전에 만들 수도 있죠.


public class TournamentModel {

    public String id;
    public String name;  
    @SerializedName("full_name")  
    public String status;  
    @SerializedName("date_start")  
    public String dateStart;  
    @SerializedName("date_end")  
    public String dateEnd;
    public int size;

}

애플리케이션 비즈니스 규칙(Application Business Rules)

애플리케이션 비즈니스 규칙은 애플리케이션에 특화된 규칙으로 애플리케이션이 수행할 작업처럼 유스 케이스에 따라 정의됩니다. 예를 들어 사진을 저장하거나 프로필을 가져오려 할 수 있습니다.

저희 앱의 유스 케이스를 하나 예로 들면 사용자가 일정을 정하는 것입니다.


public class GetSchedules extends UseCase<ProfileSchedules> {   

    private final SchedulesRepository schedulesRepository;   

    @Inject
    public GetSchedules(SchedulesRepository schedulesRepository) {  
      this.schedulesRepository = schedulesRepository;
    }   

    @Override
    public Single<ProfileSchedules> buildUseCaseObservable() {  
      return schedulesRepository.getSchedules();

    }   

}

이 계층에서는 외부 계층이 이 계층과 통신하는 방법을 정의하는 인터페이스를 제공합니다. 외부 레이어는 해당 저장소의 구체적인 구현을 전달합니다. 이 방식으로 요소들을 명확하게 만들고 더욱 쉽게 테스트할 수 있습니다.

인터페이스 어댑터

세 번째 계층은 인터페이스 어댑터입니다. 여기에는 애플리케이션의 프리젠테이션이 포함되며, 저희 경우 뷰와 프리젠터같이 MVP에 들어갈 것을 포함했습니다. 이 프리젠터는 다른 레이어로부터 데이터를 검색하는 방법을 정의하는 콜백을 가집니다. 저희는 RxJava를 사용했습니다.


public class UpdatesPresenter extends BasePresenter<UpdatesMvpView>
    implements SingleObserver<List<Update>> {

    @Override
        public void onSubscribe(Disposable d) {
            ...
    }

    @Override
        public void onSuccess(List<Match> matches) {
         ...
    }

    @Override
        public void onError(Throwable e) {
         ...
    }

}

프레임워크와 드라이버

마지막 레이어는 프레임워크와 드라이버입니다. 여기에는 액티비티, 프래그먼트, 뷰와 같은 UI 컴포넌트가 포함됩니다. 외부 레이어여도 되고 상호 작용을 돕기 위해 저장소 패턴을 사용해서 다른 레이어로 이동할 수도 있습니다.

레이어 경계

이런 여러 레이어를 통해 이제 레이어 경계를 명확히 만들 수 있었습니다. 유연성을 증대하기 위해 전달한 구체적인 구현과 인터페이스이죠. 이들 인터페이스는 어떻게 다른 요소와 통신할 것인지 지시합니다. 의존 관계 역전 원칙이라고도 불립니다.

클린 아키텍처에서 반드시 이런 레이어를 써야 하는 것은 아닙니다. 세 개의 레이어만 사용했거나, 엔터프라이즈 비즈니스 규칙을 분리할 필요가 없다면 애플리케이션 비즈니스 규칙에 병합해도 됩니다. 저희의 경우에는 데이터베이스와 분석을 분리해서 네 개가 아닌 다섯 개를 사용했습니다.

테스트 커버리지

좋은 아키텍처를 도입하면 테스트 커버리지도 넓어집니다. 모든 요소가 자신의 독립적인 책임을 가지므로 이제 저희는 더욱 집중된 테스트 클래스를 작성할 수 있습니다.

레이어 경계를 넘나드는 테스트를 포함해서 각 레이어의 모델로 유스 케이스를 테스트할 수도 있습니다.

교훈

아직 저희는 배워가는 단계로 많은 실수를 저지르고 있습니다. 클린 아키텍처나 좋은 아키텍처는 일반적인 용어이지만, 결국 더 많은 추상화를 사용하고 더 집중적인 클래스를 작성하라는 뜻입니다. 클린 아키텍처를 전혀 사용하지 않더라도 프로젝트를 개선할 방법을 배울 수 있습니다.

클린 아키텍처의 장점

  • 코트 테스트 커버리지 증대
  • 쉽게 패키지 구조 탐색 가능
  • 집중화된 클래스에 따른 프로젝트 유지 관리 증대
  • 새 기능을 빠르게 적용 가능
  • 이후의 개발에도 안정적인 구현
  • 명확한 규율로 전반적으로 따라야 할 베스트 프랙티스

클린 아키텍처의 단점

클린 아키텍처를 구현하려면 초반에 상당한 오버헤드가 발생합니다. 별도의 모듈이 필요하고 새로운 인터페이스의 구현도 추가해야 하죠. 클린 아키텍처로 전환하는 과정에서 저희는 프로젝트 전반에서 모델을 재사용해야 한다는 것을 종종 잊곤 했습니다. 이런 습관은 시간이 좀 걸려야 들기 마련입니다.

참고 자료

클린 아키텍처와 사용 방법에 대한 저희 블로그 포스트가 있습니다. 클린 아키텍처에 대한 명확한 예제가 필요하면 구글 예제도 좋습니다. 로버트 C. 마틴의 “Clean Architecture: A Craftsman’s Guide to Software Structure and Design”도 추천합니다.

질문

Q: 모델과 각 레이어의 구현, 각 레이어의 어댑터 차이점에 대해 더 자세히 말씀해주세요. Joe: 엔터프라이즈 비즈니스 규칙에는 모델이 있고 외부 레이어에는 자체 모델과 매퍼 클래스가 있으므로 매퍼 클래스에 빌더 패턴이 있습니다. 하지만 이 방법의 효율성에는 좀 논란이 있을 수 있습니다. 여러 모델에 이런 작업을 하면 효율성이 떨어질 수 있으니까요.

Q: iOS에도 클린 아키텍처를 적용하셨나요? 경험을 공유해주세요. Joe: 아직 적용하지 않지만 적용하도록 설득하고 있습니다.

Q: 데이터 매니저 클래스는 어떻게 바뀌었나요? API 호출과 캐싱은 이제 어디에서 하게 되나요? Joe: 저장소 내의 프리젠터가 유스 케이스를 주입받습니다. 유스 케이스가 저장소에 대한 참조를 가집니다. 이런 저장소 구현이 전달됩니다.

저희 데이터 저장소에는 원격 데이터 저장소와 캐시 데이터 저장소가 있으며 둘 다 이 인터페이스를 구현하며 같은 동작을 합니다.

Q: 외부의 두 개 레이어인 프리젠테이션과 UI 레이어의 경우 이 둘을 모두 안드로이드에 노출하나요? Joe: 통과시키는 방법을 씁니다. 프리젠테이션 레이어에 뷰 레이어로 전달하는 요소가 있습니다.

Q: 레이어간에 데이터를 전송할 때 parcel화를 하나요, 아니면 레이어 사이에서 캡슐화를 하나요? Joe: 아직은 질문 방법 중 아무것도 하지 않습니다. 이들을 통과시키고서 매퍼 클래스로 변환하고 있습니다.

다음: 안드로이드 아키텍처 #5: 액티비티와 프래그먼트에서 벗어나 간결하게 Android 앱 만들기

General link arrow white

컨텐츠에 대하여

2017년 4월에 진행한 Droidcon Boston 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.

Joe Birch

Joe는 영국의 Brighton의 안드로이드 엔지니어로 Buffer의 안드로이드 팀에서 일하고 있습니다. 코딩을 좋아하며 모바일, 웹 및 TV용의 견고하고 재미있는 프로젝트를 만드는 것을 좋아합니다. 작가이기도 한 그는 자신이 배운 것과 경험을 다른 사람들과 나누기 좋아합니다.

4 design patterns for a RESTless mobile integration »

close