-
What should we add or change to make your life better?Ocelot has a very useful feature called request aggregation, basically allows to send multiple GETs to different routes and aggregate them in a single object. The idea is to create a cluster with multiple destinations to same route and add an "aggregation policy" (load balancing would not be used in that case). E.g. {
"ReverseProxy": {
"Routes": {
"route1": {
"ClusterId": "cluster1",
"Match": {
"Path": "/products"
}
}
},
"Clusters": {
"cluster1": {
"Destinations": [
{
"cluster1/destination1": {
"Address": "https://api1.com/products"
}
},
{
"cluster1/destination2": {
"Address": "https://api2.com/products"
}
}
],
"AggregatePolicy": "Merge"
}
}
}
} Should produce a result set like this {
"cluster1/destination1": [
{
"id": 1
},
{
"id": 3
}
],
"cluster1/destination2": [
{
"id": 5
}
]
} Why is this important to you?In our microservice structure we have the same service across various server around the world because of our business intelligence aggregator. We would like to have a unique api access point for specific data so we can merge and decorate our objects before give them to client. Looking inside code, ForwarderMiddleware and HttpForwarder needs to accept multiple destinations, ProxiedDestination inside IReverseProxyFeature needs also to be renamed to ProxiedDestinations with all of the consequences of that. |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 4 replies
-
We are aware of most of the features that Ocelot has in this area - and made a conscious decision to not include direct support for them in YARP. A lot of the reasons is around performance - to enable this kind of functionality YARP would need to be intelligent about the contents of request/response bodies. To me, what is unclear is how much value being able to do this kind of thing with configuration really is? The code for each of these individual operations isn't substantial, and API endpoints/routes can be mixed with YARP as part of the overall ASP.NET route table. I suspect if we try to solve these with config, that the scope of what people want to do is substantial - so where would we draw the line - the potential for scope creep is large? What of the other capabilities of YARP would you want? is it the cluster/load balancing/health? If we had an API to select a destination from a cluster meeting the other rules - would that be sufficient? |
Beta Was this translation helpful? Give feedback.
-
Well, you're right, project name is "Yet Another Reverse Proxy", not "Yet Another API Gateway". To be honest, I totally agree about configuration or code question. In fact I tried to implement a custom middleware to handle aggregation and, well, it works. https://gist.github.com/peerpalo/b83598e2c98413a6f5af92fe4800985b But as you can see we have some problems
What can be improved? In my opinion:
|
Beta Was this translation helpful? Give feedback.
-
I wasn't expecting the gateway scenarios to be written as a proxy middleware, more as an explicit endpoint using app.MapGet("/path/to/map", MyHandler);
Task MyHandler(HttpContext context)
{
// get addresses for outbound calls
// make calls
// aggregate results
} The part from the proxy that would be interesting is how to get the addresses of the destinations for outbound calls. We have talked about having something in HttpClient that does something similar to the proxy in terms of managing a collection of destinations and balancing calls between them, handling heath checks etc. This is needed for services like gRPC where you probably have more than one destination server for a request, and want to be smart about spreading load, and handling downtime. For your scenario - do you have other routes that the proxy will be handing that don't do the request aggregation and are just passthrough, or is this the main scenario? I was thinking if we had something like: async Task MyHandler(HttpContext context)
{
var pf = context.GetReverseProxyFeature();
var cluster = pf.Clusters["cluster1"];
var dest = cluster.NextDestination();
var client = cluster.GetHttpClient();
await client.GetAsync($"https://{dest.Model.Address}/Service1");
...
} So this would retrieve the next applicable destination, and an HttpClient (wrapper) based on the YARP config. Would that be the pieces that you need? |
Beta Was this translation helpful? Give feedback.
-
Yes, we have also other routes, in fact the idea is to have something like
that would return {
"destination1": [
{
"id": 1
},
{
"id": 3
}
],
"destination2": [
{
"id": 5
}
]
} so client can be do calls like this
Currently we wrote that as a middleware because it's the only way to have AvailableDestinations "cleaned" by health monitor (and other things) but yes, if we can implement a direct route handler having already all needed info (AvailableDestinations, HttpClient for current cluster, transforms, cache, etc, etc...) it would be a good solution! |
Beta Was this translation helpful? Give feedback.
-
Perhaps Similar to #1165? |
Beta Was this translation helpful? Give feedback.
-
I've stumbled upon this discussion several times now so I thought I'd leave a comment. I'd vote in favor of getting request aggregation added as well. I currently work on a large application which doesn't use a reverse proxy (like Yarp) because the argument was always "it's not that difficult to write a WebApi controller which takes a dependency on one or more HttpClients that call downstream services". You're right, it's not. The problem begins when engineers take a couple of properties from response 1 and a couple of other properties from response 2 and create a new, composite model which represents the merge operation of those two payloads. This is a problem because it increases coupling between the downstream services and the "request aggregator". Furthermore, it causes a code reuse problem which is very difficult to rationalize without reviewing tens of thousands of pull requests from engineers on multiple teams. People are more likely to create a new composite model with the properties they need because it's less work than figuring out if there's already a composite model out there which would support thier needs. Additionally, people become inclined to add other functionality that is not strictly related to request proxying -- perhaps a call to publish a message to a message broker. As time goes on, the boundaries become unclear about what the "request aggregator" is supposed to do or not do and what contexts it's safe to be called from. I know, 20/20 in hindsight, but fast forward 10 years and I'm wishing we had a generalized component which did request aggregation naturally because it would've eliminated a collection of problems that are actually quite difficult to govern without a technology solution. Furthermore, we have an enterprise Kong license, but we're avoiding it because they don't support request aggregation (and also, because their extension hooks require Lua and we are a .NET shop). With the deprecation of Ocelot, we're in a bit of a pinch with respect to a real solution that is based on .NET. I know this is a reverse proxy, but my personal opinion is that a reverse proxy isn't quite complete without request aggregation and there's not really another .NET option out there. In any case, I did manage to solve this with a specific endpoint route and I think the solution is fairly elegant (requires less than 100 lines of C# and configuration for the route aggregates that are interesting to you). I'm not sure I'm ready to give it the "looks good for production" stamp yet, but I only spent about an hour writing it and hopefully it helps the next reader. appsettings.json
Program.cs
|
Beta Was this translation helpful? Give feedback.
Yes, we have also other routes, in fact the idea is to have something like
https://gateway/products
that would return
so client can be do calls like this
https://gateway/products/destination1/3
https://gateway/products/destination2/5