How do you avoid overengineering when developing software?

IMHO overengineering is a curse on our sector. I see it all the time, it often takes the form of layers and layers of abstraction or the choosing of flavour of the month architectures such as microservices because ‘hey, that’s what Netflix does!!!’…

There’s some really good ideas in the Reddit thread below on how to avoid overengineering…

The approach I like to use which helps me to keep a grasp of complexity is to imagine or pretend it was my money being spent when choosing the architecture. I can then ask myself honestly… for example do we really need angular?, do we really need microservices? do we really need some additional layer of abstraction to cover some ‘just in case’ scenario?

It’s easy to spend someone else’s money… but if it was really my money being spent would I consider these approaches wasteful? Is there simpler ways to implement things which will still meet the requirements?

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
CachedCatalogViewModelService.cs -> GetCatalogItems