 Hello everybody. I'm Nishi Jain. I'm glad to be here today. I'll be sharing one of my very interesting experience I recently had, a bit about myself. I'm currently working on an application that manages video and content workflows in Hotstar and prior to Hotstar, I was working as a web developer with Paytm. So my topic of discussion today is web SDK, multiple providers per service and one software development kit. So before I get started with the topic, a bit of history about us. Hotstar started as an experiment in the OTT platform. Star has an abundance of content and with the amount of people spent on their personal devices, we realized that there's an opportunity ahead to create on-demand content accessibility for the users. So started back in 2015, today we stand as a highly evolved video streaming platform. But in our journey till now, we have faced multiple challenges and each of these challenges, we have managed to find a smart solution. So here I am discussing today one of the very interesting challenges we had and our solution towards it. So let's get started. So in common scenarios, web operations are dependent on an API response. There's an API that empowers the web operation in an application to fetch the required data from the back end. But in our scenario, this looks slightly different. So in our scenario, it looks something like this. So if you look over here, there's for a particular web operation, web is accessing an API. But for different percentage of the users, it is fetching the data from an API maintained by a different provider. So how is it happening and why is the scenario like this? As I said that we started as an experiment. So Hotspot earlier was an amalgamation of multiple third party services. But with the kind of response you are getting from our audience, we realized that we should have our own tech team and we should soon move to our in-house services. Now this transition period between the third party service to the in-house services was challenging because moving to the in-house service was not possible in one go. So during this transition period, there was a requirement to have the third party services and the in-house services run parallelly until and unless we are completely sure about the in-house service. So there's this X percentage of the traffic for any particular web operation which is directed to the third party services and the Y percentage of the traffic is directed to the in-house service. Now the goal over here is to increase the percentage of Y, to increase the value of Y, reduce X and kill X once we are completely sure of Y. So in a non-tech world, our problem looks to something like this. If you see over here, there's a man who is performing a split operation between these two buses. Now this operation of the man is controlled by a remote. So how I see this, how I compare this to our scenario is I think the buses resemble to the services, the third party service and the in-house service that we are currently working on. The man is comparable to the web who is currently dependent. As the man is dependent for the split operation between the buses, our web is dependent for any web operation on the multiple services, the third party service and the in-house service. Now we wanted to make this remote that could control the dependency of the man on the different services for a particular operation. So this was challenging. Before coming to the final solutions, there were certain approaches we thought of and we, with each approach that we picked, we could figure out what was wrong before we came to the final solution. So I will take you through each solution step by step. Approach one was that whatever change we make in our APIs, let's implement that change in the code base and if things don't work, we will roll back to the previous version. But as you see the drawback over here is that we'll be moving back and forth between the recent implementations we are work making. If things don't work, we'll have to roll back to the previous code base. And also there's a service layer and the huge web code base, which is there in one repo. And whenever there's any change in the service layer, we'll have to make a deployment for the entire web code base. So this is something we didn't wanted. And also we foresee that there'll be the third party code, lot of third party code, which we want to get away with in future. So we wanted to keep that code separate. We didn't want it to get merged with the web code base and the service code base that we'll be keeping for the future. So this approach didn't work for us. Now the approach to was that let's keep all the API implementations, third party API implementation, our in-house API implementation, all the implementations that are required for a particular operation at a time. And whenever web makes any call to the API, whenever it wants any result for the, it wants to get the data for any operation from the backend, it just provides, it just mentions the provider from which it wants to fetch the data. By provider over here, I mean that whether it wants to fetch the data from the third party API, whether it wants to fetch the data from the in-house API. So over here, the challenge was that even if we were having multiple implementations of the services, we were still referring to only one API implementation at a time. For example, if the web was saying that okay, I want to do this operation and a third party provider will do this operation for me. So even when we were having the API implementation of the in-house provider, the web was only referring to the third party provider. For making the change, we'll have to make the hard coding or the provider change in the API operation itself. And then again, there'll be a deployment. Then only we'll switch to another provider. So we wanted web to be completely agnostic of the fact that from where it is able to get the response. And also again, the deployments of view layer and API implementation is not independent in this case as well. But the one problem that was solved over here is that we were having multiple API implementations at a time of the API implementations that are responsible for a particular web operation. At a time, we were we are not going back and forth between multiple implementations. So the third approach that we came towards, okay, let's have a private NPM repository. What we'll do is we'll keep all the API implementations separate. We'll have a service layer and our view layer will be separate. And there'll be a config in between. What will this config do is that it will map all the web operations, the operations that are dependent on somehow on the data that is stored in the back end. So these operations are mapped to the provider code. The config maintains this mapping. So whenever the web wants to do any operation, it refers to the config that, okay, this operation is mapped to which provider and it calls out to the API of that particular provider. So we over here most of the problems are solved. But the drawback was that we were having multiple, we were having one repository for the API implementations, one for the config and other the view layer. But as the solve most of our problems, we went with approach three. We adopted approach three and we call it Web SDK today. What is Web SDK? SDK is a wrap wrap around all our APIs. It is a service layer of web. It consists of API implementations for a particular operation from all our providers. And it also consists of the proper test suite, a very robust test suite so that the development of Web SDK is not anywhere dependent on its integration with web. So let's say whenever we want to integrate any API or any change in the API, we just change our test case and we make sure that all our test cases are running and the development in our web code base and the SDK code base can be done independently. How is the Web SDK, why it's an SDK and not an API layer? So I think SDK is something that is more complex and API is the simplest version of an SDK. SDK is something that is responsible or that makes that the operation of something that enables to create certain application. It is whereas an API is an interface that enables to interact between two different services. So let's say if there are two different software who want to interact with each other, there will be an API that will enable the communication between these two softwares. But SDK is a development tool which enables us to create a software that can run on a specific platform. So in our case, we call it an SDK and not just an API. Now, how a particular operation in web that is dependent on the data, on the backend data happens in Hotstar. So let's say if I want to fetch the details for this page, there's a tournament and I want to get all the details of the page. And over here, I'm not in normal scenario, what happens is I'll just call the API and the results will be fetched. But we do it in a slightly different way. How we are doing it is for a particular operation, web makes a call to Web SDK. It says, hey, Web SDK, fetch the details for this operation for me. SDK now knows the operation, incoming operation from the web. It sees that this operation in the config is mapped to which provider. By provider, I mean that from which providers API it should get the data from. Now, once it knows the provider, it calls out the API of that provider, it converts the response coming from the API into the web understandable format and return it to the web. So this is something, this is somehow the app, the development looks like. If you look over here, Hotstar SDK content.get season page data is, is how web is making a call to the SDK. Over here there's just the operation name and the request information that is required. So web is saying to the SDK that, okay, I want to get all the season page data for this particular page. And it is giving certain request parameters which SDK will consume to do this, to do this operation for web. Now, if you see over here, web is completely agnostic of who is fetching the response for web, whether it is a third party provider or it is an in-house provider. SDK refers the config, it is seeing that, okay, in the config get season page data is mapped to CMS. So it will call out the get season page data implementation of CMS and it will return the response to the web. Get season page data operation over here will have multiple implementations, one of CMS, let's say AGS, which is the third party implementation, but it is this config which tells the SDK that, okay, I want to call out the implementation to fetch, to do this operation from CMS. Now, we are keeping multiple API implementations of different providers, but how we are making sure that different percentage of the traffic get served from different APIs. So let's say, what we do is we keep different versions of this config. These are all the operations for which web can interact with the web SDK, for which the web is dependent on some back end response. So it will make the call to the web SDK. Now, we can have multiple versions of this config. Now, let's say in version one, this get season page data operation is mapped to CMS and in version two, it is mapped to the code of some different provider. So let's say in version two, get season page data is mapped to AGS. So at this, at the CDN level, what we do is we direct different percentage of the traffic to different versions of this config. So this is how we make sure that X percentage gets served from version one and Y percentage gets served from version two. So for a specific operation, web is able to get the response from two different APIs maintained by two different providers. And until and unless we are completely sure of the provider which we want to go with, we don't, we maintain different versions and we just increase the percentage gradually until and unless we are completely sure. Because in the case of fly match, if things fail on production, it is difficult to recover. That's why we wanted this transition from the third party to the in-house services to be gradual and not in one go. The second challenge during, in this approach was normalizing request and response. So when I just, I just showed you over here that how web is making a call to the web SDK. Over there it was completely agnostic that who is fetching the response for that operation from the web SDK. So the request is in certain format. It is not requesting SDK in a format that will be acceptable by the provider. Because there are different provider implementation. And again the response that will be fetched from different providers will not be same and will not be necessarily in the same format in which the web will expect it. So we need to normalize these requests and response in a certain format that this can be consumed by web and the request can be used by the providers to fetch the desired response. So before I come to this topic that how we are doing it, there is this interesting concept in JavaScript that we are using to create the normalization functions, decorators. So decorator is an expression that returns a function and it takes a target name and the descriptor as arguments. So what is a target? A target is any object, name is any property of that object and descriptor is the value of that particular property on the object. So decorator is something that adds some extra functionality on the property which is which it is trying to decorate and it is applied by adding at the rate on that particular expression. So if you see over here in the example at the rate read only is a very simple and clean decorator we have created. So we are using the concept of decorators to create request adapters and response adapters that are in what these request adapters and response adapters do is they convert the incoming request into into something that is understandable by the API provider which will fetch the response and also once the response is fetched by the API we convert the response again we have a adapter returned by using a decorator which do what this adapter do is this converts the response into a format that is that web understands. So if you look over here there's this request I've shown which is coming from the web. It is mentioning the operation name and the request parameter. There are multiple implementations for this particular operation. One implementation is managed by the provider CMS and other is maintained by AGS. The request expected by both these providers is different. So I need a normalizer in between that could convert this request into the format that is expected by the CMS API or the AGS API depending on whosoever fetches the response for this particular operation. And also I want a normalizer that could convert the response as the response received from both these adapters is both these providers is different. I need a normalizer that could convert the response into a standard format which is expected by the web. Web obviously understands one format. We cannot write the web code base so that it can handle the response which it will be getting from the different API implementations over here. So and also we have from the very start we have believed in the fact that we will keep the web is agnostic of the API implementations. The web will never depend on which API is fetching the data for the way. So request adapter takes a function as an argument. This function is a normalizer function and it is called before we call the provider implementation. So this is how a request adapter looks like. If you see this is a very simple decorator function which we have created and we are using it to normalize all the request of all the incoming operations in the web SDK. Over here the adapter to attach function is passed as a normalizer function. And before we actually made call out to to the property which this request adapter is decorating. We passed the incoming request to the adapter to attach function that adapted to attach function converts this incoming request into the format that is expected by the provider implementation. Now the decorator over here is doing a job in a very clean way and we are using this common request adapter for all the operations to do the normalization of the request. Similarly there are response adapters. So again the response adapter takes a function as an argument and it is called after the provider implementation is called. So the response that is fetched from the provider implementation is passed to the response adapters and the response adapters converts it into the response format expected by the web. So if you look over here the response adapter is taking an adapter to attach function and this adapted to attach function is called after we call the API implementation. The response fetched from the API implementation is passed to the adapter to attach function. Now, what this adapter to attach function is doing is, it is converting the response into the format which a web expect from that particular operation. Now, this is how the overall implementation looks like. Any get season page data has a decorator request adapter and the response adapter. It is taking the normalizer request normalizer and the response normalizer as a parameter. Request adapter is called before the API implementation and the response page from the API implementation is passed to the response adapter and finally, the decorator or the, once the response is converted into the format that is expected by the web, it is returned to the web. So the overall implementation looks clean and this is how we have made it by creating just the normalization function, separating the normalizer functions and creating the request adapter and response adapter as common for all the different API implementations. Now, there's a cash layer in between for all the API endpoints that web SDK fetches the response for. We save, we cash this response and we set the expiry time. Now, this expiry time is set for five minutes from the time when the response is fetched. After every five minutes, there is this timer that trans and it deletes or it clears the cash for the API endpoint response for which the expiry time has surpassed or it has occurred. So this is how we maintain the five minute cash at our end. Now, if you see over here, there's corresponding to every API endpoint, there is the response we are fetching from that API endpoint is stored and also the expiry time. The expiry time is any time that you can set but every, after every five minutes, it will just clear the response from the cash for which the expiry time has occurred the response that has already expired. Now, all the related modules in the web SDK are bundled together into one chunk. For example, all the API implementations related to the content module are kept in one chunk. All the implementations related to search module are kept in one chunk. Once the chunk is loaded in web, it gets cashed and it is not imported when the user reposits the same route. This is something that is handled by web back and it also helps us to in maintaining the cash for different chunks that are loaded at the web end. Now, how is this web SDK integrated with the web? As I said that we are maintaining web code base as separate and web SDK as separate. Now, how are we integrating the web into the web SDK into the web is that web SDK is created as a private NPM module. So it is integrated in the web as any other NPM package. It is created as a NPM dependency. And whenever there's any new release going for the web SDK, the version is updated in the web. How we update the version of the web SDK is an automated process. In the last commit before any release, we specify that whether it is a patch release, it is a minor release, or it is a major release. There's an automated script that runs which reads the prefix that whether it is a patch, minor and major. And accordingly using the sub versioning, it creates a new release version. And the same new release version is updated in the package.json of the web repository. But yeah, configuring a private NPM registry is not an easy process. It has its own pros and cons to that. I'll just come later after this. So how do you configure a private NPM registry is you have this .npmrc file. This .npmrc file is added, consists of your private registry parts. So whenever you are making any NPM install, it checks that whether that module exists in your private repository. If it is there in the private repository, it fetches that module from the private repository. If it is not, then it accesses the global NPM registry. And also you can make any change published to your private NPM registry. You can specify the published config parameter in your package.json, which consists of the path of your NPM registry. Now, what are the pros and cons of having a private NPM registry? The pros is obviously that you can maintain your own private packages like we have maintained a web SDK. You can override the public packages for your internal use. You can give rewrite access to the private packages so that different users can collaborate on what you are making. The cons that it is paid and maintaining this private NPM registry is a bit of a challenge. And there's a person needed to maintain and make any changes required with the private NPM registry. Now, what was the achievements with this was that we could easily go through this transition period. We have almost moved all our third party services to the in-house services. Removing the older code base was not a challenge for us because since the very start, we have thought of this problem which will occur in future and we have maintained all the third party code base in a separate folder, and it was as easy as removing one folder for us. Even though we have moved to the in-house services, we continue to follow this approach of having a separate API layer and the web layer because now we use it for different API versions of the same API. For let's say if we are releasing the new API version, we'll first roll out it to the smaller percentage of the user before we roll it out to the greater percentage. So we can still continue to follow this approach. And also we follow this because the deployments of the web layer and the API layer is independent. So we can focus whenever there's on the UI part and the API change part separately. Yeah, I think I'm done with the talk. So if any questions are there.