Last Updated: 27 September 2022
Here's our brief guide to our recommended approach to creating a new microservice solution using ASP.Net Core API.
As developers we are always aiming to separate our concerns in code. This makes it easier to add and replace functionality later on, while keeping the surface area of change - and therefore risk - as small as possible. It also makes complex code much easier to unit test, and allows us to make stronger test assertions.
It's usually a good idea to make your business logic deal purely in terms of business objects. While this sounds like a simple, or even obvious statement, it's something we see dev teams getting mixed up with quite often. When creating a new customer account for example, it's good to return something like a CreateCustomerResult object, or maybe the Customer object itself. You should avoid returning some object or property that ties your business logic to, say, a web service technology (your 'integration technology'). This makes it easy to re-use your business logic, or in this case remove or replace the API concerns altogether. An example code smell could be when your business logic returns HTTP status codes.
This brings us onto the concept of Data Transfer Objects (DTO). When creating APIs, you will often have a request and a response. These are your DTOs and their responsibility is purely to represent the data sent or received over the wire. It is often the case that these objects have the same 'shape' (i.e. properties/types) as a domain object. As developers, we are always taught to re-use where possible, and DRY (don't repeat yourself). However, in this case the two objects may look the same but they have quite different responsibilites, so it is usually preferable not to re-use. Smashing the two responsibilities together is what we coined the 'Domain-tee-oh' anti-pattern.
One issue with messy Frankenobjects is that a change to your internal business object could break your API contract, which is undesirable. For similar reasons, we recommend you also avoid the temptation to use inheritance here. Clearly, there's an overhead in having a bunch of similar objects, but the benefits will nearly always outweigh the downsides for anything but the simplest of scenarios. If you want to save having to write code to manually map between DTOs and domain objects, we like to use AutoMapper, which handles most cases and is extensible enough to cope with anything unusual.
ASP.Net Core supports request validation to ensure the client is sending values that are sensible and within acceptable bounds. It can also be used to make sure specially-crafted values are not being passed to somehow compromise your internal systems. Out of the box, you get several validation schemes that are applied to the request object by decorating the properties you want to be validated. If any validation fails, your API calls will automatically return the correct HTTP status code (400). It is quite easy to create custom validation attributes, too. A good approach is to package your organization's custom validation methods into one or more Nuget packages, so they can easily be re-used.
You can download our diagram as a PDF or JPG. Feel free you print it out, pin it up around your office, or just use it as a basis for discussion. Permission to use for any purpose, including for training courses or other commercial uses is granted providing it is not altered in any way, including our logo, website address and copyright notice.
If your organization requires assistance with ASP.Net Core API architecture and solution design advice, please contact us. We can provide on-site or remote training, including hands-on coding workshops for your dev teams. We can also advise on the best way to set up an internal Nuget feed to promote the re-use of your custom request validators.