Dear colleagues,
As discusses on the later hours of the first day of the MDM Hackathon, these are some additional technologies that can be used in tandem to consume REST APIs using the observer pattern
Retrofit makes it easy to consume REST APIs without having to configure an HttpClient by hand, nor having to define a marshalling (XML/JSON) option by hand. This library works for Java only.
Both can be used to consume REST URLs. Using the GitHub API as an example, the consumer could look like this (there will be some code in the following paragraphs to serve as inspiration):
@Inject private Github github;
public void load() { Observable<Repository> observable = github.repositories(model.getOrganization()); if (model.getLimit() > 0) { observable = observable.take(model.getLimit()); } model.setSubscription(observable .timeout(10, TimeUnit.SECONDS) .doOnSubscribe(() -> model.setState(RUNNING)) .doOnTerminate(() -> model.setState(READY)) .subscribe( model.getRepositories()::add, Throwable::printStackTrace)); }
the producer of results from the REST API could look like this
public class DefaultGithub implements Github { @Inject private GithubAPI api;
@Nonnull @Override public Observable<Repository> repositories(final @Nonnull String name) { requireNonBlank(name, "Argument 'name' must not be blank");
return paginatedObservable( () -> api.repositories(name), (Links links) -> api.repositoriesPaginate(links.next())); } }
which consumes REST results defined in a Retrofit enabled API
public interface GithubAPI { @Nonnull @GET("/orgs/{name}/repos") Observable<Response<List<Repository>>> repositories(@Nonnull @Path("name") String name);
@Nonnull @GET Observable<Response<List<Repository>>> repositoriesPaginate(@Nonnull @Url String url); } the final link to the puzzle is the composition of paginated results as a single Observable public final class ObservableUtils { private ObservableUtils() { // prevent instantiation }
@Nonnull public static <T> Observable<T> paginatedObservable(@Nonnull final FirstPageSupplier<T> firstPage, @Nonnull final NextPageSupplier<T> nextPage) { requireNonNull(firstPage, "Argument 'firstPage' must not be null"); requireNonNull(nextPage, "Argument 'nextPage' must not be null");
return processPage(nextPage, firstPage.get()); }
private static <T> Observable<T> processPage(@Nonnull final NextPageSupplier<T> supplier, @Nonnull Observable<Response<List<T>>> items) { return items.flatMap(response -> { Links links = Links.of(response.headers().get("Link")); Observable<T> currentPage = Observable.from(response.body()); if (links.hasNext()) { return currentPage.concatWith(processPage(supplier, supplier.get(links))); } return currentPage; }); }
public interface FirstPageSupplier<T> { @Nonnull Observable<Response<List<T>>> get(); }
public interface NextPageSupplier<T> { @Nonnull Observable<Response<List<T>>> get(@Nonnull Links links); } }
|