Eliminating Mapping and Redundant Validation in Domain-Centric APIs with JsonConverters and ModelBinding

Albert Starreveld
6 min readJul 12, 2023

Numerous applications are designed adhering to the principles of Clean Architecture and Domain-Driven Design (DDD). They have a rich domain models that protect the business invariants.

In most cases, this manifests in an application that has a Core library that contains Aggregates , Entities , and Value-types . Following the clean architecture principles, it will contain UseCases too. They are the implementation of the business-processes the application automates.

The general line of thought here, is that UseCasesdoes not contain business logic that is irrelevant to the process.

For instance, when a customer places an order in an online store, the UseCase responsible for this process should not be burdened with validating the correctness of the received EAN (European Article Number) parameter. Instead, the UseCase should expect an EAN parameter that already encapsulates the applicable business rules.

Implementing UseCases and ValueTypes

To provide a more concrete example, let’s examine the following code snippet:

public class PlaceOrderUseCase {

// constructor and fields left out for brevety

public async Task PlaceOrder(EAN productId, int quantity)
{
var stock = await _stockRepository.Get(productId);
if (!stock.isInStock(quantity)) {
throw new OutOfStockException();
}

var shipment = Shipment.Create(productId, quantity);
await _shipmentRepository.Create(shipment);

await stock.ReduceStock(quantity);
}
}

This class implements a business process. It ensures the product is in stock first, then it creates a shipment.

Note that the PlaceOrder method expects a ProductId parameter of type EAN rather than a string or int. EAN is not a built-in data type in C#. Therefore, you would need to create the EAN type yourself. Here's an example of how you can define the EAN type:


public struct EAN {
private readonly string _value;

public static EAN Empty => new EAN();

public static bool TryParse(string ean, out EAN result)
{
var isEan = Regex.IsMatch("^[0-9]{13}$", ean);

result = isEan ? new EAN(ean) : new EAN();

return isEan;
}

public EAN()
{
_value = "0000000000000";
}…

--

--

Albert Starreveld

Passionate about cloud native software development. Only by sharing knowledge and code we can take software development to the next level!