JAVA LIBRARIES
YOU CAN’T
AFFORD TO MISS
ANDRES ALMIRAY
@AALMIRAY
ANDRESALMIRAY.COM
@aalmiray
USE CASE
Write an application that consumes a REST
API.
Components must be small and reusable.
Say no to boilerplate code.
Behavior should be easy to test.
@aalmiray
THE LIBRARIES
PRODUCTION
Guice | Spring
OkHttp
Retrofit
JDeferred
RxJava | Reactor
MBassador
Lombok
Jackson
Slf4j | Log4j2
TEST
JUnitParams
Mockito
Jukito
Awaitility
Spock
WireMock
@aalmiray
DISCLAIMER
@aalmiray
@aalmiray
@aalmiray
@aalmiray
JCP Executive Committee Associate Seat
Committer
Committer
JSR377 Specification Lead
@aalmiray
USE CASE
Write an application that consumes a REST
API.
Components must be small and reusable.
Say no to boilerplate code.
Behavior should be easy to test.
@aalmiray
GITHUB API
Well documented REST API
Latest version located at
https://developer.github.com/v3/
We’re interested in the repositories
operation for now
@aalmiray
QUERYING REPOSITORIES
API described at
https://developer.github.com/v3/repos/#list-
organization-repositories
Given a query of the form
GET /orgs/${organization}/repos
@aalmiray
QUERY RESULT
[
{
"id": 1296269,
"owner": { /* omitted for brevity */ },
"name": "Hello-World",
"full_name": "octocat/Hello-World",
"description": "This your first repo!",
"html_url": "https://github.com/octocat/Hello-World",
/* many other properties follow */
},
/* additional repositories if they exist */
]
@aalmiray
WHAT WE’LL NEED
Dependency Injection
HTTP client & REST behavior
JSON mapping
Boilerplate buster
Handle concurrency
@aalmiray
GET THE CODE
https://github.com/aalmiray/javatrove/
@aalmiray
DEPENDENCY
INJECTION
@aalmiray
public interface Github {
Promise<Collection<Repository>> repositories(String name);
}
@aalmiray
public class AppController {
@Inject private AppModel model;
@Inject private Github github;
public void loadRepositories() {
model.setState(RUNNING);
github.repositories(model.getOrganization())
.done(model.getRepositories()::addAll)
.fail(this::handleException)
.always((state, resolved, rejected) -> model.setState(READY));
}
}
@aalmiray
Guice - https://github.com/google/guice
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Github.class)
.to(GithubImpl.class)
.in(Singleton.class);
bind(AppModel.class)
.in(Singleton.class);
bind(GithubAPI.class)
.toProvider(GithubAPIProvider.class)
.in(Singleton.class);
// additional bindings …
}
});
@aalmiray
Guice - https://github.com/google/guice
• Reference implementation for JSR-330.
• Can bind types, instances, constant values.
• Can provide lazily evaluated instances, i.e, providers.
• Extensible lifecycle.
@aalmiray
BONUS
@aalmiray
Guava - https://github.com/google/guava
• New Collections:
• MultiSet
• BiMap
• MultiMap
• Table
• Utility classes for Collections
• Utility classes for String
• Caches
• Reflection
• I/O
• Functional programming support (JDK6+)
@aalmiray
Spring - http://projects.spring.io/spring-
framework
• More than just dependency injection
• Supports JSR-330
• Assertions
• MessageSource + MessageFormat
• Serialization
• JDBC, JPA
• JMX
• Validation
• Scheduling
• Testing
@aalmiray
REDUCING
BOILERPLATE
CODE
@aalmiray
Lombok - https://projectlombok.org
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import lombok.Setter;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Repository implements Comparable<Repository> {
private String name;
private String description;
@Setter(onMethod = @__({@JsonProperty("full_name")}))
private String fullName;
@Setter(onMethod = @__({@JsonProperty("html_url")}))
private String htmlUrl;
// continued
@aalmiray
Lombok - https://projectlombok.org
// continued
@Builder
public static Repository build(String name, String fullName, String
description, String htmlUrl) {
Repository repository = new Repository();
repository.name = name;
repository.fullName = fullName;
repository.description = description;
repository.htmlUrl = htmlUrl;
return repository;
}
}
@aalmiray
Lombok - https://projectlombok.org
public class ApplicationEvent {
}
@lombok.Data
@lombok.EqualsAndHashCode(callSuper = true)
@lombok.ToString(callSuper = true)
public class NewInstanceEvent extends ApplicationEvent {
@javax.annotation.Nonnull
private final Object instance;
}
@aalmiray
Lombok - https://projectlombok.org
• Reduce boilerplate source code by generating bytecode.
• Relies on APT (Annotation Processing Tool).
• Extensible but not for the faint of heart.
• Common usages already covered, i.e, POJOs, builders.
• Don’t forget to enable annotation processing in your IDE!
@aalmiray
BEHAVIOR
@aalmiray
SLF4J - http://www.slf4j.org
• Wraps all other logging frameworks:
• java.util.logging
• Apache Commons Logging
• Apache Log4j
• Provides varargs methods
@aalmiray
HTTP
How many options are out there to build an
HTTP client?
How many ways are there to build a REST
client?
@aalmiray
OkHttp - http://square.github.io/okhttp
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
@aalmiray
OkHttp - http://square.github.io/okhttp
• Basic HTTP/HTTP2 Client API.
• Configuration is extensible via factory classes.
• HTTP lifecycle can be decorated via interceptors.
@aalmiray
ADDING JAVA ON TOP OF HTTP
What steps do we must ensure in order to
build an HTTP/REST client?
How should HTTP error codes be handled?
How to handle connection errors?
Parsing results in format X or Y.
@aalmiray
Retrofit - http://square.github.io/retrofit
public interface GithubAPI {
@GET("/orgs/{name}/repos")
Call<List<Repository>> repositories(@Path("name") String name);
@GET
Call<List<Repository>>> repositoriesPaginate(@Url String url);
}
@aalmiray
Retrofit - http://square.github.io/retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(
JacksonConverterFactory.create(objectMapper))
.build();
return retrofit.create(GithubAPI.class);
githubApi.repositories("foo");
@aalmiray
Retrofit - http://square.github.io/retrofit
• Wraps REST calls with Java interfaces.
• Extensible via factories.
• Relies on OkHttp.
@aalmiray
MULTI-THREADED CODE
The golden rule of UI programming:
- Everything related to UI must be
executed inside the UI thread (read/write
UI properties, paint/repaint, etc).
- Everything else must be executed
outside of the UI thread.
@aalmiray
JDeferred - http://jdeferred.org
public interface Github {
Promise<Collection<Repository>, Throwable, Void> repositories(String name);
}
@aalmiray
JDeferred - http://jdeferred.org
public class GithubImpl implements Github {
@Inject private GithubAPI api;
@Inject private DeferredManager deferredManager;
@Override
public Promise<Collection<Repository>, Throwable, Void> repositories(final
String name) {
return deferredManager.when(() -> {
Response<List<Repository>> response = api.repositories(name).execute();
if (response.isSuccess()) { return response.body(); }
throw new IllegalStateException(response.message());
});
}
}
@aalmiray
JDeferred - http://jdeferred.org
model.setState(RUNNING);
int limit = model.getLimit();
limit = limit > 0 ? limit : Integer.MAX_VALUE;
Promise<Collection<Repository>, Throwable, Repository> promise =
github.repositories(model.getOrganization(), limit);
promise.progress(model.getRepositories()::add)
.fail(Throwable::printStackTrace)
.always((state, resolved, rejected) -> model.setState(READY));
@aalmiray
JDeferred - http://jdeferred.org
• Delivers the concept of Promises
• Promises can be chained
• Java8 friendly, i.e, lambda expressions can be used
• One shot execution.
@aalmiray
REACTIVE PROGRAMMING
It’s time to embrace a new paradigm.
Reactive programming is a new name for
old and well-known concepts:
events and streams
@aalmiray
RxJava - http://reactivex.io
Observable<Repository> observable =
github.repositories(model.getOrganization());
if (model.getLimit() > 0) {
observable = observable.take(model.getLimit());
}
Subscription subscription = observable
.timeout(10, TimeUnit.SECONDS)
.doOnSubscribe(() -> model.setState(RUNNING))
.doOnTerminate(() -> model.setState(READY))
.subscribeOn(Schedulers.io())
.subscribe(
model.getRepositories()::add,
Throwable::printStackTrace);
model.setSubscription(subscription);
@aalmiray
Retrofit + RxJava
public interface GithubAPI {
@GET("/orgs/{name}/repos")
Observable<Response<List<Repository>>> repositories(@Path("name") String
name);
@GET
Observable<Response<List<Repository>>> repositoriesPaginate(@Url String url);
}
@aalmiray
Retrofit + RxJava
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(JacksonConverterFactory.create(objectMapper))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(GithubAPI.class);
@aalmiray
Retrofit + RxJava
// concatenating multiple results into a single Observable
public Observable<Repository> repositories(String organization) {
requireNonNull(organization, "Argument 'organization' must not be blank");
return paginatedObservable(
() -> {
LOG.info("Querying /orgs/{}/repos", organization);
return api.repositories(organization);
},
(Links links) -> {
String next = links.next();
LOG.info("Querying {}", next);
return api.repositoriesPaginate(next);
});
}
@aalmiray
Retrofit + RxJava
// concatenating multiple results into a single Observable
private static <T> Observable<T> paginatedObservable(FirstPageSupplier<T>
firstPage, NextPageSupplier<T> nextPage) {
return processPage(nextPage, firstPage.get());
}
private static <T> Observable<T> processPage(NextPageSupplier<T> supplier,
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;
});
}
@aalmiray
RxJava - http://reactivex.io
• Implements the Observable pattern.
• Delivers dozens of operations out of the box, e.g. zip, reduce,
concat.
• Supports backpressure, i.e, when the data producer is faster than
the data consumer.
@aalmiray
COMPONENT COMMUNICATION
How do you keep two unrelated
components communicated with one
another?
How do you push data down the stream
without forcing publishers to wait for
consumers?
@aalmiray
MBassador -
https://github.com/bennidi/mbassador
// event publishing
@Inject private ApplicationEventBus eventBus;
model.setSubscription(observable
.timeout(10, TimeUnit.SECONDS)
.doOnSubscribe(() -> model.setState(RUNNING))
.doOnTerminate(() -> model.setState(READY))
.doOnError(throwable -> eventBus.publishAsync(new ThrowableEvent(throwable)))
.subscribeOn(Schedulers.io())
.subscribe(model.getRepositories()::add));
@aalmiray
MBassador -
https://github.com/bennidi/mbassador
// event consuming
import net.engio.mbassy.listener.Handler;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
public class ApplicationEventHandler {
@Inject private ApplicationEventBus eventBus;
@PostConstruct private void init() { eventBus.subscribe(this); }
@PreDestroy private void destroy() { eventBus.unsubscribe(this); }
@Handler
public void handleThrowable(ThrowableEvent event) {
// handle event here !!
}
}
@aalmiray
MBassador -
https://github.com/bennidi/mbassador
• Configurable event bus based on annotations.
• Faster implementation than Guava’s event bus.
• https://github.com/bennidi/eventbus-performance
• NOTE: project is no longer actively maintained, fixes and features will
be slow to come.
@aalmiray
TESTING
@aalmiray
JUnitParams -
https://github.com/Pragmatists/JUnitParams
@RunWith(JUnitParamsRunner.class)
public class SampleServiceTest {
@Test
@Parameters({",Howdy stranger!",
"Test, Hello Test"})
public void sayHello(String input, String output) {
// given:
SampleService service = new SampleService();
// expect:
assertThat(service.sayHello(input), equalTo(output));
}
}
@aalmiray
JUnitParams -
https://github.com/Pragmatists/JUnitParams
• Parameterize multiple methods with different argument
cardinality.
• Different data provider strategies.
@aalmiray
Mockito - http://mockito.org
@Test @Parameters({",Howdy stranger!", "Test, Hello Test"})
public void sayHelloAction(String input, String output) {
// given:
SampleController controller = new SampleController();
controller.setModel(new SampleModel());
controller.setService(mock(SampleService.class));
// expectations
when(controller.getService().sayHello(input)).thenReturn(output);
// when:
controller.getModel().setInput(input);
controller.sayHello();
// then:
assertThat(controller.getModel().getOutput(), equalTo(output));
verify(controller.getService(), only()).sayHello(input);
}
@aalmiray
Mockito - http://mockito.org
• Fluid DSL based on static methods.
• Provides support for Stubs, Mocks, and Spies.
• Mock interfaces, abstract classes, and concrete classes.
@aalmiray
Jukito - https://github.com/ArcBees/Jukito
@RunWith(JukitoRunner.class)
public class SampleControllerJukitoTest {
@Inject private SampleController controller;
@Before
public void setupMocks(SampleService sampleService) {
when(sampleService.sayHello("Test")).thenReturn("Hello Test");
}
@Test
public void sayHelloAction() {
controller.setModel(new SampleModel());
controller.getModel().setInput("Test");
controller.sayHello();
assertThat(controller.getModel().getOutput(),
equalTo("Hello Test"));
verify(controller.getService(), only()).sayHello("Test");
}
}
@aalmiray
Jukito - https://github.com/ArcBees/Jukito
• Combines JUnit, Guice, and Mockito
• Bind multiple values to the same source type.
• Can be used to parameterize test methods.
@aalmiray
Spock- http://spockframework.org
@spock.lang.Unroll
class SampleControllerSpec extends spock.lang.Specification {
def "Invoke say hello with #input results in #output"() {
given:
SampleController controller = new SampleController()
controller.model = new SampleModel()
controller.service = Mock(SampleService) {
sayHello(input) >> output
}
when:
controller.model.input = input
controller.sayHello()
then:
controller.model.output == output
where:
input << ['', 'Test']
output << ['Howdy, stranger!', 'Hello Test']
}
}
@aalmiray
Spock- http://spockframework.org
• Groovy based DSL.
• Parameterize multiple methods with different argument
cardinality.
• Parameterize test method names.
• JUnit friendly (can use extensions and rules).
@aalmiray
Awaitility -
https://github.com/awaitility/awaitility
@Test
public void happyPath(Github github) {
// given:
Collection<Repository> repositories = createSampleRepositories();
when(github.repositories(ORGANIZATION))
.thenReturn(Observable.from(repositories));
// when:
model.setOrganization(ORGANIZATION);
controller.load();
await().timeout(2, SECONDS).until(model::getState, equalTo(State.READY));
// then:
assertThat(model.getRepositories(), hasSize(10));
assertThat(model.getRepositories(), equalTo(repositories));
verify(github, only()).repositories(ORGANIZATION);
}
@aalmiray
Awaitility -
https://github.com/awaitility/awaitility
• DSL for testing multi-threaded code.
• Extensions available for Java8, Groovy, and Scala.
• Conditions can be customized with Hamcrest matchers.
@aalmiray
WireMock- http://wiremock.org/
import static com.github.tomakehurst.wiremock.client.WireMock.*;
String nextUrl = "/organizations/1/repos?page=2";
List<Repository> repositories = createSampleRepositories();
stubFor(get(urlEqualTo("/orgs/" + ORGANIZATION + "/repos"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/json")
.withHeader("Link", "<http://localhost:8080" + nextUrl + ">; rel="next"")
.withBody(repositoriesAsJSON(repositories.subList(0, 5), objectMapper))));
stubFor(get(urlEqualTo(nextUrl ))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/json")
.withBody(repositoriesAsJSON(repositories.subList(5, 10), objectMapper))));
@aalmiray
WireMock- http://wiremock.org/
• Supply custom HTTP response payloads.
• DSL for matching HTTP requests.
• Supports record and playback.
@aalmiray
NICE, BUT
WHAT
ABOUT …
@aalmiray
@aalmiray
HTTP://ANDRESALMIRAY.COM/NEWSLETTER
HTTP://ANDRESALMIRAY.COM/EDITORIAL
THANK YOU!
ANDRES ALMIRAY
@AALMIRAY
ANDRESALMIRAY.COM

