eShopOnWeb architecture article series (16 parts)

For learning and discussion purposes I’ve done up a series of short articles looking at the architecture of eShopOnWeb which is Microsofts sample ASP .NET Core reference application…

Even though I’d do a few things differently, eShopOnWeb is awesome for learning purposes. If I discuss alternative approaches to what it does, this is just my opinion. I’m not saying eShopOnWeb is ‘wrong’… context is everything.

Let me know what you think 😊

eShopOnWeb Architecture (1/16) – uses marker interfaces to communicate intent and enforce design constraints

eShopOnWeb Architecture (2/16) – uses Value Objects to model immutable domain concepts

eShopOnWeb Architecture (3/16) – uses custom exceptions to more explicitly express what has gone wrong

eShopOnWeb Architecture (4/16) – uses the MediatR library to keep controllers thin

eShopOnWeb Architecture (5/16) – makes regular use of Guard Clauses

eShopOnWeb Architecture (6/16) – uses private setters and non default constructors to support encapsulation which helps keep the model valid

eShopOnWeb Architecture (7/16) – encapsulates navigation collections so consumers can’t edit them directly

eShopOnWeb Architecture (8/16) – uses in memory caching to avoid sending unnecessary queries to the DB

eShopOnWeb Architecture (9/16) – uses the Aggregate root pattern to ensure child objects are not manipulated out of context

eShopOnWeb Architecture (10/16) – has unit tests which test very low level implementation details

eShopOnWeb Architecture (11/16) – uses AutoMapper

eShopOnWeb Architecture (12/16) – uses the repository and specification pattern

eShopOnWeb Architecture (13/16) – has some single implementation interfaces

eShopOnWeb Architecture (14/16) – uses parameterized tests

eShopOnWeb Architecture (15/16) – has a really nice example of the Interface Segregation Principle

eShopOnWeb Architecture (16/16) – uses clean architecture

eShopOnWeb Architecture (16/16) – uses clean architecture

On a high level eShopOnWeb uses clean architecture.

The main idea of clean architecture is that the solution is spilt into different projects (or layers) and that the domain project is at the centre with all other projects pointing into it. Other projects typically include an Infrastructure and UI project. Importantly these point into the domain project via interfaces not concrete dependencies.

Concrete dependencies on things like Entity Framework and Redis are defined in the Infrastructure project. Dependency injection is a fundamental concept in clean architecture and is used to inject these dependencies at runtime.

The diagram below (taken from the accompanying eBook for eShopOnWeb) shows a typical clean architecture setup…

The main proposed benefits of clean architecture is that because its heavily abstracted its easy to swap out implementations and also easy to test. I’ve definitely seen these benefits when I’ve used it BUT unfortunately these benefits come with huge cost in terms of extra complexity of the overall solution.

One thing in particular with clean architecture is that it’s so abstracted there’s extra cognitive work required to actually know where in the application something is actually done and how things ‘fit together’.

As clean architecture separates code by technical not functional concern it can often have really low functional cohesion and so code for any one use case is scattered all over the place. For example below shows the files (note.. some of these are needed by alternative architectures too) involved in the Get My Orders use case in eShopOnWeb. To get an end to end understanding of how this use case actually works I have to navigate through a lot of files and projects.

Click on the image for a larger view in a new window…

I find this kind of solution architecture very unsatisfying to work with as a developer and definitely notice myself using ‘Go to Implementation’ a lot when reviewing systems which use it.

Of course having so much separation of concerns also means debugging can be harder, onboarding can be harder, PRs can be larger as often a lot of files to change, tracking bugs back to check-ins can be harder, documenting the system can be harder and yes actually adding or changing features can IMHO also be much harder.

Vertical slice architecture as an alternative to clean architecture?

IMHO vertical slice architecture is a much more pragmatic and developer friendly macro-architecture.

With VSA we aim to limit abstractions and have high feature cohesion by placing concrete code which corresponds to any one feature or use case together. For example… in the below still from Derek Comartins Restructuring to a Vertical Slice Architecture video on YouTube he has restructured the eShopOnWeb code for the Get My Orders use case shown above so that it is all together…

And the code for Derek’s refactor is available on the accompanying blog post on his site.

