Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Agenda

Slide 4

Slide 4 text

● 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

Slide 5

Slide 5 text

Multi-Process Synchronization

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

The Tracking Problem

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Architectural Design Overview

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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() }

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Le Code

Slide 18

Slide 18 text

Le Code

Slide 19

Slide 19 text

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) } } )

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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) ));

Slide 22

Slide 22 text

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); }

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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") ) )

Slide 25

Slide 25 text

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) }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Conclusion

Slide 28

Slide 28 text

Conclusion

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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