Interactor
Introduction
An interactor is based in a design pattern called Command Pattern. A command has the exclusive and unique responsibility of performing one single operation. In other words, a command is an application's use case. Typically, an application will have many different interactor classes, each one responsible of executing atomically a different use case.
By having interactors, the business logic of your app is not going to be duplicated nor spread along the application layer. All business logic is going to be wrapped inside interactors.
As interactors classes have one single responsibility, all interactors will have one (and only one) public method that will be called execute
(or will start with this name).
For example, we could have an interactor that returns the current time:
- Kotlin
- Swift
- TypeScript
class CurrentTimeInteractor { operator fun invoke(): Date = Date()}
Composition
Interactors can perform from very easy to extremely complex operations. For this reason, it is a good practice to compose interactors in order to divide the reponsibility of each operation in its own interactor class.
For example, we could have an interactor that returns the time between now and a date:
- Kotlin
- Swift
- TypeScript
class ElapsedTimeSinceNowInteractor(val currentTimeInteractor: CurrentTimeInteractor) { operator fun invoke(date: Date): Long { val now = currentTimeInteractor() return now.time - date.time }}
Asynchrony
All the examples on this page show synchronous interactors. To read about asynchronous interactors (and threading), visit the page Threading & Asynchrony.
Default Interactors
In order to access the CRUD operations of a Repository
, there are interactors for each CRUD operation.
Default interators have one method called execute
(in Kotlin, it is using the invoke
operator) which contains the same parameters than the Repository functions, plus an optional Executor
.
Get
- Kotlin
- Swift
- TypeScript
- GetInteractor<T>- GetAllInteractor<T>
Put
- Kotlin
- Swift
- TypeScript
- PutInteractor<T>- PutAllInteractor<T>
Delete
- Kotlin
- Swift
- TypeScript
- DeleteInteractor
Important
Note the naming conventions used in Swift: Interactor
is a struct used to define a namespace and all default interactors are nested classes defined within that struct (namespace).
Swift Notes
For each interactor there are two sub-types: Interactor.XXX
and Interactor.XXXByQuery
. The ByQuery
interactors are the ones equivalent to Android XXXInteractor
. These interactors have an execute
method than has the query
as a parameter. Additionally, Swift has the Interactor.XXX
which the query is passed upon initialization and not as a parameter in the execute
method.
IdQuery
CRUD extensions
Similar to the Repository
public interface, all default interactors interfaces are extended with methods to access the CRUD functions by an Id using the IdQuery
.
Default Interactors Composition
Typically, your custom interactors will require repositories to access the data manipulation layer.
tip
It is recomended to compose default interactors instead of having direct references to repositories. Same applies with custom interactors composing other custom interactors.
- Kotlin
- Swift
- TypeScript
class CountAllUsersInteractor constructor ( private val executor: Executor = AppExecutor, private val getUsers: GetAllInteractor<User>) {
operator fun invoke ( executor: Executor = this.executor ): Future<Int> = executor.submit(Callable { return@Callable getUsers(executor = DirectExecutor).get().size })}
class UserLimitReachedInteractor constructor( private val executor: Executor = AppExecutor, private val userCount: CountAllUsersInteractor) {
operator fun invoke( executor: Executor = this.executor, limit: Int ): Future<Boolean> = executor.submit(Callable { val count = userCount(executor = DirectExecutor).get() return@Callable count > limit })
}
Best Practices
Try to wrap generic interactors inside a custom one. It has better naming, encapsulate a specific logic and reusable.
All interactors will receive a executor via constructor.
Also, supply a executor in the invoke/execute method doing reference to the contructor one as default. It gives us the possibility to change it for a particular execution.
- Kotlin
- Swift
class CurrentTimeInteractor(private val executor: Executor) { operator fun invoke(executor: Executor = this.executor): Date = Date()}
- In the constructor, always have interactor references, avoid repository references. Adding one more layer of abstraction.
- Kotlin
- Swift
class CurrentTimeInteractor(private val executor: Executor, private val getTimeZones: GetInteractor<TimeZones>) { operator fun invoke(executor: Executor = this.executor): Date = executor.submit(Callable{ Date() })}