0

I'm working on a reusable client library to abstract a REST endpoint that we use in many of our in-house applications. It is actually split into 3 APIs, and certain endpoints/resources require different types of authentication (OAuth, JWT, and OAuth + additional BASIC). So to simplify all of that, I'd like to JAR up a client library and just have the app that's using it provide its credentials without having to worry about the HTTP calls.

How I would like the client API to work is something like the following:

OAuthClient apiClient = APICLient.getOAuthClient(oauth, credentials);
// or OAuthClient apiClient = new OAuthClient(oauth, credentials), but see below 
JWTClient apiClient = APIClient.getJWTClient(jwt, credentials);
AdminClient apiClient = APIClient.getAdminClient(oauth, and, basic, credentials);

I would prefer to use factory methods to instantiate the client based on the advantages listed in the first few items in the first chapter of Effective Java (name of method makes clear the arguments needed, not allowing creation of multiples, returning a superclass but instantiating a subclass), but that's not really my question.

The trickier part is that I have a ton of resources/endpoints that I need to abstract, so I would like to use an interface for each, rather than have the client class implement every single method. Interfaces would be like so:

CustomerResource
    searchCustomers(SearchQuery searchQuery) : List<Customer>
    getCustomer(int customerId) : Customer
    updateCustomer(Customer customer) : void
    removeCustomer(Customer customer) : void

OrdersResource
    searchOrders(SearchQuery searchQuery) : List<Order>
    getOrder(int orderId) : Order
    etc... etc...

...as opposed to...

apiClient.searchCustomers(...);
apiClient.getCustomer(...);
apiClient.searchOrders(...);
.
.
.

The resources would, however, require an instance of an APIClient to keep track of authentication tokens and expiration, so that renewal and auth headers can all happen in the background after initial setup. I think that's better than explicitly getting a token and having to pass it with every resource's method call as an argument. So I would like for the library user to get an instance of the interface through the client they created, like so:

CustomerResource cr = apiClient.createCustomerResource();

I'm struggling here because it feels like these XXXResource classes should be interfaces, but I don't want to have things like

interface CustomerResource
public class CustomerResourceImpl

Because there's some code smell there. I could also just have my concrete APIClient classes implement all the resources and return this but then I've got a God class which is also an anti-pattern. Am I overthinking this, and should the resource classes just be concrete? It seems strange to have every one of them have the same constructor and see a bunch of these calls in the code:

CustomerResource cr = new CustomerResource(apiClient);
OrderResource or = new OrderResource(apiClient);

Is there a good design pattern for this situation that I'm overlooking?

0

An alternative would be to actually model the problem domain of the remote API and implement it using your remote call protocols (HTTP or whatever).

What I mean is, don't reveal that the user is working with resources and http calls, design the library "normally" as if you would implement it locally. For example: don't do CustomerResource, do:

public interface Customer {
    void remove();

    ... etc ...
}

Or if you feel like indicating that this takes a while (and enable async composition):

public interface Customer {
    CompletableFuture<Void> remove();

    ... etc ...
}

Also, when processing list of things, you should normally prepare to paginate or handle large responses somehow. One way to do this is have an interface like this:

public interface Orders {
    void search(SearchQuery query, Consumer<Order> orderHandler);

    ... etc ...
}

Don't just "return" a list of things, because you take away control from your library and put it in the unsure hands of users. This way, you can implement your Orders simply by one request-response, or later expand it to do pagination as well if needed.

Again, if you feel fancy, you can use Stream objects, like this:

public interface Orders {
    Stream<Order> search(SearchQuery query);

    ... etc ...
}

Stream objects still preserve control, but are a bit more complicated unfortunately, at least in Java.

That is just some ideas.

  • I like this approach better than creating dumb objects with only getters and setters, but what's the approach for the user getting an instance of these interfaces? The user/app that is leveraging this library still needs to provide the credentials to access the REST endpoint, so I can't hide that part. It won't be a universal set of credentials that the library maintains. My main problem then is how to model the entry point into this API. Is the idea something like new Client(credentials), and then use that Client instance to do something like Orders orders = client.orders()? – Michael Guinn May 1 at 21:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.