Павел Шалаев
Рецепты, инструменты, личный опыт
А афтер-пати будет???
Берегу ваше время
Эти разговоры могут затянуться... ;)
Сегодня четверг...?
Точно, что НЕ надо делать:
< ответственность разработчика
Базовый
Работает
Много ***
с октября 2015
-
Слабо развивается
Документация на 3-
Отличное название!
Много фич
Быстро развивается
Есть истории переезда
Документация на 4+
с июня 2018
-
много фич
не пробовал по-полной
странное название...
Azure Function or AWS Lambda.
Schema Stitching
Query batching
Persisted Queries
Any Type
Attributes
Relay Support
Filter and Sorting Support
Reach Event and Middlewares (compare to GQL.NET)
Apollo Tracing
Это еще не страшно и не больно, поверьте ;)
type Author {
id: ID!
name: String
"""
the list of books by this author
"""
books: [Book]
}
enum BookType {
FICTION
NON_FICTION
}
type Book {
id: ID!
title: String
author: Author
votes: Int
type: BookType
}
type Query {
authors: [Author]
books: [Book]
}
type Mutation {
upvoteBook (
bookId: ID
): Book
}
Примеры описания схемы
public class AuthorType : ObjectGraphType
{
public AuthorType()
{
Name = "Author";
Description = "Автор";
Field<NonNullGraphType<IdGraphType>>("id");
Field<NonNullGraphType<StringGraphType>>("name", "Имя автора");
Field<ListGraphType<Book>>("books", "Книги автора",
new QueryArguments()
{
new QueryArgument(typeof(IntGraphType))
{
Name = "limit",
DefaultValue = 10,
Description = "Все не выводим. А вдруг, Донцова!"
}
},
ResolveBooks // Должен возвращать массив книг
);
}
}
Как описать схему gql (на примере GraphQL.NET)
Query.AddField(new FieldType {
Name = "allAuthors",
Description = "Список авторов с фильтрацией и пагинацией",
Type = typeof(ListGraphType<AuthorsType>),
Arguments = new QueryArguments(
new QueryArgument<PaggingType> {Name = "paging"},
new QueryArgument<AuthorsFilterType> {Name = "filter"}
),
Resolver = new FuncFieldResolver<ListGraphType<AuthorType>,
IEnumerable<Author>>(context =>
{
var pagging = context.GetArgument<Pagging>("paging");
var filter = context.GetArgument<AuthorFilterType>("filter");
var subFields = context.SubFields;
var userCtx = (UserContext)context.UserContext;
// Тут может быть LINQ, http-запрос, обращение к любой базе и что угодно
var queryBuilder = generateQuery(...)
.AddPagination(pagging);
var q = queryBuilder.Build();
var res = sqlManager.DoQuery(...);
return res;
})
}
Как описать схему gql (на примере GraphQL.NET)
еще поломаем глаза....
[GraphQLObject]
public class Author : IDisposable
{
[GraphQLField(Name="id", ReturnType=typeof(NonNullGraphType(IdGraphType))]
public Guid Id { get; set; }
[GraphQLField()]
public string Name { get; set; }
[GraphQLFunc]
public IEnumerable<Book> books(ResolveFieldContext context)
{
return new List<Book>() {new Book {}};
}
public void Dispose()
{
Db.Dispose();
}
}
С помощью аннотаций (GraphQL.NET.Annotations)
var schema = SchemaBuilder.New()
.AddDocumentFromString(
@"
type Query {
hello: String
}")
.AddResolver("Query", "Hello", () => "world")
.Create();
С помощью схемы (Hot Chocolate)
{
allAuthors {
name
books {
title
author {
name
books {
title
author {
name
}
}
}
}
}
}
DataLoader - решаем N+1 как facebook завещал
//Startup.cs
services.AddDataLoaderRegistry();
// Резолвер
descriptor
.Field("author")
.Type<AuthorType>().Resolver(ctx =>
{
IDataLoader<Guid, Author> dataLoader =
ctx.BatchDataLoader<Guid, Author>("AuthorById", GetAuthorsAsync);
return dataLoader.LoadAsync(ctx.Parent<Book>().authorId);
});
// Получение данных
private async Task<IReadOnlyDictionary<Guid, Author>> GetAuthorsAsync(
IReadOnlyCollection<Guid> ids,
CancellationToken cancellationToken)
{
var res = _repository.DB.Connection
.SoftBuild()
.From("authors")
.WhereIn("id",ids)
.List<Author>();
return res.ToDictionary(t => t.id);
}
DataLoader - решаем N+1 как facebook завещал
addQueryList("allAuthors", "Список всех авторов", (db, filter) =>
{
// Динамическое формирование запроса
if (filter.ids != null && filter.ids.Length > 0)
{
return (from a in db.Authors
join fi in db.AuthorsFullInfo on a.Id equals fi.AuthorId
where filter.ids.Contains(a.Id)
where a.DDate == null
// Как сделать список полей?
select new {a.Id, a.Name, a.Borndate, fi.publisherName}
);
}
return (from a in db.Authors
join fi in db.AuthorsFullInfo on a.Id equals fi.AuthorId
join b in db.Books on b.AuthorId = a.Id
where a.DDate == null
&& (
filter.q != null && (
u.name.Contains(filter.q)
||fi.publisherName.Contains(filter.q)
||b.title.Contains(filter.q)
)
|| filter.q == null
)
select new {a.Id, a.Name, a.Borndate, fi.publisherName}
);
});
GraphQL.NET + Entity Framework + MS SQL
Проблемы
public static SqlQueryBuilder generateQuery(SqlManager sqlManager, ResolveFieldContext context)
{
var q = new SqlQueryBuilder("authors", "a");
q.AddFields(new [] {"id"}); // обязательные поля
if (context.SubFields != null)
{
foreach (KeyValuePair<string,Field> fl in context.SubFields)
{
switch (fl.Key)
{
case "organisation":
q.AddFields(new [] {"a.orgId", "a.orgName", "a.orgINN"});
break;
case "totalBooks": // Всего книг
q.AddFields(new []
{$"totalBooks = (SELECT COUNT(b.Id) FROM dbo.books b WHERE b.authorId=a.Id))"});
break;
case "historyInfo": // История редактирования автора (добавляются дополнительные поля)
HistoryInfoType.addQueries(q, "calc", fl.Value);
default:
q.AddField(fl.Key);
break;
}
}
}
... следующий слайд
}
GraphQL.NET + генерация схем + MS SQL
Добавляем условия фильтрации
public static SqlQueryBuilder generateQuery(SqlManager sqlManager, ResolveFieldContext context)
{
... Предыдущий слайд
var filter = context.GetArgument<AuthorFilterType>("filter");
var f = new List<FilterOperation>();
if (filter.ids != null && filter.ids.Any())
f.Add(new FilterOperation("a.id", PropertyType.text, OperationType.In, filter.ids));
if (filter.name != null)
f.Add(new FilterOperation("a.name", PropertyType.string, OperationType.Like, filter.name));
if (f.Count > 0) q.AddFilter(f);
return q;
}
GraphQL.NET + генерация схем + MS SQL
Добавляем условия фильтрации
Это удалось реализовать и работает быстро
{
"errors": [
{
"message": "Unexpected Execution Error",
"locations": [ { "line": 7, "column": 9 } ],
"path": ["allAuthors", 0, "books", 0, "author", "name"],
"extensions": {
"message": "Could not cast the source object to `tmp.Models.Author`.",
"stackTrace": " at HotChocolate.Execution.ResolverContext.Parent[T]()\r\n at lambda_method(Closure , IResolverContext )\r\n at HotChocolate.Types.FieldMiddlewareCompiler.<>c__DisplayClass3_0.<<CreateResolverMiddleware>b__0>d.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at HotChocolate.Execution.ExecutionStrategyBase.ExecuteMiddlewareAsync(ResolverContext resolverContext, IErrorHandler errorHandler)"
}
}
],
"data": {
"allAuthors": [
{
"name": "Кинг",
"books": [
{
"title": "book name 111",
"author": {
"name": null,
"books": null
}
}
]
}
]
}
}
Разобраться стоит каждому разработчику
Сравнивая реализации в JS и на .NET: много возможных ходов в .NET, сложно сразу понять, как начинать, как изучать, как сделать правильно
Инструменты достаточно развиты
Большинство статей gql+c# - ни о чём, начиная работу с этих статей можно легко зайти в тупик
Хорошая абстракция, не протекающая серверными недрами наружу, независимо от реализации самого сервера
Очень полезно
Просто полезно
Однотипные обучалки
Дополнительные пакеты (генераторы схем, клиенты на .NET и т.д.)
Пример проблем с EF
Вредно