How to make dependent webclient calls - spring-boot

I need to make 3 dependent WebClient API calls. In the end, I want a Mono of FinalResponse Object.
I need to use the value from the first API response to make a call to the second API (which would return Mono of Purchase class. Purchase class would contain 2 member variables
user object
List
Now for each value in the list, I need to make a third API call. and then return the final mono object to the controller.
I'm currently stuck with how to go about with an asynchronous call to 3rd API for each value in the list(returned by 2nd API)
service.getPurchases returns Mono<Purchase>. service.getSimilarItems returns Mono<List<Item>>.
class Purchase{
private List<Item> purchasedItemsList;
}
class Item {
private int itemId;
private int categoryId;
private String itemName;
}
public Mono<FinalResponse> getEndResults(UserRequest userRequest) {
Mono<User> response1 = service.getUserResponse(userRequest);
return response1.flatMap(response -> {
int userId = response.getUserId();
FinalResponse finalResponse = new FinalResponse();
List<AllItems> itemList = new LinkedList<>();
return service.getPurchase(userRequest, userId)
.map(purchasedItem -> {
val.getPurchasedItemsList().forEach(oneItem -> { // please help me how to go about from here
service.getSimilarItemsInCategory(userRequest, userId, oneItem.getCategoryId)
.map(similarItem -> {
AllItems allItem = new AllItems();
allItem.setPurchasedItem(oneItem);
allItem.setSimilarItem(similarItem);
itemList.add(allItem);
});
});
finalResponse.setResults(itemList);
return finalResponse;
});
});
}
class FinalResponse {
private User user;
private List<AllItems> results;
}
class AllItems {
private Item purchasedItem;
private List<Item> similarItem;
}
Basically the end response I need would look like
{
"users":{//UserObject//},
"results": [
{
"purchasedItem": {// Purschased Item 1},
"similarItems": [
{//Similar Item 1},
{//Similar Item 2}
]
},
{
"purchasedItem": {// Purschased Item 1},
"similarItems": [
{//Similar Item 1},
{//Similar Item 2}
]
}
]
}

Following Toerktumlare's comment: This can be fairly simple, if the WebClient calls return Monos or Fluxes of simple values or lists.
You can use flatMapMany() or flatMapIterable().
What about this simplified example?
public Mono<FinalResponse> getEndResults(UserRequest userRequest) {
Mono<User> userResponse = service.getUserResponse(userRequest);
return userResponse.flatMap(response -> {
int userId = response.getUserId();
return service.getPurchase(userRequest, userId)
.map(Purchase::getPurchasedItemsList)
.flatMapIterable(purchasedItems -> purchasedItems)
.flatMap(oneItem -> getSimilarItemInCategory(userRequest, userId, oneItem))
.collectList();
})
.map(itemList -> {
FinalResponse finalResponse = new FinalResponse();
finalResponse.setResults(itemList);
return finalResponse;
});
}
public Mono<AllItems> getSimilarItemInCategory(UserRequest userRequest, int userId, Item oneItem) {
return service.getSimilarItemsInCategory(userRequest, userId, oneItem.getCategoryId())
.map(similarItem -> {
AllItems allItem = new AllItems();
allItem.setPurchasedItem(oneItem);
allItem.setSimilarItem(similarItem);
return allItem;
});
}

Related

Spring reactive : mixing RestTemplate & WebClient

I have two endpoints : /parent and /child/{parentId}
I need to return list of all Child
public class Parent {
private long id;
private Child child;
}
public class Child {
private long childId;
private String someAttribute;
}
However, call to /child/{parentId} is quite slow, so Im trying to do this:
Call /parent to get 100 parent data, using asynchronous RestTemplate
For each parent data, call /child/{parentId} to get detail
Add the result call to /child/{parentId} into resultList
When 100 calls to /child/{parentId} is done, return resultList
I use wrapper class since most endpoints returns JSON in format :
{
"next": "String",
"data": [
// parent goes here
]
}
So I wrap it in this
public class ResponseWrapper<T> {
private List<T> data;
private String next;
}
I wrote this code, but the resultList always return empty elements.
What is the correct way to achieve this?
public List<Child> getAllParents() {
var endpointParent = StringUtils.join(HOST, "/parent");
var resultList = new ArrayList<Child>();
var responseParent = restTemplate.exchange(endpointParent, HttpMethod.GET, httpEntity,
new ParameterizedTypeReference<ResponseWrapper<Parent>>() {
});
responseParent.getBody().getData().stream().forEach(parent -> {
var endpointChild = StringUtils.join(HOST, "/child/", parent.getId());
// async call due to slow endpoint child
webClient.get().uri(endpointChild).retrieve()
.bodyToMono(new ParameterizedTypeReference<ResponseWrapper<Child>>() {
}).map(wrapper -> wrapper.getData()).subscribe(children -> {
children.stream().forEach(child -> resultList.add(child));
});
});
return resultList;
}
Calling subscribe on a reactive type starts the processing but returns immediately; you have no guarantee at that point that the processing is done. So by the time your snippet is calling return resultList, the WebClient is probably is still busy fetching things.
You're better off discarding the async resttemplate (which is now deprecated in favour of WebClient) and build a single pipeline like:
public List<Child> getAllParents() {
var endpointParent = StringUtils.join(HOST, "/parent");
var resultList = new ArrayList<Child>();
Flux<Parent> parents = webClient.get().uri(endpointParent)
.retrieve().bodyToMono(ResponseWrapper.class)
.flatMapMany(wrapper -> Flux.fromIterable(wrapper.data));
return parents.flatMap(parent -> {
var endpointChild = StringUtils.join(HOST, "/child/", parent.getId());
return webClient.get().uri(endpointChild).retrieve()
.bodyToMono(new ParameterizedTypeReference<ResponseWrapper<Child>>() {
}).flatMapMany(wrapper -> Flux.fromIterable(wrapper.getData()));
}).collectList().block();
}
By default, the parents.flatMap operator will process elements with some concurrency (16 by default I believe). You can choose a different value by calling another variant of the Flux.flatMap operator with a chosen concurrency value.

Dealing with one field that is sometimes boolean and sometimes int

I'm trying to work with the reddit JSON API. There are post data objects that contain a field called edited which may contain a boolean false if the post hasn't been edited, or a timestamp int if the post was edited.
Sometimes a boolean:
{
"edited": false,
"title": "Title 1"
}
Sometimes an int:
{
"edited": 1234567890,
"title": "Title 2"
}
When trying to parse the JSON where the POJO has the field set to int, I get an error: JsonDataException: Expected an int but was BOOLEAN...
How can I deal with this using Moshi?
I also ran into a similar problem where I had fields that were sometimes booleans, and sometimes ints. I wanted them to always be ints. Here's how I solved it with Moshi and kotlin:
Make a new annotation that you will use on fields to should convert from boolean to int
#JsonQualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION)
annotation class ForceToInt
internal class ForceToIntJsonAdapter {
#ToJson
fun toJson(#ForceToInt i: Int): Int {
return i
}
#FromJson
#ForceToInt
fun fromJson(reader: JsonReader): Int {
return when (reader.peek()) {
JsonReader.Token.NUMBER -> reader.nextInt()
JsonReader.Token.BOOLEAN -> if (reader.nextBoolean()) 1 else 0
else -> {
reader.skipValue() // or throw
0
}
}
}
}
Use this annotation on the fields that you want to force to int:
#JsonClass(generateAdapter = true)
data class Discovery(
#Json(name = "id") val id: String = -1,
#ForceToInt #Json(name = "thanked") val thanked: Int = 0
)
The easy way might be to make your Java edited field an Object type.
The better way for performance, error catching, and appliaction usage is to use a custom JsonAdapter.
Example (edit as needed):
public final class Foo {
public final boolean edited;
public final int editedNumber;
public final String title;
public static final Object JSON_ADAPTER = new Object() {
final JsonReader.Options options = JsonReader.Options.of("edited", "title");
#FromJson Foo fromJson(JsonReader reader) throws IOException {
reader.beginObject();
boolean edited = true;
int editedNumber = -1;
String title = "";
while (reader.hasNext()) {
switch (reader.selectName(options)) {
case 0:
if (reader.peek() == JsonReader.Token.BOOLEAN) {
edited = reader.nextBoolean();
} else {
editedNumber = reader.nextInt();
}
break;
case 1:
title = reader.nextString();
break;
case -1:
reader.nextName();
reader.skipValue();
default:
throw new AssertionError();
}
}
reader.endObject();
return new Foo(edited, editedNumber, title);
}
#ToJson void toJson(JsonWriter writer, Foo value) throws IOException {
writer.beginObject();
writer.name("edited");
if (value.edited) {
writer.value(value.editedNumber);
} else {
writer.value(false);
}
writer.name("title");
writer.value(value.title);
writer.endObject();
}
};
Foo(boolean edited, int editedNumber, String title) {
this.edited = edited;
this.editedNumber = editedNumber;
this.title = title;
}
}
Don't forget to register the adapter on your Moshi instance.
Moshi moshi = new Moshi.Builder().add(Foo.JSON_ADAPTER).build();
JsonAdapter<Foo> fooAdapter = moshi.adapter(Foo.class);

Intersection of arrays in LINQ to CosmosDB

I'm trying find all items in my database that have at least one value in an array that matches any value in an array that I have in my code (the intersection of the two arrays should not be empty).
Basically, I'm trying to achieve this :
public List<Book> ListBooks(string partitionKey, List<string> categories)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => b.Categories.Any(c => categories.Contains(c))
.ToList();
}
With the Book class looking like this :
public class Book
{
public string id {get;set;}
public string Title {get;set;}
public string AuthorName {get;set;}
public List<string> Categories {get;set;}
}
However the SDK throws an exception saying that Method 'Any' is not supported when executing this code.
This doesn't work either :
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
The following code works because there's only one category to find :
public List<Book> ListBooksAsync(string category)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri())
.Where(b => b.Categories.Contains(category))
.ToList();
}
In plain SQL, I can queue multiple ARRAY_CONTAINS with several OR the query executes correctly.
SELECT * FROM root
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
OR ARRAY_CONTAINS(root["Categories"], 'Legend')
I'm trying to find the best way to achieve this with LINQ, but I'm not even sure it's possible.
In this situation I've used a helper method to combine expressions in a way that evaluates to SQL like in your final example. The helper method 'MakeOrExpression' below lets you pass a number of predicates (in your case the individual checks for b.Categories.Contains(category)) and produces a single expression you can put in the argument to .Where(expression) on your document query.
class Program
{
private class Book
{
public string id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public List<string> Categories { get; set; }
}
static void Main(string[] args)
{
var comparison = new[] { "a", "b", "c" };
var target = new Book[] {
new Book { id = "book1", Categories = new List<string> { "b", "z" } },
new Book { id = "book2", Categories = new List<string> { "s", "t" } },
new Book { id = "book3", Categories = new List<string> { "z", "a" } } };
var results = target.AsQueryable()
.Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));
foreach (var result in results)
{
// Should be book1 and book3
Console.WriteLine(result.id);
}
Console.ReadLine();
}
private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
{
var combinedExpression = inputExpressions.Skip(1).Aggregate(
inputExpressions[0].Body,
(agg, x) => Expression.OrElse(agg, x.Body));
var parameterExpression = Expression.Parameter(typeof(T));
var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression,
Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));
var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);
var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
return result;
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
private readonly ParameterExpression parameterExpression;
public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
{
this.parameterExpression = parameterExpressionParam;
this.targetParameterExpressions = targetParameterExpressionsParam;
}
public override Expression Visit(Expression node)
=> targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
}
}

