iOS Clean Architecture를 디자인하였고 해당 architecture를 통해 TODO list를 저장하는 샘플앱을 만들어 보았다.

github : https://github.com/ezero9/iOSCleanArchitecture

1. Architecture 

[그림 1. Architecture]

  Architecture는 일반적으로 많이 사용되고 있는 3 Layer Architecture로 구성한다. 본 Architecture[그림 1]는 명확하게 정의된 레이어를 가지고 있으며, 각각의 레이어는 잘 정의되고, 통제되는 인터페이스를 통해 응집성 있는 서비스의 집합을 제공한다. 

 

  Object간의 dependency 관계는 IoC Container를 통해 injection받는다. 각 class들의 동작에대한 구성은 아래와 같다. [그림 2] 하위 레이어에서 상위 레이어로의 change propagation은 RxSwift나 delegation pattern, callback등을 통해 notify하는 방식으로 active하게 디자인한다.

 

[그림 2. 동작 구성]

2. IoC Container

[그림 3. IoC Container Class Diagram]

  일반적인 IoC Container 라이브러리들의 무거운 기능들은 전부 제외하고 DI, Singleton지원과 같은 필수적인 기능들만 구현하여 심플하게 제공한다. (Swift IoC 라이브러리가 없어서 직접 구현함)

 

Swift에서의 DI(Dependency Injection): https://develogs.tistory.com/21

2.1 Dependency Injection

  Object를 생성하고 각 object들간의 관계를 wiring하는 책임은 모두 IoC가 갖는다. client는 자신이 사용할 object instance를 IoC를 통해 DI받는다. 따라서 client에서는 IoC가 주는 object가 어떤 concrete class로 부터 생성이 되었는지 알필요 없으며, 정의된 interface를 통해 동작하기만 하면된다. 이는 object들간의 관계가 변경 전파 없이 같은 interface로 구현된 mock나 다른 class의 object instance로 언제든 대체될 수 있음을 의미한다. IoC를 사용하여 object들과의 관계를 설정할때 되도록이면 constructor injection을 사용하는게 좋다.

 

[Post기능과 관련된 object들의 의존성을 구성하는 부분]

 

  IoC(Inversion of Control, 제어의 역전)과 DI(Dependency Injection, 의존성 주입)에 대한 자세한 설명은 아래 링크에서 확인 할 수 있다.

제어의 역전(Inversion of Control, IoC) 이란?: https://develogs.tistory.com/19

2.2 Singleton 지원

  Singleton이 필요할때는 Singleton Pattern으로 class로 디자인하지 않고 IoC에 singleton으로 register한다. clinet에서는 IoC를 통해 필요한 object가 injection되면 해당 object가 singleton인지 일반 object인지 모르는 상태에서 사용되어야 한다. 

 

Singleton Pattern으로 디자인 하지 않은 이유: https://develogs.tistory.com/8

3.  Presentation Layer

[그림 4. BaseViewController와 BaseViewModel Class Diagram]

  Presentation Layer는 [그림 2]에서 보았듯이 MVVM Design Pattern을 사용한다. BaseViewController는 toast, popup, progress같은 ViewController의 공통 로직이 구현되어 있으며, BaseViewModel도 해당 ViewController와 interaction을 위한 공통 로직이 구현되어 있다. ViewController는 controller의 역할보다는 하나의 View로 보고 있으며 거기에 더해 View와 ViewModel과의 glue code가 존재하는 곳이다.

(재사용 되는 뷰가 없으면 오히려 mvvm의 기반코드를 작성하는게 이점대비 비용이 더 클수도 있다. View들의 도메인 요구사항에 따라 MVVM, MVP, MVC, VIPER등 무엇을  쓸지 design을 잘 결정하자.)

3.1 Data Binding

