Posted 5 years ago by Caitlin Macatee
One of my first projects utilizing the AngularJS framework came to Crowd Favorite through the folks over at nugs.net, who desired a subscription-based music streaming service that would provide wide access to single bands. It would be similar to Spotify, but with a focus on specific artists and a broader selection, rather than a wide selection of artists with limited titles. To construct this site, myself, and the Crowd Favorite team, would be branching out from WordPress into front-end development against a client managed API.
Building Domain Knowledge
One of the first steps, as part of our, “Discovery Process” is to build our understanding of the site’s functions, processes, and goals. It is important to identify what needs to be handled in our code, what needs to go through the API, and what information needs to be received and provided in the interchange.
Much like design requirements, Discovery is a time to reconcile what is desired by the client with what is possessed. We take inventory of the features that will need interaction with the API, the data required, and the current support the API does or does not provide.
In the case of our LivePhish project, we determined that there were a series of “secure” interactions such as, sign-in process and personal content access (like, playlists, playing songs, and writing reviews). There were also “unsecure” interactions, such as getting a list of site content filtered by the user and reviewing the details of that content.
Once we determined what we needed, we had to dig further into what the details of those things might look like. For example, in the lists of albums, we needed to know the artist byline, date of show or release, cover image, list of tracks (each track also needing more information on their ability to play back the track), and any reviews already written on them.
If we are missing any functionality or data, this is the time when we communicate to the client any changes that need to occur and build out our development schedule based on what can be achieved with their interface, and what can be achieved on timelines set around their internal timelines to update those interfaces that are lacking.
Constructing an API Interface
While it may sound redundant, it can often be valuable to build an interface to the interface we’re speaking to. This allows us to set up consistent responses irrespective of the data format between API calls. While ideally all the similar data coming from an API will be in a consistent format, we cannot rely on it. This also gives us a better software architecture by ensuring that we only need to make updates in one file when changes in the API may occur, rather than hunting down all of the places using a certain response format. This promotes consistency, and the ability to maintain a loose coupling between the display logic and the data storage and retrieval.
By creating an API interface, we can better identify what properties we need, and where they come from. Creating this interface allows us to develop prototype functionality where the API may not yet be up to the requirements we possess. This allows the rest of the code not to care where the data is coming from, and to expect it in a consistent format.
With all this, we can better establish alternate behaviors, backup-states, caching interface to prevent overloading the API with similar queries, and the ability to finesse multiple endpoints or data formats into single, consistent, returned values and structures.
Designing for Failure
One of the most important aspects to working with an API is to plan for the possibility that the API may fail. We may be unable to interact with the API due to the server going down, the API may return an error state, garbled data, and it’s even possible that the components of the API will get redefined and pushed into a production state without our code having been updated.
Where there is an interface between two systems, it is vital to plan for the possibility that the interaction between those systems will not go as planned. Designing for the success state, when everything works perfectly, is simple and appealing but ignores the common reality that something will go wrong. When we work on any application, but specifically ones with complex interactions, we believe the key to success is to design for, and recognize, the variety of failure states that may occur.
What if the user attempts to log in and the authentication service doesn’t respond? Should we tell the user their password was incorrect? Should it stop silently?
What if the user adds an item to their cart, but when we request the cart back from the server it cannot find it?
This is also one of the key areas in which an API interface can be helpful. In the event that we get an invalid response, we can create custom error handling. In the event that we get no response, rather than break our display, we can return some form of defined default state.
When we tie directly to a system, we tie directly to the success state of that system and its error state; however, this is problematic in any networking situation where the error state could be completely silent, or return a format that can break the rest of our application.
Where there is a reliance on success, we must design for failure.