It might not be one for the ‘purists’ among us but IMHO having all (or most) of the code for a particular use case together (doesn’t necessarily have to be in the same file) is much easier to work with than having to constantly navigate around layers of abstractions and multiple projects as is often the case with clean architecture.

Of course the above is just my opinion and using clean architecture is definitely not ‘wrong’… it’s extremely popular online and because context is everything some projects will absolutely benefit from the rigid structure it provides. In particular many architects feel it helps less senior developers into the ‘pit of success‘ which basically means its an architecture which makes it harder for them to do the wrong things due to how structured and prescriptive it is.

What do you like to use in your apps? Clean architecture, vertical slice architecture or something else?

eShopOnWeb Architecture (15/16) – has a really nice example of the Interface Segregation Principle

We can see a nice example of the interface segregation principle in eShopOnWeb.

For its repository interfaces eShopOnWeb have two interfaces… IRepository and IReadRepository. This means that if a repository for any particular aggregate root only needs to query a DB it can just implement IReadRepository without having to worry about all the update related methods.

Click the image below for a larger view in a new tab…

eShopOnWeb Architecture (14/16) – uses parameterized tests

I love parameterized tests.

Instead of adding many different test methods with different inputs set in the method body we have one method and pass in the inputs and expected output(s) for each test we want to run as params to the method (one per line). This means less test methods (so test logic easier to change) and that we can quickly see all inputs and expected outputs for a particular method together. In addition to this our results from each row of params will be grouped together.

Below is one example from eShopOnWeb which uses xUnit, but most test frameworks will support the concept. Click on the image for a larger view in a new tab…

In this particular test the query inside the specification is being tested.. for me this is a little bit too low level of a test. There’s very little I can refactor in the specification without changing its signature and thus requiring a change to the test so personally I would test this stuff but would do it through the context of its parent API or use case which appears to be…

CatalogViewModelService.cs -> GetCatalogItems
and
CachedCatalogViewModelService.cs -> GetCatalogItems

eShopOnWeb Architecture (13/16) – has some single implementation interfaces

eShopOnWeb has a couple of its service classes (BasketService and OrderService) implement corresponding interfaces (IBasketService and IOrderService). These interfaces have no other implementations (even in test projects) so the value of including them in the architecture is highly questionable.

IBasketService and its references are shown below… click on the image for a larger view in a new tab.
Single implementation interfaces in my opinion are just pointless. The whole point of using interfaces is that we can swap out multiple implementations without our calling code having to care about which actual concrete class is doing the end work.

When we have only one implementation of an interface, we are paying the cost of having the interface without getting any benefit. It’s easy to extract an interface when it’s needed so there’s no need IMHO to create interfaces ahead of time ‘just in case’.. and always remember YAGNI.

Using interfaces to aid testing

Testing is a special case whereby we might only have one implementation of an interface in our app itself but we have another implementation in our test project to facilitate plugging in mocked or stub test versions of our classes. In this case we still have multiple implementations… whether it’s a good idea to add a load of interfaces to our apps just for testing is debatable and is a question for a different day but we definitely are not in a single implementation scenario.

Using interfaces for DI

We don’t need interfaces to use dependency injection, this works…

Does it make sense to ever have multiple implementations of service classes?

Of course eShopOnWeb is just a reference architecture, it’s not a completed app, if it were perhaps there would be more implementations of IBasketService and IOrderService… BUT still the problem I see with this is that our services classes (and below) are basically our apps. The only thing we usually have above our service layer might be a thin HTTP or console app layer.

Our service classes are the main way into all the use cases and functionality of our app and contains all our business logic and usually already have their dependencies passed in. This means we don’t need multiple implementations for testing.

What about having multiple implementations in our app itself? Well are we really going to have multiple ways to implement our core use cases? For example below in the AddItemToBasket method we..

  1. Check for existing basket
  2. If there is none we create one
  3. Then add the item to the basket
  4. Then persist it to our datastore

The four steps needed to add an item to basket won’t change, if they do it’s a different use case and will be implemented by a different method.

IMHO the inclusion of the IBasketService and IOrderService interfaces just adds bloat and unnecessary complexity to the architecture.