-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split Construction of Graph Types into individual modules #121
base: master
Are you sure you want to change the base?
Changes from 2 commits
9a55028
ca1f6fc
bef2064
22d63d1
d7c8b70
ceefd06
5f03f96
de04f5b
6eddb19
1ce9c77
2090c19
c2cf1a8
368092f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
using Example.Repositories; | ||
using GraphQL.Types; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Example.GraphQL | ||
{ | ||
public interface IOperation | ||
{ | ||
IEnumerable<IFieldType> RegisterFields(); | ||
} | ||
|
||
public interface IDogOperation : IOperation { } | ||
|
||
public interface ICatOperation : IOperation { } | ||
|
||
public class DogOperation : ObjectGraphType<object>, IDogOperation | ||
{ | ||
private readonly IServiceProvider _serviceProvider; | ||
|
||
public DogOperation(IServiceProvider serviceProvider) | ||
{ | ||
_serviceProvider = serviceProvider; | ||
} | ||
|
||
public IEnumerable<IFieldType> RegisterFields() | ||
{ | ||
var fields = new List<IFieldType> | ||
{ | ||
Field<StringGraphType>("say", resolve: context => "woof woof woof"), | ||
GetBreedListField(), | ||
GetImageDetailsField() | ||
}; | ||
|
||
return fields; | ||
} | ||
|
||
private IFieldType GetBreedListField() | ||
{ | ||
return Field<NonNullGraphType<ListGraphType<NonNullGraphType<DogType>>>>("dogBreeds", resolve: context => | ||
{ | ||
using var scope = _serviceProvider.CreateScope(); | ||
var dogRepository = scope.ServiceProvider.GetRequiredService<DogRepository>(); | ||
var dogs = dogRepository.GetDogs(); | ||
return dogs; | ||
}); | ||
} | ||
|
||
private IFieldType GetImageDetailsField() | ||
{ | ||
return FieldAsync<NonNullGraphType<ImageDetailsType>>("dogImageDetails", resolve: async context => | ||
{ | ||
using var scope = _serviceProvider.CreateScope(); | ||
var imageDetailsRepository = scope.ServiceProvider.GetRequiredService<DogImageDetailsRepository>(); | ||
var imageDetails = await imageDetailsRepository.GetDogImageDetails(); | ||
return imageDetails; | ||
}); | ||
} | ||
} | ||
|
||
public class CatSayOperation : ObjectGraphType<object>, ICatOperation | ||
{ | ||
public IEnumerable<IFieldType> RegisterFields() | ||
{ | ||
return new List<IFieldType> { Field<StringGraphType>("say", resolve: context => "meow meow meow") }; | ||
sungam3r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||
using GraphQL.Types; | ||||||
using System.Collections.Generic; | ||||||
|
||||||
namespace Example.GraphQL | ||||||
{ | ||||||
public class Queries | ||||||
{ | ||||||
public class DogQuery : ObjectGraphType<object> | ||||||
{ | ||||||
public DogQuery(IEnumerable<IDogOperation> dogOperations) | ||||||
{ | ||||||
foreach(var dogOperation in dogOperations) | ||||||
sungam3r marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
{ | ||||||
var fields = dogOperation.RegisterFields(); | ||||||
foreach(var field in fields) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
AddField((FieldType)field); | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
public class CatQuery : ObjectGraphType<object> | ||||||
{ | ||||||
public CatQuery(IEnumerable<ICatOperation> catOperations) | ||||||
{ | ||||||
foreach(var catOperation in catOperations) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
var fields = catOperation.RegisterFields(); | ||||||
foreach (var field in fields) | ||||||
{ | ||||||
AddField((FieldType)field); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's better to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give me some more details here? Do you have any doc handy for One reason I am doing the way I am right now, is to be non-opinionated on the setup. I give control to the implementor of Again, I might be talking about something different but will wait for information on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really sure how the use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can see a demonstration of a type-first schema here: https://github.com/graphql-dotnet/graphql-dotnet/tree/master/src/GraphQL.StarWars.TypeFirst The entire schema can be constructed via: services.AddGraphQL(b => b
.AddSystemTextJson()
.AddAutoSchema<StarWarsQuery>(c => c.WithMutation<StarWarsMutation>())); You will notice there are no 'graph types' but rather simple CLR classes decorated with attributes where necessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my own graphs, I use a slight variation of this to allow for DI injection of scoped services, which is not possible with the sample seen there. However, the code exists in the tests here: I also published it as a NuGet package here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @EmmanuelPonnudurai I'm fine with example as is without |
||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using GraphQL.Types; | ||
|
||
namespace Example.GraphQL | ||
{ | ||
public class DogType : ObjectGraphType<Dog> | ||
{ | ||
public DogType() | ||
{ | ||
Field(x => x.Breed); | ||
} | ||
} | ||
|
||
public class CatType : ObjectGraphType<Dog> | ||
{ | ||
public CatType() | ||
{ | ||
Field(x => x.Breed); | ||
} | ||
} | ||
|
||
public class ImageDetailsType : ObjectGraphType<ImageDetails> | ||
{ | ||
public ImageDetailsType() | ||
{ | ||
Field(x => x.Url); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace Example | ||
{ | ||
public class Dog | ||
{ | ||
public string Breed { get; set; } | ||
} | ||
|
||
public class Cat | ||
{ | ||
public string Breed { get; set; } | ||
sungam3r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public class ImageDetails | ||
{ | ||
public string Url { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,27 @@ | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.Extensions.Hosting; | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace Example | ||
{ | ||
public class Program | ||
{ | ||
public static Task Main(string[] args) => Host | ||
.CreateDefaultBuilder(args) | ||
.ConfigureWebHostDefaults(builder => builder.UseStartup<Startup>()) | ||
.Build() | ||
.RunAsync(); | ||
public static Task Main(string[] args) | ||
{ | ||
try | ||
{ | ||
return Host | ||
.CreateDefaultBuilder(args) | ||
.ConfigureWebHostDefaults(builder => builder.UseStartup<Startup>()) | ||
.Build() | ||
.RunAsync(); | ||
} | ||
catch (System.Exception ex) | ||
{ | ||
Console.WriteLine(ex); | ||
return Task.FromResult(0); | ||
} | ||
sungam3r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,17 @@ | ||||||||||||||
using System.Collections.Generic; | ||||||||||||||
using System.Linq; | ||||||||||||||
|
||||||||||||||
namespace Example.Repositories | ||||||||||||||
{ | ||||||||||||||
public class CatRepository | ||||||||||||||
{ | ||||||||||||||
private static readonly List<Cat> Cats = new() | ||||||||||||||
{ | ||||||||||||||
new Cat{ Breed = "Abyssinian" }, | ||||||||||||||
new Cat{ Breed = "American Bobtail" }, | ||||||||||||||
new Cat{ Breed = "Burmese" } | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
public IEnumerable<Cat> GetCats() => Cats.AsEnumerable(); | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||
using System.Net.Http; | ||||
using System.Threading.Tasks; | ||||
|
||||
namespace Example.Repositories | ||||
{ | ||||
public class DogImageDetailsRepository | ||||
{ | ||||
private readonly IHttpClientFactory _httpClientFactory; | ||||
|
||||
public DogImageDetailsRepository(IHttpClientFactory httpClientFactory) | ||||
{ | ||||
_httpClientFactory = httpClientFactory; | ||||
} | ||||
|
||||
public async Task<ImageDetails> GetDogImageDetails() | ||||
{ | ||||
var client = _httpClientFactory.CreateClient("DogsApi"); | ||||
var result = await client.GetStringAsync("api/breeds/image/random"); | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
return new ImageDetails { Url = result }; | ||||
} | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,17 @@ | ||||||||||||||
using System.Collections.Generic; | ||||||||||||||
using System.Linq; | ||||||||||||||
|
||||||||||||||
namespace Example.Repositories | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use file-scoped namespaces. We will convert codebase to use filescoped namespaces some time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean only for new files. Or just leave it as is, not a big deal. |
||||||||||||||
{ | ||||||||||||||
public class DogRepository | ||||||||||||||
{ | ||||||||||||||
private static readonly List<Dog> Dogs = new() | ||||||||||||||
{ | ||||||||||||||
new Dog{ Breed = "Doberman" }, | ||||||||||||||
new Dog{ Breed = "Pit Bull" }, | ||||||||||||||
new Dog{ Breed = "German Shepard" } | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
public IEnumerable<Dog> GetDogs() => Dogs.AsEnumerable(); | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
using Example.GraphQL; | ||
using Example.Repositories; | ||
using GraphQL; | ||
using GraphQL.MicrosoftDI; | ||
using GraphQL.Server; | ||
|
@@ -16,6 +18,12 @@ public class Startup | |
{ | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddScoped<DogRepository>(); | ||
services.AddScoped<DogImageDetailsRepository>(); | ||
services.AddScoped<CatRepository>(); | ||
|
||
services.AddOperations(); | ||
|
||
services.AddGraphQL(b => b | ||
.AddHttpMiddleware<DogSchema>() | ||
.AddHttpMiddleware<CatSchema>() | ||
|
@@ -28,6 +36,10 @@ public void ConfigureServices(IServiceCollection services) | |
|
||
services.AddLogging(builder => builder.AddConsole()); | ||
services.AddHttpContextAccessor(); | ||
services.AddHttpClient("DogsApi", x => | ||
{ | ||
x.BaseAddress = new System.Uri("https://dog.ceo/"); | ||
}); | ||
} | ||
|
||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | ||
|
@@ -40,6 +52,15 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |
|
||
app.UseGraphQLPlayground(new PlaygroundOptions { GraphQLEndPoint = "/api/dogs" }, "/ui/dogs"); | ||
app.UseGraphQLPlayground(new PlaygroundOptions { GraphQLEndPoint = "/api/cats" }, "/ui/cats"); | ||
} | ||
} | ||
|
||
public static class StartupExtensions | ||
{ | ||
public static void AddOperations(this IServiceCollection services) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would inline this method into ConfigureServices. |
||
{ | ||
services.AddSingleton<IDogOperation, DogOperation>(); | ||
services.AddSingleton<ICatOperation, CatSayOperation>(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Field<StringGraphType>(
appends field to graph type and returns reference to it.