I have a class with multiple methods which return a URL string of an API endpoint. Each method takes different parameters based on what the endpoint have to include.
Examples (among others):
- GET:
http://api.mydomain.com/v1/shopping_lists/items/categories
- GET:
http://api.mydomain.com/v1/shopping_lists/items?last_sync_date=2016-09-29T17%3A14%3A51Z&page_number=1&page_limit=10&include=categories%2Csubcategories
- POST:
http://api.mydomain.com/v1/users/{user_guid}/shopping_lists/{shopping_list_guid}/items
- GET:
http://api.mydomain.com/v1/users/guid?include=devices%2Cshopping_lists.items.images%2Cshopping_lists.occasions
As we can see in the URLs, I have query parameters as well as the HTTP method in which the API is getting, along with the body request in JSON or form-data.
This is my current approach on building those URL strings:
I'm using OkHttp as the HTTP client. Firstly, I've created an abstract class called BaseHttpCall
. Inside, I put all of the HTTP methods with the Request Builder like so:
public abstract class BaseHttpCall {
private static final String BASE_URL = "http://api.mydomain.com";
private static final String VER = "/v1/";
private static final String API_URL = BASE_URL + VER;
public Request post(String endpoint, RequestBody requestBody, boolean auth) {
return new Request.Builder()
.url(API_URL + endpoint)
.post(requestBody)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", (auth) ? "Bearer " + session.token().get() : "")
.build();
}
public Request get(String endpoint, boolean auth) {
return new Request.Builder()
.url(API_URL + endpoint)
.get()
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", (auth) ? "Bearer " + session.token().get() : "")
.build();
}
// other HTTP method builder ...
public String buildQueryString(String endpoint, HashMap<String, String> keyValuePair) throws APIError {
// process keyValuePair
return mEndpointWithQueryString;
}
}
For the endpoint management, I put it in a class called Endpoints
that holds all of the endpoints:
public class Endpoints {
public static final String DEVICES = "devices";
public static final String USERS = "users";
public static final String SHOPPING_LIST = "shopping_lists";
public static final String SMS = "sms";
// ... other endpoints
/**
* Build Shopping List endpoint for Create/View
*/
public static String buildShoppingListEndpoint(String user_guid, String[] includes) {
String mBaseEndpoint =
USERS + "/" + user_guid + "/" +
SHOPPING_LIST;
if (includes == null) {
return mBaseEndpoint;
}
return mBaseEndpoint + include(includes);
}
/**
* Build Shopping List endpoint for Update/Delete
*/
public static String buildShoppingListEndpoint(String user_guid, String shopping_list_guid, String[] includes) {
String mBaseEndpoint =
USERS + "/" + user_guid + "/" +
SHOPPING_LIST + "/" + shopping_list_guid;
if (includes == null) {
return mBaseEndpoint;
}
return mBaseEndpoint + include(includes);
}
/**
* Build User Shopping List Item endpoint
*
*/
public static String buildGroceryListEndpoint(String user_guid, String shopping_list_guid, String item_guid, String[] includes) {
String mBaseEndpoint =
USERS + "/" + user_guid + "/" +
SHOPPING_LIST + "/" + shopping_list_guid + "/" +
ITEMS + (StringUtils.isNotEmpty(item_guid) ? "/" + item_guid : "");
if (includes == null) {
return mBaseEndpoint;
}
return mBaseEndpoint + include(includes);
}
// ... other build methods
/**
* Construct the query parameter for requesting custom response
*/
private static String include(String[] includes) {
return "?include=" + StringUtils.join(includes, ",");
}
private static String slash() {
return "/";
}
}
In order to use these functions, I would do this - example for building Sub Category Item Endpoint (considering this function is inside a class which extends BaseHttpCall
abstract method):
String[] includes = {"category", "items"};
HashMap<String, String> keyValuePair = new HashMap<>();
// ... key value pair builder here
Request request =
get(
buildQueryString(
Endpoints.buildSubCategoryItemEndpoint(user_guid, sub_category_id, null), keyValuePair), true);
I think this smells terrible behind the scenes. Are there any improvements I can do to make this more readable and maintainable in the backend, along with making the usage of the Endpoint builder more easily?