Introduction to Clean Architecture
This is a structure that helps to separate functions across layers. Through this model, there is a lot of support through thinking carefully and effectively, recognizing mismatch between Use Cases and Entities, setting direction in the system. It also aims for maximum independence of any library or tool, so that it is suitable for testing and replacing them.
Apply with MVVM
- Domain Layer: Entites + Use Cases + Gatway Protocols
- Data Layer: Gateway Implementations + API (Network) + Database
- Presentation Layer: ViewModels + Views + Navigator + Scene Use Cases
Solution
Go into details
Domain class
Entities
Contains Business logic. The most important class, where you do problem solving – purpose when building the app. An entity can be an object with methods, or it is a collection of struct and functions. It doesn’t matter, entities can be used by many different ways in the same application. Entites can be simply structures data:
1 2 3 4 5 6 | <span class="token keyword">struct</span> <span class="token builtin">Product</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> id <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">""</span> <span class="token keyword">var</span> price <span class="token operator">=</span> <span class="token number">0.0</span> <span class="token punctuation">}</span> |
Use Cases
There are no apploaction-specific rules yet. It encapsulates and deploys all use cases in the system. Use cases structure data flows to and from entites, and direct those entities to Critical Business Rules -> the purpose of the use cases. UseCases are protocols, to do specific things like:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">protocol</span> <span class="token builtin">GettingProductList</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> productGateway <span class="token punctuation">:</span> <span class="token builtin">ProductGatewayType</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">extension</span> <span class="token builtin">GettingProductList</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">getProductList</span> <span class="token punctuation">(</span> dto <span class="token punctuation">:</span> <span class="token builtin">GetPageDto</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Observable</span> <span class="token operator"><</span> <span class="token builtin">PagingInfo</span> <span class="token operator"><</span> <span class="token builtin">Product</span> <span class="token operator">></span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> productGateway <span class="token punctuation">.</span> <span class="token function">getProductList</span> <span class="token punctuation">(</span> dto <span class="token punctuation">:</span> dto <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Gateway Protocols
Basically, the gateway is just an abstraction that will show the work that is implemented below. It can be a Data Store (pattern Repository), an API gateway, and so on. For example, with Database gateway there are methods to make application requests. However, do not try to hide all important rule rules through the gateway. All database queries must be relatively simple like the CRUD method, and of course some filters are also accepted.
1 2 3 4 5 6 | protocol ProductGatewayType { func getProductList(dto: GetPageDto) -> Observable<PagingInfo<Product>> func deleteProduct(dto: DeleteProductDto) -> Observable<Void> func update(_ product: ProductDto) -> Observable<Void> } |
Data class
The Data class contains Gateway implementations and 1 or more Data Stores. Gateway is the response to dispatch data from Data Stores. The Data Store can be online or offline (eg presistent database). The Data class depends only on the Domain class.
Gateway Implementations
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">struct</span> <span class="token builtin">ProductGateway</span> <span class="token punctuation">:</span> <span class="token builtin">ProductGatewayType</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">getProductList</span> <span class="token punctuation">(</span> dto <span class="token punctuation">:</span> <span class="token builtin">GetPageDto</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Observable</span> <span class="token operator"><</span> <span class="token builtin">PagingInfo</span> <span class="token operator"><</span> <span class="token builtin">Product</span> <span class="token operator">></span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">API</span> <span class="token punctuation">.</span> shared <span class="token punctuation">.</span> <span class="token function">getProductList</span> <span class="token punctuation">(</span> <span class="token constant">API</span> <span class="token punctuation">.</span> <span class="token function">GetProductListInput</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token builtin">map</span> <span class="token punctuation">{</span> <span class="token function">PagingInfo</span> <span class="token punctuation">(</span> page <span class="token punctuation">:</span> <span class="token number">1</span> <span class="token punctuation">,</span> items <span class="token punctuation">:</span> $ <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">deleteProduct</span> <span class="token punctuation">(</span> dto <span class="token punctuation">:</span> <span class="token builtin">DeleteProductDto</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Observable</span> <span class="token operator"><</span> <span class="token builtin">Void</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">update</span> <span class="token punctuation">(</span> <span class="token number">_</span> product <span class="token punctuation">:</span> <span class="token builtin">ProductDto</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Observable</span> <span class="token operator"><</span> <span class="token builtin">Void</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
UserDefaults
1 2 3 4 5 | <span class="token keyword">enum</span> <span class="token builtin">AppSettings</span> <span class="token punctuation">{</span> @ <span class="token function">Storage</span> <span class="token punctuation">(</span> key <span class="token punctuation">:</span> <span class="token string">"didInit"</span> <span class="token punctuation">,</span> defaultValue <span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token keyword">static</span> <span class="token keyword">var</span> didInit <span class="token punctuation">:</span> <span class="token builtin">Bool</span> <span class="token punctuation">}</span> |
APIs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <span class="token keyword">extension</span> <span class="token constant">API</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">getRepoList</span> <span class="token punctuation">(</span> <span class="token number">_</span> input <span class="token punctuation">:</span> <span class="token builtin">GetRepoListInput</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Observable</span> <span class="token operator"><</span> <span class="token builtin">GetRepoListOutput</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">request</span> <span class="token punctuation">(</span> input <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// MARK: - GetRepoList</span> <span class="token keyword">extension</span> <span class="token constant">API</span> <span class="token punctuation">{</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">GetRepoListInput</span> <span class="token punctuation">:</span> <span class="token builtin">APIInput</span> <span class="token punctuation">{</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> page <span class="token punctuation">:</span> <span class="token builtin">Int</span> <span class="token punctuation">,</span> perPage <span class="token punctuation">:</span> <span class="token builtin">Int</span> <span class="token operator">=</span> <span class="token number">10</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> params <span class="token punctuation">:</span> <span class="token builtin">JSONDictionary</span> <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token string">"q"</span> <span class="token punctuation">:</span> <span class="token string">"language:swift"</span> <span class="token punctuation">,</span> <span class="token string">"per_page"</span> <span class="token punctuation">:</span> perPage <span class="token punctuation">,</span> <span class="token string">"page"</span> <span class="token punctuation">:</span> page <span class="token punctuation">]</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> urlString <span class="token punctuation">:</span> <span class="token constant">API</span> <span class="token punctuation">.</span> <span class="token builtin">Urls</span> <span class="token punctuation">.</span> getRepoList <span class="token punctuation">,</span> parameters <span class="token punctuation">:</span> params <span class="token punctuation">,</span> requestType <span class="token punctuation">:</span> <span class="token punctuation">.</span> <span class="token keyword">get</span> <span class="token punctuation">,</span> requireAccessToken <span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">GetRepoListOutput</span> <span class="token punctuation">:</span> <span class="token builtin">APIOutput</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token punctuation">(</span> <span class="token keyword">set</span> <span class="token punctuation">)</span> <span class="token keyword">var</span> repos <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token builtin">Repo</span> <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">mapping</span> <span class="token punctuation">(</span> <span class="token builtin">map</span> <span class="token punctuation">:</span> <span class="token builtin">Map</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">mapping</span> <span class="token punctuation">(</span> <span class="token builtin">map</span> <span class="token punctuation">:</span> <span class="token builtin">map</span> <span class="token punctuation">)</span> repos <span class="token operator"><</span> <span class="token operator">-</span> <span class="token builtin">map</span> <span class="token punctuation">[</span> <span class="token string">"items"</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Map JSON Data to Domain Entities using ObjectMapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">import</span> <span class="token builtin">ObjectMapper</span> <span class="token keyword">extension</span> <span class="token builtin">Product</span> <span class="token punctuation">:</span> <span class="token builtin">Mappable</span> <span class="token punctuation">{</span> <span class="token keyword">init</span> <span class="token operator">?</span> <span class="token punctuation">(</span> <span class="token builtin">map</span> <span class="token punctuation">:</span> <span class="token builtin">Map</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function">mapping</span> <span class="token punctuation">(</span> <span class="token builtin">map</span> <span class="token punctuation">:</span> <span class="token builtin">Map</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> id <span class="token operator"><</span> <span class="token operator">-</span> <span class="token builtin">map</span> <span class="token punctuation">[</span> <span class="token string">"id"</span> <span class="token punctuation">]</span> name <span class="token operator"><</span> <span class="token operator">-</span> <span class="token builtin">map</span> <span class="token punctuation">[</span> <span class="token string">"name"</span> <span class="token punctuation">]</span> price <span class="token operator"><</span> <span class="token operator">-</span> <span class="token builtin">map</span> <span class="token punctuation">[</span> <span class="token string">"price"</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
CoreData Repositories
Map CoreData Entities to Domain Entitites and vice versa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import MagicalRecord protocol UserRepository: CoreDataRepository { } extension UserRepository where Self.ModelType == User, Self.EntityType == CDUser { func getUsers() -> Observable<[User]> { return all() } func add(dto: AddUserDto) -> Observable<Void> { guard let users = dto.users else { return Observable.empty() } return addAll(users) } static func map(from item: User, to entity: CDUser) { entity.id = item.id entity.name = item.name entity.gender = Int64(item.gender.rawValue) entity.birthday = item.birthday } static func item(from entity: CDUser) -> User? { guard let id = entity.id else { return nil } return User( id: id, name: entity.name ?? "", gender: Gender(rawValue: Int(entity.gender)) ?? Gender.unknown, birthday: entity.birthday ?? Date() ) } } |
So we have finished part 1, about combining the MVVM model and the Clean Architectures architecture. See you on part 2. Original article Mr. Tuan