.Net Core 3.1 with Kentico Kontent Part 1: Clean Architecture Design
Be sure to check out the whole .Net Core 3.1 with Kentico Kontent Series:
  1. .Net Core 3.1 with Kentico Kontent Part 1: Clean Architecture Design 
  2. .Net Core 3.1 with Kentico Kontent Part 2: Clean Architecture Implementation
  3. .Net Core 3.1 with Kentico Kontent Part 3: How to Route and Link to Content
  4. .Net Core 3.1 with Kentico Kontent Part 4:  Helpful Tips When Using Linked Items in Kentico Kontent  (Coming soon)

.Net Core 3.1 with Kentico Kontent Series Part 1

This is Part 1 of our series on .Net Core Kentico Kontent websites. I love working with .Net Core and Kentico Kontent so this series will focus on giving you the tools you need to to create awesome products. We’ll start at the place where all successful projects should begin from a development perspective: with a look at what type of architecture is going to position us for success based on the project’s technical requirements. 

Architecting your solutions properly is important. In Robert C Martin’s Clean Architecture, he states the goal of software architecture is “to minimize the human resources required to build and maintain the required system”. Trying to make changes to a project that has bad architecture is a bit like glueing together your legos: worked great the first time you did it but is a real headache if you need to make any changes! 😬

Introduction

If you’re like me, there’s always THAT PROJECT that you dread making changes to because changing one thing is like pulling out a bottom piece on a teetering Jenga tower, one wrong move and everything comes crashing down. 

Now imagine your boss saying there’s a critical feature malfunctioning in THAT PROJECT and YOU need to fix it right away before the company bleeds too much money! 😵

Eager to solve the critical problem, you rip open the codebase, take one look at the code, and the nightmares of the last time you had to change something in this project come rushing back.

Frustrated Gif

You psych yourself up, thinking “I can do this!” Remembering the Jenga tower you formulate a plan of attack, in and out with lightning speed, fixing the issue quickly and efficiently with no casualties. Here’s your plan for the change:

Jenga Gif

But as soon as one unforeseen force hits you, everything crumbles around you and it ends up being more like this:

Jenga Gif

This is what bad architecture can do to your project, it can cripple it making the easiest changes difficult and risky.

In this post, we’ll review the architecture of a production .Net Core 3.1 MVC website with a Kentico Kontent headless CMS. We’ll review a SOLID architecture that’s easy to scale and maintain. We’ll cover which projects you’ll want to add, what each project does, and why each project is important.

Normally we’d look at the project requirements and goals first, evaluating which technologies would be good fits for the requirements. For the purpose of this article, we’ll assume the technology requirements are already chosen. 

Technology Requirements

Goal

The goal of this architecture is to segregate the Kentico dependencies in a repository layer so the remainder of the project has no dependencies on Kentico, the external API doubling as our data layer. 

Sean Wright outlines a similar architecture for Kentico 12 MVC, N-Tier Architecture - Layers as Abstractions, in his excellent blog post, Kentico 12: Design Patterns Part 20 - Choosing a Solution Architecture.

Overview

I love all of the examples you can find online. Reading other peoples’ articles and reviewing other projects on open source platforms like GitHub has really taught me a lot about what good software architecture looks like. A great example, complete with automated tests, is the Kentico Kontent Boilerplate repo for ASP.NET Core MVC. Like most projects online, for simplicity their HomeController performs all of the logic: it queries the database, creates objects, and returns the object mashed together with the view back to the browsers. 
 
public class HomeController : BaseController<HomeController>
{
    public async Task<ViewResult> Index()
    {
        var response = await DeliveryClient.GetItemsAsync<Article>(
            new EqualsFilter("system.type", "article"),
            new LimitParameter(3),
            new DepthParameter(0),
            new OrderParameter("elements.post_date")
        );

        return View(response.Items);
    }
}

Although great for example projects, this can break things like the Single Responsibility Principle (SRP) or the Open Closed Principle. Joe does a great job of explaining why query logic (or any logic) in your controllers could be troublesome in his blog post, Visualizing Thin ASP.Net Controllers via SOLID Principles. Basically, there can be more than one reason for this code to change; you can’t test the controller or logic in isolation, it can lead to god methods, and the query isn’t reusable.

Kentico has done a great job and giving you all the tools you need to query their headless CMS, Kontent, including the heavy hitter, IDeliveryClient. This interface alone will give you enough power to do nearly everything you need to do to get data from Kontent. 💪

In order to call Kentico’s DeliveryClient from your controller, you need to install the Kentico Kontent Delivery SDK. Since this is functioning as our data access layer, we are going to create a KenticoKontent.Repository layer that will have all of the dependencies needed to interact with the Kontent API, map the results to domain objects and return them without any references to Kentico to the UI layer. Whenever you have an external API, you should consider an abstraction like this around the code with tight coupling to the API. This allows you to only change your repository layer when those external dependencies inevitably change, rather than changing every layer of your project. The UI layer shouldn’t care if the database layer changes underneath it!

The Common Reuse Principle states

    Don’t force users of a component (dll in our case) to depend on things they don’t need.

