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.
This reminds me of something Apple did in a 2014 WWDC vid -> https://developer.apple.com/videos/play/wwdc2014-232/
The first half of the vid described something called aggregate datasources. I’m liking clean swift so far. Good work.
very helpful blog
But what about tests, should I test every worker separately or maybe Interactor test is enough?
What about test all the public and internal methods of all the workers and interactors, and not test any private methods?
I have a scene called ListBusNumbers. The template created “ListBusNumbersWorker.swift” as one of the files. What do I do with that? Do I still create separate workers?
The worker created by the templates is where you want to put complex business logic specific to your scene. If all your business logic already fits in your interactor, you can safely delete the worker. If you have business logic to share between multiple scenes, you want to create a shared worker for it.
Could you please give some examples of ‘shared workers’ across different ‘scenes’.
A shared worker, or service object, contains business logic that you want to share in multiple scenes. For example, you may have some networking code to fetch sports scores from an API. You can then reuse this code from the dashboard as well as an individual team screen.
It’s similar to what you might have done anyway without using Clean Swift.
Can a shared worker be a singleton? CartWorker, for example, requests and holds shopping cart data and is used to share data between multiple pages.
Use it like this:
Or should I call it CartManager?
Your blogs are amazing. Thank you. I am converting my huge legacy project to Clean Swift right now.
I have a question about workers. Everything (V, I, P, even my networking which is called by the Worker) seem to conform to protocols, but unless I’m missing something the Worker doesn’t.
Why is it that we don’t have a protocol for the Worker defined in the Interactor?
You want to have protocols for your workers to isolate them from your interactors that are calling them too. But I would put these worker protocols at the top of the worker files instead of in the interactors though.
About the worker, which is very much like VIPER’s Services layer.
Presenter <—[result]— Interactor —[invoke]—> ServiceProvider(Class) —[conform]–> Service(Protocol)
Don’t you think so? So fun to read your blog 🙂 Thanks
Thank you for your incredible articles.
One question, Why don’t you use protocols for workers? Is there any reason?
If we use protocols for workers, obviously we can inject workers of interactor in the configuration step. This helps us for mocking data provided by workers.