Validating selection field

I have a property that has an enumeration, in one of the values of the enumeration I have "help", if the user selects that option, I would like to do two things: 1. Send the user a text with help. 2. Ask the user if he wants to continue, or if he wants to leave. I do not know how to do it.
thank you very much.
public enum ContentClassification
{
Confidential_Restricted = 1 ,
Confidential_Secret = 2,
Public = 3,
Strictly_Confidential = 4,
help = 5
};
public ContentClassification ContentClassification { get; set; }
return new FormBuilder()
.Field(nameof(ContentClassification))
You may launch the Form many times, means if you get the "Help" choice from the first Form, launch another form for confirmation.
For example:
public enum ContentClassification
{
Confidential_Restricted = 1,
Confidential_Secret = 2,
Public = 3,
Strictly_Confidential = 4,
help = 5
};
public enum Validating
{
Continue,
Leave
};
[Serializable]
public class Classification
{
public ContentClassification? Choice;
public static IForm<Classification> BuildForm()
{
return new FormBuilder<Classification>()
.Message("You want to")
.Field(nameof(Choice))
.Build();
}
public Validating? Confirmation;
public static IForm<Classification> BuildConfirmForm()
{
return new FormBuilder<Classification>()
.Message("Send your message here")
.Field(nameof(Confirmation))
.Build();
}
}
And then create your RootDialog for example like this:
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
var form = new FormDialog<Classification>(new Classification(), Classification.BuildForm, FormOptions.PromptInStart, null);
context.Call(form, this.GetResultAsync);
return Task.CompletedTask;
}
private async Task GetResultAsync(IDialogContext context, IAwaitable<Classification> result)
{
var state = await result;
if (state.Choice == ContentClassification.help)
{
var form = new FormDialog<Classification>(new Classification(), Classification.BuildConfirmForm, FormOptions.PromptInStart, null);
context.Call(form, null); //change null to your result task here to handle the result.
}
}
}
You still need to implement the logic codes for other options in GetResultAsync method together with the logic codes to handle the result of second form BuildConfirmForm.

