Client-Side Clean Architecture (Flutter)

클라이언트-사이드 클린 아키텍처 (플러터)

Doohyeon Kim
Doohyeon.kim
20 min readDec 19, 2022

--

드디어 이 글을 쓴다. 작년부터 써야지 써야지 하면서 미루고 있었다.

올해는 넘기지 말아야지라는 각오로! 연말에 클린 아키텍처에 대해 작성해본다.

여기서 얘기하는 클린 아키텍처는 Uncle Bob 아저씨가 말한 클린 아키텍처가 아니다. 참고한 내용이 많지만 로버트 마틴의 클린 아키텍처에 대한 설명을 예상했다면 기대와 다른 글일 수 있다.

왜냐하면 이 아키텍처에는 로버트 마틴의 클린 아키텍처 외에도 다양한 아키텍처 패턴이나 디자인 패턴들이 적용 되었기 때문이다.

여러 프로젝트를 진행하면서 겪은 개인적인 노하우나 가치관이 꽤 많이 들어가 있다.

클린 아키텍처에 대한 원론적인 내용보다는 실무관점에서의 내용이 많이 들어가 있으니, 다른 개발자의 사고가 궁금하다면 읽어 보기를 추천한다.

꼭 Flutter 개발자가 아니더라도, 읽는 데 지장이 없으니 아키텍처 설계에 관심이 있다면 한번 쯤 읽어봐도 좋을 것 같다.

제목을 Flutter Clean Architecture가 아니라 ‘Client Clean Architecture (Flutter)’ 라고 지은 이유다. 그리고 Flutter는 모바일 외에도 웹, 데스크탑 OS 등 다양한 클라이언트 사이드를 지원하기 때문에 그게 더 어울린다고 생각했다.

만약 대중적인(?) 클린 아키텍처의 내용과 매우 비슷한거 아니냐?! 생각한다면… 비슷할 수 밖에 없다…

왜냐하면 클린 아키텍처라는 게, 결국 천재적인 선배 개발자들이 수십년동안 사용해 본 결과 Best Practice이기 때문이다. 디테일한 부분에서 약간 변화는 있을 수 있어도 큰 부분에선 이렇게 귀결되더라… 라는 것이다보니…

서론이 길었다. 서론이 길다는 것은 방어적으로 작성해야 할 부분이 많다는 뜻이고, 그만큼 무거운 주제라는 뜻이다.

내가 구성한 클린 아키텍처의 다이어그램은 이렇다.

여느 클린 아키텍처가 그렇듯 크게 Presentation Layer, Domain Layer, Data Layer로 구분한다. 그리고 MVVM 패턴, Repository 패턴 등 다양한 패턴을 적용했다.

화살표는 데이터의 흐름이 아니라 의존성을 나타낸다.

Presentation Layer

View

ViewModel 의존적인 UI의 모음이다. 최대한 잘게 쪼갤 수록 좋다.

대부분 View를 Screen/Page와 같은 의미로 사용하는데, 나는 View와 Screen/Page를 구분하는 걸 선호한다.

왜냐하면 클라이언트 사이드는 UI쪽 코드들의 휘발성이 강하다. 디자인이 변경되면 UI 코드도 바뀌어야 하는데, View를 잘 설계 해놓고 View의 조합으로 Screen/Page를 구성하게 되면 디자인이 바뀌어도 코드가 재사용 될 확률이 높기 때문이다.

그리고 View를 잘 쪼개놓으면 한 View를 여러 화면에서 사용할 수 있는 경우가 생각보다 많이 있다.

예를들어, 닉네임을 입력받는 부분을 View로 구현 한다면, 닉네임 변경, 닉네임으로 친구 추가, 닉네임으로 검색 등 다양한 부분에서 재사용 될 수 있다. 만약 Screen/Page 단위로 View를 구성하게 되면 각 페이지별로 닉네임을 입력받는 View를 다시 만들어야 한다.

그래서 View는 단순히 유저에게 보이는 화면이라는 개념보다는, ViewModel을 소비하는 UI의 단위라는 개념으로 접근하면 중복된 코드량을 줄이고 코드의 재사용성을 높일 수 있어서 좋다.

View Model

MVVM 패턴에서 가장 까다로운 부분이다. 어떻게 설계하냐에 따라 책임이 천차만별이기 때문이다. Massive-MVVM 같은 경우는 Controller의 역할까지 View Model이 가져오기도 한다.

