Skip to content

Commit eeee954

Browse files
author
Florian Rieger
authored
Merge pull request #5 from AppCron/initial-documentation
Initial documentation
2 parents aae62f4 + e4a59fb commit eeee954

File tree

1 file changed

+323
-3
lines changed

1 file changed

+323
-3
lines changed

README.md

Lines changed: 323 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,327 @@
11
# ACInteractor
22
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/appcron/acinteractor/master/LICENSE)
33

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.
55

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

Comments
 (0)