简体   繁体   English

将数据库和网络调用与RxJava2结合

[英]Combine database and network call with RxJava2

I have 2 data sources: database (cache) and api and I need to combine them into one stream. 我有2个数据源:数据库(缓存)和api,我需要将它们组合成一个流。 I know that I can simply use concatArray or something similar but I want to achieve more complicated behavior: 我知道我可以简单地使用concatArray或类似的东西,但是我想实现更复杂的行为:

  • Observable stream which will emit up to 2 elements. 可观测的流,最多将发射2个元素。

  • It will subscribe to both sources in the beginning. 一开始它将同时订阅这两个资源。

  • If api call will be fast enough (<~300ms), it will emit only data from it and complete the stream. 如果api调用足够快(<〜300ms),它将仅从中发出数据并完成流。

  • If api call will be slow (>~300ms), emit data from database and still wait for data from api 如果api调用会很慢(>〜300ms),请从数据库中发出数据,然后仍然等待api中的数据
  • If api call won't succeed, emit data from database and emit an error. 如果api调用无法成功,则从数据库发出数据并发出错误。
  • If database somehow will be slower than api, it can't emit its data (stream completion solves it) 如果数据库以某种方式比api慢,它将无法发出其数据(流完成解决了它)

I accomplished it with the following code: 我用以下代码完成了它:

    public Observable<Entity> getEntity() {
    final CompositeDisposable disposables = new CompositeDisposable();
    return Observable.<Entity>create(emitter -> {
        final Entity[] localEntity = new Entity[1];

        //database call:
        disposables.add(database.getEntity()
                .subscribeOn(schedulers.io())
                .doOnSuccess(entity -> localEntity[0] = entity) //saving our entity because 
                                                        //apiService can emit error before 300 ms 
                .delay(300, MILLISECONDS)
                .subscribe((entity, throwable) -> {
                    if (entity != null && !emitter.isDisposed()) {
                        emitter.onNext(entity);
                    }
                }));

        //network call:
        disposables.add(apiService.getEntity()
                .subscribeOn(schedulers.io())
                .onErrorResumeNext(throwable -> {
                    return Single.<Entity>error(throwable) //we will delay error here
                            .doOnError(throwable1 -> {
                                if (localEntity[0] != null) emitter.onNext(localEntity[0]); //api error, emit localEntity
                            })
                            .delay(200, MILLISECONDS, true); //to let it emit localEntity before emitting error
                })
                .subscribe(entity -> {
                    emitter.onNext(entity); 
                    emitter.onComplete(); //we got entity from api, so we can complete the stream
                }, emitter::onError));
    })
            .doOnDispose(disposables::clear)
            .subscribeOn(schedulers.io());
}

Code is a bit clunky and I'm creating here observables inside observable, which I think is bad . 代码有点笨拙,我在这里在observable中创建了observables,我认为这很糟糕 But that way I have global access to emitter, which allows me to control main stream (emit data, success, error) in the way I want. 但是通过这种方式,我可以全局访问发射器,这使我能够以自己想要的方式控制主流(发射数据,成功,错误)。

Is there better way to achieve this? 有更好的方法来实现这一目标吗? I'd love to see some code examples. 我很想看看一些代码示例。 Thanks! 谢谢!

May be the code below could do the job. 可能是下面的代码可以完成这项工作。 From your requirements, I assumed that the api and database deal with Single<Entity> . 根据您的要求,我假设api和数据库处理Single<Entity>

private static final Object STOP = new Object();

public static void main(String[] args) {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(500, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));
    BehaviorSubject<Object> subject = BehaviorSubject.create();

    Observable.merge(
        apiService.getEntity()
                  .toObservable()
                  .doOnNext(t -> subject.onNext(STOP))
                  .doOnError(e -> subject.onNext(STOP))
                  .onErrorResumeNext(t ->
                                        Observable.concatDelayError(database.getEntity().toObservable(),
                                                                    Observable.error(t))),
        database.getEntity()
                .delay(300, MILLISECONDS)
                .toObservable()
                .takeUntil(subject)
    )
    .subscribe(System.out::println, 
               System.err::println);

    Observable.timer(1, MINUTES) // just for blocking the main thread
              .toBlocking()
              .subscribe();
}

I didn't manage to remove the use of a Subject due to the conditions "If database somehow will be slower than api, it can't emit its data" and "If api call will be slow (>~300ms), emit data from database and still wait for data from api". 由于出现以下情况,我无法删除对Subject的使用:“如果数据库以某种方式比api慢,它将无法发出其数据”和“如果api调用将是缓慢的(>〜300ms),则发出数据从数据库中,仍然等待api中的数据”。 Otherwise, amb() operator would be a nice use. 否则, amb()运算符会很好用。

I hope this helps. 我希望这有帮助。

Another solution could be this one (no subject): 另一个解决方案可能是这个(无主题):

public static void main(String[] args) throws InterruptedException {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(400, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));

    database.getEntity()
            .toObservable()
            .groupJoin(apiService.getEntity()
                                 .toObservable()
                                 .onErrorResumeNext(
                                    err -> Observable.concatDelayError(database.getEntity().toObservable(),
                                                                       Observable.error(err))),
                       dbDuration -> Observable.timer(300, MILLISECONDS),
                       apiDuration -> Observable.never(),
                       (db, api) -> api.switchIfEmpty(Observable.just(db)))
            .flatMap(o -> o)
            .subscribe(System.out::println,
                       Throwable::printStackTrace,
                       () -> System.out.println("It's the end!"));

    Observable.timer(1, MINUTES) // just for blocking the main thread
              .toBlocking()
              .subscribe();
}

If nothing is emitted from the API service within the 300 ms ( dbDuration -> timer(300, MILLISECONDS) ), then the entity from the database is emitted ( api.switchIfEmpty(db) ). 如果在300毫秒内没有从API服务发出任何信息( dbDuration -> timer(300, MILLISECONDS) ),则从数据库发出实体( api.switchIfEmpty(db) )。

If api emits something within the 300 ms, then the emits only its Entity ( api.switchIfEmpty(.) ). 如果api在300毫秒内发出某物,则仅发出其Entityapi.switchIfEmpty(.) )。

That seems to work as you wish too... 这似乎也像您希望的那样工作...

Another nicer solution: 另一个更好的解决方案:

public static void main(String[] args) throws InterruptedException {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(400, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));

    Observable<Entity> apiServiceWithDbAsBackup =
            apiService.getEntity()
                      .toObservable()
                      .onErrorResumeNext(err -> 
                            Observable.concatDelayError(database.getEntity().toObservable(), Observable.error(err)));

    Observable.amb(database.getEntity()
                           .toObservable()
                           .delay(300, MILLISECONDS)
                           .concatWith(apiServiceWithDbAsBackup),
                   apiServiceWithDbAsBackup)
              .subscribe(System.out::println,
                         Throwable::printStackTrace,
                         () -> System.out.println("It's the end!"));

We use amb() with a delay to for database observable to take the first one which will emits. 我们将amb()延迟使用,以使可观察的数据库采取将要发出的第一个数据库。 In case of error of the api service, we emit the item from the database. 如果api服务出错,我们将从数据库中发出该项目。 That seems to work as you wish too... 这似乎也像您希望的那样工作...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM