So I am trying to cache my http responses into a ConcurrentHashMap. I have set up my cache class and Api client class to return Observables as follows:
public class UserCache {
private static ConcurrentHashMap<Integer, User> cache = new ConcurrentHashMap<>();
public Observable<User> get(Integer key) {
return Observable.create(observableEmitter -> {
if(cache.contains(key)) observableEmitter.onNext(cache.get(key));
observableEmitter.onComplete();
});
}
public void update(Integer key, User user) {
cache.putIfAbsent(key, user);
}
public boolean contains(Integer key) {
return cache.contains(key);
}
}
ApiClient
public class ApiClient {
private UserApi api;
private static ApiClient apiClient;
private ApiClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com")
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
api = retrofit.create(UserApi.class);
}
public Observable<User> get(int id) {
return api.getUser(id);
}
public static ApiClient getInstance() {
if(apiClient == null) apiClient = new ApiClient();
return apiClient;
}
}
And in the App class
public class App {
ApiClient apiSource = ApiClient.getInstance();
UserCache userCache = new UserCache();
public Observable<User> get(Integer key) {
return Observable.concat(userCache.get(key), apiSource.get(key))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.doOnNext(user -> {
userCache.update(user.id, user);
})
.subscribeOn(Schedulers.io());
}
public static void main(String[] args) throws InterruptedException {
App app = new App();
app.get(1).subscribe(System.out::println);
Thread.sleep(3000);
app.get(1).subscribe(System.out::println);
Thread.sleep(1000);
}
}
Mmy understanding of .concat is that if the first observable (cache) doesn't emit anything, then the second Observable (api client) will start emitting. But I can't figure out why doOnNext(user -> userCache.update(user.id, user)) is not updating the cache, and hence when i retrieve the same key, another api call is carried out again.
I'm not sure why your doOnNext does not emit, but if you're using RX there's a way that requires less code and eliminates race conditions. In the example you give, if we call the method twice on an empty cache, it will make two network calls and the last one will overwrite the first. Here's my preferred method, which prevents this from happening and requires less code:
private final ConcurrentMap<Integer, Observable<User>> userCache = Maps.newConcurrentMap();
public Observable<User> getUser(int id) {
return userCache.computeIfAbsent(id, theId -> {
ConnectableObservable<User> cachedObservable = getUserFromApi(id)
.replay();
cachedObservable.connect();
return cachedObservable;
}).doOnError(err -> userCache.remove(id));
}
As you can see I store cached observables. That way, if a second call is made while the first is still in flight, they get the same result, and it is only cached once. All calls after that get it directly from the cached observable.
However, we don't want to cache errors (probably) so I append a doOnError which makes sure that any observables that contain an error (like a network failure) are not cached as well.
Related
I am trying to migrate my circuit breaker code from Hystrix to Resilience4j. The communication is between two applications out of which one is an artifact containing all the resilience 4j config in the java code itself and the second application which is a microservice uses it directly.
There's one RequestId which generates in the microservice and propagates to the artifact context where it gets printed in the logs. With Hystrix, it was working perfectly fine but ever since I moved to resilience, I am getting null for the request Id.
Below is my config for bulk head and context propagator :
ThreadPoolBulkheadConfig bulkheadConfig = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(maxThreadPoolSize)
.coreThreadPoolSize(coreThreadPoolSize)
.queueCapacity(queueCapacity)
.contextPropagator(new DummyContextPropagator())
.build();
// Bulk Head Registry
ThreadPoolBulkheadRegistry bulkheadRegistry = ThreadPoolBulkheadRegistry.of(bulkheadConfig);
// Create Bulk Head
ThreadPoolBulkhead bulkhead = bulkheadRegistry.bulkhead(name, bulkheadConfig);
Dummy Context Propagator :
public class DummyContextPropagator implements ContextPropagator {
private static final Logger log = LoggerFactory.getLogger( DummyContextPropagator.class);
#Override
public Supplier<Optional<Object>> retrieve() {
return () -> (Optional<Object>) get();
}
#Override
public Consumer<Optional<Object>> copy() {
return (t) -> t.ifPresent(e -> {
clear();
put(e);
});
}
#Override
public Consumer<Optional<Object>> clear() {
return (t) -> DummyContextHolder.clear();
}
public static class DummyContextHolder {
private static final ThreadLocal threadLocal = new ThreadLocal();
private DummyContextHolder() {
}
public static void put(Object context) {
if (threadLocal.get() != null) {
clear();
}
threadLocal.set(context);
}
public static void clear() {
if (threadLocal.get() != null) {
threadLocal.set(null);
threadLocal.remove();
}
}
public static Optional<Object> get() {
return Optional.ofNullable(threadLocal.get());
}
}
}
However, nothing seems to work so that I can get the RequestId.
Am I doing everything right or is there another way to do that ?
i think you want to get params from threadlocal from parent-thread when you in sub-thread, in hystrix it use command-model to decorate callabletask
in resilience4j i think u can fix it like this:
#Resource
DispatcherServlet dispatcherServlet;
#PostConstruct
public void changeThreadLocalModel() {
dispatcherServlet.setThreadContextInheritable(true);
}
i find my last answer may lead to some problems, when you use "dispatcherServlet.setThreadContextInheritable(true);"
it may pollute your custom thread-pool`s threadlocalmap;
so here is my final resolve, and it only works at resilience4j;
#Resource
Resilience4jBulkheadProvider resilience4jBulkheadProvider;
#PostConstruct
public void concurrentThreadContextStrategy() {
ThreadPoolBulkheadConfig threadPoolBulkheadConfig = ThreadPoolBulkheadConfig.custom().contextPropagator(new CustomInheritContextPropagator()).build();
resilience4jBulkheadProvider.configureDefault(id -> new Resilience4jBulkheadConfigurationBuilder()
.bulkheadConfig(BulkheadConfig.ofDefaults()).threadPoolBulkheadConfig(threadPoolBulkheadConfig)
.build());
}
private static class CustomInheritContextPropagator implements ContextPropagator<RequestAttributes> {
#Override
public Supplier<Optional<RequestAttributes>> retrieve() {
// give requestcontext to reference from threadlocal;
// this method call by web-container thread, such as tomcat, jetty,or undertow, depends on what you used;
return () -> Optional.ofNullable(RequestContextHolder.getRequestAttributes());
}
#Override
public Consumer<Optional<RequestAttributes>> copy() {
// load requestcontex into real-call thread
// this method call by resilience4j bulkhead thread;
return requestAttributes -> requestAttributes.ifPresent(context -> {
RequestContextHolder.resetRequestAttributes();
RequestContextHolder.setRequestAttributes(context);
});
}
#Override
public Consumer<Optional<RequestAttributes>> clear() {
// clean requestcontext finally ;
// this method call by resilience4j bulkhead thread;
return requestAttributes -> RequestContextHolder.resetRequestAttributes();
}
}
i got the same problem with springboot 2.5 et springboot cloud 2020.0.6
and I solved it with an implementation of ContextPropagator
public class SleuthPropagator implements ContextPropagator<TraceContext> {
ThreadLocal<ScopedSpan> scopedSpanThreadLocal = new ThreadLocal<>();
#Override
public Supplier<Optional<TraceContext>> retrieve() {
return this::getCurrentcontext;
}
#Override
public Consumer<Optional<TraceContext>> copy() {
return c -> {
if (!c.isPresent()) {
return;
}
TraceContext traceContext = c.get();
ScopedSpan resilience4jSpan = getTracer()
.map(t -> t.startScopedSpanWithParent("Resilience4j", traceContext))
.orElse(null);
scopedSpanThreadLocal.set(resilience4jSpan);
};
}
#Override
public Consumer<Optional<TraceContext>> clear() {
return t -> {
try {
ScopedSpan resilience4jSpan = scopedSpanThreadLocal.get();
if (resilience4jSpan != null) {
resilience4jSpan.finish();
}
} finally {
scopedSpanThreadLocal.remove();
}
};
}
private static Optional<Tracer> getTracer() {
return Optional.ofNullable(Tracing.current())
.map(Tracing::tracer);
}
private Optional<TraceContext> getCurrentcontext() {
return getTracer()
.map(Tracer::currentSpan)
.map(Span::context);
}
}
And use the propagator in adding this to your application.properties
resilience4j.thread-pool-bulkhead.instances.YOUR_BULKHEAD_CONFIG.context-propagators=com.your.package.SleuthPropagator
I am new to Reactor framework and trying to utilize it in one of our existing implementations. LocationProfileService and InventoryService both return a Mono and are to executed in parallel and have no dependency on each other (from the MainService). Within LocationProfileService - there are 4 queries issued and the last 2 queries have a dependency on the first query.
What is a better way to write this? I see the calls getting executed sequentially, while some of them should be executed in parallel. What is the right way to do it?
public class LocationProfileService {
static final Cache<String, String> customerIdCache //define Cache
#Override
public Mono<LocationProfileInfo> getProfileInfoByLocationAndCustomer(String customerId, String location) {
//These 2 are not interdependent and can be executed immediately
Mono<String> customerAccountMono = getCustomerArNumber(customerId,location) LocationNumber).subscribeOn(Schedulers.parallel()).switchIfEmpty(Mono.error(new CustomerNotFoundException(location, customerId))).log();
Mono<LocationProfile> locationProfileMono = Mono.fromFuture(//location query).subscribeOn(Schedulers.parallel()).log();
//Should block be called, or is there a better way to do ?
String custAccount = customerAccountMono.block(); // This is needed to execute and the value from this is needed for the next 2 calls
Mono<Customer> customerMono = Mono.fromFuture(//query uses custAccount from earlier step).subscribeOn(Schedulers.parallel()).log();
Mono<Result<LocationPricing>> locationPricingMono = Mono.fromFuture(//query uses custAccount from earlier step).subscribeOn(Schedulers.parallel()).log();
return Mono.zip(locationProfileMono,customerMono,locationPricingMono).flatMap(tuple -> {
LocationProfileInfo locationProfileInfo = new LocationProfileInfo();
//populate values from tuple
return Mono.just(locationProfileInfo);
});
}
private Mono<String> getCustomerAccount(String conversationId, String customerId, String location) {
return CacheMono.lookup((Map)customerIdCache.asMap(),customerId).onCacheMissResume(Mono.fromFuture(//query).subscribeOn(Schedulers.parallel()).map(x -> x.getAccountNumber()));
}
}
public class InventoryService {
#Override
public Mono<InventoryInfo> getInventoryInfo(String inventoryId) {
Mono<Inventory> inventoryMono = Mono.fromFuture(//inventory query).subscribeOn(Schedulers.parallel()).log();
Mono<List<InventorySale>> isMono = Mono.fromFuture(//inventory sale query).subscribeOn(Schedulers.parallel()).log();
return Mono.zip(inventoryMono,isMono).flatMap(tuple -> {
InventoryInfo inventoryInfo = new InventoryInfo();
//populate value from tuple
return Mono.just(inventoryInfo);
});
}
}
public class MainService {
#Autowired
LocationProfileService locationProfileService;
#Autowired
InventoryService inventoryService
public void mainService(String customerId, String location, String inventoryId) {
Mono<LocationProfileInfo> locationProfileMono = locationProfileService.getProfileInfoByLocationAndCustomer(....);
Mono<InventoryInfo> inventoryMono = inventoryService.getInventoryInfo(....);
//is using block fine or is there a better way to do?
Mono.zip(locationProfileMono,inventoryMono).subscribeOn(Schedulers.parallel()).block();
}
}
You don't need to block in order to get the pass that parameter your code is very close to the solution. I wrote the code using the class names that you provided. Just replace all the Mono.just(....) with the call to the correct service.
public Mono<LocationProfileInfo> getProfileInfoByLocationAndCustomer(String customerId, String location) {
Mono<String> customerAccountMono = Mono.just("customerAccount");
Mono<LocationProfile> locationProfileMono = Mono.just(new LocationProfile());
return Mono.zip(customerAccountMono, locationProfileMono)
.flatMap(tuple -> {
Mono<Customer> customerMono = Mono.just(new Customer(tuple.getT1()));
Mono<Result<LocationPricing>> result = Mono.just(new Result<LocationPricing>());
Mono<LocationProfile> locationProfile = Mono.just(tuple.getT2());
return Mono.zip(customerMono, result, locationProfile);
})
.map(LocationProfileInfo::new)
;
}
public static class LocationProfileInfo {
public LocationProfileInfo(Tuple3<Customer, Result<LocationPricing>, LocationProfile> tuple){
//do wathever
}
}
public static class LocationProfile {}
private static class Customer {
public Customer(String cutomerAccount) {
}
}
private static class Result<T> {}
private static class LocationPricing {}
Pleas remember that the first zip is not necessary. I re write it to mach your solution. But I would solve the problem a little bit differently. It would be clearer.
public Mono<LocationProfileInfo> getProfileInfoByLocationAndCustomer(String customerId, String location) {
return Mono.just("customerAccount") //call the service
.flatMap(customerAccount -> {
//declare the call to get the customer
Mono<Customer> customerMono = Mono.just(new Customer(customerAccount));
//declare the call to get the location pricing
Mono<Result<LocationPricing>> result = Mono.just(new Result<LocationPricing>());
//declare the call to get the location profile
Mono<LocationProfile> locationProfileMono = Mono.just(new LocationProfile());
//in the zip call all the services actually are executed
return Mono.zip(customerMono, result, locationProfileMono);
})
.map(LocationProfileInfo::new)
;
}
I'm trying to get the data I parse from onResponse method via Callback. Here is my ApiClient:
public class ApiClient implements Callback<Map<String, Channel>> {
private ChannelCallback listener;
static final String BASE_URL = "https://www.radyoodtu.com.tr/";
public void start(ChannelCallback listener) {
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
RestInterface restInterface = retrofit.create(RestInterface.class);
Call<Map<String, Channel>> call = restInterface.getChannels();
call.enqueue(this);
}
#Override
public void onResponse(retrofit2.Call<Map<String, Channel>> call, Response<Map<String, Channel>> response) {
System.out.println(response.code());
if(response.isSuccessful()) {
Map<String, Channel> body = response.body();
listener.setChannels(body);
List<Channel> channels = new ArrayList<>(body.values());
for (Channel channel : body.values()) {
System.out.println(channel.getSong());
}
}
}
#Override
public void onFailure(retrofit2.Call<Map<String, Channel>> call, Throwable t) {
//TODO
}
}
and this is the class I'm trying to get the data:
public class Radio implements ChannelCallback {
private ApiClient apiClient = new ApiClient();
public Radio(){
apiClient.start(this);
}
#Override
public void setChannels(Map<String, Channel> body) {
this.apiClient.onResponse(body); // NOT WORKING
}
}
here is my Interface:
public interface ChannelCallback {
void setChannels(Map<String, Channel> body);
}
what I need to do is get the onResponse body data for Radio class I'm using right now. In Radio class I have to create a List of channel objects with that data I need but I can't even get the data so I can't even create that list. I don't know how to manipulate the data from listener at all and I don't know how can I access that listener I use in ApiClient in Radio class.
It looks like you've got a cyclic reference here. Radio calls ApiClient start, which triggers the network request, which calls Radio.setChannels, which tries to call the client again.
I think you need to resolve two things:
Avoid this cyclic reference
You pass the listener to ApiClient.start() but you never assign it to the actual value inside ApiClient. So, my guess is that you get an NPE here if you have a successful response.
//i think no need to impliments ChannelCallback if this code works
//just asign your interface and apiclient in class
public class Radio implements ChannelCallback {
private ApiClient apiClient = new ApiClient();
private ChannelCallback channelCallback;
and in inside radio()
public Radio(){
apiClient.start(this);
channelCallback = apiClient.onResponse(body).create(ChannelCallback.class);
channelCallback.setChannels(Map<String, Channel> body)
}
//and recieve callback
}
Have been having issue using Rxjava with Retrofit in my android app, everything seems ok and fine in the code implementation but app crashes whenever i navigate to the activity/fragment with the error message below.
//Error message
java.lang.IllegalArgumentException: Unable to create converter for java.util.List<com.thebestprice.bestprice.model.SearchRequestModel>
for method DataEndpointService.getStarredRepositories
//Endpoint Declaration
#GET("users/{user}/starred")
Observable<List<SearchRequestModel>> getStarredRepositories(#Path("user") String username);
//Client Class
public class DataClient {
private static final String PROJECT_BASE_URL = "https://api.github.com/";
private static DataClient instance;
private DataEndpointService queryResultService;
private DataClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final Retrofit retrofit = new Retrofit.Builder().baseUrl(PROJECT_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
queryResultService = retrofit.create(DataEndpointService.class);
}
public static DataClient getInstance(){
if (instance == null){
instance = new DataClient();
}
return instance;
}
public io.reactivex.Observable<List<SearchRequestModel>> queryForUserRepo(#NonNull String searchRequestModel){
return queryResultService.getStarredRepositories(searchRequestModel);
}
}
//Fragment to display the list
private void queryResultForSearchData(String userName){
disposable = DataClient.getInstance().
queryForUserRepo(userName).
subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).
subscribe(new Consumer<List<SearchRequestModel>>() {
#Override
public void accept(List<SearchRequestModel> searchRequestModels) throws Exception {
mShimmerFrameLayout.stopShimmerAnimation();
rvAllSearch.setAdapter(new ResultAdapter(getActivity(), searchRequestModels));
}
},
new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
}`enter code here`
});
}
//gradle dependencies for rxjava and retrofit with compileSdk27
//For using RxJava
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
//logging interceptor
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
//For Using retrofit to do network request
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
Don't know what i'm doing wrong, have search for similar issues but doesn't seems to help in my own case
Main point of using RxJava it's avoid loops and list, to invoke Subscriptions and map functions, cause Observables managed all items. So you don't neet to return List.
Just one of example of using Github API with Rx.
public interface GithubService {
String SERVICE_ENDPOINT = "https://api.github.com";
#GET("/users/{login}")
Observable<Github> getUserRx(#Path("login") String login);
#GET("/users/{login}")
Github getUser(#Path("login") String login);
}
.
public class ServiceFactory {
/**
* Creates a retrofit service from an arbitrary class (clazz)
* #param clazz Java interface of the retrofit service
* #param endPoint REST endpoint url
* #return retrofit service with defined endpoint
*/
public static <T> T createRetrofitService(final Class<T> tClass, final String endPoint) {
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(endPoint)
.build();
T service = restAdapter.create(tClass);
return service;
}
}
.
// .....
service = ServiceFactory.createRetrofitService(
GithubService.class, GithubService.SERVICE_ENDPOINT);
service.getUserRx(login)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.cache()
.subscribe(new Subscriber<Github>() {
#Override
public final void onCompleted() {
}
#Override
public final void onError(Throwable e) {
Log.e(LOG, " ErrorRx Default Github" + e.getMessage());
}
#Override
public final void onNext(Github response) {
mCardAdapter.addData(response);
}
});
// .....
As you add GsonConverterFactory.
Check out the Bean you use (SearchRequestModel) the variable or the #SerializedName(alternate) annotation does not have the same variable name.
it work for me
I'm totally new to RxJava and I've spent all day understanding it, I'm tying to think how to solve this problem:
I have one object, fetched by Retrofit, it contains two ArrayLists, I have to process every ArrayList differently. Currently it looks like:
apiService.getUser(token).enqueue(new Callback<User>() {
#Override
public void onResponse(Response<User> response) {
final User user = response.body();
for (Skill s : user.getSkills()) {
// process here first ArrayList
}
for (OrganizerAction o : user.getOrganizerActions()) {
// process here second ArrayList
}
}
#Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
});
UPDATE:
public class User {
// fields
#SerializedName("organizer_actions")
#Expose
private List<OrganizerAction> mOrganizerActions;
#SerializedName("skills")
#Expose
private List<Skill> mSkills;
public List<OrganizerAction> getOrganizerActions() {
return mOrganizerActions;
}
public List<Skill> getSkills() {
return mSkills;
}
}
Thanks,
Anton
This answer is for Retrofit 2.0.0-beta, which is what you appear to be using. Also, you didn't give your POJO or service definitions, so going to use a general GitHub API example as a guide, modify to match your specify data.
First step is to convert your service definition to use Observable instead of Call.
public interface GitHubService {
#GET("/users/{user}")
Observable<User> getUser(#Path("user") String user);
}
Where User is
public class User {
public String login;
public int id;
}
Next, add a custom call adapter with to your retrofit builder with addCallAdapterFactory --
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Get your service in the usual way --
GitHubService gitHubService = retrofit.create(GitHubService.class);
Next, get your observable and call cache on it to create an Observable that will replay the result. From that Observable, you can subscribe multiple times, in your case, you can subscribe twice. Once for each type of data you are interested in, and use the map function to transform from the User object to your specific fields. map allows you to apply function to the data in the observable. See the docs for more details. In this example, we will make two streams. One each for the id and login fields.
Observable<User> getUserResult = gitHubService.getUser("octocat").cache(1);
getUserResult.map(new Func1<User, Integer>() {
#Override
public Integer call(User user) {
return user.id;
}
}).subscribe(new Action1<Integer>() {
#Override
public void call(Integer id) {
Log.d("Stream 1", "id = " + id);
}
});
getUserResult.map(new Func1<User, String>() {
#Override
public String call(User user) {
return user.login;
}
}).subscribe(new Action1<String>() {
#Override
public void call(String login) {
Log.d("Stream 2", "login = " + login);
}
});
Finally, make sure your gradle file has the needed dependencies,
compile 'io.reactivex:rxjava:1.0.14'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta1'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
And, not directly related to your question, but if you are going to doing RxJava in Android, I recommend you checkout Retrolambda if you have not already. The above map and subscribe code, and Rx code in general, is more succinct with lambdas.
getUserResult.map(user -> user.id).subscribe(
id -> { Log.d("Stream 1", "id = " + id); }
);
getUserResult.map(user -> user.login).subscribe(
login -> { Log.d("Stream 2", "login = " + login); }
);