Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean Architecture CQRS with GraphQL API

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.

like image 577
Arthur Kater Avatar asked Jul 30 '20 18:07

Arthur Kater


2 Answers

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).

like image 193
nunohpinheiro Avatar answered Sep 16 '22 14:09

nunohpinheiro


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.

like image 37
Wayne Davis Avatar answered Sep 20 '22 14:09

Wayne Davis