BlackSlope: A Deeper Look at the Components of our DotNet Reference Architecture

Exploring the structure and built-in options of Slalom Build’s DotNet Core reference architecture

Parham Motameni
Slalom Build
Published in
7 min readMar 26, 2020

--

Reference architectures can be useful for delivering reliable code consistently while also preventing the need to “re-invent the wheel” on future projects. At Slalom Build, we often develop reference architectures based on our experience in delivering successful solutions for our clients. One of these is BlackSlope, an API reference architecture for ASP.NET Core applications. It provides a great solution for designing highly scalable API layers, while also fulfilling requirements for modularity and extensibility.

Our open-sourced version of BlackSlope was first introduced in a post by Stephen Weinrich earlier this year. Now let’s deep dive into the architecture that BlackSlope proposes, and dive into its components and features in more detail. (Editor’s Note: A follow-up post in this series now also provides instructions for spinning up a BlackSlope-based API application in a docker container in order to see it in action.)

All source code is available on GitHub, and at the time of this writing, BlackSlope targets ASP.NET Core 3.1.

Multi-tier Architecture

BlackSlope provides a baseline reference architecture for implementing an API application. Each layer of an API application is equipped with a set of components to implement the required functionality for that layer. Even though each component plays a specific role in a particular layer, they can still be utilized within multiple layers of the application.

The diagram below depicts BlackSlope’s architecture and some of the components it’s comprised of, at a very high level.

BlackSlope Components

The core of the BlackSlope architecture is made of three layers: Operations, Services, and Repositories. Let’s look at each.

Operations

This layer contains the controllers and Request/View Models. The validation process for requests should happen at this layer.

Controllers define endpoints for the API being exposed by the app. The requests are first validated then, if needed, a domain object is created and passed into the service layer to perform further operations. Note that BlackSlope’s validator uses Fluent Validation underneath, but like all of the components, you can plug in your preferred implementation.

After validation and processing in the service layer, the result received from the service layer is converted into Response/View Model to be returned to the requester.

Services

This layer handles the business and domain logic. It contains Domain Models and knows about the available repository layer components and DTOs (data transfer objects) used by the repository. When a service needs to access data, it uses a repository — the service is responsible for first mapping Domain Model objects to DTOs before passing them down to the repository.

Usually, the result from a repository call is the data which needs to be returned back to the operation layer. In those cases, the service is also responsible for converting the DTOs back to Domain Models prior to returning the results up to the operation layer.

Repositories

Repositories are generally used for data access, and can be grouped into two major categories. One type provides access to data storage, and the other type manages calls to external APIs and services which are not part of the current application. DTOs are also defined at this layer, and logic in this layer knows how to use data providers to communicate with data storage.

Components

BlackSlope utilizes both internal code and some third-party packages to handle specific responsibilities. Each of these packages makes up a Component and can define any necessary settings specific to its responsibilities in the appsettings.json file.

The following are descriptions of various components contained within BlackSlope. Implementations of these components are based on convenient, well-tested third-party packages, but implementations can be swapped out for your own preferred code as well.

Fluent Validation

Validation of requests happens in the Operations layer. To do this, BlackSlope implements Fluent Validation.

Swagger

For API documentation, BlackSlope utilizes Swagger. Keep in mind that any necessary settings for Swagger can be set in the appsettings.json file.

AutoMapper

The three different layers within BlackSlope use different data models for different purposes: Operations primarily uses View Models (formatted for endpoint consumption), Services use Domain Models (internal application representation), and Repositories use DTOs (storage representation).

Translating data between the three different model types for each layer is necessary: In the Operations layer, View Models need to be transformed into Domain Models for the Services to use. Similarly in the Services layer, Domain Models need to be transformed into DTOs for the Repositories to use. To simplify this process, BlackSlope utilizes AutoMapper, which greatly simplifies the process of mapping one type of object to another type.

Serilog

BlackSlope uses Serilog to perform all of its logging. Serilog has the ability to store log data in various ways— It uses “Sinks” to write logs out to different storage types. It provides sinks for many major storage types out-of-the-box, but it is also possible to create a custom sink if there isn’t one available for a specific storage type that you need. Again keep in mind that any specific settings can be put in the appsettings.json file in the Serilog section.

Application Insights

For performance management, logging, and monitoring, BlackSlope utilizes Application Insights. Serilog also has Sink for Application Insights. We will explain how to configure and use this sink in part two of this article series.

Versioning

Versioning is a simple service that allows you to get the version of the running API application. The version is exposed through a GET request to the /api/version endpoint.

Database (EF Core)

For Database queries, BlackSlope uses Entity Framework (EF) Core. EF Core has providers to support many database engines. To maintain proper segregation between layers, EF Core entities and queries should only be used within the Repository layer.

Middleware Components

Authentication Middleware

ASP.NET Core provides some tools and features to manage the security aspects of an application. One of the features is the Authentication middleware added as part of Core 2.0 release.

Besides adding the Authentication middleware, a service which implements the desired authentication mechanism based on an identity service should be registered. To demonstrate this in BlackSlope, Azure AD is being used as an identity service. The implementation will be described in part two of this article series.

CorrelationId Middleware

In distributed system architecture, requests are usually processed asynchronously. For example, a system or a service sends a request to another one, and the second system might put a request into a message broker queue which would be picked by a service to be processed, and there might be multiple other steps involved in processing that request.

We need a mechanism to be able to keep track of the transactions between all these asynchronous steps. This can be useful for logging, debugging, and other purposes. One approach to achieve this goal is using a CorrelationId that is added to requests so that each can be uniquely tracked throughout all the downstream systems.

Even in a simpler application architecture with fewer layers, requests are still usually processed asynchronously. Consider a very simple scenario when there is just a frontend web application using a backend API application to perform data-related actions; Even in this simple case, the CorrelationId can be a very useful tool for tracking unique requests in the system.

Exception Handling Middleware

Handling exceptions is an inevitable part of application development and maintenance. Exceptions can happen at any layer and at any time of execution. The challenge is implementing a way to centralize the exception handling process instead of implementing exception handlers all over the application code base. The approach that BlackSlope proposes to overcome this challenge is an exception handling middleware which can be added to the request processing pipeline.

Configuration Binding

BlackSlope uses a configuration binding mechanism to bind strongly typed objects to configuration values. In the Config directory, there can be found Classes that correspond to the sections in the appsettings.json file. For example, SwaggerConfig.cs has properties for Version, ApplicationName, and XmlFile, all of which correspond to a key-value pair in the Swagger section of the appsettings.json file.

Folder Structure and File Name

Besides being a reference architecture, BlackSlope provides some suggestions for structuring the folders, projects, and solution. In addition to the proposed structures, there are some suggestions regarding naming files as well.

Folder Structure

The below diagram depicts the suggested folder structure for the projects that make up a BlackSlope-based solution:

Suggested Folder Structure for BlackSlope

Future Improvements

Slalom Build plans to go forward with maintenance and improvements to BlackSlope with the help of the open-source community. As of this writing, there’s already some improvement work queued in the BlackSlope backlog:

If you decide to base a project on BlackSlope, please share your experience! We’d love to hear your ideas to improve it, and welcome PRs if you would like to contribute.

Remember to also check out the second article in this series, which gets even more hands-on and explains practical configuration of BlackSlope. Also, we will see BlackSlope being deployed in a Linux container, and interact with a database which lives in a separate container.

We hope you find this useful on your next project. Get started with BlackSlope on GitHub!

--

--