Chaining authentication requests to multiple services using RxSwift
At a company that I have worked in the past, a high-traffic online classifieds, the backend was transitioning from a monolithic architecture, to a micro-services one.
At the time I joined, the apps had to consume some endpoints from the legacy (monolith) backend, and some endpoints already migrated to the new backend. For example, getting the User’s details were on the old one, but getting the favorited items were on the new one.
The backend engineers haven’t considered the creation of a single interface in order to handle the login in both architectures, meaning the apps needed to authenticate the user into both backends independently.
Furthermore, the access token returned from the legacy backend, had to be sent as a parameter when authenticating on the new backend.
The existing implementation, using NSOperation, was buggy and hard to debug/maintain.
We decided to rebuild the flow from scratch using RxSwift, because provided an easy way to chain async network requests, map responses into another observable, handle error scenarios, and unit test using RxTest & RxBlocking.
We were already using RxSwift in other parts of the app.
If you are unfamiliar with Reactive Programming, I recommend this article: https://medium.com/@saru2020/introduction-to-functional-reactive-programming-using-swift-ea30b1e38309
Atomicity
One important consideration we had to take into account, is the fact that both requests need to succeed in order to update the app state. If either of the auth requests fails, the whole login operation is considered as failed.
The flowchart is pretty straightforward:
Implementation
Let’s start by creating a protocol to authenticate using Username and Password.
This is the only method that will be exposed from the network layer. It will return a Single
of type Void
. A Single
is basically an observable that can emit either one value or error.
Then we create private methods to authenticate on each backend, and a private method to store locally the tokens:
Finally, with the private methods in place, we can write the body of the public method authenticate(username: String, password: String)
The implementation looks clean and easy to understand thanks to chaining methods and the use of flatMap. When the login is successful we will emit a Void
value. Any error returned by any of the private methods will be surfaced and stop the execution of the following chained methods. Therefore, the subscriber can be configured as follow:
Mapping errors
I mentioned before that any error returned by the private methods (eg: network error, server error, validation error) will be surfaced and ultimately returned to the client consuming the protocol AuthProtocol
.
However it is possible that for simplicity and/or to hide internal behaviour, we want to just map the internal error to another.
We can achieve that using the operator catchError
Conclusion
Reactive programming is a broadly used paradigm, as an alternative to Imperative programming, because provides an easier way to handle async calls, manipulate the results through the large number of operators, and a cleaner code organization.
A disadvantage of using 3rd party libraries like RxSwift or ReactiveSwift, is that your code will be depending on the evolution of the library, that is embedded in several layers of your app, and it’s going to be a non-trivial task if you decide to use another paradigm, or replace the existing library for another.
The new framework Combine introduced in the WWDC19 by Apple, proposes a native solution to reactive programming, and an alternative to the 3rd party libraries mentioned above.