About a .NET 5 (RC1) Web Application with GraphQL 3

This week (09/14) was released the first version (RC1) from .NET 5, ASP.NET, EF Core and C# 9.

Still talking about releases, since last week is available the version 3 from GraphQL for .NET.

On occasion, this project exemplify the implementation and Dockerization of a simple Razor Web MVC Core consuming an GraphQL 3 Web API, build in a .NET 5 multilayer project, considering development best practices, like SOLID and DRY, applying Domain-Driven concepts in a Hexagonal Architecture.

Highlights

Notifications (pattern/context)

To avoid handle exceptions, was implemented a NotificationContext that's allow all layers add business notifications through the request, with support to receive Domain notifications, that by other side, implement validators from Fluent Validation and return a ValidationResult.

To GraphQL the notification context delivers a ExecutionErrors that is propagated to result from execution by a personalized Executer:

Resolving Scoped dependencies with Singleton Schema.

Is necessary, in the same personalized Executer to define the service provider that will be used for resolvers on fields:

Abstractions

With abstract designs, it is possible to reduce coupling in addition to applying DRY concepts, providing resources for the main behaviors:

...Domain.Abstractions

...Repositories.Abstractions

...Services.Abstractions

From EF TPH to GraphQL Interface

GraphQL interfaces provide a very interesting way to represent types derived from entities. In turn, in EF Core we can structure with TPH.

ENTITY

INHERITOR

INTERFACE

OBJECT

Environment configuration

Development

SECRETS

To configure database resource, init secrets in .../src/Dotnet5.GraphQL3.Store.WebAPI, and then define the DefaultConnection:

dotnet user-secrets initdotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost,1433;Database=Store;User=sa;Password=!MyComplexPassword"

After this, to configure the HTTP client, init secrets in .../src/Dotnet5.GraphQL3.Store.WebMVC and define Store client host:

dotnet user-secrets initdotnet user-secrets set "HttpClient:Store" "http://localhost:5000/graphql"

APP SETTINGS

If you prefer, is possible to define it on WebAPI appsettings.Development.json and WebMVC appsettings.Development.json files:

WebAPI

{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost,1433;Database=Store;User=sa;Password=!MyComplexPassword"
}
}

WebMVC

{
"HttpClient": {
"Store": "http://localhost:5000/graphql"
}
}

Production

Considering use Docker for CD (Continuous Deployment). On respective compose both web applications and sql server are in the same network, and then we can use named hosts. Already defined on WebAPI appsettings.json and WebMVC appsettings.json files:

APP SETTINGS

WebAPI

{
"ConnectionStrings": {
"DefaultConnection": "Server=mssql;Database=Store;User=sa;Password=!MyComplexPassword"
}
}

WebMCV

{
"HttpClient": {
"Store": "http://webapi:5000/graphql"
}
}

Running

The ./docker-compose.yml provide the WebAPI, WebMVC and MS SQL Server applications:

docker-compose up -d

GraphQL Playground

By default Playground respond at http://localhost:5000/ui/playground but is possible configure the host and many others details in ../...WebAPI/GraphQL/DependencyInjection/Configure.cs

Queries

Fragment for comparison and Arguments

{
First: product(id: "2c05b59b-8fb3-4cba-8698-01d55a0284e5") {
...comparisonFields
}
Second: product(id: "65af82e8-27f6-44f3-af4a-029b73f14530") {
...comparisonFields
}
}
fragment comparisonFields on Product {
id
name
rating
description
}

RESULT

{
"data": {
"First": {
"id": "2c05b59b-8fb3-4cba-8698-01d55a0284e5",
"name": "libero",
"rating": 5,
"description": "Deleniti voluptas quidem accusamus est debitis quisquam enim."
},
"Second": {
"id": "65af82e8-27f6-44f3-af4a-029b73f14530",
"name": "debitis",
"rating": 10,
"description": "Est veniam unde."
}
}
}

Query named’s and Variables

query all {
products {
id
name
}
}
query byid($productId: ID!) {
product(id: $productId) {
id
name
}
}

VARIABLES

{
"productId": "2c05b59b-8fb3-4cba-8698-01d55a0284e5"
}

HTTP BODY

{
"operationName": "byid",
"variables": {
"productId": "2c05b59b-8fb3-4cba-8698-01d55a0284e5"
},
"query": "query all {
products {
id
name
}
}
query byid($productId: ID!) {
product(id: $productId) {
id
name
}
}"
}

PLAYGROUND

Variables with include, skip and default value

query all($showPrice: Boolean = false) {
products {
id
name
price @include(if: $showPrice)
rating @skip(if: $showPrice)
}
}

VARIABLES

{
"showPrice": true
}

HTTP BODY

{
"operationName": "all",
"variables": {
"showPrice": false
},
"query": "query all($showPrice: Boolean = false) {
products {
id
name
price @include(if: $showPrice)
rating @skip(if: $showPrice)
}
}"
}

Mutations

Creating / adding a new Review to the respective product.

mutation($review: reviewInput!) {
createReview(review: $review) {
id
}
}

VARIABLES

{
"review": {
"title": "some title",
"comment": "some comment",
"productId": "0fb8ec7e-7af1-4fe3-a2e2-000996ffd20f"
}
}

RESULT

{
"data": {
"createReview": {
"title": "some title"
}
}
}

Subscriptions

The Mutation stay listening if a new review is added.

subscription {
reviewAdded {
title
}
}

RESULT

{
"data": {
"reviewAdded": {
"title": "Some title"
}
}
}

Built With

Microsoft Stack — v5.0 (RC 1)

GraphQL Stack — v3.0 (preview/alpha)

  • GraphQL — GraphQL is a query language for APIs and a runtime for fulfilling those queries with data;
  • GraphQL for .NET — This is an implementation of GraphQL in .NET;
  • GraphQL.Client — A GraphQL Client for .NET over HTTP;
  • GraphQL Playground — GraphQL IDE for better development workflows.

Community Stack

  • AutoMapper — A convention-based object-object mapper;
  • FluentValidation — A popular .NET library for building strongly-typed validation rules;
  • Bogus — A simple and sane fake data generator for C#, F#, and VB.NET;
  • Bootstrap — The most popular HTML, CSS, and JS library in the world.

Computer Scientist | Software Architecture Specialist | Senior .NET & Cloud Engineer.