Marq asked a question on the role of the interactor and worker on my post about Testing Business Logic in Interactor.
I would be curious to see more about the worker concept as I am little confused by it’s purpose. The way that I interpreted it was more like a Service layer or Gateway (another boundary), which would retrieve objects (from a service, database, etc…) and then provide them to the interactor which would do something and pass the output back to the presenter. If the business logic is inside of the Workers, then what’s the purpose of the interactor? also where would code to a service or database then reside?
That’s a good question. When you are adding your first worker to do some business logic, does it feel like you are just moving code from the interactor to the worker?
Does it then make the interactor unnecessary?
Why do you need an extra worker object to do stuff that your interactor already does?
How complex is your business logic?
The answer depends on the complexity of your business logic.
So far, in the CleanStore example, the business logic in the interactor is very simple, so I confine all the business logic in the interactor directly. I didn’t use any worker.
However, when your business logic is more complex, your interactor can become very big. To avoid massive interactor, you want to break it down and move it to the workers.
What exactly are workers?
Marq was right. Service or gateway objects are a very good candidate for workers. You can have a
FacebookWorker to fetch data from the API. You can also have a worker for your backend API.
Because the API worker itself can become very big, I’ve even tried multiple workers for different aspects of the same API.
For asynchronous operations, you need to construct a request, send it, wait for a response, parse it, and possibly validate it, before you can create an entity model for the interactor to use.
These are all good candidates for creating dedicated workers.
Workers can also be other things
Let’s assume you’re writing an app for accountants. You have an
EmployeeInteractor. The UI needs to display the employee ID, name, and salary, among other things. There is a lot of business logic involved here.
Instead of dumping all these responsibilities on the interactor, it is better to create an
EmployeeInfoWorker to fetch an employee, a
PayrollWorker to fetch the salary and calculate paid time off. What about employee photo, the division(s) they belong to in the organization, health and retirement benefits?
As you can see, this can easily make the interactor thousands of lines long.
Although I can have one gigantic
EmployeeWorker that does all these things to keep my interactor slim, but then the
EmployeeWorker itself will become a massive worker.
This is another good use of separate workers each doing one thing.
Do you still need the interactor?
If you extract all of your business logic to workers, what is the interactor then?
The interactor becomes the coordinator of its workers. Let’s see some examples.
If there is an error fetching an employee by the
EmployeeInfoWorker, it makes no sense to ask the
PayrollWorker to do anything. Or, if an employee is terminated, you may want to display tenure information rather than salary data.
Think of the interactor as the project manager and the workers as the developers and designers.
Is there now a new boundary then? Do you need another kind of model (structs) to pass through it?
What does the boundary between the interactor and its workers look like?
Since only the interactor can ask the workers to do work, it is as simple as the following figure.
Just like in a large company, the CEO doesn’t typically talk to the developer, or vice versa.
When the interactor invokes a method on the worker, the worker does some work and returns. The result are retuned as a function return value. If the work is asynchronous, you can use a block or delegate method. There is some flexibility here. It really depends on the nature of the work.
Also, remember the workers only return results back to the interactor. They don’t talk to the presenter directly. Otherwise, you’ll end up with a messy dependency graph over time, negating the nice separation of concerns enforced by Clean Swift.
Good question, Marq. I hope I answered your question. Keep them coming.