UI와 상호작용하면서 사용자 인터페이스를 업데이트하는 데 사용되는 중간 레이어다. 비즈니스 로직과 관련된 내용을 처리하지 않는다. 대신, 이벤트를 받아들이고 이벤트를 처리하는 데 필요한 데이터를 제공한다.

View model의 가장 큰 역할은 View와 Model 간의 의존성을 분리하는 것이다. 즉, View가 Model에 직접 접근하는 것이 아니라 View model을 통해 접근하도록 하는 것이다. 이를 통해 Model을 변경하더라도 View가 영향을 받지 않으며, View model을 변경해도 Model이 영향을 받지 않는다. 이렇게 구현하면 유닛 테스트나 View를 변경할 때 코드 수정이 쉬워지므로, 유지보수성이 높아진다. 하지만 단어 그대로 View를 위한 Model이기 때문에 UseCase나 Model을 호출하지 않는 경우도 있다.

프레임워크나 외부 라이브러리, 패키지에 대한 제약이 없기 때문에 구현의 핵심이 되는 부분이다.

View Model하면 빠질 수 없는 게 Test Code다. MVVM 패턴을 도입하는 이유는 View Model 때문이고, View Model이 필요한 이유 두 가지를 뽑으라면, 코드 간 의존성을 줄이는 것과 테스트를 잘 하기 위해서라고 볼 수 있다.

Test Code를 만들다보면 View Model에 대한 이해도도 높아진다.

빡빡한 개발일정을 소화 하다보면 ViewModel을 짬통처럼 쓰고 싶은 욕구가 올라올 때가 있다. 어디까지 짬처리를 시킬 것인가? Test Code를 짜보면 그것에 대한 최소 기준을 정할 수 있다. 테스트 코드를 짜기 어려운 View Model은 리팩토링이 필요한 View Model이다.

보통 서버 개발자들이 API를 만들고 나서, 클라이언트 개발자들이 API를 보고 개발을 하면서 API 수정 요청을 한다. 가끔 그 요청 자체를 안 좋게 생각하고 Response를 자기 마음대로 주는 서버 개발자들이 있는데, 그렇게 되면 MVVM 패턴이 필요 없어진다.

View와 ViewModel에 대한 이해가 명확하면, 왜 클라이언트에서 Response를 요청해야 하는지 서버 개발자를 설득하는데 도움이 된다.

학원이나 부트캠프에서 잘못 교육하는 경우가 “백엔드 개발자가 DB를 알기때문에 Response Data의 주도권이 백엔드 개발자에게 있다.”인데 이건 완전히 잘못된 개발이다.
서버도 아키텍처가 있고 아키텍처라는 거 자체가 유지보수성을 증가시키고, 유지보수성이 증가한다는 것은 변경에 유연하게 대처할 수 있다는 거다.
특정한 상황에서 DB 구조상 또는 기획변경 등의 이유로 팀 전체의 공수가 증가하는 게 아니라면, Response 변경은 클라이언트에서 요청할 수 있다.

애초에 Client — Server라고 부르는데는 이유가 있다. Response Data를 가져다 쓰는 건 서버가 아니라 클라이언트다. 클라이언트에서 사용할 데이터에 대해서 서버 개발자에게 요청할 수 있다.

다만, 요청할 수 있다이지 클라이언트가 API에 대해서 갑질해도 된다는 건 아니니 오해 하지 않았으면 한다.

그리고 에러코드, 에러메시지, 상태코드 등 API에 대한 설계는 정책을 구현해야 하는 백엔드 개발자가 주도하는 게 낫다.

그런 이유로 Figma 보면서 API 명세를 작성하고 있는 백엔드 개발자를 보면, 화면만 보고 API 만들지 말고 클라이언트 개발자랑 협의해서 작성하라고 얘기한다.

클라이언트든 서버든 도메인/비지니스를 중심으로 설계를 해야지 화면을 보고 설계하면 안된다. API도 마찬가지다.

UI 코드는 휘발성이 강하다. 업데이트가 잦기 때문에, 백엔드 개발자가 화면을 보고 Response를 만드는 잘못된 개발은 없어져야 할 관습이다.

항상 예외는 있을 수는 있다. 예를들면 SI 같은 곳은 서비스를 운영에 대해선 비교적 자유롭고, 빠르게 납품하는 게 중요하기 때문에 경험 많은 백엔드 개발자가 화면보고 API 뽑아내는 게 훨씬 낫다.

