ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
https://chillicream.com
MIT License
5.24k stars 744 forks source link

Unable to query simple Cosmos DB EF Core example with a single collection #4487

Closed jkears closed 2 years ago

jkears commented 2 years ago

Is there an existing issue for this?

Describe the bug

I am using EF Core with the CosmosDB provider as my DB context which consists of a very simple document that contains a simple items collection.

When I filter on a property on the top level of my document, the graph query works. When I attempt to filter on a property within an item on my items collection it fails with the following exception;

"message": "The LINQ expression 'DbSet()\r\n .Where(t => EF.Property<List>(t, \"BCs\")\r\n .AsQueryable()\r\n .Any(o => o.Test != null && o.Test.Contains(__p_0)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.",

"stackTrace": " at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.gCheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0& )\r\n at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.Visit(Expression expression)\r\n at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.Visit(Expression expression)\r\n at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)\r\n at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>cDisplayClass12_01.<ExecuteAsync>b__0()\r\n at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetAsyncEnumerator(CancellationToken cancellationToken)\r\n at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator()\r\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)\r\n at HotChocolate.Data.ToListMiddleware1.InvokeAsync(IMiddlewareContext context)\r\n at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>cDisplayClass2_11.<<UseDbContext>b__4>d.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_11.<b4>d.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\r\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"

Steps to reproduce

  1. Create simple model
    
    public class Test1 
    {
    public string Comment { get; set; }
    public List<BC> BCs { get; set; }
    public Guid Id { get; set; }
    }

public class BC { public string Test { get; set; } public Guid Id { get; set; } }

2. Create EF DB Context

public class Test1Context : DbContext { public Test1Context(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) {

    var dataObject = modelBuilder.Entity <Test1>();

    dataObject
        .ToContainer("Test1")
        .HasNoDiscriminator();
    dataObject.Property(p=>p.Id).ToJsonProperty("id");

    dataObject
      .HasKey(p => p.Id);

}
public DbSet<Test1> Test1
{
    get;
    set;
}

}

3. Create QueryType

public class GetAllTest1sQuery { [Serial] [UseDbContext(typeof(Test1Context))] [UseProjection] [UseFiltering] public IEnumerable GetAllTest1s([Service] Test1Context serviceDBContext) { return serviceDBContext.Test1.AsNoTracking(); } }

4. Add the following document to CosmosDB container "Test1"

