|
1 | 1 | # ACInteractor |
2 | 2 | [](https://raw.githubusercontent.com/appcron/acinteractor/master/LICENSE) |
3 | 3 |
|
4 | | -Swift Package for a use case centric architecture as proposed by Robert C. Martin and others. |
| 4 | +Swift Package for a Use Case centric architecture as proposed by Robert C. Martin and others. |
5 | 5 |
|
6 | | -## Current Status |
7 | | -Work in progress … 🐳 |
| 6 | +## Overview |
| 7 | +ACInteractor is a Swift Package that supports a Use Case centric architecture and TDD in Swift projects. The basic idea is that one Use Case, and only one Use Case, is executed by a single class. As proposed by Robert C. Martin, these kind of classes are called Interactors. |
| 8 | +- Each Interactor has a Request model. |
| 9 | +- Each Interactor has a Response model. |
| 10 | +- Each Interactor has a Execute function that takes the Request model an returns the Response model. |
| 11 | + |
| 12 | +``` Swift |
| 13 | +class LoginViewController: UIViewController { |
| 14 | + ... |
| 15 | + func login() { |
| 16 | + let request = LoginIntactor.Request() |
| 17 | + |
| 18 | + request.email = "first.last@appcron.com" |
| 19 | + request.password = "1234" |
| 20 | + |
| 21 | + request.onComplete = { (response: LoginIntactor.Response) in |
| 22 | + self.userLabel.text = response.username |
| 23 | + } |
| 24 | + |
| 25 | + request.onError = { (error: InteractorError) in |
| 26 | + self.displayError(error.message) |
| 27 | + } |
| 28 | + |
| 29 | + Logic.executer.execute(request) |
| 30 | + } |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +Consumers, like ViewControllers, can easily execute **InteractorRequests** with the help of the **InteractorExecuter**. Therefore an initialized instance of each Interactor has to be registered on the **InteractorExecuter**. Interactors should be stateless, since all Requests of a given Interactor are handled by the same instance of that Interactor. |
| 35 | + |
| 36 | +ACInteractor adds no constraints to dependency management. It's up to you how initialize the Interactor instances. I'd recommend [Dependency Injection with a custom initializer](https://www.natashatherobot.com/swift-dependency-injection-with-a-custom-initializer/). More details can be found at the section "Dependency Injection". |
| 37 | + |
| 38 | +ACInteractor was build with TDD in mind. Each Interactor has a single execution function, a defined request and response, a stateless implementation and injected dependencies. This helps writing isolated Unit Tests for each Interactor. See section "Unit Testing" for more details about writing tests. |
| 39 | + |
| 40 | +### License |
| 41 | +[Apache License, Version 2.0, January 2004](http://www.apache.org/licenses/LICENSE-2.0) |
| 42 | + |
| 43 | +### Must watch |
| 44 | +If you're new to *Clean Code* and **TDD**, [Robert C. Martin's Talk at Ruby Midwest](https://www.youtube.com/watch?v=WpkDN78P884) is highly recommended. It's fun to watch and worth the time. The entire architecture around ACInteractor and the goals of it are based on this talk. |
| 45 | + |
| 46 | +### Content |
| 47 | +ACInteractor currently consists of the following files: |
| 48 | +``` |
| 49 | +ACInteractor |
| 50 | +├── LICENSE |
| 51 | +├── README.md |
| 52 | +├── Sources // The source files |
| 53 | +├── Tests // The unit test files |
| 54 | +└── Xcode // The Xcode project to run the test |
| 55 | +``` |
| 56 | + |
| 57 | +## Setup |
| 58 | +Since Swift 3 and Swift Package are not available with an stable Xcode release, just add the Files of the **Sources** folder to your project. |
| 59 | + |
| 60 | +### via Git Submodule |
| 61 | +At the moment it's recommended to add the entire ACInteractor project as a [Git Submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to your repository. |
| 62 | + |
| 63 | +0. Navigate to the root directory of your Git repository in Terminal. |
| 64 | +0. Run the following command: |
| 65 | + ``` |
| 66 | + git submodule add https://github.com/AppCron/ACInteractor.git |
| 67 | + ``` |
| 68 | +0. Add the files of the **Sources** folder to your project. |
| 69 | + |
| 70 | +### via Download |
| 71 | +Alternatively you can just download the files directly from Github and add the files of the *Sources* folder to your project. |
| 72 | + |
| 73 | +## Writing Intactors |
| 74 | +``` Swift |
| 75 | +class LoginIntactor: Interactor { |
| 76 | + class Request: InteractorRequest<Response> {...} |
| 77 | + class Response {...} |
| 78 | + |
| 79 | + func execute(request: Request) {...} |
| 80 | +} |
| 81 | +``` |
| 82 | +Let's write our first Interactor. It should handle a user login and accepts all logins as long as the user provides a password. |
| 83 | + |
| 84 | +Each Interactor has to implement the **Interactor** protocol, which requires the Interactor to have an **execute()** function. Since each Interactor handles exactly one Use Case, only one execute function is necessary. |
| 85 | + |
| 86 | +Usually it contains two nested classes. The Request and the Response. |
| 87 | + |
| 88 | +### The Request |
| 89 | +``` Swift |
| 90 | +class LoginIntactor: Interactor { |
| 91 | + class Request: InteractorRequest<Response> { |
| 92 | + var email: String? |
| 93 | + var password: String? |
| 94 | + } |
| 95 | + ... |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +The Request contains all the parameters that are necessary to execute it. In our case the email and the password. |
| 100 | + |
| 101 | +It has to be derived from the generic `InteractorRequest<ResponseType>` and should by typed with the **ResponseType** of the Interactor. This will help us execute the Request later. See section "The Execute Function" for details. |
| 102 | + |
| 103 | +### The Response |
| 104 | +``` Swift |
| 105 | +class LoginIntactor: Interactor { |
| 106 | + ... |
| 107 | + class Response { |
| 108 | + var username: String? |
| 109 | + var sessionToken: String? |
| 110 | + } |
| 111 | + ... |
| 112 | +} |
| 113 | +``` |
| 114 | +The Response is the result of the Use Case. In our example it contains the username and a session token. |
| 115 | + |
| 116 | +The Response can be of any type, as long as it is the same type the **InteractorRequest** is typed with. It can even be common type like **String** or **Bool**. |
| 117 | + |
| 118 | +### The Execute Function |
| 119 | +``` Swift |
| 120 | +class LoginIntactor: Interactor { |
| 121 | + ... |
| 122 | + func execute(request: Request) { |
| 123 | + if (request.password?.characters.count > 0) { |
| 124 | + |
| 125 | + // Create Response |
| 126 | + let response = Response() |
| 127 | + |
| 128 | + // Set values |
| 129 | + response.username = request.email?.componentsSeparatedByString("@").first |
| 130 | + response.sessionToken = "123456910" |
| 131 | + |
| 132 | + // Call completion handler |
| 133 | + request.onComplete?(response) |
| 134 | + |
| 135 | + } else { |
| 136 | + // Do error handling |
| 137 | + } |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | +The Execute Function is the part where the business logic of the Use Case is implemented. It takes the **Request** as an argument and returns the **Response** to the completion handler. |
| 142 | + |
| 143 | +The **onComplete** closure is already defined in the **InteractorRequest** and already typed with the Response. That's why the **Request** has to be a subclass of **InteractorRequest**. |
| 144 | + |
| 145 | +## Registering Interactors |
| 146 | +``` Swift |
| 147 | +class Logic { |
| 148 | + |
| 149 | + static let executer = InteractorExecuter() |
| 150 | + |
| 151 | + static func registerInteractors() { |
| 152 | + let loginInteractor = LoginIntactor() |
| 153 | + let loginRequest = LoginIntactor.Request() |
| 154 | + |
| 155 | + executer.registerInteractor(loginInteractor, request: loginRequest) |
| 156 | + } |
| 157 | + |
| 158 | +} |
| 159 | +``` |
| 160 | +To enable consumers, like ViewControllers, to easily execute **InteractorRequests** it's necessary to register the corresponding Interactor first. |
| 161 | + |
| 162 | +In our case we use a helper class called **Logic**. It contains a static function **registerInteractors()** that creates a **LoginInteractor** instance an registers it with the corresponding **Request** at the **InteractorExecuter**. |
| 163 | + |
| 164 | +Besides that, the **Logic** contains a static property with a global **Executer** instance. This makes it easier for the consumer to access the given instance. |
| 165 | + |
| 166 | +## Executing Requests |
| 167 | +``` Swift |
| 168 | +class LoginViewController: UIViewController { |
| 169 | + ... |
| 170 | + func login() { |
| 171 | + let request = LoginIntactor.Request() |
| 172 | + |
| 173 | + request.email = "first.last@appcron.com" |
| 174 | + request.password = "1234" |
| 175 | + |
| 176 | + request.onComplete = { (response: LoginIntactor.Response) in |
| 177 | + self.userLabel.text = response.username |
| 178 | + } |
| 179 | + |
| 180 | + Logic.executer.execute(request) |
| 181 | + } |
| 182 | +} |
| 183 | +``` |
| 184 | +To execute an *Request* you can simply call the **executeMethod()** on the **InteractorExecuter**. Just make sure you have registered the that Interactor class with its Request on the same **InteractorExecuter** instance. |
| 185 | + |
| 186 | +In our example this instance is stored in the static **executer** property on the Logic class, as shown above. |
| 187 | + |
| 188 | +## Error Handling |
| 189 | +Basic error handling is already part of ACInteractor. Each **InteractorRequest** has an **onError** property that stores a closure for the error handling. |
| 190 | + |
| 191 | +### On the Interactor Implementation |
| 192 | +``` Swift |
| 193 | +class LoginIntactor: Interactor { |
| 194 | + ... |
| 195 | + func execute(request: Request) { |
| 196 | + if (request.password?.characters.count > 0) { |
| 197 | + ... |
| 198 | + } else { |
| 199 | + let error = InteractorError(message: "Empty password!") |
| 200 | + request.onError?(error) |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | +``` |
| 205 | + On the Interactor all you have to do is create an instance of **InteractorError**, supply an error message and call the error handler with the error object. |
| 206 | + |
| 207 | +### On the Execute Call |
| 208 | +``` Swift |
| 209 | +class LoginViewController: UIViewController { |
| 210 | + ... |
| 211 | + func login() { |
| 212 | + let request = LoginIntactor.Request() |
| 213 | + |
| 214 | + ... |
| 215 | + |
| 216 | + request.onError = { (error: InteractorError) in |
| 217 | + self.displayError(error.message) |
| 218 | + } |
| 219 | + |
| 220 | + Logic.executer.execute(request) |
| 221 | + } |
| 222 | +} |
| 223 | +``` |
| 224 | +When executing an request make sure to set an closure for error handling on its **onError** property. |
| 225 | + |
| 226 | +## Extended Completion Handlers |
| 227 | +It is not necessary to use the default completion and error handlers. You can add custom completion closures, like **onUpdate(UpdateResponse)**, or custom error closures, like **onExtendedError(ExtendedError)**. This can be either done by adding them as properties to specific request or by subclassing **InteractorRequest**. |
| 228 | + |
| 229 | +**Be aware when adding custom error handlers.** ACInteractor uses the default **onError** closure to report internal errors, like not finding an Interactor for a given request. See section "Troubleshooting" for Details. |
| 230 | + |
| 231 | +## Asynchronous Requests |
| 232 | +Since ACInteractor uses closures for result handling, you can easily switch between synchronous and asynchronous behavior without the need to adjust your Interactor's interface. |
| 233 | + |
| 234 | +When making asynchronous callbacks from your Interactor, it's recommend to dispatch your **onCompletion** and **onError** closure calls to the thread the Interactor's **execute()** method has been called from. Whether to use background threads and when and how to dispatch back to the caller's thread is a technical detail of your Interactor, that should be hidden from the caller. It is not the responsibility of a ViewController to dispatch your asynchronous stuff back on the main thread. Maybe it can by done with `dispatch_async`, maybe `dispatch_sync` is necessary, the ViewController can't know. |
| 235 | + |
| 236 | +## Dependency Injection |
| 237 | +In a real world example the LoginInteractor would call a webservice to verify the login credentials and store the session token in local database. Since we don't want all these technical details in our Interactor we encapsulate them in two separate classes. |
| 238 | +``` Swift |
| 239 | +protocol LoginWebservicePlugin |
| 240 | +class LoginHttpWebservicePlugin: LoginWebservicePlugin |
| 241 | + |
| 242 | +protocol UserEntityGateway |
| 243 | +class UserCoreDataEntityGateway: UserEntityGateway{ |
| 244 | +``` |
| 245 | +The **LoginWebservice** handles the webservice calls and calls the onCompletion closure once finished. |
| 246 | + |
| 247 | +The **UserEntityGateway** has functions to create new users and to save users. It is responsible for creating and saving new entities. So our **LoginInteractor** does not need to now how we persist data. It can be a CoreData-, a Realm- or just a In-Memory-Database. |
| 248 | + |
| 249 | +Additionally it is useful to define a protocol for each dependency. This let's us replace them easily with mocks when writing unit tests. |
| 250 | + |
| 251 | +### On the Interactor Implementation |
| 252 | +``` Swift |
| 253 | +class LoginIntactor: Interactor { |
| 254 | + private let webservicePlugin: LoginWebservicePlugin |
| 255 | + private let userEntityGateway: UserEntityGateway |
| 256 | + |
| 257 | + init(webservicePlugin: LoginWebservicePlugin, userEntityGateway: UserEntityGateway) { |
| 258 | + self.webservicePlugin = webservicePlugin |
| 259 | + self.userEntityGateway = userEntityGateway |
| 260 | + } |
| 261 | + |
| 262 | + func execute(request: Request) { |
| 263 | + ... |
| 264 | + self.webservicePlugin.doStuff() |
| 265 | + self.userEntityGateway.doStudd() |
| 266 | + ... |
| 267 | + } |
| 268 | +} |
| 269 | +``` |
| 270 | +On the Interactor we need a custom **init** function that takes the dependencies as parameters and stores them in private properties. We can then use them in the *execute* function. |
| 271 | + |
| 272 | +### Register with Dependencies |
| 273 | +``` Swift |
| 274 | +class Logic { |
| 275 | + |
| 276 | + static let executer = InteractorExecuter() |
| 277 | + |
| 278 | + static func registerInteractors() { |
| 279 | + let webservicePlugin = LoginHttpWebservicePlugin() |
| 280 | + let userEntityGateway = UserCoreDataEntityGateway() |
| 281 | + |
| 282 | + let loginInteractor = LoginIntactor(webservicePlugin: webservicePlugin, userEntityGateway: userEntityGateway) |
| 283 | + let loginRequest = LoginIntactor.Request() |
| 284 | + |
| 285 | + executer.registerInteractor(loginInteractor, request: loginRequest) |
| 286 | + } |
| 287 | +} |
| 288 | +``` |
| 289 | +When registering the **LoginInteractor** we use the custom **init** method a supply the matching implementations. At this point you can easily replace the concrete implementations of the Plugin and the Gateway with other implementations as long as they conform to the specified protocols. For example the **UserCoreDataEntityGateway** could be replaced with a **UserInMemoryGateway**. |
| 290 | + |
| 291 | +### On the Execute Call |
| 292 | +``` Swift |
| 293 | +class LoginViewController: UIViewController { |
| 294 | + ... |
| 295 | + func login() { |
| 296 | + let request = LoginIntactor.Request() |
| 297 | + ... |
| 298 | + Logic.executer.execute(request) |
| 299 | + } |
| 300 | +} |
| 301 | +``` |
| 302 | +Nothing changes :) The Interactor is still executed with the same Request on the **InteractorExecuter**. This means you can easily refactor Interactors behind the scenes and extract technical details in EntityGateways and Plugins without breaking the API that is used by the caller. |
| 303 | + |
| 304 | +## Unit Testing |
| 305 | +ACInteractor was build with TDD in mind. Each Interactor has a single execution function, a defined request and response, a stateless implementation and injected dependencies. This helps writing isolated Unit Tests for each Interactor. |
| 306 | + |
| 307 | +If you use the Dependency Injection approach described above you can easily mock the dependencies in your Unit Tests. For example this means you don't need to execute a real webservice call or setup a real database to run your tests. The mocks can just return the values that support you current test. Mocks can also help you to test edge cases that would be otherwise hard to |
| 308 | +simulate, like a long taking webservice request or a full database. |
| 309 | + |
| 310 | +## Troubleshooting |
| 311 | +0. I can not register my Interactor at the InteractorExecuter. I get an Compiler Error. |
| 312 | + * Make sure the `interactor` implements the `Interactor` protocol |
| 313 | + * Make sure the `request` is a subclass of `InteractorRequest<Response>` and is correctly typed. |
| 314 | + * Make sure the `request` is an initialized object instance. |
| 315 | + |
| 316 | +0. Calling execute on the InteractorExecuter does not call the execute method of my Interactor. |
| 317 | + * Make sure you have registered the Interactor with the corresponding InteractoRequest by calling the `registerInteractor()` function on the `InteractorExecuter`. |
| 318 | + * Make sure you have called the `registerInteractor()` and the `execute()` function on the **same** instance of `InteractorExecuter`. |
| 319 | + * Make sure you have set an `onError` closure on the `request`. It might provide additional details in the error message about what went wrong. |
| 320 | + |
| 321 | +## Credits |
| 322 | +Special thanks from [Florian Rieger](https://github.com/florieger) to the people that helped getting ACInteractor together. |
| 323 | +- [Andreas Hager](https://github.com/casid) For inspiring me with the wonderful [JUsecase](https://github.com/casid/jusecase) and helping with an initial Swift version of it. |
| 324 | +- Victoria Gärtner For evaluating the Interactor-based architecture in several Objective-C and Swift projects. |
| 325 | +- [Aleksandar Damjanovic](https://github.com/codejanovic) For introducing me to the Github workflow. |
| 326 | + |
| 327 | +Happy Coding 🐳 |
0 commit comments