I don’t want to have to install the entire Kentico Kontent Delivery SDK in every piece of my project!

Project Architecture

Project Dependency Diagram

Project Dependency Diagram

You can see here that the Web, Infrastructure and Repository projects depend on Core, but do not depend on each other. We’ll use the D of SOLID, Dependency Inversion Principle, to inject the concrete classes the Web, Infrastructure and Repository layers need without adding dependencies to those projects by putting only the interfaces in core! 🤠
 
The most important rule of the Dependency Inversion Principle is:

Don’t refer to volatile concrete classes. Refer to abstract interfaces instead. This rule applies in all languages, whether statically or dynamically typed. 

If you use a new keyword, chances are you’re probably violating this principle.

Project Data Flow Diagram



There are two ways data can flow through the layers of this application. For Create/Update/Read/Delete (CRUD) pass through style calls, the UI layer calls an IRepository interface directly (green arrows). If our application has additional business logic that’s required like an EmailService, we implement that business logic in our Infrastructure project. In these cases, the web layer calls an IService that resides in the Infrastructure project, which then can call IRepository data access layer if it needs to (blue arrows). 

Although this project doesn’t have a traditional database, this architecture also works well for applications that do!

Project Overview

Demo.Web

This project is the .Net Core 3.1 web application. 

It contains  This project is also responsible for the dependency injection container configuration. However, I would consider moving this logic into a Mapping or Bootstrap layer because it requires references to all concrete classes in order to tell the application how to resolve the interfaces. Here’s a snippet of the code required to setup dependency injection for Kontent’s IDeliveryClient. IDeliveryClient, CachedDeliveryClient, ProjectOptions, DeliveryOptions, ITypeProvider are all from Kentico and therefore require a Kentico dependency.
services.AddSingleton<IDeliveryClient>(
    serviceProvider => new CachedDeliveryClient(
        serviceProvider.GetRequiredService<IOptions<ProjectOptions>>(),
        serviceProvider.GetRequiredService<ICacheManager>(),
        DeliveryClientBuilder.WithOptions(_ => serviceProvider.GetRequiredService<IOptions<DeliveryOptions>>().Value)
            .WithTypeProvider(serviceProvider.GetRequiredService<ITypeProvider>())
            .WithContentLinkUrlResolver(new CustomContentLinkUrlResolver())
            .Build()
    )
);

Demo.Infrastructure

This project is the business logic layer that contains concrete implementations of services.

This layer should not depend on the Web or Repository layers. It can call the repository layers via IRepositories that are injected into it.

Demo.KenticoKontent.Repository

This project is a repository layer for Kentico Kontent. The purpose of this project is to abstract Kentico API calls and models into its own layer for use throughout the application.

This layer enables a software system to interact with external systems by receiving, storing and providing data when requested. 

The output of this layer are Core models designed with Domain Driven Design in mind, with no references to Kentico. 
 

Responsibilities

  • Holding all the generated models from Kontent
  • Contain all the API calls and logic to query Kontent
  • Mapping the result of the API calls to Core models to return to consumers

Adding Additional Data Integrations

If you have additional data sources, add another repository layer. When we wanted to add Azure Search to our project, we created a Demo.Search.Repository. If a service needs information from both, call both repository layers and consolidate the result in a core model and return the core model to the Web layer.

Additional Data Integrations Diagram

Demo.Core

This project is the core layer where the domain models or core models live. It also houses all interfaces for the project.
 

Responsibilities 

  • Core Models
  • Interfaces for Repositories and Services
  • Constants, Enums, Global Extensions, CustomAttributes, etc.

Demo.KenticoKontent.Mapping

The purpose of this project is to hold all of the automapping profiles so the application knows how to map Kontent models to core models. We also started to migrate some of the dependency injection registration here but did not complete it.
 

Responsibilities

  • Automapper mapping profiles
  • Automapper configuration
  • Default dependency injection configuration

Wrapping Up

In Part 2, we’ll look at the code associated with this architecture and trace a request from a controller down into the repository layer and back up to the UI. 🙌
  1. .Net Core 3.1 with Kentico Kontent Part 1: Clean Architecture Design 
  2. .Net Core 3.1 with Kentico Kontent Part 2: Clean Architecture Implementation
  3. .Net Core 3.1 with Kentico Kontent Part 3: How to Route and Link to Content
  4. .Net Core 3.1 with Kentico Kontent Part 4:  Helpful Tips When Using Linked Items in Kentico Kontent  (Coming soon)

Share This Post:

Twitter Pinterest Facebook Google+
Click here to read more Kentico posts
Start a Project with Us
Photo of the author, Joel Burke

About the author

Born in Ohio, Joel is an energetic programmer with over 12 years of Agile experience as a Full Stack Developer. He loves his brilliant wife Rebekah, documentation, communication, food, self-improvement, dressing up, doing things for people and learning. Incredibly passionate about faith, family, agile processes, people, software development and solving hard problems in each of these areas. Outside of work you’ll find him gaming with Rebekah, relaxing and enjoying great food.

View other posts by Joel

Subscribe to Updates

Stay up to date on what BizStream is doing and keep in the loop on the latest with Kentico.