Java Libraries You Can't Afford To Miss

  • 1.
    JAVA LIBRARIES YOU CAN’T AFFORDTO MISS ANDRES ALMIRAY @AALMIRAY ANDRESALMIRAY.COM
  • 2.
    @aalmiray USE CASE Write anapplication that consumes a REST API. Components must be small and reusable. Say no to boilerplate code. Behavior should be easy to test.
  • 3.
    @aalmiray THE LIBRARIES PRODUCTION Guice |Spring OkHttp Retrofit JDeferred RxJava | Reactor MBassador Lombok Jackson Slf4j | Log4j2 TEST JUnitParams Mockito Jukito Awaitility Spock WireMock
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
    @aalmiray JCP Executive CommitteeAssociate Seat Committer Committer JSR377 Specification Lead
  • 9.
    @aalmiray USE CASE Write anapplication that consumes a REST API. Components must be small and reusable. Say no to boilerplate code. Behavior should be easy to test.
  • 10.
    @aalmiray GITHUB API Well documentedREST API Latest version located at https://developer.github.com/v3/ We’re interested in the repositories operation for now
  • 11.
    @aalmiray QUERYING REPOSITORIES API describedat https://developer.github.com/v3/repos/#list- organization-repositories Given a query of the form GET /orgs/${organization}/repos
  • 12.
    @aalmiray QUERY RESULT [ { "id": 1296269, "owner":{ /* omitted for brevity */ }, "name": "Hello-World", "full_name": "octocat/Hello-World", "description": "This your first repo!", "html_url": "https://github.com/octocat/Hello-World", /* many other properties follow */ }, /* additional repositories if they exist */ ]
  • 13.
    @aalmiray WHAT WE’LL NEED DependencyInjection HTTP client & REST behavior JSON mapping Boilerplate buster Handle concurrency
  • 14.
  • 15.
  • 16.
    @aalmiray public interface Github{ Promise<Collection<Repository>> repositories(String name); }
  • 17.
    @aalmiray public class AppController{ @Inject private AppModel model; @Inject private Github github; public void loadRepositories() { model.setState(RUNNING); github.repositories(model.getOrganization()) .done(model.getRepositories()::addAll) .fail(this::handleException) .always((state, resolved, rejected) -> model.setState(READY)); } }
  • 18.
    @aalmiray Guice - https://github.com/google/guice Injectorinjector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(Github.class) .to(GithubImpl.class) .in(Singleton.class); bind(AppModel.class) .in(Singleton.class); bind(GithubAPI.class) .toProvider(GithubAPIProvider.class) .in(Singleton.class); // additional bindings … } });
  • 19.
    @aalmiray Guice - https://github.com/google/guice •Reference implementation for JSR-330. • Can bind types, instances, constant values. • Can provide lazily evaluated instances, i.e, providers. • Extensible lifecycle.
  • 20.
  • 21.
    @aalmiray Guava - https://github.com/google/guava •New Collections: • MultiSet • BiMap • MultiMap • Table • Utility classes for Collections • Utility classes for String • Caches • Reflection • I/O • Functional programming support (JDK6+)
  • 22.
    @aalmiray Spring - http://projects.spring.io/spring- framework •More than just dependency injection • Supports JSR-330 • Assertions • MessageSource + MessageFormat • Serialization • JDBC, JPA • JMX • Validation • Scheduling • Testing
  • 23.
  • 24.
    @aalmiray Lombok - https://projectlombok.org importcom.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Data; import lombok.Setter; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class Repository implements Comparable<Repository> { private String name; private String description; @Setter(onMethod = @__({@JsonProperty("full_name")})) private String fullName; @Setter(onMethod = @__({@JsonProperty("html_url")})) private String htmlUrl; // continued
  • 25.
    @aalmiray Lombok - https://projectlombok.org //continued @Builder public static Repository build(String name, String fullName, String description, String htmlUrl) { Repository repository = new Repository(); repository.name = name; repository.fullName = fullName; repository.description = description; repository.htmlUrl = htmlUrl; return repository; } }
  • 26.
    @aalmiray Lombok - https://projectlombok.org publicclass ApplicationEvent { } @lombok.Data @lombok.EqualsAndHashCode(callSuper = true) @lombok.ToString(callSuper = true) public class NewInstanceEvent extends ApplicationEvent { @javax.annotation.Nonnull private final Object instance; }
  • 27.
    @aalmiray Lombok - https://projectlombok.org •Reduce boilerplate source code by generating bytecode. • Relies on APT (Annotation Processing Tool). • Extensible but not for the faint of heart. • Common usages already covered, i.e, POJOs, builders. • Don’t forget to enable annotation processing in your IDE!
  • 28.
  • 29.
    @aalmiray SLF4J - http://www.slf4j.org •Wraps all other logging frameworks: • java.util.logging • Apache Commons Logging • Apache Log4j • Provides varargs methods
  • 30.
    @aalmiray HTTP How many optionsare out there to build an HTTP client? How many ways are there to build a REST client?
  • 31.
    @aalmiray OkHttp - http://square.github.io/okhttp publicstatic final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); return response.body().string(); }
  • 32.
    @aalmiray OkHttp - http://square.github.io/okhttp •Basic HTTP/HTTP2 Client API. • Configuration is extensible via factory classes. • HTTP lifecycle can be decorated via interceptors.
  • 33.
    @aalmiray ADDING JAVA ONTOP OF HTTP What steps do we must ensure in order to build an HTTP/REST client? How should HTTP error codes be handled? How to handle connection errors? Parsing results in format X or Y.
  • 34.
    @aalmiray Retrofit - http://square.github.io/retrofit publicinterface GithubAPI { @GET("/orgs/{name}/repos") Call<List<Repository>> repositories(@Path("name") String name); @GET Call<List<Repository>>> repositoriesPaginate(@Url String url); }
  • 35.
    @aalmiray Retrofit - http://square.github.io/retrofit Retrofitretrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .addConverterFactory( JacksonConverterFactory.create(objectMapper)) .build(); return retrofit.create(GithubAPI.class); githubApi.repositories("foo");
  • 36.
    @aalmiray Retrofit - http://square.github.io/retrofit •Wraps REST calls with Java interfaces. • Extensible via factories. • Relies on OkHttp.
  • 37.
    @aalmiray MULTI-THREADED CODE The goldenrule of UI programming: - Everything related to UI must be executed inside the UI thread (read/write UI properties, paint/repaint, etc). - Everything else must be executed outside of the UI thread.
  • 38.
    @aalmiray JDeferred - http://jdeferred.org publicinterface Github { Promise<Collection<Repository>, Throwable, Void> repositories(String name); }
  • 39.
    @aalmiray JDeferred - http://jdeferred.org publicclass GithubImpl implements Github { @Inject private GithubAPI api; @Inject private DeferredManager deferredManager; @Override public Promise<Collection<Repository>, Throwable, Void> repositories(final String name) { return deferredManager.when(() -> { Response<List<Repository>> response = api.repositories(name).execute(); if (response.isSuccess()) { return response.body(); } throw new IllegalStateException(response.message()); }); } }
  • 40.
    @aalmiray JDeferred - http://jdeferred.org model.setState(RUNNING); intlimit = model.getLimit(); limit = limit > 0 ? limit : Integer.MAX_VALUE; Promise<Collection<Repository>, Throwable, Repository> promise = github.repositories(model.getOrganization(), limit); promise.progress(model.getRepositories()::add) .fail(Throwable::printStackTrace) .always((state, resolved, rejected) -> model.setState(READY));
  • 41.
    @aalmiray JDeferred - http://jdeferred.org •Delivers the concept of Promises • Promises can be chained • Java8 friendly, i.e, lambda expressions can be used • One shot execution.
  • 42.
    @aalmiray REACTIVE PROGRAMMING It’s timeto embrace a new paradigm. Reactive programming is a new name for old and well-known concepts: events and streams
  • 43.
    @aalmiray RxJava - http://reactivex.io Observable<Repository>observable = github.repositories(model.getOrganization()); if (model.getLimit() > 0) { observable = observable.take(model.getLimit()); } Subscription subscription = observable .timeout(10, TimeUnit.SECONDS) .doOnSubscribe(() -> model.setState(RUNNING)) .doOnTerminate(() -> model.setState(READY)) .subscribeOn(Schedulers.io()) .subscribe( model.getRepositories()::add, Throwable::printStackTrace); model.setSubscription(subscription);
  • 44.
    @aalmiray Retrofit + RxJava publicinterface GithubAPI { @GET("/orgs/{name}/repos") Observable<Response<List<Repository>>> repositories(@Path("name") String name); @GET Observable<Response<List<Repository>>> repositoriesPaginate(@Url String url); }
  • 45.
    @aalmiray Retrofit + RxJava Retrofitretrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .addConverterFactory(JacksonConverterFactory.create(objectMapper)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); return retrofit.create(GithubAPI.class);
  • 46.
    @aalmiray Retrofit + RxJava //concatenating multiple results into a single Observable public Observable<Repository> repositories(String organization) { requireNonNull(organization, "Argument 'organization' must not be blank"); return paginatedObservable( () -> { LOG.info("Querying /orgs/{}/repos", organization); return api.repositories(organization); }, (Links links) -> { String next = links.next(); LOG.info("Querying {}", next); return api.repositoriesPaginate(next); }); }
  • 47.
    @aalmiray Retrofit + RxJava //concatenating multiple results into a single Observable private static <T> Observable<T> paginatedObservable(FirstPageSupplier<T> firstPage, NextPageSupplier<T> nextPage) { return processPage(nextPage, firstPage.get()); } private static <T> Observable<T> processPage(NextPageSupplier<T> supplier, 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; }); }
  • 48.
    @aalmiray RxJava - http://reactivex.io •Implements the Observable pattern. • Delivers dozens of operations out of the box, e.g. zip, reduce, concat. • Supports backpressure, i.e, when the data producer is faster than the data consumer.
  • 49.
    @aalmiray COMPONENT COMMUNICATION How doyou keep two unrelated components communicated with one another? How do you push data down the stream without forcing publishers to wait for consumers?
  • 50.
    @aalmiray MBassador - https://github.com/bennidi/mbassador // eventpublishing @Inject private ApplicationEventBus eventBus; model.setSubscription(observable .timeout(10, TimeUnit.SECONDS) .doOnSubscribe(() -> model.setState(RUNNING)) .doOnTerminate(() -> model.setState(READY)) .doOnError(throwable -> eventBus.publishAsync(new ThrowableEvent(throwable))) .subscribeOn(Schedulers.io()) .subscribe(model.getRepositories()::add));
  • 51.
    @aalmiray MBassador - https://github.com/bennidi/mbassador // eventconsuming import net.engio.mbassy.listener.Handler; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; public class ApplicationEventHandler { @Inject private ApplicationEventBus eventBus; @PostConstruct private void init() { eventBus.subscribe(this); } @PreDestroy private void destroy() { eventBus.unsubscribe(this); } @Handler public void handleThrowable(ThrowableEvent event) { // handle event here !! } }
  • 52.
    @aalmiray MBassador - https://github.com/bennidi/mbassador • Configurableevent bus based on annotations. • Faster implementation than Guava’s event bus. • https://github.com/bennidi/eventbus-performance • NOTE: project is no longer actively maintained, fixes and features will be slow to come.
  • 53.
  • 54.
    @aalmiray JUnitParams - https://github.com/Pragmatists/JUnitParams @RunWith(JUnitParamsRunner.class) public classSampleServiceTest { @Test @Parameters({",Howdy stranger!", "Test, Hello Test"}) public void sayHello(String input, String output) { // given: SampleService service = new SampleService(); // expect: assertThat(service.sayHello(input), equalTo(output)); } }
  • 55.
    @aalmiray JUnitParams - https://github.com/Pragmatists/JUnitParams • Parameterizemultiple methods with different argument cardinality. • Different data provider strategies.
  • 56.
    @aalmiray Mockito - http://mockito.org @Test@Parameters({",Howdy stranger!", "Test, Hello Test"}) public void sayHelloAction(String input, String output) { // given: SampleController controller = new SampleController(); controller.setModel(new SampleModel()); controller.setService(mock(SampleService.class)); // expectations when(controller.getService().sayHello(input)).thenReturn(output); // when: controller.getModel().setInput(input); controller.sayHello(); // then: assertThat(controller.getModel().getOutput(), equalTo(output)); verify(controller.getService(), only()).sayHello(input); }
  • 57.
    @aalmiray Mockito - http://mockito.org •Fluid DSL based on static methods. • Provides support for Stubs, Mocks, and Spies. • Mock interfaces, abstract classes, and concrete classes.
  • 58.
    @aalmiray Jukito - https://github.com/ArcBees/Jukito @RunWith(JukitoRunner.class) publicclass SampleControllerJukitoTest { @Inject private SampleController controller; @Before public void setupMocks(SampleService sampleService) { when(sampleService.sayHello("Test")).thenReturn("Hello Test"); } @Test public void sayHelloAction() { controller.setModel(new SampleModel()); controller.getModel().setInput("Test"); controller.sayHello(); assertThat(controller.getModel().getOutput(), equalTo("Hello Test")); verify(controller.getService(), only()).sayHello("Test"); } }
  • 59.
    @aalmiray Jukito - https://github.com/ArcBees/Jukito •Combines JUnit, Guice, and Mockito • Bind multiple values to the same source type. • Can be used to parameterize test methods.
  • 60.
    @aalmiray Spock- http://spockframework.org @spock.lang.Unroll class SampleControllerSpecextends spock.lang.Specification { def "Invoke say hello with #input results in #output"() { given: SampleController controller = new SampleController() controller.model = new SampleModel() controller.service = Mock(SampleService) { sayHello(input) >> output } when: controller.model.input = input controller.sayHello() then: controller.model.output == output where: input << ['', 'Test'] output << ['Howdy, stranger!', 'Hello Test'] } }
  • 61.
    @aalmiray Spock- http://spockframework.org • Groovybased DSL. • Parameterize multiple methods with different argument cardinality. • Parameterize test method names. • JUnit friendly (can use extensions and rules).
  • 62.
    @aalmiray Awaitility - https://github.com/awaitility/awaitility @Test public voidhappyPath(Github github) { // given: Collection<Repository> repositories = createSampleRepositories(); when(github.repositories(ORGANIZATION)) .thenReturn(Observable.from(repositories)); // when: model.setOrganization(ORGANIZATION); controller.load(); await().timeout(2, SECONDS).until(model::getState, equalTo(State.READY)); // then: assertThat(model.getRepositories(), hasSize(10)); assertThat(model.getRepositories(), equalTo(repositories)); verify(github, only()).repositories(ORGANIZATION); }
  • 63.
    @aalmiray Awaitility - https://github.com/awaitility/awaitility • DSLfor testing multi-threaded code. • Extensions available for Java8, Groovy, and Scala. • Conditions can be customized with Hamcrest matchers.
  • 64.
    @aalmiray WireMock- http://wiremock.org/ import staticcom.github.tomakehurst.wiremock.client.WireMock.*; String nextUrl = "/organizations/1/repos?page=2"; List<Repository> repositories = createSampleRepositories(); stubFor(get(urlEqualTo("/orgs/" + ORGANIZATION + "/repos")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "text/json") .withHeader("Link", "<http://localhost:8080" + nextUrl + ">; rel="next"") .withBody(repositoriesAsJSON(repositories.subList(0, 5), objectMapper)))); stubFor(get(urlEqualTo(nextUrl )) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "text/json") .withBody(repositoriesAsJSON(repositories.subList(5, 10), objectMapper))));
  • 65.
    @aalmiray WireMock- http://wiremock.org/ • Supplycustom HTTP response payloads. • DSL for matching HTTP requests. • Supports record and playback.
  • 66.
  • 67.
  • 68.
  • 69.