14

I'm working on an API server (ASP.NET Core). To prevent spaghetti code and other nastiness in the future, I design the system following Clean Architecture/CQRS (using MediatR).

I'm considering to use GraphQL for the API instead of REST (Hot Chocolate GraphQL). In several examples from HotChocolate GraphQL, the database is directly queried using a GraphQL-EF mechanism. Though this might seem beneficial, I am worried this might complicate the code in the long run. The database structure might change, etc. The API should, in my opinion, remain separated from the repository layer. Even though more work, I believe GraphQL should communicate with CQRS instead.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
Arthur Kater
  • 826
  • 10
  • 17

4 Answers4

5

Despite not being a closed-answer "yes or no" question, I agree with you. I suspect that most of those examples may be only for demonstration. It seems hard to ensure SOLID principles without separating contracts, business logic and data layers.

Using CQRS and MediatR as you did, you are able to perform DDD more easily. So, it seems natural that the commands/queries sit in the Application layer, while the implementation of repositories can go to the Infrastructure. API contracts, on the other hand, would go to the Presentation layer.

In fact, we can develop several features throughout time in the Application layer, depending on our business needs. These features may feed varied Presentation modules, either APIs exposed in different ways, UI apps, etc. On the other hand, the features can rely in various Infrastructure data repositories or external sources, for example.

Microsoft provides a nice analysis about common web architectures here, focusing Clean Architecture here.

Concluding, in my opinion, yes, you could setup the GraphQL API in your Presentation, your business logic designed with CQRS in the Application layer and orchestrated with MediatR, and the data repositories implemented in the Infrastructure (respecting the contracts/interfaces defined in the Application).

nunohpinheiro
  • 2,169
  • 13
  • 14
1

Have you considered using GraphQL for the Q in CQRS and some other RPC mechanism for the command side of CQRS?

Mark Grenfell in his London 2020 NDC talk on gRPC emphasized the importance of supporting evolvable flexibility without api versioning and this point seems to confirm your concern about tight coupling between a database schema and GraphQL.

The NDC Talk: From WCF to gRPC - Mark Rendle | NDC Conferences

The following blog post demonstrates how to create shaped Entity Framework queries with the dynamic type. I have not yet explored how this technique could be hooked up to GraphQL but this might provide just enough abstraction from a schema.

CQRS with Entity Framework Core

Shayan Shafiq
  • 1,447
  • 5
  • 18
  • 25
camelCase
  • 1,549
  • 2
  • 16
  • 28
1

This is just the tip of the iceberg for GraphQL and there is more to using it that what I am about to show you. Just wanted to get that out there as a disclaimer.

When using GraphQL you have a single Query object that inherits from ObjectGraphType and a single Schema object that you inherit from in order to implement your custom Queries, Subscriptions, and Mutations.

These examples were used in a service that handled Admin concerns which is why you will see the word "Admin" a lot.

The heart of this implementation is in an object that inherited from ObjectGraphType:

    public class AdminQuery : ObjectGraphType
    {
        public AdminQuery(IServiceProvider sp)
        {
            ///Used to retrieve a list of users
            Field<ListGraphType<UserType>>()
                .Name("users")
                .Description(nameof(User))
                .ResolveAsync(
                async context
                => await sp.GetAsync<User>()); ////This is the extension method defined below
        }
    }

    ///GraphQL object that represents the User object
    ///Since there were numerous data models I created an abstract class
    ///to offload some of the boiler plate code
    public class UserType : IdentityDataTypeBase<User>
    {
        public UserType(IServiceProvider sp) : base()
        {
            ///This allows access to the properties of the User class
            ///The second parameter determines whether to allow null
            Field(c => c.Name, true);
            Field(c => c.Email, true);
            Field(c => c.FirstName, true);
            Field(c => c.LastName, true);
            Field(c => c.UserName, true);
        }
    }

    ///Abstract class that handles Identity Models being queried    
    public abstract class IdentityDataTypeBase<T> : ObjectGraphType<T> where T : IdentityBase
    {
        public IdentityDataTypeBase()
        {
            Field(c => c.Id, false)
                .Name(///Name of your property)
                .Description(///Description of the property);            
        }
    }

    ///Extension method defined to simplify data access
    public static Task<IEnumerable<T>> GetAsync<T>(this IServiceProvider sp)
    {           
        ///if you use IRepository<T>
        var repo = sp.GetRequiredService<IRepository<T>>();
        
        ///if you want to directly us the DbContext
        var repo = sp.GetRequiredService<DbContext>();

        ///Use the instance of whichever to retrive the object from the database
    }

This example hopefully shows that you would only need access to the data models that you are querying and the Data store you would be using to access that data.

Wayne Davis
  • 394
  • 2
  • 5
  • You shouldn't use Repository Pattern - DbContext is already a UoW. – JTinkers May 16 '21 at 13:55
  • @JamieBonnett "UoW is actually bringing multiple contexts together so that you can update each in one operation" - No, it's not - and having multiple DbContexts in a single logical domain is an anti-pattern. I'm not sure what you're disagreeing with. – JTinkers Jun 20 '23 at 11:01
  • @JamieBonnett No, I'm not confusing anything. It's clear to me that you don't understand what a UoW is if your primary focus is on how many instances of an aggregate there can be, rather than the fact that anything can be a UoW as long as it provides transactional bounds and data consistency (which DbContext does in relation to DbSets). https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#the-dbcontext-lifetime The first paragraph of the section: "[...] A DbContext instance is designed to be used for a single unit-of-work" – JTinkers Jun 21 '23 at 17:51
  • @JamieBonnett It's narrow-minded to think that something can't be a UoW because there can be more than one instance of it or because S&P Global says so (argumentum ab auctoritate much?). Plus, UoW is an aggregate... Somebody just got to Mt. Stupid of Dunning-Krueger's curve, and it ain't me. – JTinkers Jun 21 '23 at 18:47
  • @JamieBonnett I think you should brush up on architecture and design patterns before you start spreading uninformed opinions under 2 years old comments. – JTinkers Jun 21 '23 at 18:53
0

When you are considering GraphQL look at the different things that GraphQL will need to have access to. I will need access to a model that represents the data, and it will need to have access to the source of the data, whether that is a DbContext class or an IRepository. The more you abstract away from the actual implementation the more you will insulate yourself from breaking changes. I implemented GraphQL in my last project and I created a seperate library for all GraphQL related classes. The GraphQL library only needed access to the library with the data models, which also had the interfaces. Then the application layer only had to reference the GraphQL library so that it could call the extension method that plugged it into the DI pipeline.

Wayne Davis
  • 394
  • 2
  • 5