Let’s say you want to provide your users with driving directions to your locations, and you want the directions to load on every page load. When you’re designing that page, you’ll want to be able to reload it many times, and quickly, so you can try out adjustments and build functionality. The Google Maps API provides the data we need, but unfortunately, making constant requests on every refresh during development can slow down the process quite dramatically. To that end, it would be nice to be able to simply simulate the API.

It’s important to first understand your goals. When it comes to an API, that "understanding" can come from modeling the API in terms of its inputs and outputs.

You don’t need to implement the entire API.

You don’t have to model the entire API, because we only need to implement the parts that our application cares about. The point of an interface is that the way something works is unknown, and can remain unknown, and the interface’s clients can still interact with the API successfully. Additionally, an interface need not inform its clients about all interfaces it has available, in fact, the best APIs do the opposite: they give the clients only what they need. Consider what makes an API popular. It must not only be novel, useful, reasonable to implement, cost-effective, and performant, but it must also have docs that are easy to read! And the best APIs have fantastic docs.

What are the best APIs? The ones that have great example code and beautiful defaults. The ones that have client libraries and code blocks in different languages that the programmer can toggle between. But more importantly, the best APIs have docs that are opinionated – they are organized in a way that underscores the intention of the service as a whole.

What even is an API?

Visual hierarchy is a well-understood concept in the design space, but let’s not limit the metaphor to just one domain. At its foundation, the term “visual hierarchy” illustrates how one can relate two parts of the mind, and use them as a sieve to refine an idea. One part of the mind is told to think rationally, taxonomically, mathematically, procedurally, iteratively... while the other side relies strictly on its sense of instinct and intuition. The “design process” is the union of both aspects of the mind working in concert.

To apply the metaphor, consider the layout of an API, and the layout of the documentation that describes it. Consider the documentation an API itself, an interface for accessing the interface of the API. If the first layer your customer interacts with is disorganized, cluttered, or simply does not bring joy, how can the core service even hope to do so?

As above, so below.

Faking the API helps you, the programmer, understand how you could be using it better. In identifying the output you need from the service, and enumerating the information you should provide it, we should always challenge our thinking, and respond to our intuition about whether a certain paradigm should exist as it does, or if it could change.


It can help find gaps in your project planning that you hadn't considered.


Let's try it!
  1. Which API am I going to use?

    When modeling a particular endpoint, it won’t be effective if it doesn’t give you the data you need. Spend a (short) period of time going through the docs, and find one that provides the data that looks good to you. Know that you can always change this later, so don’t agonize over it, but do be thoughtful. Consider both the information and the format, as some APIs have completely separate endpoints for the similar information in different formats.

    For our project, we will use the Google Maps Directions API. It’s accessed in an incredibly straightforward way, a GET request with some basic parameters:

    
    https://maps.googleapis.com/maps/api/directions/json
    	?key={{your_api_key}}
    	&origin={{user_location}}
    	&destination={{store_location}}
    		

    An API key is required to make the request, and then it only takes a couple parameters, an origin and a destination. Since this is just a simple GET request, we can check the response by just plugging it into our browser and pressing return.

    
    {
    	"geocoded_waypoints": [...],
    	"routes": [
    		{
    		"bounds": {...}
    		},
    		"copyrights": "Map data ©2020",
    		"legs": [
    			{
    			"distance": {
    				"text": "11.2 mi",
    				"value": 17981
    			},
    			"duration": {
    				"text": "40 mins",
    				"value": 2404
    			},
    			"end_address": "11 W 53rd St, New York, NY 10019, USA",
    			"end_location": {
    				"lat": 40.7618722,
    				"lng": -73.9773405
    			},
    			"start_address": "Crown Heights, Brooklyn, NY, USA",
    			"start_location": {
    				"lat": 40.6693849,
    				"lng": -73.9421988
    			},
    			"steps": [...],
    			"traffic_speed_entry": [],
    			"via_waypoint": []
    			}
    		],
    		"overview_polyline": {...},
    		"summary": "I-278 E",
    		"warnings": [],
    		"waypoint_order": []
    		}
    	],
    	"status": "OK"
    }  
    		
    Note the use of ellipses to indicate collapsed sections of JSON, for brevity.

  2. What data do I want from the API?

    From a high level, we can see a couple key items we might be interested in. Perhaps, before showing the user all of the individual directions, we first give them some basic details about the journey, so they can decide whether to load the next page. Let’s go with the distance, duration, and status code.

    Let’s consider machine-ready formats, over the human-readable ones. While it can be tempting to use "duration": { "text": "40 mins" } because we can simply show the user "40 mins", what if we want to be able to do math with this number? We can easily parse this into its integer 40, but what about when something is “1 hour, 5 mins”? That might be a bit more difficult to parse, and error prone. Best to go with "duration": { "value": 2404 }.

    Some intuition and division tells us that this is an integer of seconds of time.


  3. What data do I want to give the API?

    We’ll be providing the API with our API key, and start and end points (origin and destination) for the journey we would like calculated. It’s important to understand the format of the data that our API expects, but luckily Google Maps is extremely good at parsing addresses, so we can just throw any reasonable address string we like at it, and it should more-or-less work.



