Designing, Implementing, and Using Reactive APIs

1,192 views

Published on

SpringOne Platform 2016
Speakers: Ben Hale; Cloud Foundry Java Lead, Pivotal. Paul Harris; Software Engineer, Pivotal. Stephane Maldini; Project Reactor Leads

The Java community is on the cusp of a major change in programming model. As the industry moves towards high-performance micro-service architectures, the need for a reactive programming model becomes clear. In this session, the lead developers of the Cloud Foundry Java Client will talk about what led them to choose a reactive API. Using that project as a lens, they'll explore how they designed and implemented this API using Project Reactor and what users will expect when using a reactive API. If you are a developer looking to provide reactive APIs, this is your chance to gain the experience of team building a large, production-ready reactive library.

Published in: Technology

Designing, Implementing, and Using Reactive APIs

  1. 1. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Designing, Implementing, and Using Reactive APIs Ben Hale (@nebhale)
 Paul Harris (@twoseat)
  2. 2. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Introduction to Reactive Programming • Reactive is used to broadly define event-driven systems • Moves imperative logic to • asynchronous • non-blocking • functional-style code • Allows stable, scalable access to external systems 2
  3. 3. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Cloud Foundry Java Client • Cloud Foundry is composed of • RESTful APIs • Secured by OAuth • Transmitting large JSON request and response payloads • APIs are low level, often requiring orchestration 3
  4. 4. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Cloud Foundry Java Client • Java has become the preferred language for orchestrating Cloud Foundry • Complex orchestration is difficult when working against generic payloads • A strongly typed Java API is required for maintainability 4
  5. 5. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Generic Request and Response Payloads 5 Map<String, Object> request = new HashMap<>();
 request.put("name", "test-application");
 request.put("application", Paths.get("target/test-application.jar"));
 request.put("buildpack", "java-buildpack");
 this.operations.applications()
 .create(request)
 .map(response -> response.get("id"));
  6. 6. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Strongly Typed Request and Response Payloads 6 this.operations.applications()
 .create(CreateApplicationRequest.builder()
 .name("test-application")
 .application(Paths.get("target/test-application.jar"))
 .buildpack("java-buildpack")
 .build())
 .map(CreateApplicationResponse::getId);
  7. 7. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Cloud Foundry Java Client • The high-latency network interaction is a good fit for a Reactive Design • Encourages asynchronous and highly concurrent usage • Partnering with Project Reactor early influenced both the design of the client and of Reactor 7
  8. 8. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Design
  9. 9. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ When To Use Reactive • Networking • Latency • Failures • Backpressure • Highly Concurrent Operations • Scalability 9
  10. 10. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ What Should a Reactive API Return? • Flux<T> • Returns 0 to N values • Mono<T> • Returns 0 to 1 value • The Java Client often returns Monos containing collections 10
  11. 11. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Using a Flux<T> Response 11 Flux<Application> listApplications() {...}
 
 Flux<String> listApplicationNames() {
 return listApplications()
 .map(Application::getName);
 }
 
 void printApplicationName() {
 listApplicationNames()
 .subscribe(System.out::println);
 }
  12. 12. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Using a Mono<T> Response 12 Flux<Application> listApplications() {...}
 
 Mono<List<String>> listApplicationNames() {
 return listApplications()
 .map(Application::getName) .collectAsList();
 }
 
 Mono<Boolean> doesApplicationExist(String name) {
 return listApplicationNames()
 .map(names -> names.contains(name));
 }
  13. 13. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Every Method Must Return Something • Unlike many imperative APIs, void is not an appropriate return type • A reactive flow declares behavior but does not execute it • Consumers must subscribe to a Flux or Mono in order to begin execution 13
  14. 14. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Imperative Operation 14 void delete(String id) { this.restTemplate.delete(URI, id); }
 
 public static void main(String[] args) {
 delete("test-id");
 }
  15. 15. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Reactive Operation 15 Mono<Void> delete(String id) {
 return this.httpClient.delete(URI, id);
 }
 
 public static void main(String[] args) {
 CountDownLatch latch = new CountDownLatch(1);
 
 delete("test-id")
 .subscribe(n -> {}, Throwable::printStackTrace, () -> latch.countDown());
 
 latch.await();
 }
  16. 16. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Scope of a Method • Methods should be designed to be small and reusable • Reusable methods can be more easily composed into larger operations • These methods can be more flexibly combined into parallel or sequential operations 16
  17. 17. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Reusable Methods 17 Mono<ListApplicationsResponse> getPage(int page) {
 return this.client.applicationsV2()
 .list(ListApplicationsRequest.builder()
 .page(page)
 .build());
 }
 
 public static void main(String[] args) {
 getPage(1)
 .map(PaginatedResponse::getTotalPages)
 .flatMap(totalPages -> Flux.range(2, totalPages)
 .flatMap(page -> getPage(page)))
 .subscribe(System.out::println);
 }
  18. 18. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Implement
  19. 19. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Sequential and Parallel Coordination • All significant performance improvements come from concurrency • But, concurrency is very hard to do properly • Reactive frameworks allow you to define sequential and parallel relationships between operations • The framework does the hard work of coordinating the operations 19
  20. 20. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Coordination 20 Mono<ListApplicationsResponse> getPage(int page) {
 return this.client.applicationsV2()
 .list(ListApplicationsRequest.builder()
 .page(page)
 .build());
 }
 
 public static void main(String[] args) {
 getPage(1)
 .map(PaginatedResponse::getTotalPages)
 .flatMap(totalPages -> Flux.range(2, totalPages)
 .flatMap(page -> getPage(page)))
 .subscribe(System.out::println);
 }
  21. 21. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Conditional Logic • Like normal values, errors pass through the flow’s operations • These errors can be passed all the way to consumers, or flows can change behavior based on them • Flows can transform errors or generate new results based on the error 21
  22. 22. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Error Logic 22 public Mono<AppStatsResponse> getApp(GetAppRequest request) {
 return client.applications()
 .statistics(AppStatsRequest.builder()
 .applicationId(request.id())
 .build())
 .otherwise(ExceptionUtils.statusCode(APP_STOPPED_ERROR),
 t -> Mono.just(AppStatsResponse.builder().build()));
 }
  23. 23. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Conditional Logic • It is valid for a flow to complete without sending any elements • This is often equivalent to returning null from an imperative API • This completion can be passed all the way to consumers • Alternatively, flows can switch behavior or generate new elements based on the completion 23
  24. 24. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Empty Logic 24 public Flux<GetDomainsResponse> getDomains(GetDomainsRequest request) {
 return requestPrivateDomains(request.getId())
 .switchIfEmpty(requestSharedDomains(request.getId));
 }
  25. 25. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Empty Logic 25 public Mono<String> getDomainId(GetDomainIdRequest request) {
 return getPrivateDomainId(request.getName())
 .otherwiseIfEmpty(getSharedDomainId(request.getName()))
 .otherwiseIfEmpty(ExceptionUtils.illegalState( "Domain %s not found", request.getName()));
 }
  26. 26. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Conditional Logic • Sometimes you want to make decisions not based on errors or emptiness, but on values themselves • While it’s possible to implement this logic using operators, it often proves to be more complex than is worthwhile • It’s OK to use imperative conditional statements 26
  27. 27. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Conditional Logic Avoiding Imperative 27 public Mono<String> getDomainId(String domain, String organizationId) {
 return Mono.just(domain)
 .filter(d -> d == null)
 .then(getSharedDomainIds()
 .switchIfEmpty(getPrivateDomainIds(organizationId))
 .next()
 .otherwiseIfEmpty(ExceptionUtils.illegalState("Domain not found")))
 .otherwiseIfEmpty(getPrivateDomainId(domain, organizationId)
 .otherwiseIfEmpty(getSharedDomainId(domain))
 .otherwiseIfEmpty(ExceptionUtils.illegalState("Domain %s not found", domain)));
 }
  28. 28. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Conditional Logic Using Imperative 28 public Mono<String> getDomainId(String domain, String organizationId) {
 if (domain == null) {
 return getSharedDomainIds()
 .switchIfEmpty(getPrivateDomainIds(organizationId))
 .next()
 .otherwiseIfEmpty(ExceptionUtils.illegalState("Domain not found"));
 } else {
 return getPrivateDomainId(domain, organizationId)
 .otherwiseIfEmpty(getSharedDomainId(domain))
 .otherwiseIfEmpty(ExceptionUtils.illegalState("Domain %s not found", domain));
 }
 }
  29. 29. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Testing • Most useful flows will be asynchronous • Testing frameworks are aggressively synchronous, registering results before asynchronous results return • To compensate for this you must block the main thread and make test assertions in the asynchronous thread 29
  30. 30. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Test Finishing Before Assertions 30 @Test
 public void noLatch() {
 Mono.just("alpha")
 .subscribeOn(Schedulers.computation())
 .subscribe(s -> assertEquals("bravo", s));
 }
  31. 31. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Test Finishing Before Assertions 31 @Test
 public void noLatch() {
 Mono.just("alpha")
 .subscribeOn(Schedulers.computation())
 .subscribe(s -> assertEquals("bravo", s));
 }
  32. 32. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ @Test
 public void latch() throws InterruptedException {
 CountDownLatch latch = new CountDownLatch(1);
 AtomicReference<String> actual = new AtomicReference<>();
 
 Mono.just("alpha")
 .subscribeOn(Schedulers.computation())
 .subscribe(actual::set, t -> latch.countDown(), latch::countDown);
 
 latch.await();
 assertEquals("bravo", actual.get());
 } Test Blocking Using CountDownLatch 32
  33. 33. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ @Test
 public void latch() throws InterruptedException {
 CountDownLatch latch = new CountDownLatch(1);
 AtomicReference<String> actual = new AtomicReference<>();
 
 Mono.just("alpha")
 .subscribeOn(Schedulers.computation())
 .subscribe(actual::set, t -> latch.countDown(), latch::countDown);
 
 latch.await();
 assertEquals("bravo", actual.get());
 } Test Blocking Using CountDownLatch 33
  34. 34. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Test Subscriber • Testing a Reactive design requires more than just blocking • Multiple value assertion • Expected error assertion • Unexpected error failure • A TestSubscriber<T> can collect this behavior into a single type 34
  35. 35. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Test Using TestSubscriber<T> 35 @Test
 public void testSubscriber() throws InterruptedException {
 TestSubscriber<String> testSubscriber = new TestSubscriber<>();
 
 Flux.just("alpha", "bravo")
 .subscribeOn(Schedulers.computation())
 .subscribe(testSubscriber
 .assertEquals("alpha")
 .assertEquals("bravo"));
 
 testSubscriber.verify(ofSeconds(5));
 }
  36. 36. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Test Using TestSubscriber<T> 36 @Test
 public void testSubscriber() throws InterruptedException {
 TestSubscriber<String> testSubscriber = new TestSubscriber<>();
 
 Flux.just("alpha", "bravo")
 .subscribeOn(Schedulers.computation())
 .subscribe(testSubscriber
 .assertEquals("alpha")
 .assertEquals("bravo"));
 
 testSubscriber.verify(ofSeconds(5));
 }
  37. 37. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Use
  38. 38. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Countdown Latches • Reactive frameworks can only coordinate their own operations • Many execution environments have processes that outlast individual threads • Reactive programming plays nicely here • Some execution environments aggressively terminate the process before any individual thread 38
  39. 39. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ main() Finishing Before Subscriber<T> 39 public static void main(String[] args) {
 Mono.just("alpha")
 .delaySubscription(Duration.ofSeconds(1))
 .subscribeOn(Schedulers.computation())
 .subscribe(System.out::println);
 }
  40. 40. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ main() Finishing Before Subscriber<T> 40 public static void main(String[] args) {
 Mono.just("alpha")
 .delaySubscription(Duration.ofSeconds(1))
 .subscribeOn(Schedulers.computation())
 .subscribe(System.out::println);
 }
  41. 41. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ main() Waiting for Subscriber<T> 41 public static void main(String[] args) throws InterruptedException {
 CountDownLatch latch = new CountDownLatch(1);
 
 Mono.just("alpha")
 .delaySubscription(Duration.ofSeconds(1))
 .subscribeOn(Schedulers.computation())
 .subscribe(System.out::println, t -> latch.countDown(), latch::countDown);
 
 latch.await();
 }
  42. 42. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ main() Waiting for Subscriber<T> 42 public static void main(String[] args) throws InterruptedException {
 CountDownLatch latch = new CountDownLatch(1);
 
 Mono.just("alpha")
 .delaySubscription(Duration.ofSeconds(1))
 .subscribeOn(Schedulers.computation())
 .subscribe(System.out::println, t -> latch.countDown(), latch::countDown);
 
 latch.await();
 }
  43. 43. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Blocking flows • It’s very common to bridge between imperative and reactive APIs • In those cases blocking while waiting for a result is appropriate. 43
  44. 44. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Bridging Imperative and Reactive using block() 44 Mono<User> requestUser(String name) {...}
 @Override
 User requestUser(String name) {
 return getUser(name)
 .block();
 }
  45. 45. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Bridging Imperative and Reactive using block() 45 Flux<User> requestUsers() {...}
 @Override
 List<User> listUsers() {
 return requestUsers()
 .collectList()
 .block();
 }
  46. 46. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Error Handling • Because errors are values they must be handled, they do not just show up • Subscribe has 0…3 parameters • Release latches on error 46
  47. 47. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Error Handling 47 public static void main(String[] args) throws InterruptedException {
 CountDownLatch latch = new CountDownLatch(1);
 
 Flux.concat(Mono.just("alpha"), Mono.error(new IllegalStateException()))
 .subscribe(System.out::println, t -> {
 t.printStackTrace();
 latch.countDown();
 }, latch::countDown);
 
 latch.await();
 }
  48. 48. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Composable Method References • Reactive programming is susceptible to callback hell, but it doesn’t have to be • Extracting behavior into private methods can be even more valuable than in imperative programming • Well named methods, and method reference syntax, lead to very readable flows 48
  49. 49. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Composable Method References 49 @Override
 public Flux<ApplicationSummary> list() {
 return Mono
 .when(this.cloudFoundryClient, this.spaceId)
 .then(function(DefaultApplications::requestSpaceSummary))
 .flatMap(DefaultApplications::extractApplications)
 .map(DefaultApplications::toApplicationSummary);
 }
  50. 50. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Point Free • You may have noticed that our code samples use a very compact style • Point Free (http://bit.ly/Point-Free) • It helps the developer think about composing functions (high level), rather than shuffling data (low level) • You don’t have to use, but we find that most people prefer it (eventually) 50
  51. 51. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Point Free Style 51 Mono<Void> deleteApplication(String name) {
 return PaginationUtils
 .requestClientV2Resources(page -> this.client.applicationsV2()
 .list(ListApplicationsRequest.builder()
 .name(name)
 .page(page)
 .build()))
 .single()
 .map(applicationResource -> applicationResource.getMetadata().getId())
 .then(applicationId -> this.client.applicationsV2()
 .delete(DeleteApplicationRequest.builder()
 .applicationId(applicationId)
 .build()));
 }
  52. 52. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Summary
  53. 53. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Designing, Implementing and Using Reactive APIs • Design • When to use • Return Mono/Flux • Reusable Methods • Implement • Sequential and Parallel • Conditional logic • Testing
 • Use • Countdown Latches • Blocking flows • Error Handling • Composable methods • Point Free Style 53
  54. 54. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Learn More. Stay Connected. https://github.com/cloudfoundry/cf-java-client @springcentral spring.io/blog @pivotal pivotal.io/blog @pivotalcf http://engineering.pivotal.io

×