Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An intro to Reactive Extensions

An intro to Reactive Extensions

An not so short introduction to Reactive Extensions

https://www.youtube.com/watch?v=YnL1dPyFyhg

Benoît Quenaudon

March 07, 2017
Tweet

More Decks by Benoît Quenaudon

Other Decks in Programming

Transcript

  1. Reactive Extensions
    入門勉強会
    #漫才ではない
    Benoît Quenaudon 金子 良太

    View full-size slide

  2. Agenda
    ● Why Reactive?
    ○ Problem with Android
    ○ Problem with Back-end
    ● What is Reactive Extensions?
    ○ Observables
    ○ Operators
    ○ Schedulers
    ● Use Cases
    ○ API gateway pattern
    ○ Data writing across distributed systems

    View full-size slide

  3. Why Reactive?
    #Android

    View full-size slide

  4. Why Reactive? #android
    interface UserManager {
    User getUser();
    void setName(String name);
    void setAge(int age);
    }

    View full-size slide

  5. Why Reactive? #android
    interface UserManager {
    User getUser();
    void setName(String name);
    void setAge(int age);
    }
    UserManager um = new UserManager();
    System.out.println(um.getUser());
    um.setName("Bo Jackson");
    System.out.println(um.getUser());

    View full-size slide

  6. interface UserManager {
    User getUser();
    void setName(String name); // <-- now async
    void setAge(int age); // <-- now async
    }

    View full-size slide

  7. interface UserManager {
    User getUser();
    void setName(String name, Runnable callback);
    void setAge(int age, Runnable callback);
    }

    View full-size slide

  8. interface UserManager {
    User getUser();
    void setName(String name, Runnable callback);
    void setAge(int age, Runnable callback);
    }
    UserManager um = new UserManager();
    System.out.println(um.getUser());
    um.setName("Bo Jackson", new Runnable() {
    @Override public void run() {
    System.out.println(um.getUser());
    }
    });

    View full-size slide

  9. interface UserManager {
    User getUser();
    void setName(String name, Listener listener);
    void setAge(int age, Listener listener);
    interface Listener {
    void success(User user);
    void failure(IOException exception);
    }
    }

    View full-size slide

  10. UserManager um = new UserManager();
    System.out.println(um.getUser());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    System.out.println(um.getUser());
    }
    @Override public void failure(IOException exception) {
    // TODO show the error...
    }
    });

    View full-size slide

  11. UserManager um = new UserManager();
    System.out.println(um.getUser());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    System.out.println(um.getUser());
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    um.setAge(54, new UserManager.Listener() {
    @Override public void success() {
    System.out.println(um.getUser());
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });

    View full-size slide

  12. UserManager um = new UserManager();
    System.out.println(um.getUser());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    System.out.println(um.getUser());
    um.setAge(54, new UserManager.Listener() {
    @Override public void success() {
    System.out.println(um.getUser());
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });

    View full-size slide

  13. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    tv.setText(um.getUser().toString());
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }
    }

    View full-size slide

  14. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    tv.setText(um.getUser().toString());
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }
    }

    View full-size slide

  15. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    if (isDestroyed()) {
    tv.setText(um.getUser().toString());
    }
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }
    }

    View full-size slide

  16. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    if (isDestroyed()) {
    tv.setText(um.getUser().toString());
    }
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }
    }

    View full-size slide

  17. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    if (isDestroyed()) {
    tv.setText(um.getUser().toString());
    }
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }
    }

    View full-size slide

  18. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    runOnUiThread(new Runnable() {
    @Override public void run() {
    if (isDestroyed()) {
    tv.setText(um.getUser().toString());
    }
    }
    });
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }

    View full-size slide

  19. public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();
    @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.user);
    TextView tv = (TextView) findViewById(R.id.username);
    tv.setText(um.getUser().toString());
    um.setName("Bo Jackson", new UserManager.Listener() {
    @Override public void success() {
    runOnUiThread(new Runnable() {
    @Override public void run() {
    if (isDestroyed()) {
    tv.setText(um.getUser().toString());
    }
    }
    });
    }
    @Override public void failure(IOException exception) { // TODO show the error… }
    });
    }

    View full-size slide

  20. ぶっちゃけ・・・

    View full-size slide

  21. Why Reactive?
    #BackEnd

    View full-size slide

  22. Netflix Story

    View full-size slide

  23. Client Server
    Old Netflix Model
    API

    View full-size slide

  24. Problems
    ● Low spec devices
    ● Why not leverage the servers?
    ⇒ Client = one request only

    View full-size slide

  25. Client Server
    New Netflix Model
    API

    View full-size slide

  26. Async + Callbacks

    View full-size slide

  27. Challenge
    ● Service Composition
    ● Error handling
    ● Developer Productivity
    ⇒ Callbacks hell
    ⇒ Too hard

    View full-size slide

  28. Async + Callbacks

    View full-size slide

  29. Try
    ● java.util.concurrent.Future
    ○ Future.get() is blocking…
    ○ Future>>
    ● How about Reactive Programming?
    ○ Observable
    ⇒ Reactive Programming for the win

    View full-size slide

  30. Reactive Programming ?

    View full-size slide

  31. Reactive programming is
    programming with
    asynchronous data streams.

    View full-size slide

  32. Reactive Extensions?

    View full-size slide

  33. “ReactiveX is a library
    for composing asynchronous
    and event-based programs
    by using observable
    sequences”

    View full-size slide

  34. ReactiveX
    ● Extension of Observable Pattern
    ● Declarative Composition of Streams
    ● Abstraction of
    ○ low-level threading,
    ○ Synchronization,
    ○ Thread-safety,
    ○ concurrent data structures,
    ○ and non-blocking I/O.

    View full-size slide

  35. What is the
    Observable Pattern?

    View full-size slide

  36. 例:Stream of Click
    time
    Click event Stream has
    completed
    Error

    View full-size slide

  37. Observable Pattern
    ● Listen to Stream = Subscribing
    ● Only async
    ○ event ⇒ funcA()
    ○ error ⇒ funcB()
    ○ complete ⇒ funcC()
    ● funcA, B, C = Observers = Consumer
    ● Stream = Subject = Observable

    View full-size slide

  38. Iterator vs Observable
    デモンストレーション

    View full-size slide

  39. event Iterable (pull) Observable (push)
    retrieve data T next() onNext(T)
    discover error throws Exception onError(Exception)
    complete !hasNext() onCompleted()

    View full-size slide

  40. Reactive Extensions
    ● Observables
    ● Operators
    ● Schedulers

    View full-size slide

  41. Observables
    ● Usually do work when start/stop listening
    ● One event, many events or empty
    ● Terminates with an error or completion
    ● May never terminate

    View full-size slide

  42. Observables
    interface UserManager {
    User getUser();
    void setName(String name);
    void setAge(int age);
    }

    View full-size slide

  43. Observables
    interface UserManager {
    Observable getUser();
    void setName(String name);
    void setAge(int age);
    }

    View full-size slide

  44. Observables
    interface UserManager {
    Observable getUser();
    Completable setName(String name);
    Completable setAge(int age);
    }

    View full-size slide

  45. Creating Observables
    ● RxJava
    ○ Observable.just("Bo Jacks");
    ○ Observable.fromArray(array);
    ○ Observable.fromIterable(list);
    ○ Observable.create(...);
    ● RxJS
    ○ Rx.Observable.just(42)
    ○ Rx.Observable.from(iterable)
    ○ Rx.Observable.create(subscribe)

    View full-size slide

  46. Operators
    ● Manipulate or combine data
    ● Manipulate threading
    ● Manipulate emissions

    View full-size slide

  47. Operators
    String greeting = "Hello";
    String yelling = greeting.toUpperCase();

    View full-size slide

  48. Operators
    Observable greeting = Observable.just("Hello");
    String yelling = greeting.toUpperCase();

    View full-size slide

  49. Operators
    Observable greeting = Observable.just("Hello");
    Observable yelling = greeting.map(s -> s.toUpperCase());

    View full-size slide

  50. Operators
    Observable greeting = Observable.just("Hello");
    Observable yelling = greeting.map(s -> s.toUpperCase());

    View full-size slide

  51. map as a marble diagram

    View full-size slide

  52. Transforming Operators
    ● Buffer
    ● FlatMap
    ● GroupBy
    ● Map
    ● Scan
    ● Window

    View full-size slide

  53. Filtering Operators
    ● Debounce
    ● Distinct
    ● ElementAt
    ● Filter
    ● First
    ● IgnoreElements
    ● Last
    ● Sample
    ● Skip
    ● SkipLast
    ● Take

    View full-size slide

  54. filter as a marble diagram

    View full-size slide

  55. Combining Operators
    ● And/Then/When
    ● CombineLatest
    ● Join
    ● Merge
    ● StartWith
    ● Switch
    ● Zip

    View full-size slide

  56. So on...
    ● Error Handling Operators
    ○ catch, retry
    ● Observable Utility Operators
    ○ delay, timeout, observeOn, subscribeOn, etc
    ● Conditional and Boolean Operators
    ○ all, skipUntil, contains, etc
    ● Mathematical and Aggregate Operators
    ○ average, count, max, etc
    ● etc

    View full-size slide

  57. Schedulers
    getDataFromNetwork() // Observable
    .skip(10)
    .take(5)
    .map(s -> s + " transformed")
    .subscribe(s -> println s)

    View full-size slide

  58. Schedulers
    getDataFromNetwork()
    .skip(10)
    .take(5)
    .map(s -> s + " transformed")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread()))
    .subscribe(s -> println s)

    View full-size slide

  59. Schedulers
    ● subscribeOn:
    ○ Everything from top to the next observeOn run
    on my thread.
    ● observeOn:
    ○ Everything below me run on my thread.

    View full-size slide

  60. Schedulers
    getDataFromNetwork()
    .skip(10)
    .take(5)
    .map(s -> s + " transformed")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread()))
    .subscribe(s -> println s)
    IO
    Main

    View full-size slide

  61. RxJS: 事前
    [1, 2, 3].map(x => x + 1)
    // [2, 3, 4]
    [ [1], [2, 3], [], [4] ].concatAll()
    // [1, 2, 3, 4]

    View full-size slide

  62. RxJS
    getElementDrags = function(elmt) {
    return elmt.mouseDowns.
    map(mouseDown =>
    document.mouseMoves.
    takeUntil(document.mouseUps)).
    concatAll();
    }
    getElementDrags(image).
    forEach(pos => image.position = pos);

    View full-size slide

  63. D D D
    map(mouseDown => document.mouseMoves
    .takeUntil(document.mouseUps))
    U U U
    concatAll()
    forEach(pos => image.position = pos)
    Operator
    Operator
    Observer
    M M
    M M M
    M
    M
    M
    M
    M

    View full-size slide

  64. RxJS
    getElementDrags = function(elmt) {
    return elmt.mouseDowns.
    map(mouseDown =>
    document.mouseMoves.
    takeUntil(document.mouseUps)).
    concatAll();
    }
    getElementDrags(image).
    forEach(pos => image.position = pos);

    View full-size slide

  65. RxJava
    Observable editText;
    editText
    .filter(text -> text.length > 2)
    .debounce(250, MILLISECONDS)
    .map(text -> api.search(text))
    .subscribe(result -> showResult(result));

    View full-size slide

  66. filter(text -> text.length > 2)
    map(text -> api.search(text))
    result -> showResult(result)
    Operator
    Operator
    Observer
    k
    debounce(250, MILLISECONDS)
    Operator
    ka kai kaiz kaize kaizen
    kai kaiz kaize kaizen
    kaiz kaizen
    250ms 250ms

    View full-size slide

  67. RxJava
    Observable editText;
    editText
    .filter(text -> text.length > 2)
    .debounce(250, MILLISECONDS)
    .map(text -> api.search(text))
    .subscribe(result -> showResult(result));

    View full-size slide

  68. Back Pressure
    デモンストレーション

    View full-size slide

  69. Hot vs Cold Observable
    ● Cold
    ○ Start to work on subscription
    ● Hot
    ○ Start to work on creation

    View full-size slide

  70. How about Functional
    Reactive Programming?
    A different Animal

    View full-size slide

  71. Background
    ● Moore’s Law Is Dead. Now What?
    ● We need the power.
    ○ multi-devices, IoT, Big Data
    ○ For organizations' scalability

    View full-size slide

  72. Background
    ● Microservices
    ○ ≒ distributed systems
    ● Need API-based collaboration
    ● Need combination of multi tasks

    View full-size slide

  73. Complexity
    ● How do we combine multi data?
    ● How do we resolve dependencies between tasks?
    ● Concurrency / multi-threading / blocking
    ● Network
    ● Retry strategy / Message delivery reliability
    ● Eventual consistency
    ● Anti-fragile / Fault tolerant

    View full-size slide

  74. Use case 1
    Service Composition

    View full-size slide

  75. Pattern: API Gateway
    http://microservices.io/patterns/apigateway.html

    View full-size slide

  76. Scenario
    ● API-based collaboration
    ○ Authenticate
    ○ Get video contents
    ○ Get recommendations

    View full-size slide

  77. API as a Stream

    View full-size slide

  78. API as a Stream
    public interface AccountApi {
    @POST("api/account/login")
    Observable login(@Body LoginInputForm inputForm);
    }
    ● Retrofit & RxJava2 Adapter
    ● If you don't use these adapters, you
    can wrap API responses by yourself

    View full-size slide

  79. Service as a Stream

    View full-size slide

  80. Service as a Stream
    @Service
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class AccountService {
    private final ApiRegistry apiRegistry;
    public Observable
    login(String account, String password) {
    AccountApi accountApi = apiRegistry.of(AccountApi.class);
    return accountApi.login(new LoginInputForm(account, password));
    }
    }

    View full-size slide

  81. Service Composition

    View full-size slide

  82. @RequestMapping(path = "/api/example1", method = RequestMethod.POST)
    public ReadMergeOutputForm run(@Validated @RequestBody ReadMergeInputForm inputForm) {
    return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();
    }

    View full-size slide

  83. @RequestMapping(path = "/api/example1", method = RequestMethod.POST)
    public ReadMergeOutputForm run(@Validated @RequestBody ReadMergeInputForm inputForm) {
    return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();
    }

    View full-size slide

  84. @RequestMapping(path = "/api/example1", method = RequestMethod.POST)
    public ReadMergeOutputForm run(@Validated @RequestBody ReadMergeInputForm inputForm) {
    return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();
    }

    View full-size slide

  85. @RequestMapping(path = "/api/example1", method = RequestMethod.POST)
    public ReadMergeOutputForm run(@Validated @RequestBody ReadMergeInputForm inputForm) {
    return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();
    }

    View full-size slide

  86. @RequestMapping(path = "/api/example1", method = RequestMethod.POST)
    public ReadMergeOutputForm run(@Validated @RequestBody ReadMergeInputForm inputForm) {
    return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();
    }

    View full-size slide

  87. @RequestMapping(path = "/api/example1", method = RequestMethod.POST)
    public ReadMergeOutputForm run(@Validated @RequestBody ReadMergeInputForm inputForm) {
    return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();
    }

    View full-size slide

  88. Error Handling

    View full-size slide

  89. return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .map(loginOutputForm -> {
    if (!loginOutputForm.isSuccess()) {
    throw new RuntimeException();
    }
    return loginOutputForm;
    })
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();

    View full-size slide

  90. Fallback to default
    Or cache

    View full-size slide

  91. return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .map(loginOutputForm -> {
    if (!loginOutputForm.isSuccess()) {
    throw new RuntimeException();
    }
    return loginOutputForm;
    })
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    .onErrorReturnItem(RECOMMENDATIONS_ON_ERROR)
    )
    .zipWith(
    videoService.get()
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();

    View full-size slide

  92. Multi-Threading

    View full-size slide

  93. return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .map(loginOutputForm -> {
    if (!loginOutputForm.isSuccess()) {
    throw new RuntimeException();
    }
    return loginOutputForm;
    })
    .flatMap(
    loginOutputForm ->
    recommendationService
    .get(loginOutputForm.accountId())
    .onErrorReturnItem(RECOMMENDATIONS_ON_ERROR)
    )
    .zipWith(
    videoService.get().subscribeOn(Schedulers.computation())
    , (recommendationOutputForm, videoOutputForm) ->
    new ReadMergeOutputForm(
    videoOutputForm.getVideos()
    , recommendationOutputForm.getRecommendations()
    )
    )
    .blockingSingle();

    View full-size slide

  94. return accountService
    .login(inputForm.getAccount(), inputForm.getPassword())
    .doOnError(Throwable::printStackTrace)
    .doOnNext(loginOutputForm ->
    log.info(Thread.currentThread().getName())
    )
    .map(loginOutputForm -> {
    if (!loginOutputForm.isSuccess()) {
    throw new RuntimeException();
    }
    return loginOutputForm;
    });

    View full-size slide

  95. Use case 2
    Write data across
    distributed systems

    View full-size slide

  96. Consistency in a single system
    ● Can use the transaction
    ● e.g.
    ○ When a corporation data is created, an address
    data is also created
    ○ The corporation object manages the address
    object's life cycle

    View full-size slide

  97. Consistency in a single system
    @Transactional(rollbackFor = {Exception.class})
    public Corporation create(Corporation anonymousCorporation, Address anonymousAddress) {
    Corporation corporation = corporationRepository.save(anonymousCorporation);
    Address unidentifiedAddress = Address.builder()
    .corporationId(corporation.getId())
    .state(anonymousAddress.getState())
    .phone(anonymousAddress.getPhone())
    .build();
    Address address = addressRepository.save(unidentifiedAddress);
    return Corporation.aggregate(corporation, address);
    }

    View full-size slide

  98. Eventual Consistency for multi-services
    ● We couldn't use the transaction for the data
    consistency in HTTP protocol based on
    multi-services collaboration
    ● Instead of the transaction, we often use the
    eventual consistency
    ● We need take care of some things:
    ○ Idempotency
    ○ Message delivery reliability

    View full-size slide

  99. Scenario
    ● End user inputs corporation and address data
    ● Service A enqueues with end users' input data
    ● Worker processes the queue
    ○ Worker is responsible for guaranteeing for
    message delivery reliability
    ● Save corporation data in the service A with
    unique key message
    ● Service A requests Service B to save address
    data via Web API
    ○ Retry till successful response from Service B
    ⇒ Has to be Idempotent

    View full-size slide

  100. Service down

    View full-size slide

  101. Network busy

    View full-size slide

  102. Guarantee the Message delivery reliability

    View full-size slide

  103. Objects life cycle
    ● Requirements
    ○ When a corporation data is created, an address
    data should also be created.

    View full-size slide

  104. Repository for creating a corporation

    View full-size slide

  105. Repository
    public Corporation save(Corporation corporation) {
    Corporation saved = findByMessageUniqueKey(corporation.getMessageUniqueKey());
    if (saved == null) {
    return insert(corporation);
    }
    Corporation adjusted = Corporation.of(saved.getId(),
    corporation.getName(),
    corporation.getMessageUniqueKey());
    return update(adjusted);
    }

    View full-size slide

  106. Wrap Repository using Observables
    @Service
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class CorporationService {
    private final CorporationRepostory corporationRepository;
    public Observable save(Corporation corporation) {
    return Observable.fromCallable(() -> corporationRepository.save(corporation));
    }
    }

    View full-size slide

  107. Web API request/response

    View full-size slide

  108. Web APIs
    public interface AddressApi {
    @GET("api/service-b/address/{key}")
    Observable> get(@Path("key") String key);
    @POST("api/service-b/address/create")
    Observable> create(@Body CreateAddressInputForm inputForm);
    }

    View full-size slide

  109. Wrap Web API for idempotency
    public Observable save(Address anonymousAddress) {
    AddressApi addressApi = apiRegistry.of(AddressApi.class);
    Observable> getAddressResp$ =
    addressApi.get(anonymousAddress.getMessageUniqueKey());
    return getAddressResp$.flatMap(getAddressResp -> {
    if (getAddressResp.isSuccessful()) {
    return Observable.just(getAddressResp.body().getAddress());
    }
    if (getAddressResp.code() != 404) {
    new UnsupportedOperationException(getAddressResp.errorBody().string());
    }
    return addressApi.create(
    new CreateAddressInputForm(anonymousAddress.getCorporationId(),
    anonymousAddress.getState(), anonymousAddress.getMessageUniqueKey()))
    .map(createAddressOutputFormResp -> {
    Long addressId = createAddressOutputFormResp.body().getAddressId();
    return Address.of(addressId, anonymousAddress);
    });
    });
    }

    View full-size slide

  110. Compose idempotent APIs
    public Observable execute(CreateCorporationCommand aCommand) {
    Corporation anonymousCorporation = Corporation.anonymous(
    aCommand.getCorporationName()
    , aCommand.getMessageUniqueKey()
    );
    return corporationService.save(anonymousCorporation).flatMap(
    corporation -> {
    Address anonymousAddress = Address.anonymous(
    corporation.getId()
    , aCommand.getState(),
    aCommand.getMessageUniqueKey()
    );
    return addressService.save(anonymousAddress)
    .map(address -> Corporation.aggregate(corporation, address));
    });
    }

    View full-size slide

  111. Conclusion
    ● Rx’s APIs provide us high level
    abstraction
    ● We write how we process data
    ● We can modify our code for:
    ○ error handling,
    ○ logging,
    ○ multi-threading and so on
    ● The more we break into appropriate
    components, the easier the testing

    View full-size slide

  112. Fin
    Slides: https://goo.gl/ygaIgW
    Benoît Quenaudon 金子 良太

    View full-size slide

  113. Resources
    ● Rx official website
    ○ http://reactivex.io/
    ● The introduction to Reactive Programming you've been missing
    ○ https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
    ● RxJava
    ○ https://github.com/ReactiveX/RxJava
    ● RxJS
    ○ https://github.com/ReactiveX/RxJS
    ● Reactive Fault Tolerant Programming with Hystrix and RxJava
    ○ https://goo.gl/2BrwPd
    ● Functional Reactive Programming with RxJava • Ben Christensen
    ○ https://www.youtube.com/watch?v=_t06LRX0DV0
    ● Exploring RxJava 2 for Android • Jake Wharton
    ○ https://www.youtube.com/watch?v=htIXKI5gOQU

    View full-size slide