Kentico 12: Design Patterns Part 20 - Choosing a Solution Architecture
Tags: kentico, architecture, aspnet, dotnet
Published: 2020-02-03 15:13:16

---
title: Kentico 12: Design Patterns Part 20 - Choosing a Solution Architecture
published: true
description: Learn how to decide which solution architecture is best for your Kentico 12 MVC projects
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/p8o28z7ymd3otit0swam.jpg
tags: kentico, architecture, aspnet, dotnet
series: Kentico 12 - Design Patterns
---

<figcaption>Photo by <a href="https://unsplash.com/@pietz">Paul-Louis Pröve
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

## Table of Contents

- [What's a Solution Architecture?](#whats-a-solution-architecture)
- [The Foundation with Kentico 12 MVC](#the-foundation-with-kentico-12-mvc)
- [Solution Architectures](#solution-architectures)
  - [No Abstractions, Single Layer](#no-abstractions-single-layer)
  - [Multiple Abstractions, Single Layer](#mutiple-abstractions-single-layer)
  - [N-Tier Architecture - Layers as Abstractions](#n-tier-architecture)
      - [A Brief Discussion of N-Tier Architectures](#discussion-of-n-tier)
  - [Onion Architecture - Layers as Implementations](#onion-architecture)
- [Conclusion](#conclusion)

## What's a Solution Architecture? <a name="whats-a-solution-architecture"></a>

For a .NET developer, a solution architecture is the pattern that defines the way projects and classes are organized in our Visual Studio solution.

This includes how things are named and also the relationships between projects (the project-to-project dependencies) and classes (abstractions or dependencies) 🤔.

The choices we make in our solution architecture, when we start building a new Kentico EMS application, can have significant impacts on many things 🧐:

- Time to completion of the project
- Ease of on-boarding of developers
  - New team members
  - Anyone tasked with quickly diagnosing and solving problems
  - Ourselves in the future when we've forgotten everything
- Maintainability of the code base
  - Prevention of regressions when introducing new features or corrections
- Flexibility of the application to handle changes
  - New business requirements
  - Upgrades
  - Introductions of new technology
- Testability (both manual and automated)

We cannot have the ideal amounts of everything above 😟.

Some of the choices that would improve testability can increase development time and cost.

A quickly built website may end up being un-maintainable, and require a re-write if the business needs substantial functionality changes.

At an even higher level, choosing the right (and potentially different) approach for each project is more flexible, but could make it more difficult for team members to move between projects if we build multiple websites.

> For any agencies developing with Kentico, this is likely something you will want to discuss before choosing an architecture.

---

## The Foundation with Kentico 12 MVC <a name="the-foundation-with-kentico-12-mvc"></a>

Before we begin analyzing our options for solution architecture, we should establish the foundation defined for us when we decide to create a Kentico 12 MVC site 👍.

> Take a look at Kentico's documentation on the differences between the [Portal Engine and MVC development models](https://docs.kentico.com/k12sp/kentico-development-models) and also the description of [Kentico's MVC implementation](https://docs.kentico.com/k12sp/developing-websites/mvc-development-overview) if you haven't already.

![Filesystem structure of a fresh Kentico 12 MVC install](https://dev-to-uploads.s3.amazonaws.com/i/2j6a7bks52amxxu8bcfr.jpg)

Above we see what the [Kentico Installation Manager](https://docs.kentico.com/k12sp/external-utilities/kentico-installation-manager) (KIM) creates when we choose to install a brand new Kentico 12 MVC code base.

We can note a couple things about the solution architecture we are provided:

1. There are (2) projects:
  - ✔ Content Management - identified by the `CMS` folder, this is the same Kentico Web Forms application we've been using for years to manage and maintain our content.
  - ✔ Content Delivery - identified by the `Sandbox` folder, this is a standard empty ASP.NET MVC 5 codebase with only a few extra lines sprinkled in for Kentico EMS integration.
1.  Besides the "MVC" project, the directory looks identical to what we'd see if we had a new Portal Engine site.

> From here on I'm going to refer to the "MVC" project as Content Delivery and the "CMS" project as Content Management. This is a differentiation that exists in many other CMS products and frameworks, and I think it's a valuable one 😉.

> It also avoids tying us to a specific technology - couldn't our Content Delivery be built on Web API 2 and a React client application? We don't call the CMS project by the technology (Web Forms) it is built on 🤔!

What this means is that we are already working with, at least, a (2) project solution, whereas with previous Kentico Portal Engine sites, we could build everything with (1) project.

We can no longer just think about our project as "My app manages and displays content". Instead we have a clear separation of "Content Management" and "Content Delivery" 😮 (though we can blur those lines with shared libraries, as we will see going forward).

> For all the options below, I recommend adding both the Content Management and Content Delivery projects to the same solution file, if only so you don't have to keep (2) instances of Visual Studio running during development 🤗.

> I am also going to present all architectures as using [Feature Folders for file organization](http://www.kamilgrzybek.com/design/feature-folders/) 🏆, which I detailed earlier in my Design Patterns series:

> {% post seangwright/kentico-12-design-patterns-part-3-tips-and-tricks-application-structure-1l2o %}

Let's now take a look at our solution architecture options.

---

## Solution Architectures <a name="solution-architectures"></a>

### No Abstractions, Single Layer <a name="no-abstractions-single-layer"></a>

The simplest solution architecture takes what we are given "out of the box" and makes very few modifications to it.

I'm calling this architecture "No Abstractions, Single Layer" because we are not creating any custom abstractions (beyond what we get from Kentico) and we only have the (2) .NET projects we start out with.

We create [custom Page Types](https://docs.kentico.com/k12sp/developing-websites/defining-website-content-structure/creating-and-configuring-page-types) and use them to retrieve data in our Content Delivery project.

Class organization and naming are still important, but we don't need to separate our solution into additional new projects because there is no shared code - we can consider the Content Management part of our code, complete, and we won't make modifications to that project 😄.

![Content Delivery project using Feature Folders organization](https://dev-to-uploads.s3.amazonaws.com/i/ik76295f5jxmz2xjfou6.jpg)

Above we can see how each Feature (in this case, each page), has its own folder and all the classes that implement that feature are contained within it. Classes used for rendering the View (`HomeController`, `HomeViewModel`) are next to classes used for data access (`HomePageProvider`, `HomePage`).

Below we can see what a very simplistic implementation of our `HomeController` might be. It includes data access directly in the `Index` action method, but still creates a `HomeViewModel` to pass data to the Razor view.

> We still create a view model because this post is about making trade-offs, not cutting corners 😎.

```csharp
// HomeController.cs

public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        var page = HomePageProvider.GetHomePages()
            .OnSite(SiteContext.CurrentSiteName)
            .Culture(LocalizationContext.CurrentCulture.CultureCode)
            .CombineWithDefaultCulture()
            .TopN(1)
            .TypedResult
            .FirstOrDefault();

        if (page is null)
        {
            return HttpNotFound("Could not find page 🤷🏿‍♀️");
        }

        var viewModel = new HomeViewModel
        {
            Text = page.Fields.Text
        };

        return View(viewModel);
    }
}
```

What if we want to leverage [caching](https://docs.kentico.com/k12sp/configuring-kentico/configuring-caching/caching-in-custom-code) and Kentico's powerful [MVC-based Page Builder functionality](https://docs.kentico.com/k12sp/developing-websites/page-builder-development) 🤔?

Well, since we are keeping this as simple as possible, we will implement these things in our controller action method:

```csharp
public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        var page = CacheHelper.Cache(() =>
            HomePageProvider.GetHomePages()
                .OnSite(SiteContext.CurrentSiteName)
                .Culture(LocalizationContext.CurrentCulture.CultureCode)
                .CombineWithDefaultCulture()
                .TopN(1)
                .TypedResult
                .FirstOrDefault(),
            new CacheSettings(1, nameof(HomeController), nameof(Index)));

        if (page is null)
        {
            return HttpNotFound("Could not find page 🤷🏿‍♀️");
        }

        HttpContext.Kentico().PageBuilder().Initialize(page.DocumentID);

        var viewModel = new HomeViewModel
        {
            Text = page.Fields.Text
        };

        return View(viewModel);
    }
}
```

We can already see that if we continue to add functionality (marketing automation, logging, multiple data sources) to the home page, we are going to end up with a large and complex controller 😖.

This approach is often referred to as the [Fat Controller](http://codebetter.com/iancooper/2008/12/03/the-fat-controller/) and is considered an anti-pattern. 

> It's comparable to placing all our code in a Web Forms page code-behind when building a Kentico Portal Engine site 💣.

There are ways we can help solve the increasing complexity of our controller with abstractions and infrastructure. That said, if we need to grow the code base we will run into additional architecture problems.

Let's summarize the benefits and problems with this approach:

- Pros
  - Extremely quick to implement
  - Easy to locate the right classes
  - No project dependencies (simple builds)
  - Few abstractions or layers ("just look in the controller!")
  - Changes to our Page Types propagate through the code quickly (minimal data mapping)

- Cons
  - Difficult or impossible to unit test (due to direct use of `*Context`, `CacheHelper` and data access in the controller)
  - Cross-cutting concerns (caching, logging, Page Builder) are not [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself)
  - Lack of abstractions means the controller has to know how to do everything
  - Growth in controller complexity is not sustainable

With this solution architecture we could probably build a site extremely quickly, and that might be its best sales-pitch 💰.

It does, however, come with all the caveats, our industry has discussed for years, that show up when we build the entire application in the view layer 💩.

---

### Multiple Abstractions, Single Layer <a name="mutiple-abstractions-single-layer"></a>

Moving on from the simplest approach, we next see the design that Kentico uses in its DancingGoat demo site 🤓.

This architecture has plenty of infrastructure and abstractions, but continues with a single layer of projects (Content Management and Content Delivery).

> The DancingGoat sample project uses the traditional MVC class organization that we get when creating a blank MVC project (File -> New Project) in Visual Studio. I'm not a fan of this organization (I call it "Framework Features" organization, instead see "Feature Folders" above).

So, what are the key differences we see with the "Multiple Abstractions, Single Layer" architecture and the previous one?

Let's look at the `HomeController` code as an example:

```csharp
public class HomeController : Controller
{
    private readonly IHomeRepository homeRepository;

    public HomeController(IHomeRepository homeRepository)
    {
        if (homeRepository is null)
        {
            throw new System.ArgumentNullException(nameof(homeRepository));
        }

        this.homeRepository = homeRepository;
    }

    [HttpGet]
    public ActionResult Index()
    {
        var page = homeRepository.GetHomePage();

        if (page is null)
        {
            return HttpNotFound("Could not find page 🤷🏿‍♀️");
        }

        HttpContext.Kentico().PageBuilder().Initialize(page.DocumentID);

        var viewModel = new HomeViewModel
        {
            Text = page.Fields.Text
        };

        return View(viewModel);
    }
}
```

Looking at this code, we can see that we have a constructor dependency, `IHomeRepository`, in the `HomeController`, which handles getting data from our data source.

```csharp
// IHomeRepository.cs

public interface IHomeRepository
{
    HomePage GetHomePage();
}
```

```csharp
// HomeRepository.cs

public class HomeRepository : IHomeRepository
{
    public HomePage GetHomePage() =>
        CacheHelper.Cache(() =>
            HomePageProvider.GetHomePages()
                .OnSite(SiteContext.CurrentSiteName)
                .Culture(LocalizationContext.CurrentCulture.CultureCode)
                .CombineWithDefaultCulture()
                .TopN(1)
                .TypedResult
                .FirstOrDefault(),
            new CacheSettings(1, nameof(HomeRepository), nameof(GetHomePage)));
}
```

Page Builder functionality is still handled in the controller and the Kentico custom Page Types are exposed to the view layer, via the `IHomeRepository`.

> I consider all the pieces of the MVC paradigm (Models, Views, Controllers) to be concerns of the View layer. So anything we have access to in a controller is exposed to the view layer.

With the addition of abstractions we can have the possibility of automated unit tests. The more abstractions we add, the easier it is to unit test the parts of our application in isolation 😁.

While the solution architecture has changed, these new abstractions are not being shared with the Content Management application and there are no shared libraries.

![Solution view of the Sandbox project with a repository interface and class added for the Home feature](https://dev-to-uploads.s3.amazonaws.com/i/id8nlq4yxcqw1bumsuu7.jpg)

Let's summarize the benefits and problems with this approach:

- Pros
  - Still relatively quick to implement
  - Easy to locate the right classes (feature folders, single project)
  - No project dependencies (simple builds)
  - Changes to our Page Types propagate through the code quickly (weak abstraction layers)
  - Parts of our application can be verified by automated tests
  - Cross-cutting concerns (caching, logging, Page Builder) can be DRY by applying them to abstractions
  - More flexible application architecture

- Cons
  - Requires setting up a [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/) and thinking differently about dependencies
  - Abstractions cannot be shared with the Content Management application
  - The flexibility that abstractions create also introduces some complexity
  - Controllers still have to know about data access (custom Page Type classes) types

> To learn about how we can take advantage of abstractions for Cross-cutting concerns, see my post _Kentico 12: Design Patterns Part 12 - Database Query Caching Patterns_

> {% post seangwright/kentico-12-design-patterns-part-12-database-query-caching-patterns-43hc %}

---

### N-Tier Architecture - Layers as Abstractions <a name="n-tier-architecture"></a>

Once we begin to focus on our abstractions we see that the most obvious and common area of abstractions is in data access - especially in a Kentico application 🧐.

The next evolution of our application architecture moves data access into its own project layer.

This results in the classic [N-Tier Layered Architecture](https://stackify.com/n-tier-architecture/) for the solution:

![Solution view of Sandbox projects which includes a new Sandbox.Data project](https://dev-to-uploads.s3.amazonaws.com/i/8tjfeihkuur13rdfmeqg.jpg)

Above, we can see a new `Sandbox.Data` project where all the data access code lives. The Kentico generated `HomePage` type has been moved to this project, along with the repository abstraction and implementation.

> The `Sandbox.Data` project will need to add the [Kentico.Libraries](https://www.nuget.org/packages/Kentico.Libraries/) NuGet package as a dependency to enable the underlying Kentico database access types.

In addition to the move of these files from the view-layer-focused Sandbox project, we have added a new `HomePageData` class which helps preserve the boundary between the view layer and the data access layer 😄.

Let's look at how our Home feature classes have changed:

```csharp
// HomePageData.cs

public class HomePageData
{
    public int DocumentId { get; set; }
    public string Text { get; set; }
}
```

```csharp
// IHomeRepository.cs

public interface IHomeRepository
{
    HomePageData GetHomePage();
}
```

```csharp
// HomeRepository.cs

public class HomeRepository : IHomeRepository
{
    public HomePageData GetHomePage() =>
        CacheHelper.Cache(() =>
            HomePageProvider.GetHomePages()
                .OnSite(SiteContext.CurrentSiteName)
                .Culture(LocalizationContext.CurrentCulture.CultureCode)
                .CombineWithDefaultCulture()
                .TopN(1)
                .Columns(
                    nameof(HomePage.DocumentID),
                    nameof(HomePage.HomePageText)
                )
                .TypedResult
                .Select(page => new HomePageData
                {
                    DocumentId = page.DocumentID,
                    Text = page.Fields.Text
                })
                .FirstOrDefault(),
            new CacheSettings(1, nameof(HomeRepository), nameof(GetHomePage)));
}
```

The addition of the `HomePageData` class means we gain a couple things:

1. ✔ The "View" layer doesn't have to interact with Kentico's data access technology for unit testing.
1. ✔ We can be more specific in our querying because we know exactly how much we expose to consumers of the `IHomeRepository`.
1. ✔ The Content Management application could consume our `Sandbox.Data` project if there are common ways of querying and updating data in our data access, or business logic implementations.

From the points above, 1) is great for being able to focus on verifying the quality and accuracy of our code and prevent regressions since it makes testing easier 👏🏿.

2) reduces flexibility of the application in some ways (we now have 2 classes to map our data to `HomeViewModel` and `HomePageData`), while increasing it in others (we can make optimizations and changes in the data access without affecting consumers) 👍.

3) is helpful if we have a need for code re-use at the data access level, and many larger projects do see these opportunities (ex: [Scheduled Tasks](https://docs.kentico.com/k12sp/configuring-kentico/scheduling-tasks/scheduling-custom-tasks), [Global Events](https://docs.kentico.com/k12sp/custom-development/handling-global-events)) 🤓.

Our `HomeController` code is effectively unchanged since we had already abstracted away the querying details in our **Multiple Abstractions, Single Layer** architecture.

- Pros
  - Somewhat easy to locate the right classes (feature folders and abstractions co-located with implementations, but there are multiple projects)
  - Data access logic is easier to optimize
  - Our View layer has no dependencies on Kentico data access, making automated unit testing much simpler
  - Cross-cutting concerns (caching, logging, Page Builder) can be DRY by applying them to abstractions
  - Data access code can be re-used in the Content Management application
  - More flexible application architecture
- Cons
  - Takes longer to implement since more preparation and design is required up-front
  - More complex builds and project dependencies requires a better understanding of how Visual Studio and .NET work
  - Requires setting up a [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/) and thinking differently about dependencies
  - Changes to our Page Types propagate through the code slowly because the abstractions are stronger (we have to update 3 "data transfer" classes with each Page Type change)

#### A Brief Discussion of N-Tier Architectures <a name="discussion-of-n-tier"></a>

If we look at what we've created, it's not too hard to see that the entire `Sandbox.Data` project is layer and also an abstraction. It exposes new types (`HomePageData`) to the consuming layer above it (`Sandbox`).

With the N-Tier Architecture, we can add as many layers as we need, which means a new abstraction for each layer.

Imagine we need to add a layer of complex business logic in a `Sandbox.Service` project.

`Sandbox.Service` exposes `IHomeService` and implements this interface through `HomeService`. `HomeService` has a dependency on `IHomeRepository` to get its data. `IHomeService` is then consumed by `HomeController`.

> `HomeController` -> `IHomeService` -> `IHomeRepository` (__->__ means "depends on")

![N-Tier Architecture diagram with multiple application layers](https://dev-to-uploads.s3.amazonaws.com/i/8mbh6x2zixuf67ox91kb.jpg)

Each layer we add brings its own abstractions and implementations and the direction of dependencies of these layers is from the top (View layer) down (to the data layer) 🤔.

Classic N-Tier applications didn't use interfaces and so the layers were very tightly coupled, with the View layer being coupled to the data access layer through transitive dependencies 😨.

But, we've taken advantage of interfaces so we've escaped that tight coupling, right?

Technically yes, but mentally no 😕.

We are still looking at the application as layers stacked on each other with the database being the foundation. This mental model will inevitably lead us into decisions that reinforce this perspective and potentially introduce coupling (perhaps through leaky abstractions) that we did not intend 🤯.

N-Tier Architectures work fine for a large number of applications, especially when the layers are clearly defined and abstractions are introduced to lessen coupling 👍.

However, there is one more evolution of our solution architecture to help resolve this issue.

---

### Onion Architecture - Layers as Implementations <a name="onion-architecture"></a>

We've already created our abstractions and implementations, so this final change will focus more on their separation.

The N-Tier Architecture has us looking at our application as a vertical set of layers, with each layer depending on those below it. This treats the layers as abstractions.

The [Onion Architecture](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/) treats layers as implementations because it places the abstractions and core "domain" data classes at the center of multiple layers, like an onion 🤓.

![Concentric circles showing an onion architecture with dependencies in the outer layers and abstractions in the middle](https://dev-to-uploads.s3.amazonaws.com/i/mjdoxxkgs2vfy3ynxrn3.jpg)

Above we can see that all the interfaces (including our made up `IHomeService`) are contained within the center of the architecture, whereas the implementations are all in the outer layers.

It might be strange to think about the View layer being the same as the Data Access layer, but this mental shift brings some benefits.

With an Onion Architecture we stop thinking of the database as the foundation or center of the our world and instead we have to fill that void with abstractions and models of our data 🙂.

For smaller and less complex applications this is definitely overkill 😅.

For larger ones that source data from multiple locations (web service, remote database, local database, file system, ect...) or have complex business rules about how that data is retrieved and modified, then focusing on the shape of the data that flows through the application and the abstractions that work with that data, independent of their implementation, helps us manage complexity and maintain flexibility 😎.

Let's implement this approach in our Sandbox solution:

![Onion architecture in the Visual Studio solution](https://dev-to-uploads.s3.amazonaws.com/i/i8ivae9s2nx2m25wxxh4.jpg)

Above we can see that the `IHomeRepository` and `HomePageData` have been moved to a new project `Sandbox.Core`. This project only includes models and interfaces 😮.

We've also changed the implementation details of `HomePageData`, treating it as a readonly object once created that also protects its internal state with constructor guards:

```csharp
public class HomePageData
{
    public int DocumentId { get; }
    public string Text { get; }

    public HomePageData(
        int documentId,
        string text)
    {
        if (text is null)
        {
            throw new System.ArgumentNullException(nameof(text));
        }

        DocumentId = documentId;
        Text = text;
    }
}
```

Since this type has become the core package of data passed around our application, we want to ensure it can't be created incorrectly or modified in a way that puts it into an invalid state 💪.

If we look at the dependencies of `Sandbox.Core` we can see there are almost none, especially dependencies like Kentico libraries that define implementation details:

![Sandbox.Core expanded References node in Visual Studio](https://dev-to-uploads.s3.amazonaws.com/i/ugg1a1696taeiclxm19x.jpg)

Our `Sandbox.Data` project depends on `Sandbox.Core` for the `IHomeRepository` interface and `HomePageData` model class.

The `Sandbox` project depends on `Sandbox.Core` as well, for the `IHomeRepository` interface, which is still a constructor dependency of `HomeController`.

While, `Sandbox` will have to take a dependency on `Sandbox.Data` to configure the composition root (likely for an Inversion of Control container library like [Autofac](https://github.com/autofac/Autofac)), we won't expose the types of `Sandbox.Data` anywhere - it has now become an implementation detail unto itself 👍.

The rest of our code remains unchanged with the exception of some namespace updates to reflect moving some types to `Sandbox.Core`.

---

Now that we've identified our abstractions and pushed the implementation details to the outside of our "onion", we are in good position to review the requirements of our application and start identifying other areas that could be moved around.

What's part of our core? What's part of the outer shell? What should be in the middle 🤔?

These questions are not always easy to answer, but by changing the way we think about the architecture and patterns of our application, adopting an inside -> out approach with an Onion Architecture, we are in a good place to consider them 👏🏿.

- Pros
  - Data access logic is easier to optimize
  - Our View layer has no dependencies on Kentico data access, making automated unit testing much simpler
  - Our business logic (if it exists) has no dependencies on implementations (View layer, Data Access, Web Service technologies), making it very easy to unit test
  - Cross-cutting concerns (caching, logging, Page Builder) can be DRY by applying them to abstractions
  - Data access code can be re-used in the Content Management application
  - Extremely flexible and robust application architecture that forces us to focus on abstractions and data flow and consitency
- Cons
  - Separating abstractions from implementations makes implementations harder to locate
  - Takes the longest to implement since we think about abstractions and models instead of technologies and implementations
  - More complex builds and project dependencies requires a better understanding of how Visual Studio and .NET work
  - Requires setting up a [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/) and thinking differently about dependencies
  - Changes to our Page Types propagate through the code slowly because the abstractions are stronger

---

## Conclusion <a name="conclusion"></a>

We've covered a lot of ground in this post 😅!

We started out reviewing [what a Solution Architecture really is](#whats-a-solution-architecture) and why the choices we make around this architecture should matter to us, touching on a few examples of the impact that our decisions could have.

Then we looked at the [foundation of our Kentico 12 MVC projects](#the-foundation-with-kentico-12-mvc) - the separation of Content Management from Content Delivery. This set the stage for the [different architectures](#solution-architectures) that were proposed.

The [No Abstractions, Single Layer](#no-abstractions-single-layer) architecture was the simplest and fastest to implement. It's a great option for small sites with limited complexity that need to be built quickly 🤗.

The [Multiple Abstractions, Single Layer](#mutiple-abstractions-single-layer) design can be seen in Kentico's DancingGoat demo site. This architecture allows for plenty of complexity and abstraction 😊, but still treats the Content Delivery application as a homogeneous unit.

Many sites will probably start out using this approach but then migrate into the [N-Tier - Layers as Abstractions](#n-tier-architecture) architecture as they mature.

The creation of separate .NET projects for layers of our application enables code re-use in the Content Management app and also places a strong focus on our abstractions 👍🏿.

The [N-Tier Architecture](#discussion-of-n-tier) is a classic approach which models where most applications end up. There are variations of it, but the consistent factor in all of them is that Data Access is the foundation of the application, and the View or Presentation layer is the top.

Finally, we looked at [Layers as Implementations with Onion Architectures](#onion-architecture), as an alternative to building a Data Access centered application.

By moving all of our implementations to the outer shell of our architecture and then focusing on abstractions and data models as our Core, we end up with a design that forces us to consider, more flexibly, how everything fits together 💪.

If you have any questions about the architectures we reviewed or how to implement the patterns, leave a comment below.

As always, thanks for reading 🙏!

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

{% tag kentico %}

Or my Kentico blog series:

- [Kentico 12: Design Patterns](https://dev.to/search?q=Kentico%2012%3A%20Design%20Patterns)
- [Kentico CMS Quick Tips](https://dev.to/search?q=Kentico%20CMS%20Quick%20Tip)