Swift | A use cases approach.
Talking with a colleague about how to write and implement a use case, I decided to show you this approach and learn together about it.
Firstly, What is a use case? What’s the reason we need it for? How can we implement it? What about the testing? Let’s jump into answering all these questions from an Protocol Oriented Programming perspective using Swift.
Clean Architecture says:
The use cases orchestrate the flow of data to and from the entities, and direct those entities to use their Critical Business Rules to achieve the goals of the use case.
However, I would like to venture and do something else with them, giving a different but similar perspective for a use case, which is:
A use case solves one and only one particular task, solving all their dependencies in order to can be executed.
As you can see with this definition, we can solve any particular task and not only business rules; Therefore, this give us a powerful advantage for testing and implement use case around the app, for sure, following the next approach.
In order to demonstrate the approach, we are going to solve an easy task as set data in some storage and a second example for ...
Initially, we must create our protocol, specifying the task to solve:
protocol setUserInfoIntoStorageUseCase {
func execute(info: [String: String]) -> Void
}
We can notice that our protocol is very descriptive about the task to solve, so, the next step is create our implementation:
// 1.
final class SetUserInfoIntoStorageImp {}// 2.
extension SetUserInfoIntoStorageImp: SetUserInfoIntoStorageUseCase {
func execute(info: [String: String]) -> Void {
// store info
}
}
- First, we create the concrete implementation for our protocol, which is going to set the user info into UserDefaults.
- Second, we have to extend the protocol in our class implementation in order to execute our use case.
Now something really important that we must consider are the dependencies and how we are going to inject, so, for that we can do this:
final class SetUserInfoIntoStorageImp { // 1.
struct Dependencies {
// 2.
var storage: MyStorage = MyStorageImp()
} //3.
let dependencies: Dependencies // 4.
init(dependencies: Dependencies = .init()) {
self.dependencies = dependencies
}}extension SetUserInfoIntoStorageImp: SetUserInfoIntoStorageUseCase {
func execute(info: [String : String]) {
dependencies.storage.set(info, forKey: Self.kUserKey)
}
}
- We created a struct Dependencies, which containing all the dependencies that our class needs to works.
- Here we are adding the storage for save the data.
- Property of dependencies for the class.
- The constructor function of the class, it’s the way as we are going to inject our dependencies, this is great for our tests and custom scenarios.
Remember that the dependencies can be resolved by themselves and if you need to send something in execution time, you should send it as parameter in our execute function.
And that’s it, we can use this use case across our app in order to set user info wherever we need it, for sure, we should inject this use case in our dependencies of viewModel, interactor or whatever pattern you are using.
The dependencies struct approach can be use in any class that you want in order to know what are the dependencies of that class and again, for testing.
To finish, let’s talk about the tests, as I mentioned is really easy, look:
Conclusion
Let’s remember single responsibility of SOLID, which we are achieving with this approach and is really useful when we want to test it, or to apply a specific scenario when the business rules changes without any impact on thoe who are using it 😉.
Be free to tell me your thoughts ❤️
Here’s the example project: https://github.com/LeonardoDzo/UseCaseExampleSwift