{ "BCs": [ { "Test": "string", "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ], "Comment": "string", "CreatedAt": "2021-11-27T03:34:17.255324+00:00", "ModifiedAt": null, "DeletedAt": null, "Deleted": false, "ModelVersion": null, "id": "3fa85f64-5717-4562-b3fc-2c963f66afa9", "_rid": "xsZ5AMMjmLECAAAAAAAAAA==", "_self": "dbs/xsZ5AA==/colls/xsZ5AMMjmLE=/docs/xsZ5AMMjmLECAAAAAAAAAA==/", "_etag": "\"00000000-0000-0000-e395-35ddb44c01d7\"", "_attachments": "attachments/", "_ts": 1638020802 }

5.  Run top level query as follows

{ allTest1s ( where: { comment:{
contains :"r"} } )
{ comment
id bCs{ test id } } }

returns the following response:

{ "data": { "allTest1s": [ { "comment": "string", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa9", "bCs": [ { "test": "string", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ] } ] } }

6. Run query that filters to an item in the collection

{ allTest1s ( where: { bCs:{ some: {
test:{
contains :"r"} } } } )
{ comment
id bCs{ test id } } }

returns the following errors in response:

{ "errors": [ { "message": "Unexpected Execution Error", "locations": [ { "line": 2, "column": 3 } ], "path": [ "allTest1s" ], "extensions": { "message": "The LINQ expression 'DbSet()\r\n .Where(t => EF.Property<List>(t, \"BCs\")\r\n .AsQueryable()\r\n .Any(o => o.Test != null && o.Test.Contains(p_0)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.", "stackTrace": " at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.g__CheckTranslated|15_0(ShapedQueryExpression translated, <>cDisplayClass15_0& )\r\n at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.Visit(Expression expression)\r\n at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.Visit(Expression expression)\r\n at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)\r\n at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>cDisplayClass12_01.<ExecuteAsync>b__0()\r\n at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetAsyncEnumerator(CancellationToken cancellationToken)\r\n at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator()\r\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)\r\n at HotChocolate.Data.ToListMiddleware1.InvokeAsync(IMiddlewareContext context)\r\n at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_1`1.<b4>d.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_1`1.<b__4>d.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\r\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)" } } ], "data": { "allTest1s": null } }



### Relevant log output

_No response_

### Additional Context?

I am not stuck on using EF Core however my real documents will be much more complex and if I can't query via EF Core than how else can I accomplish that so that I don't have to write CosmosDB SQL per each type of query.  I would like to have HC resolve the LINQ if possible.

### Product

Hot Chocolate

### Version

12.3.0
PascalSenn commented 2 years ago

HotChocolate translate the query to a expression tree based on linq. It is up to the EF Core provider to translate this expression to the database specific query. It can very well be, that the CosomosDb provider is not capable of all the expressions that filtering generates.

HotChocolate could translate the queries directly to CosmosDB queries, but this database provider is not implemented at the moment. Writing a Database provider for HotChocolate is quite a bit of work, but it would be doable. The question is if it is worth the complexity of writing a dynamic database provider or if you can live with custom made input objects.

If you define the input objects and from these build the filters, you will definitely end up with the nicer graphql schema.

jkears commented 2 years ago

Thank you @PascalSenn

We have an in-house modeling tool we use to capture complete (DDD-based) domain models and from these domain models, code-generate complete ASP.Net Web API microservices projects, complete with all of the domain aggregate, entities, value objects classes as well as the backing DTO models, API Command and Event Handlers, etc. We integrate a proprietary business rules engine within each API command

One of our persistence options we are currently working on persists aggregates via the transactional outbox pattern into CosmosDB via the Azure CosmosDB SQL SDK. This ensures that both the state of aggregate as well as any generated domain events are committed into CosmosDB within an atomic transaction.

My hope was that we could utilize EF Core (with the CosmosDB provider), along with HC GraphQL to provide a graph query endpoint per each aggregate. Without projection this works extremally well, but of course the Query is pulling down every aggregate from Cosmos then applying the graph filters. Obviously this will not be our solution but I really liked how easy HC makes this work.

I get it that the EF Cosmos provider is the culprit but I would really like a proper solution so that I can keep with HC as I think it's very slick!

As mentioned, I am not stuck on using EF Core, especially if it doesn't work for me. I would like to understand what code patterns I would need to follow in order to code-generate the appropriate classes so that Hot Chocolate could easily query into any aggregate schema as well as query across multiple (code-generated) domain microservice aggregate endpoints.

I followed some online docs in an attempt to better understand what I need to do to accomplish this via CosmosDB SQL (see below) and while this works to return every thing (still), I am not sure what to do next.

public class Test1Query
{
  private readonly ITest1Repository _test1Repository;
  private readonly IMapper _mapper;
  public Test1Query(ITest1Repository test1Repository, IMapper mapper)
  {
      _test1Repository = test1Repository;
      _mapper= mapper;
  }

  [UseFiltering]
  [UseSorting]
  public async Task<IEnumerable<Test1Models.Test1>> GetAllTest1s(IResolverContext resolverContext)
  {
      List<Test1Models.Test1> results = new();

      // todo: to use resolver context to formulate query???
      var sqlQueryText = "SELECT * FROM c WHERE c.type = @type AND c.data.deleted = false AND c.data.bcs.col2S.someProp ='ff'";
      var results1 = await _test1Repository.ReadAllAsync(sqlQueryText, 10, null);

      foreach(var result in results1.Item1) 
      {
          results.Add(_mapper.Map<Test1Models.Test1>(result.Item1));
      }

      return results;
  }

  public async Task<Test1Models.Test1> GetTest1(IResolverContext context, string id)
  {
      var result = await _test1Repository.ReadAsync(Guid.Parse(id),null);
      if(result.Item1 != null)
      { 
          _mapper.Map<Test1Models.Test1>(result.Item1);
      }
      throw new ArgumentException("ID does not match a question in the database");
  }
}

public class Test1QueryType : ObjectType<Test1Query>
{
  protected override void Configure(IObjectTypeDescriptor<Test1Query> descriptor)
  {
      descriptor.Field(q => q.GetAllTest1s(default!))
          .Description("Get all Type1s")
          .Type<NonNullType<ListType<NonNullType<Test1Type>>>>();

      descriptor.Field(q => q.GetTest1(default!, default!))
          .Description("Get a Type1 by Id")
          .Argument("id", d => d.Type<IdType>())
          .Type<NonNullType<Test1Type>>();
  }
}

public class Test1Type : ObjectType<Test1Models.Test1>
{
  protected override void Configure(IObjectTypeDescriptor<Test1Models.Test1> descriptor)
  {
      descriptor.Field(q => q.Id)
          .Type<IdType>();
  }
}  

Can you please point me to any guidance that I can follow with defining input objects/filters so that I can integrate with CosmosDB SQL?

Thank you in advance!

PascalSenn commented 2 years ago

I think the issue you run into is the following: https://github.com/dotnet/efcore/issues/20441 I guess to support the current scenario you would have to really generate SQL .

If you want to use the built in filtering you would have to use visitors to analyze the input objects. While this is possible, it is complex to transalte the input object to your SQL query. We have some PoC with SQL Kata laying around, but there are a couple of pieces missing for a full integration.

Do you need dynamic filtering? meaning, do you need to expose all possible filters on the domain object? Or do you just use a subset of them and use UseFiltering for convenience?

jkears commented 2 years ago

Thank you @PascalSenn.

I really like the idea of supporting dynamic filtering but the UseFiltering option may suffice, I am just not 100% sure. I am willing to go down either direction.

The following is a fictious e-commerce domain model captured using our domain modeling tool. It is by no means complete but it should help me better explain what I am hoping to achieve.

image

In the diagram above, each bounded context is generated into a single microservice. As per the example model, there would be a microservice per the following bounded contexts: Inventory, Customers, SalesChannel, Catalog, Cart's and Checkout, Orders, Fullfillment, Shipping, Discounts and MarketingEvent.

Within each bounded context (microservice aka domain service) is one or more aggregates. Each domain service can be configured to support either an Event Sourcing (append-only) or State persistence pattern (CRUD). Currently we only support Event Store DB within the Event Sourcing options but later will support most of the Document Dbs that provide a Change Feed stream.

Within State persistence we will eventually support the complete EF Core DB provider list, but initially we only will support CosmosDB and MS SQL. As well, we support Elastic Search, Redis, and will be able to support other No SQL DB providers that are not supported within EF Core.

With either Event Sourcing or State patterns, we further code-generate an aggregate projection microservice, where each projection can be in any of the supported State persistent types (EF Core DB, Elastic, Redis etc..). Each projection service would have it's own GraphQL endpoint. The projections will not have any API other than the GraphQL endpoint as it's a read only store.

Within each domain service, each aggregate has it's own API, and I am hoping to further code generate a HC query type to query and filter the Aggregate.

Each domain service will expose a single GraphQL endpoint which resolves to each of the domain services aggregate query types.

Remotely we will use schema stitching over related domain services to support cross-service queries. For example, in the model above, since Orders relates to Customers as well as to Products , we should be able to query all Orders for a given Customer, where Product name is equal to some value. Or, as well, find all Products that a Customer as ordered within a data range, etc.

To accomplish this using CosmosDB as our backing aggregate state store, we would likely require dynamic filtering although, perhaps you could tell me otherwise. I suspect that this would involve (as you say), implementation of the Visitor pattern within the expression build life-cycle. (not sure).

We have the capability within our code-generator to code-generate any and all supporting classes, interfaces as well as methods/fields/properties within classes as required to support either the Visitor or the UseFiltering approach.

Can you provide links to any documentation and guidance on what we would need to generate and support for either the Visitor or the UseFiltering approach?

jkears commented 2 years ago

@PascalSenn, having reflected on this I decided I will create a Neo4J Projection that projects Cosmos DB based aggregates as nodes within a Neo4J database and use events to add relationships.

As per this example, it would be represented as a simple domain model we could code generate a projector from Cosmos to an equivalent Neo4J DB :

image

It seems that we should be in a much better position to query against an equivalent Neo4J DB with Hot Chocolate.

Please let me know if there are any issues I should be aware of regarding Neo4J support within Hot Chocolate, otherwise I think this would be a pretty good solution for our needs.

Thanks for your support!

PascalSenn commented 2 years ago

@jkears So the Neo4j driver for hotchocolate is still experimental. There are still a lot of open tasks to do. @arif-hanif is the core maintainer of this part of the library. Maybe it is best to check with him if your idea would currently be supported

jkears commented 2 years ago

Thank you @PascalSenn. That's really, very disappointing as Neo4J seems like it would provide a highly scalable and performant, graph based queryable data base.

@arif-hanif can you please let me know the state of Neo4J support based upon my comments above.

I was thinking that one option would be to code-generate TypeScript Neo4J schema node/relationship definition files based upon my domain model then host a GraphQL server using the Neo4J graphql.js library within ASP.Net, wrapped by Hot Chocolate.
I can easily generate the supporting Neo4J TypeScript files, I just need to understand how to integrate the ASP.Net server with Hot Chocolate. Can anyone point me to some docs on how this could be configured?

I could easily code-generate a NodeJS host but I'd prefer to host within ASP.Net so that as MS and ChilliCream evolve GraphQL support, the ASP.Net GraphQL Server could be refactored. In other words, I want this to be a HC GraphQL server.

jkears commented 2 years ago

@PascalSenn I need assistance. I can't see any simple example, within HC documentation on how to implement a custom override of filters. I must be either missing it or just plain dumb.

Would you (or someone on the HC team) please provide me an example of how to implement custom filters which override the default filters IQueriable/IEnumerable and replace that behavior with custom method stubs which I would replace with methods that invoke Cosmos core SQL to resolve the required data.

Please use the example for the given GraphQL query and types below as to how and what do I need to implement within Hot Chocolate to support the following scenario:

{
   allFoos(
    where: {
      bars: {
         some: { 
            name: { 
               eq: "test"
            }   
         }
      }  
    }
   )
   {
      bars{
          name
      }
   }
}
public class Foo
{
    public string Name {get; set;}
    public List<Bar> Bars {get; set}
}

public class Bar
{
    public string Name {get; set;}
}
PascalSenn commented 2 years ago

This is a faily complex process. you can see in the Mongo Extension what has to be done to impelemnt a whole driver.

The concept of how this all fits together you can see here: https://chillicream.com/docs/hotchocolate/api-reference/extending-filtering

What CosmosDB driver do you want to use? SQL, Native, Mongo or Gremlin?

jkears commented 2 years ago

My preference would be to use the cosmos SQL. Does the Mongo extension work?

michaelstaib commented 2 years ago

Mongo is stable ... but the driver implementation from Microsoft was quite unstable back when I tried it. If you use MongoDB in the backend its rock solid.

michaelstaib commented 2 years ago

You can also chime in and help us build the neo4j implementation. I know @arif-hanif is at the moment quite busy with other matters so if you are committed and want to help move that forward we can help you with knowledge.

jkears commented 2 years ago

Okay, so I would project all changes flowing into the CosmosDB SQL into a separate (but structurally the same), Mongo instance, and then it would seem like I would require very little effort to implement a domain wide GraphQL server with HC. Awesome!

This would be an eventually consistent approach which would suffice for my needs.

Once CosmosDB EF provides proper support I could then follow my original approach. It might be a good idea to keep this open until MS provides that support. I still am perplexed at how this is not supported with the EF Cosmos Provider, given that there will almost always likely be IEnumerables within most domain model that would be persisted into Cosmos. The lack of Any/All/Some/None was called out early 2021 so hopefully it is hot on the EF Core teams radar to get this resolved.

Thank you HC team!

jkears commented 2 years ago

You can also chime in and help us build the neo4j implementation. I know @arif-hanif is at the moment quite busy with other matters so if you are committed and want to help move that forward we can help you with knowledge.

We are looking at the following solution architecture which may further utilize Gremlin in CosmosDB as our graph store...

Note: I tucked in the Mongo projection store, (could be pure Mongo instead of Cosmos with Mongo API).

image

jkears commented 2 years ago

Hi @PascalSenn / @michaelstaib, we now project our CosmosDB OLTP into Mongo and setup a HC GraphQL and all works very well, very impressed!

There is one issue remaining that we are seeing which is that ID is showing incorrectly.

This is the class that we use to project and query data to/from MongoDB ....

    [DataContract]
    [ProtoContract]
    [Serializable]
    [BsonIgnoreExtraElements]
    [Node(
    IdField = nameof(Id),
    NodeResolverType = typeof(Test1NodeResolver),
    NodeResolver = nameof(Test1NodeResolver.ResolveAsync))]
    public class Test1  
    {

        [ProtoMember(1)]
        [Field(Settable = false)]
        [DataMember]
        public string Comments { get; set; }

        [ProtoMember(2)]
        [Field(Settable = false)]
        [DataMember]
        public string SomeProp { get; set; }

        [ProtoMember(3)]
        [Field(Settable = false)]
        [DataMember]
        public ObservableCollection<BC> BCs { get; set; }

        [ProtoMember(4)]
        [Field(Settable = false)]
        [DataMember]
        public NumberTypes NumberTypes { get; set; }

        [ProtoMember(5)]
        [Field(Settable = false)]
        [DataMember]
        [BsonId(IdGenerator = typeof(GuidGenerator)), BsonRepresentation(BsonType.String)]
        public System.Guid Id { get; set; }
    }

This is data as stored in MongoDB ,

{
  "_id" : "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "Comments" : "Comments data",
  "SomeProp" : "Some additional data",
  "BCs" : [{
      "Test" : "some text",
      "Col2S" : [{
          "SomeProp" : "Lou is a Leafs Fan",
          "_id" : "3fa85f64-5717-4562-b3fc-2c963f66afa6"
        }],
      "NumberTypes" : {
        "Name" : "One",
        "_id" : 1
      },
      "_id" : "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }],
  "NumberTypes" : {
    "Name" : "One",
    "_id" : 1
  }
}

Here is the query.

query{
   test1ById( id:"3fa85f64-5717-4562-b3fc-2c963f66afa6"){
      comments
      someProp
      id
   }
}

Here is the query response ..

{
  "data": {
    "test1ById": {
      "comments": "Comments data",
      "someProp": "Some additional data",
      "id": "VGVzdDEKZzNmYTg1ZjY0NTcxNzQ1NjJiM2ZjMmM5NjNmNjZhZmE2"
    }
  }
}

We are currently attempting to implement a gateway as per this diagram (below) assuming we understand this to be the way, and would like to resolve the id first. Thanks in advance!

image

PascalSenn commented 2 years ago

Cool! :) This is because relay uses RelayIds. By specifying the id of a node it probably is translated from UUID to ID The Id that is returned there is base 64 encdoed

VGVzdDEKZzNmYTg1ZjY0NTcxNzQ1NjJiM2ZjMmM5NjNmNjZhZmE2

is

Test1
g3fa85f6457174562b3fc2c963f66afa6

It encodes the type name into the Id so that you can fetch the node over the node(id: ID)

jkears commented 2 years ago

@PascalSenn , now that is cool insight which I did not glean from the docs! Many thanks for your prompt support. Is it possible to inject additional namespace detail into the node, for example in the test model below, we purposely created two aggregates with same named aggregates that originate in different services, example Test2 is purposely defined in Service1 as well as Service2 in my example below....

image

Also, is my understanding and approach somewhat correct as per the gateway design? We plan to code generate the SDL schema stitching files based upon aggregate dependencies along with the domain services currently code generated.

jkears commented 2 years ago

@michaelstaib I am watching your excellent presentation on Schema stitching and the first part on setting up the Gateway service and noted that there is an HttpClient service per each downstream GraphQL service. Have you are do you know of anyone who has utilized Dapr to communicate to/from GraphQL services? This would provide a more robust form of integration and utilize Dapr routing/load balancing to down stream GraphQL services.

jkears commented 2 years ago

@michaelstaib / @PascalSenn / @tobias-tengler Do you know if anyone has configured a HC Gateway to utilize Dapr's GraphQL Binding Spec instead of named HttpClient?

jkears commented 2 years ago

Hi @michaelstaib / @PascalSenn / @tobias-tengler sorry to be such a pest, but is there any way to override or replace the following code (without altering the existing source in HotChocolateStitchingRequestExecutorExtensions.cs) from the current code as follows ...

 return AddRemoteSchema(
  builder,
  schemaName,
  async (services, cancellationToken) =>
  {
      // The schema will be fetched via HTTP from the downstream service.
      // We will use the schema name to get a the HttpClient, which
      // we expect is correctly configured.
      HttpClient httpClient = services
          .GetRequiredService<IHttpClientFactory>()
          .CreateClient(schemaName);

      // Next we will fetch the schema definition which contains the
      // schema document and other configuration
      return await new IntrospectionHelper(httpClient, schemaName)
          .GetSchemaDefinitionAsync(cancellationToken)
          .ConfigureAwait(false);
  },
  ignoreRootTypes);

... with this code, which will instead manufacture an HTTP Client via DaprClient as per the following...

return AddRemoteSchema(
  builder,
  schemaName,
  async (services, cancellationToken) =>
  {
      // The schema will be fetched via HTTP from the downstream service.
      // We will use the schema name to get a the HttpClient, which
      // we expect is correctly configured.

     HttpClient httpClient = DaprClient.CreateInvokeHttpClient(schemaName);

      // Next we will fetch the schema definition which contains the
      // schema document and other configuration
      return await new IntrospectionHelper(httpClient, schemaName)
          .GetSchemaDefinitionAsync(cancellationToken)
          .ConfigureAwait(false);
  },
  ignoreRootTypes);

Such that in the second code example, the down stream GraphQL service's Dapr App ID matches it's Schema Name.

The reason for this request is so that I can achieve our current microservice based application architecture and approach using Dapr and it's side-car proxy support ...

image

Such that when the container orchestrator (Kubernettes) were to create additional container instances due to increased load, that gateway would still route to one of those instances, and further, gateway could in fact be of multiple instances.

Dapr provides retries, encryption of data in transit and more.

Note: Ideally, we would also want to use the Dapr Mongo state component between HC GraphQL and Mongo to route that traffic also via the Dapr Side Car proxy as well.

Thank you in advance!

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.