My REST controller method should return Mono which must be built of 2 parallel requests to another web services and processing their response where one request return Mono and another request return Flux
How to combine responses of Mono with Flux and process them?
Model:
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ClassModel {
#Id
private String id;
private String roomNr;
private String className;
private String teacherId;
private List<String> studentIds;
public void addStudentId(String studentId) {
studentIds.add(studentId);
}
}
Controller:
public Mono<ClassModel> addRandomClassFull() {
return Mono.zip(
//request teacher microservice and return Mono - single teacher
reactiveNetClient.addRandomTeacher(),
//request students microservice and return Flux - list of students
reactiveNetClient.addRandomStudents(10),
(teacher, students) -> {
ClassModel classModel = new ClassModel();
classModel.setRoomNr("24B");
classModel.setClassName(faker.educator().course());
classModel.setTeacherId(teacher.getId());
students.forEach(student -> classModel.addStudentId(student.getId());
return classModel;
}).flatMap(classRepository::save);
}
Obviously, the controller is wrong as:
1) Mono.zip() takes 2 or more Mono's, where I have Mono and Flux - How to combine them?
2) Also not sure if:students.forEach(student -> classModel.addStudentId(student.getId());is right aproach?
Any suggestions, please?
You can change your method addRandomStudents() to return Mono<List<Student>>
You can use collectList() on Flux<Student>, it will return then Mono<List<Student>> and then in addStudents() will convert Student object to id.
public Mono<ClassModel> addRandomClassFull() {
return Mono.zip(
reactiveNetClient.addRandomTeacher(),
reactiveNetClient.addRandomStudents(10).collectList(),
(teacher, students) -> {
ClassModel classModel = new ClassModel();
classModel.setRoomNr("24B");
classModel.setClassName(faker.educator().course());
classModel.setTeacherId(teacher.getId());
classModel.addStudents(students);
return classModel;
}).flatMap(classRepository::save);
}
Related
Hello recently started studying Webflux.
And sometimes I encounter the tasks that you need to form a simple DTO and return it
Take for example the usual class dto
#Data
#Builder
public static class Dto {
private long id;
private String code1;
private String code2;
}
And a primitive service with two methods...
#Nullable Mono<String> getCode1(long id);
#Nullable String getCode2(long id);
And wrote a method that forms at the output of Mono
private Mono<Dto> fill(long id) {
var dto = Dto.builder()
.id(id)
.build();
//doOnNext
var dtoMono1 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.doOnNext(innerDto -> innerDto.setCode2(service.getCode2(id)));
//map
var dtoMono2 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.map(unused -> service.getCode2(id))
.map(code -> {
dto.setCode1(code);
return dto;
});
//just
var dtoMono3 = Mono.just(dto)
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
//just
var dtoMono4 = Mono.fromCallable(() -> dto)
.subscribeOn(Schedulers.boundedElastic())
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
}
QUESTION:
Is it possible to simply create DTO and use it in the Webflux call
chain ... Or I need to wrap it in mono.just or mono.fromcallable
(what are the pros and cons)
How best to fill in values via doOnNext
or through MAP. An extra line (Return DTO) appears in the case of
MAP and some people also told me if for example NULL comes, doOnNext will
miss it and go further to fill up current dto. But on the other, the MAP is used
to transform the object, and the doOnNext is more for debugging and
logging
Thanks you...
How about using zip operator in such a case?
I hope this example can help you:
private Mono<Dto> fill(long id) {
return Mono.zip(someService.getCode1(id), Mono.just(someService.getCode2(id)))
.map(tuple ->
Dto.builder()
.id(id)
.code1(tuple.getT1())
.code2(tuple.getT2())
.build()
);
}
I came across an amazing FREE News Api on GitHub but I don't know how to implement it in retrofit
SauravKanchan/NEWSAPI
This API is the cracked version of NEWSAPI with the same functionality, I just want to know how to implement the code in retrofit
API Documentation
BASE_URL = "https://saurav.tech/NewsAPI/"
top_headlines_api = "<BASE_URL>/top-headlines/category//<country_code>.json"
everything_api = "<BASE_URL>/everything/<source_id>.json"
in the above documentation I am confused on how to pass <county_code>.json in the retrofit client, help, please!
Ok, so I figured out how to implement above in retrofit 2
use the below code in your retrofit request INTERFACE in my case its GetNewsByCountry.java
public interface GetNewsByCountry {
#GET("top-headlines/category/general/{country}.json")
Call<NEWSPOJO> getAllNews(#Path("country") String country);
}
now In your retrofit client instance write the following code
public class RetrofitClientInstance {
public static final String BASE_URL = "https://saurav.tech/NewsAPI/";
private static Retrofit retrofit = null;
public static Retrofit getRetrofitInstance() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Note I have created different classes,i.e. GetNewsByCountry, java and RetrofitClientInstance.java for the above code
and to finally make an HTTP call write the below code in your activity/fragment class
GetNewsByCountry service = RetrofitClientInstance.getRetrofitInstance()
.create(GetNewsByCountry.class);
Call<YOUR_POJO_CLASS> call = service.getAllNews("us");
call.enqueue(new Callback<YOUR_POJO_CLASS>() {
#Override
public void onResponse(Call<YOUR_POJO_CLASS> call, Response<YOUR_POJO_CLASS> response) {
/** TODO: implement the logic you want when you receive the
JSON data, most probably you want to show
the result in recycler view so I am pasting
my code here, u know just in case
Log.v("onResponse", "we are in th on response callback");
assert response.body() != null;
List<Article> list = response.body().getArticles();
adapter = new NewsAdapter(getActivity(), list);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
*/
}
#Override
public void onFailure(Call<NEWSPOJO> call, Throwable t) {
/** progressDialog.dismiss();
Log.e("“out”", t.toString());
Toast.makeText(getActivity(), "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
*/
}
});
comment if u have any doubt left!
I have a repository that returns a flux and wanted to set the result to another object which is expecting a list. Is there any other way to get the results as a list without blocking?
The block is working but it is taking long time.
public class FluxToListTest {
#Autowired PostRepository postRepository;
public void setUserPosts(User user) {
user.setPostList(postRepository.findAllByOrderId(user.getId()).collectList().block());
}
}
interface PostRepository {
Flux<Post> findAllByOrderId(final UUID userId);
}
#Data
class User {
UUID id;
List<Post> postList;
}
class Post {
UUID id;
String content;
}
In short - NO.
You don't need to extract List from Flux.
If you've started use Reactor Streams - stay with it.
Try this code:
public void setUserPosts(User user) {
postRepository.findAllByOrderId(user.getId())
.collectList()
.doOnNext(user::setPostList)// (1)
.subscribe(); // (2)
}
if you set operation is blocking please use publishOn/subscribeOn to avoid blocking for the all stream.
it starts your stream performing
http://localhost:8080/users?firstName=a&lastName=b ---> where firstName=a and lastName=b
How to make it to or ---> where firstName=a or lastName=b
But when I set QuerydslBinderCustomizer customize
#Override
default public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(String.class).all((StringPath path, Collection<? extends String> values) -> {
BooleanBuilder predicate = new BooleanBuilder();
values.forEach( value -> predicate.or(path.containsIgnoreCase(value) );
});
}
http://localhost:8080/users?firstName=a&firstName=b&lastName=b ---> where (firstName=a or firstName = b) and lastName=b
It seem different parameters with AND. Same parameters with what I set(predicate.or/predicate.and)
How to make it different parameters with AND like this ---> where firstName=a or firstName=b or lastName=b ??
thx.
Your current request param are grouped as List firstName and String lastName. I see that you want to keep your request parameters without a binding, but in this case it would make your life easier.
My suggestion is to make a new class with request param:
public class UserRequest {
private String lastName;
private List<String> firstName;
// getters and setters
}
For QueryDSL, you can create a builder object:
public class UserPredicateBuilder{
private List<BooleanExpression> expressions = new ArrayList<>();
public UserPredicateBuilder withFirstName(List<String> firstNameList){
QUser user = QUser.user;
expressions.add(user.firstName.in(firstNameList));
return this;
}
//.. same for other fields
public BooleanExpression build(){
if(expressions.isEmpty()){
return Expressions.asBoolean(true).isTrue();
}
BooleanExpression result = expressions.get(0);
for (int i = 1; i < expressions.size(); i++) {
result = result.and(expressions.get(i));
}
return result;
}
}
And after you can just use the builder as :
public List<User> getUsers(UserRequest userRequest){
BooleanExpression expression = new UserPredicateBuilder()
.withFirstName(userRequest.getFirstName())
// other fields
.build();
return userRepository.findAll(expression).getContent();
}
This is the recommended solution.
If you really want to keep the current params without a binding (they still need some kind of validation, otherwise it can throw an Exception in query dsl binding)
you can group them by path :
Map<StringPath,List<String>> values // example firstName => a,b
and after that to create your boolean expression based on the map:
//initial value
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (Map.Entry entry: values.entrySet()) {
result = result.and(entry.getKey().in(entry.getValues());
}
return userRepository.findAll(result);
I am working in Scout and need SmartField. For this I need to set up lookup for suggestions.
I see the example with creating Lookup Call and than implement in Lookup Service getConfiguredSqlSelect
but I use Hibernate to work with classes, so my question is how to connect Smart field with Hibernate object filled service?
create a new lookup call according to [1] with the following differences:
don't select AbstractSqlLookupService as a lookup servic super type, but AbstractLookupService
in the associated lookup service you now need to implement getDataByAll, getDataByKey, and getDataByText
to illustrate the following snippet should help:
public class TeamLookupService extends AbstractLookupService<String> implements ITeamLookupService {
private List<ILookupRow<String>> m_values = new ArrayList<>();
public TeamLookupService() {
m_values.add(new LookupRow<String>("CRC", "Costa Rica"));
m_values.add(new LookupRow<String>("HON", "Honduras"));
m_values.add(new LookupRow<String>("MEX", "Mexico"));
m_values.add(new LookupRow<String>("USA", "USA"));
}
#Override
public List<? extends ILookupRow<String>> getDataByAll(ILookupCall<String> call) throws ProcessingException {
return m_values;
}
#Override
public List<? extends ILookupRow<String>> getDataByKey(ILookupCall<String> call) throws ProcessingException {
List<ILookupRow<String>> result = new ArrayList<>();
for (ILookupRow<String> row : m_values) {
if (row.getKey().equals(call.getKey())) {
result.add(row);
}
}
return result;
}
...
[1] https://wiki.eclipse.org/Scout/Tutorial/4.0/Minicrm/Lookup_Calls_and_Lookup_Services#Create_Company_Lookup_Call