Skip to main content

1. Project Overview

About this chapter

In this chapter we detail the project we're going to build from the following perspectives:

  • Value proposition
  • Domain model
  • Functional
  • Technical Component
  • Architectural

Learning outcomes:

Just one: a comprehensive understanding of what we are building.

Value proposition

As a developer we rely upon a myriad of technologies to undertake our role (it seems like too many sometimes) with new ones emerging all the time.

In the vast majority of cases these tools require some form of command line interaction, more often a significant amount. The cognitive load in trying to remember these commands, even frequently used ones, can be significant.

Enter our API....

This API will act as a repository for all our systems (in our domain we'll call these Platforms), and the Commands associated with them. This means we can connect any client to our API, thus providing command search and retrieval functionality.

Domain Model

Our domain model is exceedingly simple, comprised of 2 primary entities:

  • Platform: represents the platforms that can have commands
    • A platform can have 0 or more commands
  • Command: represents the commands associated to a platform
    • A command must have only 1 platform (a command cannot exist without an associated platform)

Figure 1.1 Domain Model


info

We will model other entities in our domain, but these are more to support the operational side of the API (e.g. API Key registrations). Here I just wanted to cover the functional domain elements.

We will of course cover all entities in the relevant sections.

Functions of our API

The API will provide the following functionality for both Platforms and Commands (Create, Read, Update and Delete)1 as well as some other operational functions relating to health and authentication:

FunctionHTTP VerbRoute Fragment
Get all commands (paginated)GET/api/commands
Get command by IDGET/api/commands/{id}
Create commandPOST/api/commands
Fully update a commandPUT/api/commands/{id}
Partially update a commandPATCH/api/commands/{id}
Delete commandDELETE/api/commands/{id}
Get overall health statusGET/api/health
Get readiness statusGET/api/health/ready
Get PostgreSQL healthGET/api/health/db
Get Redis healthGET/api/health/cache
Get Hangfire healthGET/api/health/jobs
Get all platforms (paginated)GET/api/platforms
Get platform by IDGET/api/platforms/{id}
Create platformPOST/api/platforms
Update platformPUT/api/platforms/{id}
Delete platformDELETE/api/platforms/{id}
Get commands for platformGET/api/platforms/{platformId}/commands
Bulk import platformsPOST/api/platforms/bulk
Get bulk import statusGET/api/platforms/bulk/{jobId}/status
Register API keyPOST/api/registrations

HTTP Verbs

A HTTP request requires that you specify a "verb" when making that request. Looking at the verb attached to call can tell you a lot about the intention on that call. General rule of thumb in the API world is:

  • GET = read or fetch data
  • POST = create data
  • PUT = update (all) data
  • PATCH = update (some or all) data
  • DELETE = destroy data

The verb and route taken together make the endpoint unique. You can read more about these concepts in the REST theory section.


It should be noted that the API we'll build provides much more than just simple CRUD-based endpoints, but we'll discuss those deeper features in the coming chapters.

Technical components

We've already covered the core build technologies (.NET 10 & C#) in the Welcome section, here we'll detail out the other tech we'll be using as part of our build.

PostgreSQL

I've chosen PostgreSQL for our primary database for a number of reasons, including but not limited to:

  • It's an awesome production-grade relational db
  • It's cross platform
  • Related to the above, it's ubiquitous - you will find it everywhere so I feel it's a good DB to use
  • There's great .NET support

EF Core

We'll be using EF Core as the projects Object Relational Mapper (ORM) primarily for its feature set, ubiquity and the fact it's by Microsoft. While there are many other fine ORMs out there in the .NET space, EF Core is a solid choice—and as a .NET developer, you will encounter it.

What's an ORM?

ORMs allow developers to access databases (in this case PostgreSQL) using the native programming language of their given project without the need to write vendor specific SQL. You can read more about ORMs in the theory section.

Redis

The name Redis is synonymous with caching, (even though it can do much more than just act as a cache). In this project however that is what we are using it for.

Fluent Validation

Validations provide a standardized way for developers to define what constitutes a valid input to our system. Validation frameworks come out the box with our .NET API project, (which we'll cover), but moving beyond this we'll opt to use Fluent Validation for defining our system-wide validation rule set. Fluent validation has been picked for its clean separation of concerns (keeping validation logic separate from our Data Transfer Objects), excellent testability, expressive fluent interface that makes validation rules highly readable, and its ability to handle complex validation scenarios.

Mapster

Object mapping provides the ability to easily traverse between object types (in our case this would be Data Transfer Objects and our Domain Models). While this can be achieved manually, as you move to using larger objects with more complex mapping requirement, a mapping framework like Mapster can reduce the burden on the developer.

Serilog

Often overlooked (by me a lot of the time), structured logging is absolutely essential in a production-grade API as it provides a means to track issues that may occur in the platform, thus locating the root cause, and then ultimately implementing a fix. We'll start our structured logging journey by using the ILogger interface by Microsoft, then layer Serilog on top of this to provide rich structured logging with multiple output sinks (console & file).

Auth0

We'll implement a self-rolled API Key authentication solution for one aspect of our look at Authentication, but when we turn our sights to implementing OAuth we'll make use of Auth0 as our authentication provider. I've chosen to use a 3rd party to manage this side of our authentication efforts as it greatly simplifies what we need to build, allowing us to focus on the unique value-proposition of our API. It also backs-off this mission critical component of our API to experts in the domain.

Hangfire

As we move through into the later part of our build we'll implement a batch API endpoint that will process "a lot" of data in an asynchronous way using backend workers. For this feature we'll use Hangfire, principally for its rich feature set, but also for its widespread use in the .NET world.

Other stuff

In the interests of keeping this section somewhat focused, I've only covered the direct technical components that our API will employ, I've not covered the "tech around the sides" of our project like: Docker, Git, GitHub etc. as we'll cover those off in more detail later.

Architecture

I've broken this section down into 3 parts:

  • REST: covers API design pattern we'll be using
  • MVC: covers internal architectural pattern we'll use for the build
  • Solution: Covers the end to end key component structure of the app

This is an outside / in approach to looking at the overall architecture of our API build starting with the exposed interface and working our way inwards.

REST

REST (REpresentational State Transfer) is an architectural style for designing APIs that places the concept of resources (like our Platforms and Commands) front and centre. Resources are identified by URLs and manipulated using HTTP verbs:


FunctionResourceHTTP VerbRoute Fragment
Get all commands (paginated)CommandGET/api/commands
Get command by IDCommandGET/api/commands/{id}
Create commandCommandPOST/api/commands
Fully update a commandCommandPUT/api/commands/{id}
Partially update a commandCommandPATCH/api/commands/{id}
Delete commandCommandDELETE/api/commands/{id}
Get all platforms (paginated)PlatformGET/api/platforms
Get platform by IDPlatformGET/api/platforms/{id}
Create platformPlatformPOST/api/platforms
Update platformPlatformPUT/api/platforms/{id}
Delete platformPlatformDELETE/api/platforms/{id}
Get commands for platformCommandGET/api/platforms/{platformId}/commands
Bulk import platformsPlatformPOST/api/platforms/bulk
Get bulk import statusPlatformGET/api/platforms/bulk/{jobId}/status

This approach promotes stateless communication between client and server, where each request contains all the information needed to process it.

REST is by far and away the most popular API design pattern2 (others include GraphQL, gRPC etc.), and is easy to pick up and learn, hence why we're using it. You can read more about the intricacies of REST in the Theory section.

MVC

Moving from the external or client facing architecture of our API, we turn now to the internal architecture of the API and the Model View Controller (MVC) pattern.


Clients using the API would not know, nor should they care about this aspect of our architecture.


MVC (Model View Controller) is an architectural pattern that separates an application into three interconnected components: Models (data and business logic), Views (presentation layer), and Controllers (request handling and flow control). In the context of .NET API development, the pattern is slightly modified—Controllers handle incoming HTTP requests and return data responses, Models represent our domain entities and DTOs, while the "View" layer is effectively the JSON serialization of our response objects rather than HTML templates. This separation of concerns promotes cleaner, more maintainable code by ensuring each component has a distinct responsibility.


Figure 1.2 MVC


You can read more on the MVC patter in the Theory section.

Solution Architecture

Finally we take a look at the overall high-level solution architecture, from top to bottom we have:

  • API Client: not technically part of our solution, but we will make use of http request files from within our project to test our endpoints
  • Data Transfer Objects (DTOs): these act as the external facing contract with our client.
    • We'll use Fluent Validation here to ensure DTOs used for system inputs are valid
    • We'll use Mapster to provide mappings between DTOs and the Domain Models
  • Controllers: implemented as part of the MVC pattern, controllers will act as the orchestrators for API requests.
  • Hangfire Service: This service will be responsible for managing bulk API requests through the use of background workers.
  • Repository: We're going to make use of the Repository Pattern to provide a layer of further abstraction between our database and our app code. A possibly controversial choice we'll cover the reasons for its use in that chapter.
  • DB Context: Provided by EF Core, the DB Context "models" the Domain Models down to the PostgreSQL DB and acts as an intermediary between the Repository and the PostgreSQL Database.
  • Redis Connection: Used to connect into our Redis Cache (we don't use a DB Context here)
  • Models: Internal representations of our domain, exposed externally in this instance as DTOs. Models are ultimately represented down at our PostgreSQL database.
  • PostgreSQL DB: The database persistence layer used to store domain as well as operation data.
  • Redis Cache: Key Value pair database used in this instance as a supplementary cache to speed up the serving of some data.

We will revisit this architecture throughout the build, using it to aid understanding and mark progress through the build.


Figure 1.3 MVC

Project Structure

The folder structure of our project will be as follows (don't worry if you don't know what some of those folders relate to yet - you will soon!):


APIProject/
├── Controllers/
├── Data/
├── Dtos/
├── Logs/
├── Mappings/
├── Middleware/
├── Migrations/
├── Models/
├── Properties/
├── Requests/
├── Services/
├── Validators/
└── wwwroot/

I have explicitly gone for this technical or layer-based view as this is a technical guide covering these exact concepts, so segmenting them in this way made sense to me (for this book).

It is worth mentioning that you do have another common approach to structuring your project: the Domain or Feature view. This segments the project around domain lines (e.g. Platforms and Commands for example), and may look something like this:

APIProject/
├── Platforms/
│ ├── PlatformsController.cs
│ ├── Platform.cs
│ ├── PlatformCreateDto.cs
│ ├── PlatformReadDto.cs
│ ├── IPlatformRepository.cs
│ ├── PgSqlPlatformRepository.cs
│ └── PlatformValidator.cs
├── Commands/
│ ├── CommandsController.cs
│ ├── Command.cs
│ ├── CommandCreateDto.cs
│ └── ...
└── Registrations/
└── ...

I've decided against this approach for the reasons already stated above, but also because we have a small domain, and as such I think it would make the project less readable.

When implementing your own projects, where the domain is more complex and you understand the technical concepts involved, this approach can be a good one.

Conclusion

That's it for this chapter. Both a lot of information, while at the same time, not deep-diving at all! Bear in mind this chapter is just a high-level overview of what we're about to embark on, so I've tried to keep it as light as possible, while at the same time providing enough grounding on what we'll be building.

Do not worry if at this point you don't understand all the concepts involved, we'll dive into them in much more detail as we progress with the practical build.

Let's start to get our hands dirty, and set up our development environment in the next section!



Footnotes

  1. We'll refer to these as CRUD operations for the rest of the book.

  2. REST Vs GraphQL 2025