We create applications, programs, and to do so we write code. Different programming languages work in quite different ways. But there are generally two ways to slice and abstract application code. You can organise it by grouping domain meanings and processes, or you can do it by grouping implementation details.
Organising your code by implementation detail.
The dotnet world is terrible for this. You will see namespaces like Your.App.Api.Services and Your.App.Api.Models. And they will be compiled as separate DLLs in this weird flat structure which actually isn’t flat, it’s just not represented in the file-structure in the same way it is in the dependency graph.
This means that in order to create a new endpoint on your API, one needs to touch all of these different DLLs, each namespace. Add a new controller, or method on an existing one. Add a new service, or a new method on an existing one. Maybe some new models? Maybe some new repositories?
And now comes along the new Engineer on your team. Who is trying to work out how it all works and piece it all together. In order to follow the chain of execution they open up a controller, and start hitting F11 to follow through to method definitions. You jump between DLLs, seeing lots of code all around the little bits you are trying to pick out. They then try to change something, modify a method. Which endpoints does this affect? Let me work backwards through all the calls.
By now you have probably realised, that this is the worst way to organise your code. Or is it? This guy isn’t saying probably we should have our database calls in the same method as the thing processing your business logic surely? No, I am not. Code needs to be cut up in both ways. But I would like to take a moment to present the alternative approach.
Cohesion and Coupling
A lot of code architecture decisions come down to trying to get the balance right here. We want a cohesive application, with just the right amount of coupling.
In the above example, where our code is organised by its “layer” or grouping of functionality in the codebase. I would argue that we have created an extremely un-cohesive module. Why should the BananaService and OrangeService both be in the Services folder, just because they are doing some business logic? Along with the PartyService?
Once an application reaches a certain size, we must begin to consider breaking it down into Modules. This is basic stuff that is already written down in many books. But something I have sadly rarely seen put into practice, so here I am saying it again. We should probably create modules for Fruits, and another one for Functions to put our above services. These Modules will have their own well defined API (not necessarily an HTTP API, but the original meaning of API).
Domain Driven Modules
The balance that I have found works best, is to create modules bound by some kind of domain concept. In Domain Driven Design, these might be your aggregates. But it is probably best for you to do some kind of Domain Modelling session with your team, discussion important processes and concepts, and work out some “Modules” that make sense to group these into.
This way you’ve got cohesive modules, which make not only good conceptual code sense, but also good business sense!
Start your refactors small, you will need to gradually evolve your system towards this, and it will always be a moving target.
LLMs & Context
I found a way to talk about Agentic Coding in this one too. I have found, that these Modules are a great way to keep LLM context windows under control too.
This is twofold, you can both use Agentic Coding tools to refactor towards better more modular domain driven architecture than ever before, and once you do, reap the rewards of having better Agentic performance going forward.
Where the rubber hits the road
Ultimately you have to slice your code in both directions. Both by concept, and by abstraction layer. You don’t really want your database calls, business logic, json parsing, and HTTP response builder all to be in the same method. But we have a lot of options. And I have certainly found that grouping your classes and methods by Domain concepts as much as possible is a huge boon.