Skip to content

Commit

Permalink
Introduce Fallback Mechanisms (#545)
Browse files Browse the repository at this point in the history
* Superstore

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Move to impl

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Add firstData

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Add Superstore factory

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Add README

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Add SuperstoreTests

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Move to util

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Rename to primary

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Address comments from @tsenggordon

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Format

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Cover fallback to SOT on fresh fail

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Refactor based on @yigit feedback in #540

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Enable Fetcher identification

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Cover failing fallback

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Remove superstore

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Format

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Remove logs

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Update README.md

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Add Proposal template (#523)

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Support Rx2 (#531)

* Add rx2 module

* Support Rx2

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Add unit tests

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Format

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

---------

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Superstore

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Remove superstore

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Remove ReactiveCircus/android-emulator-runner

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Fix Rx2 merge issues

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Send failure if no fallback

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Remove Fallback interface and FallbackResponse class

Signed-off-by: matt-ramotar <mramotar@dropbox.com>

* Document Fetcher.name

Signed-off-by: matt-ramotar <mramotar@dropbox.com>

---------

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>
Signed-off-by: matt-ramotar <mramotar@dropbox.com>
  • Loading branch information
Matt Ramotar committed May 8, 2023
1 parent 7d73f08 commit d1e46a9
Show file tree
Hide file tree
Showing 32 changed files with 804 additions and 391 deletions.
13 changes: 3 additions & 10 deletions .github/workflows/.ci_test_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ name: Build project & run tests

on:
push:
branches: [ main, Kmp, store5 ]
pull_request:
branches: [ main ]

jobs:
publish:
runs-on: macos-latest
if: github.repository == 'MobileNativeFoundation/Store' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/store5')
if: github.repository == 'MobileNativeFoundation/Store' && github.ref == 'refs/heads/main'

steps:
- name: Checkout
Expand Down Expand Up @@ -60,11 +58,6 @@ jobs:
distribution: zulu
java-version: 11
- name: Run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew check --rerun-tasks --stacktrace
env:
API_LEVEL: ${{ matrix.api-level }}
run: ./gradlew check --rerun-tasks --stacktrace
- name: Upload code coverage
run: bash <(curl -s https://codecov.io/bash)
run: bash <(curl -s https://codecov.io/bash)
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import org.mobilenativefoundation.store.store5.StoreBuilder
* @param scheduler - scheduler to use for sharing
* if a scheduler is not set Store will use [GlobalScope]
*/
fun <Key : Any, Network : Any, Output : Any, Local : Any> StoreBuilder<Key, Network, Output, Local>.withScheduler(
scheduler: Scheduler
): StoreBuilder<Key, Network, Output, Local> {
fun <Key : Any, Output : Any> StoreBuilder<Key, Output>.withScheduler(
scheduler: Scheduler
): StoreBuilder<Key, Output> {
return scope(CoroutineScope(scheduler.asCoroutineDispatcher()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ class HotRxSingleStoreTest {
assertThat(pipeline.stream(StoreReadRequest.cached(3, refresh = false)))
.emitsExactly(
StoreReadResponse.Loading(
origin = StoreReadResponseOrigin.Fetcher
origin = StoreReadResponseOrigin.Fetcher()
),
StoreReadResponse.Data(
value = "three-1",
origin = StoreReadResponseOrigin.Fetcher
origin = StoreReadResponseOrigin.Fetcher()
)
)
assertThat(
Expand All @@ -56,11 +56,11 @@ class HotRxSingleStoreTest {
assertThat(pipeline.stream(StoreReadRequest.fresh(3)))
.emitsExactly(
StoreReadResponse.Loading(
origin = StoreReadResponseOrigin.Fetcher
origin = StoreReadResponseOrigin.Fetcher()
),
StoreReadResponse.Data(
value = "three-2",
origin = StoreReadResponseOrigin.Fetcher
origin = StoreReadResponseOrigin.Fetcher()
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,82 +29,82 @@ class RxFlowableStoreTest {
private val testScheduler = TestScheduler()
private val atomicInteger = AtomicInteger(0)
private val fakeDisk = mutableMapOf<Int, String>()
private val store = StoreBuilder.from<Int, String, String, String>(
fetcher = Fetcher.ofResultFlowable<Int, String> {
Flowable.create(
{ emitter ->
emitter.onNext(
FetcherResult.Data("$it ${atomicInteger.incrementAndGet()} occurrence")
)
emitter.onNext(
FetcherResult.Data("$it ${atomicInteger.incrementAndGet()} occurrence")
)
emitter.onComplete()
},
BackpressureStrategy.BUFFER
)
},
sourceOfTruth = SourceOfTruth.ofFlowable<Int, String>(
reader = {
if (fakeDisk[it] != null)
Flowable.fromCallable { fakeDisk[it]!! }
else
Flowable.empty<String>()
},
writer = { key, value ->
Completable.fromAction { fakeDisk[key] = value }
}
private val store = StoreBuilder.from<Int, String, String>(
fetcher = Fetcher.ofResultFlowable<Int, String> {
Flowable.create(
{ emitter ->
emitter.onNext(
FetcherResult.Data("$it ${atomicInteger.incrementAndGet()} occurrence")
)
emitter.onNext(
FetcherResult.Data("$it ${atomicInteger.incrementAndGet()} occurrence")
)
emitter.onComplete()
},
BackpressureStrategy.BUFFER
)
.withScheduler(testScheduler)
.build()
},
sourceOfTruth = SourceOfTruth.ofFlowable<Int, String>(
reader = {
if (fakeDisk[it] != null)
Flowable.fromCallable { fakeDisk[it]!! }
else
Flowable.empty<String>()
},
writer = { key, value ->
Completable.fromAction { fakeDisk[key] = value }
}
)
)
.withScheduler(testScheduler)
.build()

@Test
fun simpleTest() {
val testSubscriber1 = store.observe(StoreReadRequest.fresh(3))
.subscribeOn(testScheduler)
.test()
.subscribeOn(testScheduler)
.test()
testScheduler.triggerActions()
testSubscriber1
.awaitCount(3)
.assertValues(
StoreReadResponse.Loading(StoreReadResponseOrigin.Fetcher),
StoreReadResponse.Data("3 1 occurrence", StoreReadResponseOrigin.Fetcher),
StoreReadResponse.Data("3 2 occurrence", StoreReadResponseOrigin.Fetcher)
)
.awaitCount(3)
.assertValues(
StoreReadResponse.Loading(StoreReadResponseOrigin.Fetcher()),
StoreReadResponse.Data("3 1 occurrence", StoreReadResponseOrigin.Fetcher()),
StoreReadResponse.Data("3 2 occurrence", StoreReadResponseOrigin.Fetcher())
)

val testSubscriber2 = store.observe(StoreReadRequest.cached(3, false))
.subscribeOn(testScheduler)
.test()
.subscribeOn(testScheduler)
.test()
testScheduler.triggerActions()
testSubscriber2
.awaitCount(2)
.assertValues(
StoreReadResponse.Data("3 2 occurrence", StoreReadResponseOrigin.Cache),
StoreReadResponse.Data("3 2 occurrence", StoreReadResponseOrigin.SourceOfTruth)
)
.awaitCount(2)
.assertValues(
StoreReadResponse.Data("3 2 occurrence", StoreReadResponseOrigin.Cache),
StoreReadResponse.Data("3 2 occurrence", StoreReadResponseOrigin.SourceOfTruth)
)

val testSubscriber3 = store.observe(StoreReadRequest.fresh(3))
.subscribeOn(testScheduler)
.test()
.subscribeOn(testScheduler)
.test()
testScheduler.triggerActions()
testSubscriber3
.awaitCount(3)
.assertValues(
StoreReadResponse.Loading(StoreReadResponseOrigin.Fetcher),
StoreReadResponse.Data("3 3 occurrence", StoreReadResponseOrigin.Fetcher),
StoreReadResponse.Data("3 4 occurrence", StoreReadResponseOrigin.Fetcher)
)
.awaitCount(3)
.assertValues(
StoreReadResponse.Loading(StoreReadResponseOrigin.Fetcher()),
StoreReadResponse.Data("3 3 occurrence", StoreReadResponseOrigin.Fetcher()),
StoreReadResponse.Data("3 4 occurrence", StoreReadResponseOrigin.Fetcher())
)

val testSubscriber4 = store.observe(StoreReadRequest.cached(3, false))
.subscribeOn(testScheduler)
.test()
.subscribeOn(testScheduler)
.test()
testScheduler.triggerActions()
testSubscriber4
.awaitCount(2)
.assertValues(
StoreReadResponse.Data("3 4 occurrence", StoreReadResponseOrigin.Cache),
StoreReadResponse.Data("3 4 occurrence", StoreReadResponseOrigin.SourceOfTruth)
)
.awaitCount(2)
.assertValues(
StoreReadResponse.Data("3 4 occurrence", StoreReadResponseOrigin.Cache),
StoreReadResponse.Data("3 4 occurrence", StoreReadResponseOrigin.SourceOfTruth)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,50 +29,50 @@ class RxSingleStoreExtensionsTest {
private val atomicInteger = AtomicInteger(0)
private var fakeDisk = mutableMapOf<Int, String>()
private val store =
StoreBuilder.from<Int, String, String, String>(
fetcher = Fetcher.ofResultSingle {
Single.fromCallable { FetcherResult.Data("$it ${atomicInteger.incrementAndGet()}") }
},
sourceOfTruth = SourceOfTruth.ofMaybe(
reader = { Maybe.fromCallable<String> { fakeDisk[it] } },
writer = { key, value ->
Completable.fromAction { fakeDisk[key] = value }
},
delete = { key ->
Completable.fromAction { fakeDisk.remove(key) }
},
deleteAll = {
Completable.fromAction { fakeDisk.clear() }
}
)
StoreBuilder.from<Int, String, String>(
fetcher = Fetcher.ofResultSingle {
Single.fromCallable { FetcherResult.Data("$it ${atomicInteger.incrementAndGet()}") }
},
sourceOfTruth = SourceOfTruth.ofMaybe(
reader = { Maybe.fromCallable<String> { fakeDisk[it] } },
writer = { key, value ->
Completable.fromAction { fakeDisk[key] = value }
},
delete = { key ->
Completable.fromAction { fakeDisk.remove(key) }
},
deleteAll = {
Completable.fromAction { fakeDisk.clear() }
}
)
.withScheduler(Schedulers.trampoline())
.build()
)
.withScheduler(Schedulers.trampoline())
.build()

@Test
fun `store rx extension tests`() {
// Return from cache - after initial fetch
store.getSingle(3)
.test()
.await()
.assertValue("3 1")
.test()
.await()
.assertValue("3 1")

// Return from cache
store.getSingle(3)
.test()
.await()
.assertValue("3 1")
.test()
.await()
.assertValue("3 1")

// Return from fresh - forcing a new fetch
store.freshSingle(3)
.test()
.await()
.assertValue("3 2")
.test()
.await()
.assertValue("3 2")

// Return from cache - different to initial
store.getSingle(3)
.test()
.await()
.assertValue("3 2")
.test()
.await()
.assertValue("3 2")
}
}
Loading

0 comments on commit d1e46a9

Please sign in to comment.