어떤 상황이든 협의해서 하는 게 가장 좋다. (모든 직장인의 필수 능력, 커뮤니케이션 능력!! 당연히 개발자도 해당된다.)

Domain Layer

도메인 영역이다. 도메인 영역은 흔히 비개발자들도 이해할 수 있는, 프레임워크나 라이브러리가 아닌 도메인에 종속적인 코드가 담긴다.

Use Case

사용에 대한 내용이 들어간다. Service와의 차이점은 단어 그대로 Service는 해당 애플리케이션이 제공하는 서비스에 대한 내용이고, Use Case는 사용에 대한 내용이 들어간다.

은행 앱을 예로 들어보자.

은행 앱에는 자산조회, 이체, 적금 가입, 대출, 청약 등 다양한 서비스가 있다.

위 서비스들을 사용하려면 사용자 인증이 필요할 텐데 이 부분이 유즈 케이스에 해당한다.

서비스에 종속되지 않는 유즈케이스를 만들면 다양한 서비스에서 해당 유즈케이스를 사용할 수 있다. 즉, 코드의 재사용성이 증가한다. 그리고 서비스 하나 안에 여러 개의 유즈케이스를 작성하면 한 파일에 코드가 방대해진다.

코드가 방대해진다는 것은 개발자가 실수할 확률이 늘어나고, 코드분석, 협업 시 비용이 증가함을 의미한다.

이해를 돕기 위해 한 번 더 자세히 예를 들어본다.

대출 상환이든 계좌이체든 적금납입이든 여러 서비스에서 ‘송금’이라는 게 필요로 한다. 이 송금이 Use Case에 해당한다.

Use Case를 잘게 쪼갤 수록 테스트가 용이해지고, 코드를 재사용 할 수 있게 된다.

Use Case의 코드를 잘 짜게 되면 비개발자가 읽어도 코드가 읽힌다. (특정 언어 제외…) 아래 pseudo code를 예시로 들어본다.