[그림 5. DataBinding]

  View와 ViewModel은 RxSwift, RxCocoa 라이브러리를 사용하여 two way binding을 한다. ViewModel은 binding해야 될 데이터를 정의해야하며, DataBinding Interface를 통해 바인딩 전략을 구현해야한다.

 

[AllPostViewModel에서 DataBinding 전략을 구현한 부분]

 

[AllPostViewController에서 AllPostViewModel과 binding하는 부분]

3.2 Routing Mechanism

[그림 6. Navigator Class Diagram]

  MVVM에서는 controller가 없기 때문에 Routing Mechanism이 필요한데, 그 역할은 Navigator가 담당하게 된다. Navigator는 자신이 이동해야 될 ViewController 혹은 Navigator를 IoC를 통해 전달 받아 정해진 interface를 통해 동작하게 된다. 

 

[BaseNavigator에서 View를 받아서 present하는 부분]

4. Domain Layer

[그림 7. 상위 레이어에서 requirement를 정의하고 하위 레이어에서 concrete를 구현]

  Application Logic을 담당하는 Layer임으로 Domain Layer는 반드시 다른 레이어들과 독립성을 유지해야한다. 다른 레이어들의 dependency가 존재하면 안되기 때문에 DIP(Dependency Inversion Principle)가 지켜져야 하는 레이어이다. Domain Layer에서는 필요한 requirements를 정의하고 Psersistent Layer에서는 이 정의된 requirement interface를 이용해 concrete class를 구현한다.

 

  여기서 다루는 내용중에 Domain Layer의 isolation이 가장 중요하다고 할 수 있다. 위에서 말했듯이 Presentation Layer에서의 view design pattern은 View 요구사항에 따라 바뀌겠지만 domain layer의 isolation은 반드시 지키는게 좋다.

5. Persistent Layer

[그림 8. Dependency Inversion 되어 구현되고 있는 Persistent Layer]

  Persistent Layer는 data source에 대한 decoupling을 위한 layer이며 Domain Layer에서 정의된 requirement interface를 통해 concrete class를 구현한다. Persistent Layer의 object들도 마찬가지로 IoC를 통해 Domain Layer에 injection되며, 여기서 injection된 object는 다른 구현체로 변경된다 하더라도 절대로 상위 레이어로 변경 전파가 이루어지지 않아야 한다.

6. Error Handling

[그림 8. TraceError Class Diagram]

  error stack trace를 확인하기 위해 ErrorTrace라는 최상위 error를 정의했다. ErrorTrace를 상속받아 명확한 이름을 가진 Error를 정의하고 예외는 do try catch 구문으로 처리한다. 

 

[그림 9. Error Wrapping]

  error는 하위 레이어에서 상위 레이어로 thorw될 때, 상위 레이어에서 requirement로 정의된 좀 더 구체적인 이름을 가진 error로 wrapping하여 올려 준다.

 

getStackTrace()을 찍어보면 아래와 같이 layer별로 명확한 이름으로 정의된 error의 stack trace를 확인 할 수 있다.

🔶[ERROR TRACE]🔶

🔶 iOSCleanArchitecture/Scenes/AllPost/AllPostViewModel.swift 50: viewDidLoad() - PostInterface.LoadFailError: not found Post Data. 

    🔶iOSCleanArchitecture/Domain/Model/AllPostModel.swift 42: parsing() - PostInterface.ParseFailError: parsing failed. 

        🔶 iOSCleanArchitecture/Repository/PostNetworkDAO.swift 35: network() - PostNetworkDAO.NetworkError: 404 error   

 

Error handling - 우아하게 실패하는 방법 : https://develogs.tistory.com/9

'Architecture & Design Pattern' 카테고리의 다른 글

제어의 역전(Inversion of Control, IoC) 이란?  (16) 2019.12.08
if문을 없애는 디자인  (0) 2019.11.17
Data Access Object Pattern  (0) 2019.09.07
Singleton Pattern의 함정  (0) 2019.07.14

+ Recent posts