💡Your Code tell the what the app can do
Last article, we already learn how to create a use case specs. In this article, we will learn how to convert a use case specs to the code. I will bring the previous use case specs below :
Load Transactions From Cache Use Case
Input:
- no data needed
Primary course:
- Execute “Load Transactions” command with above data.
- System retrieves transaction data from cache.
- System creates transaction from cached data.
- System delivers transactions.
Retrieval error course (sad path):
- System delivers error.
Empty cache course (sad path):
- System delivers no transactions.
First, we define the high level things that the use case can do, from the use case specs. The use case can delivers list of transaction and an error, without any input. In Swift, we can represent the code like this :
protocol LoadTransactionsUseCase {
func execute(completion: @escaping (Result<[Transaction], Error>) -> Void)
}
Second, we can start to create the real implementation of the use case, conform to the protocol. Because of the use case needs to talk to the cache, then we need to depends on a cache manager. Assumed that we have the cache protocol or class :
class LoadTransactionsFromCacheUseCase: LoadTransactionsUseCase {
private let cacheStore: TransactionCacheStore
init(cacheStore: TransactionCacheStore) {
self.cacheStore = cacheStore
}
func execute(completion: @escaping (Result<[Transaction], Error>) -> Void) {
cacheStore.loadTransactions { result in
switch result {
case .success(let transactions):
completion(.success(transactions))
case .failure(let error):
completion(.failure(error))
}
}
}
}
Here, we can see that the LoadTransactionsFromCacheUseCase
will talks to cache to load the transactions, and delivers the transactions result or error result, just like what the use case specs wants, and just like what the BDD specs wants.
We can create each of the business logic specs into the use cases component, so that the code will improved from the readability, understandability, reusability, extendability, and of course, testability!.
With this approach, we will end up with clear separation of the business logic with another layer. And the project will beautifully explain itself what it can do, just by looking the folders :
SimpleApp // Project
**> UseCases // Folder or Group
> LoginUseCase.swift
> RegisterUseCase.swift
> LoadTransactionsFromRemoteUseCase.swift
> LoadTransactionsFromCacheUseCase.swift
> DeleteAllTransactionsUseCase.swift
> UpdateTransactionUseCase.swift**
> ...
> Models // Folder or Group
> User.swift
> Transaction.swift
> ...
> Network // Folder or Group
> TransationService.swift
> AuthService.swift
> ...
> UI // Folder or Group
> LoginViewController.swift
> TransactionsViewController.swift
> ...
The UseCases
folder, or group, tells anyone in the project that the app can do Login
, DeleteAllTransactions
, etc.
Another benefit of this is to be able to have decoupled business logic from any other components. Example is separated Login logic from the login view. In this case, we can tests the logic in isolation, without depending on the UI or UI framework.
Conclusion
The use case component can helps the developers to understand the code better, by creating a reusable business logic, that actually, the value of the app, separated from any other fragile component or layer, such as UI, database, networking, etc. This use case will be reused across application, such is in the View, or ViewModel, or in the Presenter. Also, we can create independent business logic tests for each use case components! So, this components is really useful.
References :
Object Oriented Software Engineering – A Use Case Driven Approach (Ivar Jacobson)
Robert C Martin – Clean Architecture and Design (https://www.youtube.com/watch?v=Nsjsiz2A9mg)