Transfer(){
call(remittance: remittance, from: myAccount, into: userAccount){
bankRepository = BankRepositry();
userAccount = await bankRepository.find(userAccount);

이런식으로 개발자가 아니어도 읽을 수 있는 코드를 작성할 수 있다.

실제 송금 과정은 더 복잡하고 많은 코드가 들어가지만, 영어로 술술 읽히는지만 보자.

영어가 익숙하지 않은 사람들을 위해 한글로 표현해보았다. (영어와 어순이 다르기 때문에 감안해서 작성했다.)

송금(){
(송금액: 송금액, 내_계좌: 로 부터, 사용자_계좌: 쪽으로){
은행저장소 = 은행저장소();
사용자_계좌 = 기다려 은행저장소.찾기(사용자_계좌);

만약(사용자_계좌.유효한가){
만약(내_계좌.잔액 > 송금액){
기다려 은행저장소.송금(양: 송금액, 내_계좌: 로부터, 사용자_계좌: 쪽으로);
반환해 결과물("성공");
}그렇지 않으면{
반환해 에러메시지("잔액이 부족합니다.");
}
}그렇지 않으면{
행동멈춰();
반환해 에러메시지("이 사용자의 계좌는 유효하지 않습니다.");
}
}함수호출
.
.
.
}

음… 작성해보니 영어가 모국어인 개발자는 참 편하겠구나 라는 생각이 들었다.

View Model에서 Repository만 필요한 경우는 Use Case를 구현하지 말아야 하나? 고민될 수 있다.

그러나 이 부분 뿐만 아니라, 어디든 단순히 전달만의 목적이 있어도 bypass하지 말고 구현해야 한다. 이유는 코드의 일관성 때문이다.

View Model에서 어디는 Use Case를 호출하고 어디는 Repository를 호출하고 그런식이면 코드가 많아졌을 때 추적하기 어려워진다.

그리고 호출하는 Use Case의 이름을 명확하게 해놓으면 그 뒤의 구현에 대해서는 신경쓰지 않아도 된다는 점에서 협업에 도움이 된다.

특히 Use Case는 싱글톤 패턴으로 구현하지 않기 때문에, 코드를 작성한다고 메모리에서 손해를 본다거나 그러지 않는다.

Model

Model은 Entity라고도 부르는 사람이 많은데, 어떻게 설계하냐에 따라 다르다. 심지어 Model이 없어도 된다. 하지만 Model로 사용하기를 적극 추천한다.

현재 클린 아키텍처라고 하면, 당연한 얘기지만 iOS 진영과 구글의 모바일 클린 아키텍처 레퍼런스가 대부분이다. 그러다보니 로버트 마틴이 말한 클린 아키텍처와 각종 패턴들을 혼용하면서 헷갈릴 수 밖에 없는 것이다.

왜냐하면 로버트 마틴의 클린 아키텍처에서는 Entity가 도메인을 구성하는 가장 기본적인 객체를 뜻하는 반면, MVVM패턴에서는 Model이 데이터와 비지니스 로직을 갖는 도메인 모델이기 때문이다.

그래서… 결국 내가 작성하는 Client-Side Clean Architecture에서 어떻게 할 것인가가 문제인데, 나는 Domain Layer에서는 Model을 추천한다. 이유는 다음과 같다.

Entity는 속성의 변경과 무관한 개체로, 일반적으로 DB에 저장되는 개체를 뜻한다. E-R 다이어그램도 DB에서 데이터 모델링할 때 사용하는 방법인데, Entity-Relationship의 줄임말이다.

Entity와 Model 모두 아키텍처 패턴에 따라 각 도메인을 뜻한다면, 둘 중에 Entity는 좀 더 DB와 연관되어 있기 때문에, Data Layer로 보냈다.

만약 도메인 영역의 Model을 Entity라고 한다면, toJson이나 fromJson 같이 데이터를 포맷팅하는 부분을 사용하면 안된다. (의미론적으론 그렇다.)

Model과 Entity의 차이에 대해서 더 자세히 알고 싶다면, Difference between Model and Entity를 보자. 영어와 한국어로 작성했다.

참고로 Model Class를 만들 때 Class 이름을 UserModel 이렇게 하는 경우가 종종 있는데, 해당 언어나 프레임워크에서 명시하고 있지 않다면 그냥 User라고 적는 게 더 좋다.

저렇게 적는다고 프로그램의 속도가 빨라진다거나 그런 것은 아니지만, SW 공학에는 영어적 사고가 많이 들어가 있다. ASCII 코드부터가 영어 기반이고, Class와 Object의 관계만 하더라도 영어권 개발자에게는 Car, a Car라고 설명하면 매우 간단해진다.

그래서 가급적 영어적 사고에 어울리는 네이밍을 하는 게 맥락을 이해하는데 도움이 된다고 생각한다.

모델링은 이 모델을 어떻게 형식화 하는지를 뜻한다. (Graphics나 빅데이터 분야에선 다른 뜻으로 쓰인다.) 그래서 이 모델을 어떻게 작성하느냐(모델링을 어떻게 하느냐)에 따라 전반적인 코드가 달라진다.

모델은 서버-사이드에서 넘겨주는 Response와 같을 필요가 없다. 모델은 정말 애플리케이션만 신경쓰고 작성해야 한다. (Domain Layer에 있는 이유다.)

만약 백엔드 개발자가 “아몰랑 난 협의 안 할거야~ 내가 주는대로 받아~”를 시전한다고 해도 Entity를 통해 받으면 내가 원하는대로 Model을 구성할 수 있다.

모델에 대한 예시로는 대표적으로 User 모델을 들 수 있겠다. User의 정보를 담는다. User가 어떤 정보를 담고 있는지는 당연히 개발자만 아는 정보가 들어가면 안된다. 이렇듯 도메인 영역의 특징은 도메인에 대한 이해가 있는 사람이라면, 비개발자도 이해할 수 있어야 한다.

Repository

Domain 영역의 Repository는 도메인 영역의 데이터에 대한 인터페이스다.

도메인에 들어오는 데이터에 대해서 도메인이 주도권을 갖고 인터페이스를 만들겠다는 것이다.

즉, “내 interface는 이거니까 너네가 맞춰!”, “나 이 Domain 구현하려면 이 Data가 필요해!” 라고 하는 것이다.

이렇게 하면 Domain은 어떤 Data가 어디서 오는지, 어떻게 오는지 알 필요 없다. Business Logic에 집중할 수 있다.

클린 아키텍쳐의 핵심은 관심사의 분리인데, 그것을 명확하게 보여준다. 클린 아키텍처는 이렇게 각 모듈의 관심사를 분리함으로써 역할을 부여할 수 있다.

인터페이스와 구현체를 나눴을 때의 장점은 이것 말고도 더 있다.

여러 개발자가 협업 시 하나의 인터페이스로 다양한 구현체를 구현할 수 있고, 테스트 할 때도 더 편하다.

Use Case나 View Model은 테스트코드를 작성하거나 지원하는 Unit Test로 테스트할 수 있는데, Repository를 테스트하려면 Mock을 만들 필요가 있다. 이 때 인터페이스로 Mock Repositroy를 구현해서 만들면, 테스트하기도 편하고 나중에 배포 코드에서 제외 하기도 편하다.

Data Layer

Repository

Domain Layer에 있는 Repository 인터페이스의 구현이 작성된다.

간혹 Repository를 data source가 넘겨주는 데이터를 단순히 pass만 하게끔 사용하는 경우가 있는데 어떤 모듈이든 data를 pass만 하면 안된다. 그러면 해당 모듈이 있을 필요가 없다. 위에서 언급했듯 Repository는 Data Layer의 추상화를 위해 있다. 자세한 건 data source 순서에서 얘기하겠다.

Entity

Entity는 Data Source의 구조와 형식을 추상화하여 표현한 객체다. 애플리케이션에서 가장 기본적인 데이터 단위를 나타낸다.

Entity를 항상 만들어야 하는가, 아닌가에 대해서 고민을 많이 했다. 개발하다보면 Entity가 Model과 다를 일이 많이 없기 때문에, 굳이 중복되는 Entity를 만들어서 Mapper까지 많아지게 할 필요가 있나? 의문을 품게 되었다.

백엔드에선 한 앱에서 여러 요청을 처리해야 하기 때문에 Mapper가 많아지면 Mapping 과정이 비대해지면서 애플리케이션의 속도가 느려질 수 있다. 그래서 가능한 DAO를 만들지 않도록 하는 경우도 있기는 한다.

반면에, 클라이언트는 요청이 Data를 받아오는 것만 있는 게 아니라, 인터랙션이나 네비게이션, 센서 제어 등 다양하게 분산되어 있다. 게다가 일반적으로 한 디바이스에서 한 앱을 한 유저가 사용하다보니, Mapper가 많다고 Mapping 과정이 비대해져 속도 저하를 일으킬 일이 없다.

그런데 일관된 코드를 작성하자는 이유로 Entity를 작성한다면, Model 클래스를 상속받아서 구현하게 되면 Mapper 없이 모델타입으로 반환해 줄 수 있다.

아무튼… 원론적인 설명을 하자면 각 계층에서는 해당 계층에 맞는 데이터를 가져야 한다. Presentation Layer에선 ViewModel이, Domain Layer에선 Model이, Data Layer에선 Entity가 그 역할을 한다.

그래서 원론적으로는 만드는 게 맞긴 하다…

Data Layer에서 Entity를 사용하는 이유는 데이터 소스와의 상호작용에서 발생하는 문제를 해결하기 위해서이기 때문이다. 데이터 소스는 일반적으로 DB query, API 호출, File System 등을 포함한다. 이러한 데이터 소스는 특정 데이터 형식과 구조를 갖고 있을 수 있다. 때문에, 이러한 데이터를 직접 도메인 계층에서 처리하는 것은 복잡성과 유지보수성을 저하시키는 결과를 초래할 수 있다.

Entity는 이러한 데이터 소스로부터 받아들일 수 있는 데이터 형식을 추상화한다. Entity는 데이터 소스에서 가져온 데이터를 객체 형태로 저장하며, 이 객체를 데이터 소스에서 쉽게 읽고 쓸 수 있는 형식으로 변환하는 책임을 갖는다. 따라서, 데이터 소스와의 상호작용은 Data Layer의 Entity를 통해 이루어지게 된다. 이를 통해 데이터 소스와의 상호작용을 효과적으로 추상화하고, 도메인 레이어에서 데이터 소스와의 직접적인 상호작용을 방지할 수 있다.

Mapper (Translater/Translator)

Entity로부터 받은 데이터를 처리해서 모델로 반환해주는 모듈이 Mapper다. Translater나 Translator라고도 한다.

보통 선언은 별도로 하고 Repository 구현체 안에서 생성해서 사용한다. 평소에는 필요할 때만 드물게 사용하는 모듈이다.

그러나 개발일정이 빡빡하고, 빠른 구현이 최우선일 때는 무지성으로 사용하게 된다. ‘선 설계 → 후 구현’하지 못하고, ‘선 구현 → 후 나중에 생각’ 할 때 자주 등장한다. 일단 데이터를 어떻게든 Model에만 전달해주기만 하면 도메인 영역부터는 로직에 문제가 있지 않는 이상 정상동작은 하기 때문에…

그리고 백엔드 개발자의 수준이 심각해서 Response를 가변적으로 뿌려주는데, 내 코드는 오염 당하고 싶지 않을 때도 유용하게 쓰인다.

예를들면 같은 데이터인데 어떤 건 boolean으로 true라고 보내고 어떤 건 String으로 “true”를 보낸다거나… 어떤 건 키 값을 gender라고 보내고 어떤 건 sex라고 보내는 경우나… 이렇게 Response에 각종 부비트랩을 설치 해놓는 경우가 있다.

“그런 사람이 있어?” 싶겠지만, 난 겪어본 적이 있다… 그리고 이정도 수준이면 대화만 걸어도 “왜 백엔드 탓하세요?” 라고 하는 분들이기 때문에 협업이 불가능하다. (일정만 아니었으면 백엔드도 내가 개발하고 싶은 경우…)

그 외에도 서버 사이드와 클라이언트 사이드 사이의 도메인 용어가 통합되지 않았을 때도 사용하게 된다. 그래서 내가 입사하면 제일 먼저 하는 일이 전사적인 도메인 용어사전 저작이다.

할 얘기가 없으면 짧게 설명하면 되는데 뭐라도 적다보니 이상한 얘기를 해버렸다.

Data Source

어디서 데이터를 가져올 지 정하는 모듈이다.

Repository로부터 호출되며, 보통 local data source와 remote data source로 나뉜다.

local data source는 캐싱된 데이터나 로컬 스토리지, 또는 local DB에 있는 데이터를 불러올 때 사용한다.

예를들면, 자동 로그인을 구현할 때 Access Token을 불러와야 하는데, 그럴 때 Secure Storage 같은 곳에서 값을 읽어 온다면 local data source에 해당 코드를 작성한다.

remote data source는 local이 아닌 외부로부터 데이터를 받아올 때 사용한다. 대표적인 예가 API를 호출해서 서버로부터 data를 가져올 때다.

실제로 개발하다 보면 상황에 따라 data source를 다양한 방법으로 사용하게 된다.

local data source의 결과에 따라 remote data source를 타야할 수도 있고(local에서 불러온 Access Token이 만료된 경우), 그 반대의 경우도 있다.

그리고 한 repository 안에서 어떤 data는 local에서, 어떤 data는 remote에서 가져와서 구성해야하는 경우도 있다.

그래서 Repository가 단순히 data source가 주는 데이터를 패스하는 역할만 하는 게 아니라고 한 것이다.

마무리

글의 내용이 길다. 최대한 개인적인 경험이나 노하우를 담아내보려 하다보니 길어졌다.

최대한 Why에 대해 설명하려고 했다. 이유 없이 개발서적들만 보고 따라하는 것은 의미가 없다. 개발서적간 상충되는 내용들도 많다. 따라서 개발서적들은 참고만하고 그 내용을 실무에 녹이는 게 중요하다.

그래서 ‘마틴 파울러는 이렇게 말했고요, 로버트 마틴은 이렇게 말했고요, 누구는 이렇게 말했어요’가 아니라, 실무에서 내가 사용하면서 Why가 충족되는 부분들을 작성하려고 했다. 그 이유들이 주니어 개발자들에게 잘 전달되었으면 한다.

Flutter 강의들을 보면 대부분 초급자 강의는 기능구현, 중급자 강의는 firebase, 고급자 강의는 상태관리에 치중해 있는데, Flutter 아키텍처에 대한 고민이 있는 분들에게 도움이 되었으면 한다.

아래 깃허브는 이 글에 해당하는 File Structure example이다. 포킹해서 가져가서 써도 좋다. (가져갈 때 깃허브 스타가 눌려있다면 성취감이 생길 것 같아요^^)

이 글이 아키텍처 개념을 잘 모르거나, 잘못 된 개념을 갖고 있는 사람에게는 많은 도움이 되었을거라 생각한다.

글 잘 읽었으면 Clap과 Follow 부탁드립니다 :)

--

--

Doohyeon Kim
Doohyeon.kim

Developer, SW Engineer, Product Manager. Expert for startup company.