Our fake API will be “inverting” all of the above.


Impersonation time!

Let’s implement this in ruby, as a class we can interact with and make “requests” to.


require 'json'

class MapsAPI

	def get_directions(origin, destination, real_request = false)
		response = api_endpoint(google_maps_api_key(real_request), origin, destination, real_request)
		parsed = JSON.parse(response)
		{
			'status': parsed['status'],
			'distance': parsed['routes'][0]['legs'][0]['distance']['value'].to_i,
			'duration': parsed['routes'][0]['legs'][0]['duration']['value'].to_i
		}
	end
	
	private
	
	def google_maps_api_key(real_request = false)
		if real_request
			Rails.application.credentials.google_maps[:api_key]
		else 
			'FAKEKEY_FJ8302S93LAAZD'
		end
	end

	def api_endpoint(api_key, origin, destination, real_request = false)
		# If any of the fields are not provided
		if api_key == '' || origin == '' || destination == ''
			# return early with a status
			return JSON.generate({'status': 'BAD REQUEST'})
		end

		if real_request
			return RestClient.get("https://maps.googleapis.com/maps/api/directions/json?&origin=#{origin}&destination=#{destination}&key=#{api_key}")
		end

		# some code that returns what we want
		distance_value = 17981
		duration_value = 2404
		status = 'OK'

		# re-create the data structure of the API we're modeling.
		output_hash = {
			"geocoded_waypoints": [], 
			"routes": [
				{"legs": [
					{
						"distance": {"value": distance_value},
						"duration": {"value": duration_value}
					}
				]}
			],
			"status": "OK"
		}

		JSON.generate(output_hash)
	end
end	

Planning ahead, we’ve built in some optional parameters called ‘real_request’, so we can very easily switch to using the real API when we’re ready. We always want our interfaces to work consistently, regardless of what “magic” is happening inside.

Now we can make requests to this class and get the data we need.


maps = MapsAPI.new
=> <#MapsAPI:0x00007fd004940fd0>

maps.get_directions('Crown Heights, Brooklyn, NY','Museum of Modern Art, Manhattan, NYC')
=> {:status=>"OK", :distance=>648723, :duration=>1425}    

We’ve done it! We now have the ability to develop the rest of our application without incurring unnecessary hits to our real API.


What's next?

How can we improve this? For one, it would be nice to have our responses be more realistic. Making two different requests should result in different data. The data we return can be random, but still look realistic, so the different pieces of data in the responses should fall into some reasonable constraints, both individually, and relative to each other. Additionally, our API should be deterministic – if you submit the same request, you’d expect to get the same response.

Part 2: Enhancements for a fake API: Realistic Randomness and Determinism