Skip to content

Commit

Permalink
Feature/graphql (ThreeMammals#312)
Browse files Browse the repository at this point in the history
* ThreeMammals#298 initial hacking around better aggregation

* ThreeMammals#298 bit more hacking around

* ThreeMammals#298 abstraction over httpresponsemessage

* ThreeMammals#298 tidying up

* ThreeMammals#298 docs

* ThreeMammals#298 missed this

* ThreeMammals#306 example of how to do GraphQL
  • Loading branch information
TomPallister authored Apr 12, 2018
1 parent a15f75d commit b46ef19
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 4 deletions.
15 changes: 15 additions & 0 deletions docs/features/graphql.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
GraphQL
=======

OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate
the `graphql-dotnet <https://github.com/graphql-dotnet/graphql-dotnet>`_ library.


Please see the sample project `OcelotGraphQL <https://github.com/ThreeMammals/Ocelot/tree/develop/samples/OcelotGraphQL>`_.
Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do.
However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give
you enough instruction on how to do this!

Good luck and have fun :>


1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
features/configuration
features/routing
features/requestaggregation
features/graphql
features/servicediscovery
features/servicefabric
features/authentication
Expand Down
19 changes: 15 additions & 4 deletions docs/introduction/notsupported.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ Ocelot does not support...

* Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :(

* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore
* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision
I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your
Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns
it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore

.. code-block:: csharp
Expand All @@ -25,8 +28,16 @@ Ocelot does not support...
app.UseOcelot().Wait();
The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration.
The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json.
If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json
with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration.

In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. Unless I rolled my own Swagger implementation.
In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service
and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has
no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return
multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle
package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot
information would not match. Unless I rolled my own Swagger implementation.

If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this.
If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might
even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this.
18 changes: 18 additions & 0 deletions samples/OcelotGraphQL/OcelotGraphQL.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Update="configuration.json;appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6"/>
<PackageReference Include="Ocelot" Version="5.5.1"/>
<PackageReference Include="GraphQL" Version="2.0.0-alpha-870"/>
</ItemGroup>
</Project>
131 changes: 131 additions & 0 deletions samples/OcelotGraphQL/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Ocelot.Middleware;
using Ocelot.DependencyInjection;
using GraphQL.Types;
using GraphQL;
using Ocelot.Requester;
using Ocelot.Responses;
using System.Net.Http;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;

namespace OcelotGraphQL
{
public class Hero
{
public int Id { get; set; }
public string Name { get; set; }
}

public class Query
{
private List<Hero> _heroes = new List<Hero>
{
new Hero { Id = 1, Name = "R2-D2" },
new Hero { Id = 2, Name = "Batman" },
new Hero { Id = 3, Name = "Wonder Woman" },
new Hero { Id = 4, Name = "Tom Pallister" }
};

[GraphQLMetadata("hero")]
public Hero GetHero(int id)
{
return _heroes.FirstOrDefault(x => x.Id == id);
}
}

public class GraphQlDelegatingHandler : DelegatingHandler
{
private readonly ISchema _schema;

public GraphQlDelegatingHandler(ISchema schema)
{
_schema = schema;
}

protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//try get query from body, could check http method :)
var query = await request.Content.ReadAsStringAsync();

//if not body try query string, dont hack like this in real world..
if(query.Length == 0)
{
var decoded = WebUtility.UrlDecode(request.RequestUri.Query);
query = decoded.Replace("?query=", "");
}

var result = _schema.Execute(_ =>
{
_.Query = query;
});

//maybe check for errors and headers etc in real world?
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(result)
};

//ocelot will treat this like any other http request...
return response;
}
}

public class Program
{
public static void Main(string[] args)
{
var schema = Schema.For(@"
type Hero {
id: Int
name: String
}
type Query {
hero(id: Int): Hero
}
", _ => {
_.Types.Include<Query>();
});

new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
})
.ConfigureServices(s => {
s.AddSingleton<ISchema>(schema);
s.AddOcelot()
.AddSingletonDelegatingHandler<GraphQlDelegatingHandler>();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.UseIISIntegration()
.Configure(app =>
{
app.UseOcelot().Wait();
})
.Build()
.Run();
}
}
}
71 changes: 71 additions & 0 deletions samples/OcelotGraphQL/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Ocelot using GraphQL example

Loads of people keep asking me if Ocelot will every support GraphQL, in my mind Ocelot and GraphQL are two different things that can work together.
I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorisation / authentication or I would
bring in the awesome [graphql-dotnet](https://github.com/graphql-dotnet/graphql-dotnet) library and use it in a [DelegatingHandler](http://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html). This way you could have Ocelot and GraphQL without the extra hop to GraphQL. This same is an example of how to do that.

## Example

If you run this project with

$ dotnet run

Use postman or something to make the following requests and you can see Ocelot and GraphQL in action together...

GET http://localhost:5000/graphql?query={ hero(id: 4) { id name } }

RESPONSE
```json
{
"data": {
"hero": {
"id": 4,
"name": "Tom Pallister"
}
}
}
```

POST http://localhost:5000/graphql

BODY
```json
{ hero(id: 4) { id name } }
```

RESPONSE
```json
{
"data": {
"hero": {
"id": 4,
"name": "Tom Pallister"
}
}
}
```

## Notes

Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in configuration.json e.g.

```json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/graphql",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "yourgraphqlhost.com",
"Port": 80
}
],
"UpstreamPathTemplate": "/graphql",
"DelegatingHandlers": [
"GraphQlDelegatingHandler"
]
}
]
}
```
19 changes: 19 additions & 0 deletions samples/OcelotGraphQL/configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "jsonplaceholder.typicode.com",
"Port": 80
}
],
"UpstreamPathTemplate": "/graphql",
"DelegatingHandlers": [
"GraphQlDelegatingHandler"
]
}
]
}

0 comments on commit b46ef19

Please sign in to comment.