Clean architectures: Inner and Outer-Scene layers
If you work in a tech company as an iOS developer, certainly your project is designed with some architecture, may it be MVVM, VIPER, VIP or any other. When you work with an architecture, some concept that must be very clear in your mind is the scene. Scenes are nothing less than a screen organizing its own classes containing displaying, presenting, navigation or business rules that can only be applied to that scene. With that, we can securely say that each of those classes shall exist only in the context of that screen, which means that its screen, logic and business rules are just applied in that context.
The question that everyone asks at some time while developing is: What happens if I have some rules that can also be followed by another context? What if I have an API endpoint that must be called in a totally different place inside the app? I work in a payment company where I have faced this exact problem. I am creating a new screen that will execute some kind of payment, but for that I need to know how much cash the logged user has in its wallet to proceed. Problem is: The wallet consumer call is already being made in the user's wallet scene and if I recycle that call in my new screen, I will have some duplicated code and the CI may complain about that, since the two API Service classes are almost the same.
What should be the solution for this problem? We will introduce the concepts of Inner and Outer-Scene layers
Inner-Scene layers
As we talked before, everything that applies only to a single screen must be encapsulated in there, being unavailable to other contexts. Every business rule that only makes sense to one screen is placed in its Interactor, any presentation rule stays in its Presenter, the same with the UI updating operations that stay in its ViewController. With that in mind, we build the scene normally with our VIP template, achieving this result:
Outer-Scene layers
The outer-scene layers, as the name says, stay out of the screen context and can be recycled through a lot of places since they hold some general logic. Services are great candidates to be separated from most scenes since the idea of the endpoints is to be consumed anywhere. We work with clean architectures and we must separate responsabilities somewhere anyone can use. We are also saying that not only a Service can be used in multiple scenes but also a scene may have multiple Services representing different parts of the back-end. Just remembering you, the backend is a totally different project from your app, although related, and the Service and Network classes are the closest points to it in your front:
As I explained to you, the biggest reason to maintain your Service separated from any scene is that this layer is very close to a separated application, which is your backend, and since the Service is an interface to fetch, create, delete or update data in another place, it makes sense that multiple parts of your app can trigger that. So, the Service should be separated in another folder. Repair that a single Interactor can consume the work of multiple Services, like in our previous example where it may fetch data from the wallet Service at the same time it can execute a payment.
Suggestion of new layer: Domain
The service is not the same place we can put some reusable logic. What if we have some logic operations related with the business rules that can be replicated to other contexts? Naturally, everything that is related to use cases is placed in the Interactor layer, and there is nothing wrong with that. The point is that sometimes we have some very close conceptions we create in two or more screens that could be reused and kept in a single place. An example of that is with error handling. Let's suppose we make some API calls that we want to handle at some way when status code is 400, other treatment with 400, another one with 500 and so it goes. Service is not supposed to handle API errors, but only make the call, and pass the parsed result to the lower levels with a completion.
Sometimes we want to merge all those logic operations in a single place to be reusable instead of writing the same code in more than one Interactor. Here the Domain layer comes into action. This new class is meant to make all the logic that could fit in multiple contexts. If we want the error handling with the same status code for all the wallet API calls, we can place it all together.
Another case the domain can be useful is with very similar concepts around two or more screens. Let's suppose we have a payment app which deals with cards. When we have cards we may have different screens like a dashboard, an invoice panel, a card list, blocking/unblocking and much more. Each of those scenarios is related to an Interactor of course, but we must consider some logic that is inside the Card atmosphere. In another enterprise I worked, all the operations with VISA cards were done by one API, while the other operations linked to Mastercards were responsability of another API. Those decisions about which API to call were computed by a single Cards Domain. We can think of an Interactor as use cases and a Domain as an atmosphere of some domain, as the name already says. Keep in mind that an Interactor can keep multiple Domains, as a single Domain can also communicate with multiple Interactors. Redesigning our architecture, this is how our application must be:
See the scenes in green as the whole set of layers related to some screen(Interactor, Presenter, ViewController and Router all together)
What about the UI?
Well, until this point we talked only about business rules and API calls, but we didn't talk about user interfaces. Of course it can have some reusable "layers". I put curly braces because as you as a SOLID studier may know, the UI layer cannot have a single logic. It only executes UI operations updating colors, layouts and constraints.
You probably have heard about a concept known as Design System(DS). DS is the part of the code that deals with the visual identity of your application, where we save all the colors palette, the typography and all the most used components. Usually, the colors and fonts are placed in enum
extensions just to make the values accessible to the entire project, but the components are just extended classes from the Apple Core(UIKit and other basic frameworks). Those components, since they don't have a single business context, can be consumed in the entire application, no matter the atmosphere, it is only a front shell. You may have a custom header that is presented in the home screen, in the login screen or even in the card dashboard. We are just showing our app's content through an identity interface.
Of course we can have some reusable views that don't belong to DS. The app is yours(or better saying, your enterprise's..), the decision of placing some components in a single atmosphere like in the Card context doesn't affect your app's performance. With that in mind, our new architecture will be:
Just an observation: Usually the DS is kept in a separated module
Design Patterns
This article is not meant to talk about design patterns, but since we are talking about architectures and reusable layers, there is no sense of not talking about so much close concept.
As the name already says, every class related to design patterns(Singleton, Observer, Facade, Factory, etc) may not be enclosed by any screen context. It is a reusable solution. In my opinion, each pattern solution should be included in a different module. The only singular part of it will be the consumed data:
Look how the relationship between the scenes and outer layers is the same.
Dependencies
A very common concept in any iOS app is dependency injection. What are dependencies? They are any class or instance which a class of our application relies on its output. Is that the same concept as a layer in our clean architecture? Absolutely not! Of course each layer of a scene may depend on another one injected as a dependency, but what we are talking about are dependencies that have some rules that doesn't relate to any of our scenes.
A great example of what we are talking about are JSONDecoders, which parse our outputs from the Service, UserDefaults, which work as a persisted memory for chunks of data in our application, tracking services and other.
We can think of them as something very close to Domains, but with no business rules of our app. They are:
- JsonDecoder
- UserDefaults
- Tracking services
- Internet monitors
- URLSession
- GCD DispatchQueue
- more
Conclusion
In this article, we explored a much wider horizon of clean architectures giving an emphasis on the parts that can be reused in many scenes, but don't necessarily belong to one. We must know how one or more scenes can be related in some concepts to create layers that fit in that atmosphere and how we can take one or more external instances to fill their jobs. I hope this knowledge helps you on your project design and that you enjoyed ;)