There is a concept in Domain-Drive Design (DDD) called the anti-corruption layer which, according to Microsoft explanation of an anti-corruption layer “Implements a façade or adapter layer between different subsystems that don’t share the same semantics”. DDD uses anti-corruption layers between subsystems (known in DDD as a bounded contexts), say inventory, orders, fulfilment/delivery in an e-commerce system.
But I think the anti-corruption layer concept can also help us inside even small applications. In fact, I expect many of you are already the anti-corruption layer concept in your application, but you call them “adapters”, or “interfaces”, or “ViewModels”.
In this article I’m going to explore the application of the anti-corruption layer not to big subsystems, but to individual NET assemblies in even a small web application, e.g. between the database, the business logic, and the front-end. I hope to show how to implement anti-corruption layers and why they will make your applications easier to write and maintain.
This article was inspired by a problem I encountered in the anti-corruption feature in my EfCore.GenericBizRunner library. I had configured the DTOs (data transfer objects) with the AutoMapper library (which I use for class-to-class mapping) in such a way that some AutoMapper config properties were turning up in my output classes/DTOs. That can cause a bit of a problem when using DTOs in Web APIs, so I fixed it in version 3 (out now).
Other articles about business logic and GenericBizRunner:
- Architecture of Business Layer working with Entity Framework (Core and v6)
- A library to run your business logic when using Entity Framework Core.
- How to write good, testable ASP.NET Core Web API code quickly.
- Wrapping your business logic with anti-corruption layers – NET Core (this article)
The examples I am using are based around ASP.NET Core web applications and Entity Framework Core (EF Core) for database accesses.
TL;DR; – summary
- DDD defines an anti-corruption layer is an adapter pattern that isolates one part of a system, known in DDD as a bounded context, from another bounded context. Its job is to ensure that the semantics in one bounded concept do not “corrupt” the other bounded concept’s semantics.
- I think that the anti-corruption layer concept can also be used between assemblies even in small application. Some examples of using an anti-corruption layer in a web application are:
- Business logic: Your business logic shouldn’t have to concern itself about how the database or the front-end works.
- Front-end: Human user’s need the data that suits them, not how the database was built.
- Web API: Your Web API must provide a service that fits the external needs.
- Anti-corruption layers can help with security too, by only passing the exact data that the other area needs.
Business logic – the classic area for anti-corruption layers
In the book Domain-Driven Design, Eric Evan that “Domain Model” (what I call “business logic”) is “the heart of the Software” (see Eric Evans book, page 4). Eric goes on to say “When the domain is complex, this is a difficult task, calling for the concentrated effort of talented ad skilled people”.
I wrote an article called “Architecture of Business Layer working with Entity Framework” where I go through a design for handling business logic. That article is worth a read, but in this article I focus on the anti-corruption layers as applied to my business logic.
From Eric Evan’s book, and my own experience, I want to have no distractions when I’m writing my business logic. That translates into the following “don’t wants”:
- I don’t want to worry about how the database works.
- I don’t want to worry about how my business logic communicates to a user.
- I don’t want to know I am using an external service – I just want methods to call.
How I achieve this isolation differs for each part: in this case I use a repository pattern for the database side and my GenericBizRunner library to talk to the front-end. I will describe these two anti-corruption layer implementations later, but first here is a diagram give you an overview of my approach.
I’m now going to describe each of these two anti-corruption layers, i.e.
- The database to business logic anti-corruption layer.
- The business logic to front-end anti-corruption layer.
1. Implementing the database to business logic anti-corruption layer
Eric Evans talks in his book about separating the business logic from the database and EF Core’s navigational properties makes this possible. The navigational properties allow the business logic to work with normal (POCO) classes that have properties that references other classes. That means the business logic doesn’t have to know about primary keys, foreign keys, etc.
Let’s look at a specific piece of business logic, placing an order for some books, to show this in action. The user selects a series of books (front-end lineItems) and the business logic creates an Order containing database LineItems. To do this my business logic, called PalceOrder, has to take in the front-end lineitems and create a new Order class and adds a series of database LineItem classes to it.
That’s fine, but it does need to access the database for two things:
- The front-end lineItems contain the primary key of each book, but the database LineItem class needs the Book entity class to create it. This needs a database lookup.
- The PlaceOrder code creates a new Order class, but somehow that class needs to be added to the database.
My solution is to use a repository pattern to provide the business logic with methods to handle these two database requirements. Here is the code, which the business logic accesses via constructor injection via the IPlaceOrderDbAccess interface.
public class PlaceOrderDbAccess : IPlaceOrderDbAccess { private readonly EfCoreContext _context; public PlaceOrderDbAccess(EfCoreContext context) { _context = context; } public LineItem BuildLineItem(int bookId, byte numBooks) { return new LineItem(bookId, _context.Find<Book>(bookId), numBooks); } public void Add(Order newOrder) { _context.Orders.Add(newOrder); } }
This repository is small because it only implements database access methods for just the PlaceOrder business logic. A typical repository has tens or hundreds of methods to be used all over the application, but I don’t find that that approach very useful. I have applied the single responsibility principle to the repository pattern and come up with a per-business logic repository, which I call a “mini-repository”.
The benefit of the mini-repository pattern is that the code is much simpler to write and maintain. It also means later changes in architecture, such as splitting a monolith architecture up into microservices or serverless, is easier to do as the business logic and the associated mini-repository can move together.
NOTE: I haven’t talked about saving the data to the database – in EF Core that means calling the method called SaveChanges. I could have included that in my mini-repository (most likely in the Add method), but my GenericBizRunner library handles calling the SaveChanges method. That allows the library to add validation of the entities to be saved, which I think is important for business logic.
2. Implementing the business logic to front-end anti-corruption layer
The business logic and the front-end have a common goal: to place an order for books. But both parts have their own concepts – the business logic is about applying a business rule while the front-end wants to provide an easy-to-use user interface. So, in fact the anti-corruption cuts both ways – stopping concepts/data that are useful in one assembly from leaking into the other assembly.
Here is a diagram that shows the various NET assemblies and what happens in each stage of the process of running some business logic that accesses the database. It’s quite detailed, but it conveys in one picture all the main things that are going on. I include a commentary on what my GenericBizRunner library is doing – you could of course write your own code to handle these jobs.
NOTE: The Service Layer is an assembly that I use for adapting data from lower layers to the front-end. I got this concept from Dino Esposito (who got it from Martin Fowler). You can get more information in the section on the Service Layer in my article “Six ways to build better Entity Framework (Core and EF6) applications”.
2a. front-end to business logic anti-corruption layer
The front-end might need all sorts of data to show a page to the user, but the business logic only wants the list of books with their unique IDs (primary key in this case). In ASP.NET the HTML page often sends back extra data in case there is an error and the needs to be redisplay. But the business logic doesn’t need that data, and in some case that data is in NET Type that the business logic doesn’t even know about, i.e. a SelectList type. How do we handle that?
The way I handle that is to use AutoMapper in my library to copy the front-end ViewModel/DTO into the input class that my business logic needs. Here is an example from another piece of business logic to change the delivery date of an order. The diagram below displays the front-end focused DTO and the business focused input DTO.
You can see that the front-end needs extra information to show the user what order they are changing (left-hand side of diagram) while on the right-hand side the business logic only needs the first three properties. I (or to be more correct my GenericBizRunner library) uses AutoMapper to copy the data across before the business logic is called.
I think this is a very good example of an anti-corruption process going on. The front-end has extra properties to display a meaningful page to the user, but the business logic doesn’t even know about these front-end only properties. Also, the anti-corruption layer means that the business layer doesn’t have to know about or support front-end types such as SelectList etc.
My GenericBizRunner has a few features to help in a case like this. It provides the front-end DTO with a method called SetupSecondaryData, which can be run to fill in extra data needed for the display to the user – in this case filling the extra properties to show or re-show the display. Here is the code in the ASP.NET Core MVC controller to display and ChangeDelivery page and called the business logic process the user’s request.
public IActionResult ChangeDelivery(int id, [FromServices]IActionService<IChangeDeliverAction> service) { //This runs SetupSecondaryData to fill in dropdownlist etc. var dto = service.GetDto<WebChangeDeliveryDto>(x => { x.OrderId = id; x.UserId = GetUserId(HttpContext); }); return View(dto); } [HttpPost] [ValidateAntiForgeryToken] public IActionResult ChangeDelivery(WebChangeDeliveryDto dto, [FromServices]IActionService<IChangeDeliverAction> service) { if (!ModelState.IsValid) { //Rerun SetupSecondaryData if need to redisplay service.ResetDto(dto); return View(dto); } service.RunBizAction(dto); if (!service.Status.HasErrors) { return RedirectToAction("ConfirmOrder", "Orders", new { dto.OrderId, message = service.Status.Message }); } //Otherwise copy errors in and redisplay the page service.Status.CopyErrorsToModelState(ModelState, dto); service.ResetDto(dto); return View(dto); //redisplay the page, with the errors }
You can see on lines 2 and 16 that I inject an instance of the GenericBizRunner, with the specific business logic, referred to via the interface IChangeDeliverAction, as a parameter to the action. This is more efficient than using the normal constructor DI injection as we only inject the exact service we need for each action method.
2b. business logic to front-end anti-corruption layer
Sometimes its also useful to not send everything that the business logic sends back. In the PlaceOrder example the business logic works with classes and (in theory) shouldn’t need to think what the front-end needs. Therefore, the PlaceOrder business logic will return the Order class.
The problem is that Order class might have all sorts of data that is private, like information on the payment, but the front-end only wants to have the primary key of the Order so that it can display an “order success” page. The solution is the same as the input: only copy over the properties that the front-end needs – in this case only copy the OrderId from the Order class that the business logic returns, but only after SaveChanges has been called and the primary key is available.
To make this work I need two things:
- An OrderIdDto class with just one property in it called OrderId, which should hold the primary key of the created Order. This is the only bit of data that my front-end needs.
- Once SaveChanges has been called I copy over the OrderId property in the Order into the OrderIdDto class’s OrderId property.
Here is the controller action that calls PlaceOrder. Note that the user’s basket of items is held in a cookie, so you will see some code to get and clear the checkout cookie.
[HttpPost] [ValidateAntiForgeryToken] public IActionResult PlaceOrder(PlaceOrderInDto dto, [FromServices]IActionService<IPlaceOrderAction> service) { if (!ModelState.IsValid) { //model errors so return to checkout page, showing the basket return View("Index", FormCheckoutDtoFromCookie(HttpContext)); } //This runs the PlaceOrder business logic var orderDto = service.RunBizAction<OrderIdDto>(dto); if (!service.Status.HasErrors) { //If successful I need to clear the checkout cookie ClearCheckoutCookie(HttpContext); return RedirectToAction("ConfirmOrder", "Orders", new { orderDto.OrderId, Message = "Your order is confirmed" }); } //There were errors so redisplay the basket from the cookie var checkoutDto = FormCheckoutDtoFromCookie(HttpContext); //This copies the errors to the ModelState service.Status.CopyErrorsToModelState(ModelState, checkoutDto); return View("Index", checkoutDto); }
You can see the GenericBizRunner’s call the PlaceOrder’s method on line 13, via the service injected as a parameter. In that call I tell it that the return type should be OrderIdDto, which I have created as a DTO type that GenericBizRunner knows needs to be mapped from the Order class. This means only primary key of the order ends up in the front-end and all the other properties in the Order class is discarded. The front-end can then pass on this primary key to the ConfirmOrder page which knows how to display a successful order.
GenericBizRunner and Web APIs
As I said at the start I found a problem that placed unwanted properties in the DTOs. To me it matters a lot that I get the data returned from the business logic in the exact format needed by the Web API. This really mattered on one of my client’s project which used Swagger and NSwagStudio to create Angular 6 code to match the Web API. NSwagStudio was amazingly useful, as it automates the creation of the Angular code to interface to the Web APIs, BUT you must get exactly the return type to make it work property.
Now, is this an anti-corruption feature, or just the normal adaption that we are already used to? I think adaption of data is what is mainly going on, but thinking “what anti-corruption issues are there?” is useful. Here are my thoughts:
- Anti-corruption says we need to only pass the data that is required by the Web API definition.
- Anti-corruption makes us thing about the data we do pass: is it in the correct form, are the names of the objects right for the Web API, etc.
A look at GenericBizRunner in a controller
With the help of my companion EfCore.GenericServices.AspNetCore library using GenericBizRunner in Web API is pretty simple. Here is a simple example taken from the EfCore.GenericServices.AspNetCore’s ExampleWebAPI. It uses some business logic to create a TodoItem.
[ProducesResponseType(typeof (TodoItem), 201)] [HttpPost] public ActionResult<TodoItem> Post(CreateTodoDto item, [FromServices]IActionService<ICreateTodoBizLogic> service) { var result = service.RunBizAction<TodoItem>(item); return service.Status.Response(this, "GetSingleTodo", new { id = result?.Id }, result); }
Let’s look at each line of this code
- Line 1: I am using Swagger, and I want to tell Swagger a) the type of result it provides, and b) the status code on success.
- Line 3: It pass in a CreateTodoDto, which contains the properties that the business logic needs to create the TodoItem.
- Line 4: This is where I inject the BizRunner instance, linked via the interface to the business logic to run.
- Line 6: I run the business logic, which returns the created TodoItem instance.
- Lines 7 and 8: The Response method is from the EfCore.GenericServices.AspNetCore library. This form returns a “CreatedAsRoute” result, which fits the definition of the HTTP 201 create response.
In this case I have returned the created class, but I have found in real applications you really don’t want to do that. It turns out that EF Core’ entity classes very often have navigational properties. These are properties that link to other entity classes in the database, e.g. a Book has a collection of Reviews.
The problem is that many SPA’s (single page applications, like Angular and React.js) only want the non-navigational data. In these cases, we need to use DTOs that remove those navigational properties and/or applying some form of transformation to the data when sending database classes to the Web API. Thankfully the GenericBizRunner can do that.
Conclusion
Well, I hope I have made you think about the anti-corruption concept and whether it would improve your business logic. You are most likely doing something similar with adapter patterns and ViewModels/DTO, but you might not call it an anti-corruption layer. That’s fine, but thinking “anti-corruption” can help you implement a good pattern for your business logic.
The down-side of the approach I have described is it does create more code. For small bit of business logic then it might feel like overkill, but when the business logic gets more complicated then I think some separation of concerns pattern is essential. The pattern I have described is one way, but there are plenty of other approaches that achieve the same result.
I created the EfCore.GenericBizRunner library to reduce the amount of extra code I must write, especially in the front-end where it makes it very easy to call. Also, having a standard pattern for my business logic helps me write my business code quickly. I do have to write the mini-repository as well, but that database access code had to go somewhere and my mini-repository per business logic makes it easy to change or performance tune. So far this approach and the GenericBizRunner library has served me well.
Happy coding.
Thank you for your library GenericBizRunner, I have downloaded it to study for my project. It’s great library, I was very impressed with it. But I have a concern about ServiceLayer. It’s seem you have a convention that each service class in ServiceLayer contain only one method. Could you please explain benefit of these convention ? that mean if I combine many action to one class such as OrderService, BookService… What is the disadvantage ?
You say…
I think you are referring to the way that the classes in the ServiceLayer only handle one thing – I hope that is what you are asking. My answer is I apply the Single Responsibly principal to almost all I do. That means that I split off every service into its own class (well, I did have add/remove promotion in the same class). I do this for the following reasons:
– It makes the code cleaner because the code is about one thing and there are no distractions. I had a bad experience with using a Repository Pattern (see this article https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/ ) that has lots of different methods/services in it and I learnt that Single Responsibility principal really work for me.
– It makes testing and refactoring much easier – the service does one thing.
– My EfCore.GenericBizRunner also followes the Single Responsibility principal, i.e. you only call one method in the business logic code.
The down side is you write more code, but its all boilerplate stuff, like defining a new class and method. I am happy with that tradeoff, but it does mean large applciations have lots of small classes in them.
PS. I put in ‘hand-built’ Services in the GenericBizRunner example ServiceLayer because it came from my EfCoreInAction repo, which goes with my book (chapter 4 covers busines logic). But after I had finished GenericBizRunner I wrote the EfCore.GenericServices library that handles all the CRUD actions. The GenericServices means I (mostly) only need a DTO to build any CRUD services.
@Jon P Smith: Thanks for your enthusiastic answer. I downloaded GenericServices, all your libraries are great !