Promoting SoC Through Application Layering

Creating a layered design in an application is a fundamental element of modern software architecture. The goal is to promote the Separation of Concerns (SoC) design principle. Separation of Concerns The ideas behind SoC date back to Dijkstra's 1974 paper "On the role of scientific thought" . In computer science, separation of concerns (sometimes abbreviated as SoC) is a design principle for separating a computer program into distinct sections. Each section addresses a separate concern, a set of information that affects the code of a computer program. A concern can be as general as "the details of the hardware for an application", or as specific as "the name of which class to instantiate". A program that embodies SoC well is called a modular program. Modularity, and hence separation of concerns, is achieved by encapsulating information inside a section of code that has a well-defined interface. - Wikipedia SoC is a broad design principal th...

Using Automapper to Promote Loose Coupling

There are numerous application design architectures (Clean, Onion, Hexagonal, etc). All of these architectural patterns help to increase the quality, maintainability, and success of software projects. One important aspect of all architectures is loose coupling between applications (or even layers within a single distributed application).

For example, let's take an ASP.NET UI calling multiple WebAPIs. For this scenario, the WebAPIs are gateways to different data repositories and may contain logic and validation of their own. Let's say they are black boxes, managed by different organizations. The UI that we've created should not be tightly coupled to these WebAPIs.

Automapper is a tool that makes decoupling certain application dependencies fairly straightforward. For the purpose of demonstration, we're going to be using a single Visual Studio Solution with a UI and two WebAPIs. We'll assume the WebAPIs are black boxes, even though they are not in this sample. We're going to call the WebAPIs by configuring them as OpenAPI Service References in our Data Access layer.

Sample Application Diagram

Let's examine the Visual Studio solution. There are five projects, two are the "blackbox WebAPIs", GadgetWebAPI and WidgetWebAPI. The other three projects represent our sample application, AutomapperDemo. There is a UI project, a project that contains the UI models, and a Data Access project.

Visual Studio Projects Screenshot

The first step is to configure Visual Studio to start the UI and both WebAPIs in IIS Express when debugging the solution. To do this, right click the solution, click "Configure Startup Projects..." and select both the UI and the two WebAPIs to start up as shown below.

Visual Studio Multiple Startup Projects Screenshot

For details on calling WebAPIs that support OpenAPI, please refer to the earlier blog post Using OpenAPI Service References in .NET 8.

After the Automapper.DataAccess project is configured to call the blackbox WebAPIs, we can implement Automapper to isolate the majority of our application from the WebAPIs. We'll do this by configuring Automapper to convert the data received from the WebAPIs to Models in the AutomapperDemo application. First, we must add an Automapper reference to the AutomapperDemo.DataAccess project using the NuGet Package Manager.

NuGet Package Manager in Visual Studio Screenshot

Next, we'll configure Automapper; in this scenario, we'll use simple Mapping Profile classes.

To illustrate one feature of Automapper, we'll intentionally create some differences between the WebAPI class definitions and the Models. Even if the blackbox WebAPI changes (which it should not!), our application is more insulated from that change.

WebAPI Class vs Model Screenshot

The code below will "map" all of the matching properties automatically, and we can create mapping rules for the property names that do not match. Automapper is very flexible, so refer to the documentation for more complex scenarios.

public class WidgetAPIMappingProfile : Profile
{
	public WidgetAPIMappingProfile()
	{
		CreateMap<WidgetWebAPI.Widget, Widget>()
			.ForMember(d => d.Cost, opt => opt.MapFrom(s => s.Price));
	}
}

In the WebAPI Invoker classes, we convert the WebAPI objects to AutomapperDemo Model objects as below:

public class WidgetAPIInvoker : IWidgetAPIInvoker
{
	private readonly HttpClient _httpClient;
	private readonly IMapper _mapper;

	public WidgetAPIInvoker(HttpClient httpClient)
	{
		_httpClient = httpClient;

		// set the Automapper configuration profile that should be used for this WebAPI Invoker class
		var config = new MapperConfiguration(cfg => cfg.AddProfile<WidgetAPIMappingProfile>());
		_mapper = new Mapper(config);
	}

	public async Task<List<Widget>> GetWidgets()
	{
		// create a WebAPI client
		var widgetAPI = new WidgetWebAPI.WidgetWebAPI("https://localhost:44317", _httpClient);

		// fetch the data
		var widgets = await widgetAPI.GetWidgetsAsync();

		// use Automapper to convert the list of WidgetWebAPI.Widget objects to a list of AutomapperDataAccess.Models.Widget objects
		return _mapper.Map<List<Widget>>(widgets);
	}
}

Running the solution should result in the following:

Application Running Screenshot

This example was created using Visual Studio 2022 running an ASP.NET Core Web App project and ASP.NET Web API projects using IIS Express.

Source code: https://github.com/jharrell-bits/AutomapperDemo