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

Android Meetup Multi-process synchronisation with RxJava

Android Meetup Multi-process synchronisation with RxJava

Sharing my experience on designing a solution to deal with the Producer-Consumer Problem using RxJava.

Natalya Blanco López

September 04, 2018
Tweet

Other Decks in Programming

Transcript

  1. The Tracking Problem:
    An Architectural approach to multi-process Synchronisation
    with RxJava
    Natalya Blanco López
    Mobile Developer @

    View Slide

  2. Natalya Blanco López
    Software Engineer - Venezuela
    Mobile Developer @ Ubilabs

    View Slide

  3. Agenda

    View Slide

  4. ● What is multi-process synchronisation?
    ● Description of the problem.
    ● Architectural approach to solve the problem.
    ● A bit of code RxJava (not live coding - Sorry!)
    ● Conclusion.
    Agenda

    View Slide

  5. Multi-Process Synchronization

    View Slide

  6. The Producer's sole function is to insert data into the data-area, it is not
    allowed to remove any data from the area. Similarly, for the Consumer to be
    able to remove information from the data area, there must be information there
    in the first place. Once again, the sole function of the Consumer is to remove
    data from the data area.
    http://www.dcs.ed.ac.uk/home/adamd/essays/ex1.html
    “This problem is one ... well-known problems in concurrent programming:
    a finite-size buffer and two classes of threads, producers and consumers,
    put items into the buffer (producers) and take items out of the buffer
    (consumers).”
    https://docs.oracle.com/cd/E19455-01/806-5257/sync-31/index.html
    Multi-process synchronization

    View Slide

  7. The Tracking Problem

    View Slide

  8. We want to be able to visualise in real-time the
    position of certain devices on a map.
    This included several devices under different and
    sometimes problematic conditions:
    - Internet connection loss
    - GPS loss
    The Tracking Problem

    View Slide

  9. Things to consider:
    ● We needed to provide accurate positional information: even when
    the GPS doesn't work as expected.
    ● Consider possible connection loss due bad reception or no
    connection to the server.
    ● We needed to make sure that the data received by the server was
    complete and in the correct order.
    ● Handle all possible failures.
    The Tracking Problem

    View Slide

  10. Architectural Design Overview

    View Slide

  11. Position Producer Service: generates positions every 2 seconds even
    when there's no more GPS available (or not accurate enough).
    Position Consumer Service: fetches from the database all the
    positions that haven’t been sent yet, makes sure that they are sorted
    and executes the request. Handles possible failures with the sending.
    Position Interpolator Service: makes sure all positions have a location
    that can be mapped in the server. Linear Interpolation
    Architectural Design Overview

    View Slide

  12. class Position {
    var sequenceNumber: Int = 0
    var createdAt: Long = DateTime.now().millis
    var distance: Float = 0f
    var isTransmitted: Boolean = false
    var geoLocation: GeoLocation? = null
    var notes: List = List()
    }

    View Slide

  13. Producer
    Architectural Design Overview
    Every certain time generates positions even if
    there are incomplete.
    - Poor accuracy (>20m)
    - No GPS
    All the positions should be saved locally on the
    phone:
    - cope with internet connection loss
    - somebody has to fix those incomplete
    position

    View Slide

  14. Consumer
    Architectural Design Overview
    This component works based on a number to
    keep the correct order.
    After the positions are sent they are then marked
    as 'transmitted'.
    If the position with the upcoming sequence
    number is not complete, the consumer should
    wait until the interpolator does its job, and send
    the position.

    View Slide

  15. Interpolator
    Architectural Design Overview
    If the GPS doesn't provide a location, the
    interpolator comes to the rescue.
    we always want to have an idea of where the
    devices are.
    This component checks the database looking
    for incomplete positions and does a linear
    Interpolation with last valid position and the next
    valid.

    View Slide

  16. 2 3 6
    4 5 7 8
    1 9 10
    Architectural Design Overview

    View Slide

  17. Le Code

    View Slide

  18. Le Code

    View Slide

  19. Le Code: Producer
    LocationEngine:
    fusedLocationProviderClient.requestLocationUpdates(createLocationRequest(), locationCallback, null)
    LocationProducerService:
    compositeDisposable.add(Observable
    .interval(POSITION_PRODUCER_INTERVAL, TimeUnit.MILLISECONDS)
    .subscribe { time ->
    if (locationChanged(lastLocation, locationEngine.location!!)) {
    lastLocation = locationEngine.location
    sendLocationBroadcast(lastLocation)
    } else {
    sendLocationBroadcast(null)
    }
    }
    )

    View Slide

  20. Le Code: Producer
    PositionProducer:
    fun update(location: Location?) {
    compositeDisposable.add(Single.just(Position(location))
    .flatMap { position ->
    repository.savePosition(position)
    .map { [email protected] position }
    }.subscribe(
    { position -> Timber.d("Position ${position.sequenceNumber} saved") },
    { throwable -> Timber.d("error saving position: $throwable") }
    )
    )
    }
    Repository:
    fun savePosition(position: Position): Single

    View Slide

  21. Le Code: Consumer
    PositionConsumer:
    compositeDisposable.add(fetchAndSendPositionsToServer()
    .delay(2, TimeUnit.SECONDS)
    .repeat()
    .subscribe(
    success -> Timber.d("Positions sent?“ + success),
    throwable -> Timber.e("error sending position: " + throwable)
    ));

    View Slide

  22. Le Code: Consumer
    PositionConsumer:
    fun fetchAndSendPositionsToServer(): Single {
    return positionRepo.getSequenceNumberOfLastTransmittedPosition()
    .onErrorReturn(throwable -> {
    if (throwable instanceof IllegalStateException) {
    return 0;
    }
    throw new Exception(throwable);
    })
    .flatMap(this::fetchAndBuildPositionDTOList)
    .flatMap(this::sendPositionsToServer)
    .doOnError(throwable -> { Timber.e(throwable);})
    .onErrorReturnItem(false);
    }

    View Slide

  23. PositionConsumer:
    fun sendPositionsToServer(positionDTOs: List): Single {
    if (positionDTOs.empty) {
    return Single.just(false)
    }
    return api.sendPositions(positionDTOs) // Return Single
    .toObservable()
    .flatMapIterable(responseBody -> list)
    .flatMap(positionDTO ->
    positionRepo.markAsTransmitted( positionDTO.sequenceNumber)
    .toObservable())
    .all(success -> success)
    }
    Le Code: Consumer

    View Slide

  24. Le Code: Interpolator
    PositionInterpolator:
    compositeDisposable.add(interpolatePositions()
    .delay(2, TimeUnit.SECONDS)
    .repeat();
    .subscribe(
    success -> Timber.d("positions interpolated? " + success),
    throwable -> Timber.d("positions could not be interpolated: $throwable")
    )
    )

    View Slide

  25. Le Code: Interpolator
    PositionInterpolator:
    fun interpolatePositions(): Single {
    return positionRepo.getFirstInvalidPositionAfter(startNumber)
    .flatMap(startPosition -> positionRepo.getValidPositionAfter(startPosition.sequenceNumber)
    .flatMap(endPosition ->
    positionRepo.getPositionsBetween(
    startPosition.sequenceNumber - 1, endPosition.sequenceNumber)
    )
    )
    .doOnError(throwable -> Timber.e("No interpolation " + throwable))
    .flatMap(this::interpolate) // set to startNumber = endPosition.sequenceNumber
    .onErrorReturn(throwable -> false)
    }

    View Slide

  26. interval(): create an
    Observable that emits a
    sequence of integers spaced
    by a given time interval
    all(): Returns an Observable that
    emits a Boolean that indicates
    whether all of the items emitted
    by the source Observable
    satisfy a condition.
    http://reactivex.io/RxJava/javadoc/index.html?rx/Observable.html

    View Slide

  27. Conclusion

    View Slide

  28. Conclusion

    View Slide

  29. Conclusion
    Wrap-up:
    - With RxJava:
    - Easily define loops: repeat / retry (error)
    - Handle errors: doOnError / onErrorReturn
    - Three independent components:
    - Separate concerns
    - Work asynchronously
    - Write test more easily
    - Code looks clean and tidy
    - Code is easy to read

    View Slide

  30. Juliusstraße 25
    22769 Hamburg
    Deutschland
    [email protected]
    +49 40 60 94 661-0
    www.ubilabs.net
    Natalya Blanco López
    Thank you!

    View Slide