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:
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)
- .NET 5.0 — Base framework;
- ASP.NET 5.0 — Web framework;
- Entity Framework Core 5.0 — ORM;
- Microsoft SQL Server on Linux for Docker — Database.
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.