Unit test WebApi2 passing header values

I am working on a project using WebApi2. With my test project I am using Moq and XUnit.
So far testing an api has been pretty straight forward to do a GET like
[Fact()]
public void GetCustomer()
{
var id = 2;
_customerMock.Setup(c => c.FindSingle(id))
.Returns(FakeCustomers()
.Single(cust => cust.Id == id));
var result = new CustomersController(_customerMock.Object).Get(id);
var negotiatedResult = result as OkContentActionResult<Customer>;
Assert.NotNull(negotiatedResult);
Assert.IsType<OkNegotiatedContentResult<Customer>>(negotiatedResult);
Assert.Equal(negotiatedResult.Content.Id,id);
}
Now I am moving onto something a little complicated where I need to access value from the request header.
I have created my own Ok() result by extending the IHttpActionResult
public OkContentActionResult(T content,HttpRequestMessage request)
{
_request = request;
_content = content;
}
This allows me to have a small helper that reads the header value from the request.
public virtual IHttpActionResult Post(Customer customer)
{
var header = RequestHeader.GetHeaderValue("customerId", this.Request);
if (header != "1234")
How am I meant to setup Moq with a dummy Request?
I have spent the last hour or so hunting for an example that allows me to do this with webapi however I cant seem to find anything.
So far.....and I am pretty sure its wrong for the api but I have
// arrange
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var headers = new NameValueCollection
{
{ "customerId", "111111" }
};
request.Setup(x => x.Headers).Returns(headers);
request.Setup(x => x.HttpMethod).Returns("GET");
request.Setup(x => x.Url).Returns(new Uri("http://foo.com"));
request.Setup(x => x.RawUrl).Returns("/foo");
context.Setup(x => x.Request).Returns(request.Object);
var controller = new Mock<ControllerBase>();
_customerController = new CustomerController()
{
// Request = request,
};
I am not really sure what next I need to do as I havent needed to setup a mock HttpRequestBase in the past.
Can anyone suggest a good article or point me in the right direction?
Thank you!!!
I believe that you should avoid reading the headers in your controller for better separation of concerns (you don't need to read the Customer from request body in the controller right?) and testability.
How I will do it is create a CustomerId class (this is optional. see note below) and CustomerIdParameterBinding
public class CustomerId
{
public string Value { get; set; }
}
public class CustomerIdParameterBinding : HttpParameterBinding
{
public CustomerIdParameterBinding(HttpParameterDescriptor parameter)
: base(parameter)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
actionContext.ActionArguments[Descriptor.ParameterName] = new CustomerId { Value = GetIdOrNull(actionContext) };
return Task.FromResult(0);
}
private string GetIdOrNull(HttpActionContext actionContext)
{
IEnumerable<string> idValues;
if(actionContext.Request.Headers.TryGetValues("customerId", out idValues))
{
return idValues.First();
}
return null;
}
}
Writing up the CustomerIdParameterBinding
config.ParameterBindingRules.Add(p =>
{
return p.ParameterType == typeof(CustomerId) ? new CustomerIdParameterBinding(p) : null;
});
Then in my controller
public void Post(CustomerId id, Customer customer)
Testing the Parameter Binding
public void TestMethod()
{
var parameterName = "TestParam";
var expectedCustomerIdValue = "Yehey!";
//Arrange
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost/someUri");
requestMessage.Headers.Add("customerId", expectedCustomerIdValue );
var httpActionContext = new HttpActionContext
{
ControllerContext = new HttpControllerContext
{
Request = requestMessage
}
};
var stubParameterDescriptor = new Mock<HttpParameterDescriptor>();
stubParameterDescriptor.SetupGet(i => i.ParameterName).Returns(parameterName);
//Act
var customerIdParameterBinding = new CustomerIdParameterBinding(stubParameterDescriptor.Object);
customerIdParameterBinding.ExecuteBindingAsync(null, httpActionContext, (new CancellationTokenSource()).Token).Wait();
//Assert here
//httpActionContext.ActionArguments[parameterName] contains the CustomerId
}
Note: If you don't want to create a CustomerId class, you can annotate your parameter with a custom ParameterBindingAttribute. Like so
public void Post([CustomerId] string customerId, Customer customer)
See here on how to create a ParameterBindingAttribute

Resources