Let Others Solve Your Problems: Change Your Mindset From APIs to SPIs
What if I told you there is a way to scale your system to infinity without writing a single line of code?
Moreover, by adopting this paradigm your system can become a platform, that is not limited only to what you develop, but can be plugged in with further business logic from the outside?
As engineers, our job is to solve problems, and we’re probably OK at solving them (give or take a few extra tricky ones). However, sometimes, these problems can be delegated. All we need to do is to change the mindset from writing APIs, to writing “reverse APIs”, also known as SPIs.
A year ago, we decided at Wix Stores that we need a way to give our users the capability to provide their store’s customers with real-time shipping rates, from as many delivery services as possible during the checkout phase.
Wix Stores is Wix’s eCommerce solution. We provide a platform for merchants to sell goods online and give them tools to help build a stunning store that contains everything an online shop needs: cart, checkout, catalog, order services, shipping management, and much more.
To accomplish the shipping rates challenge, we took advantage of the SPI pattern and established a platform that enables seamless integration between us and various shipping rates providers (i.e “delivery/carrier services”). Today, we have a fully functioning platform with multiple integrated partners. For us, plugging in a new service is a no-brainer.
In this post, I will present the SPI approach and the paradigm shift it brings with it. I’ll share our takeaways and the important lessons we learned from the process. By the end of it, you will have yet another tool in your toolbox that may help you (or someone else) solve tomorrow’s problem.
So what on earth does SPI mean? Good question. It stands for “Service Provider Interface”. Technically it is an API — a contract. However, contrary to “normal” cases in which you define and you implement, with SPIs, you define and let others implement. A form of “reverse API”, if you will, where each implementation is an external extension to your system.
SPI has been around for quite some time within the world of software engineering. As answered in Stack Overflow:
“The API is the description of classes/interfaces/methods/… that you call and use to achieve a goal, and the SPI is the description of classes/interfaces/methods/… that you extend and implement to achieve a goal.
Put differently, the API tells you what a specific class/method does for you, and the SPI tells you what you must do to conform.
In this article I’ll talk about the SPI within the context web architecture, which is the young, hipster brother of the classic SPI presented in software engineering classic literature.
As previously mentioned, our use case was to provide real-time shipping rates from any delivery service (the “service provider” within the context of Service Provider Interface). The naive approach would be to go ahead and study the API of each one of the different services, and call each one separately.
Obviously, this does not scale.
Let’s say that today we want to retrieve shipping rates from USPS and Canada Post. Studying their APIs, implementing the code, testing, rolling out — this whole cycle takes time. Now let’s say we wish to add an additional provider — Royal Mail. We would have to start all over again. Now, imagine we need to add 20 more providers — this will never end.
Instead, we wanted to have a single API, let’s call it getShippingRates, which all the delivery services would implement on their side. Then we would just have to call each one with the same request, expect the same response, collect all the results, and finally show them to the user. This way of adding a new service requires zero effort from our perspective. Our application works with a single contract — the SPI.
So how do we start? API first.
The first thing we had to do, which was the most difficult part of the process, was to define the so-called getShippingRates API. Think about it — it has to be good enough that the different partners would actually agree to implement.
getShippingRates’s request is the payload we send to each one, and the response is what we expect them to return. Thus, the request must contain all the relevant information the partners need in order to calculate shipping rates, and the response needs to make sense so it is easier for them to answer.
This was not an easy task at all. We went back and forth with the engineering and business departments of the different partners, prepared countless drafts and made changes over and over again until we came up with the final version.
And once the API is defined?
Once we came up with the final version of the API, and at least one partner willing to implement it and work with us, we had to design and write the software infrastructure for the two main components of the system: the SPI discovery mechanism and the aggregation logic.
Before diving into the implementation, let’s go over the two main requirements of the feature:
- A merchant (store owner) can enable a delivery service within a shipping region. For example, USPS in New York. Now, all New-York-based customers that shop in her store should be able to receive shipping rates from USPS during the checkout phase.
- During the checkout phase and having entered a shipping address, the buyer will receive shipping rates from all the providers that the merchant has enabled for that region. To further our example, if I’m a customer in the checkout phase and I enter a New-York address, I will receive shipping rates from USPS.
Extensions Discovery Mechanism
The first component of the system is the extensions discovery mechanism. Decent infrastructure is required to allow for adding and removing extensions from the system. The end-game is to offer some form of self-service portal so that each extension could implement the SPI and register itself to the system by providing the endpoint URL it’s listening to. Then, merchants will be able to search through all of the registered services and choose the ones they want.
That being said, we decided to begin with a simpler and faster solution and keep a hard-coded list of providers & URLs. Each service had to manually communicate with us and provide its URL, and only then could we add it to the system. It worked well in the beginning, but we realized very quickly that it does not scale and began to head towards the self-service solution. The reason we started with the hard coded solution is because we didn’t want this development to be a dependency, and didn’t want this to effect the time-to-market. Also, a generic solution by Wix Framework team was in the making.
Aggregation
The second component of the system is the aggregator. It calls the relevant extensions during runtime, collects all the responses, and returns those to the client.
Some things to consider:
- Timeouts — we cannot afford a single extension becoming a bottleneck and thus impinging on the end-user’s experience. We had to define a timeout for all the requests, and those that did not respect it were simply cut out. It is worth mentioning that even though we called the extensions in parallel, it was still synchronous.
- Security — the extensions would probably want to know that the request indeed came from you. Consider adding an authentication header to each request, and according to your favorite security protocol allow each service to verify the request’s origin.
- Failures — different extensions can throw errors. Don’t let them explode in your face and have the entire request fail — collect only the successful responses.
- Monitoring — this is practically a must. You really want to have visibility on what’s going on with each extension: log every request/response; have success rates & response time metrics; define alerts. This is gold.
- Bugs — never forget that the extension’s logic can contain bugs as well. You must accept that you can’t control it and can’t expect everything to happen as planned. You need to protect your system from faulty and incorrect responses from the extensions.
Summary
When designing software architecture, try identify the “extension points” of your system. These are sweet spots that, if defined correctly, can take your system to the next level in becoming a platform. In the best case scenario your system will become much more scalable. Worst-case scenario? “Platform” is a buzzword these days, so you may just impress your bosses while you’re at it. Do consider that I have presented only one use-case and implementation for the SPI pattern — remember that there are many, many more.
Thanks for reading! As always, you’re welcome here to read more of my posts.
Roy Shachori contributed to this article.