It seems every time I read a new software tutorial or library, the code is organized per a recognizable design pattern. For example. a Model, View, Controller (MVC) library will probably suggest that you to organize your project directories with names corresponding to the design pattern.
> TodoApp > Models - TodoModel.code - TodoListModel.code > Views - TodoListView.code > Controllers - TodoController.code
This makes sense from a tutorial perspective, because the goal is to familiarize the reader with the different software components that constitute the pattern. Often this organizational paradigm is used in the applications we write, and with simple domains this isn’t terribly difficult to manage. The trouble is, we don’t work on software with simple domains. We work on software with domains that have multiple contexts. We need to manage authentication and authorization. We aggregate domain entities, and generate reports for management. It seems the more complicated our applications grow, the more difficult they become to update.
Domain Driven Organization
I’d like to propose a different method for project organization. I’ll call this Domain Driven Organization, as it borrows heavily from Domain Driven Design. The idea is to organize our code around domain contexts and implementation details. In our imaginary MVC app, instead of folders for Models, Views, and Controllers, we would start by creating folders for our domain contexts:
> Todos - TodoModel.code - TodoListModel.code - TodoListView.code - TodoController.code
Organizing our code this way has several advantages that will help us maintain our application as it grows.
It Encourages us to Keep our Application Modular
Every application we write is composed of a collection of modules. In every language, we import libraries of code we didn’t write, tie them together, and use them to build something new. If we had to write everything ourselves, projects would never get finished. Modularity is important - not just in the software that we use, but also in the software that we develop and maintain. Properly separating code based on domain context can help to ensure decoupling of your application logic from other domains.
Modularizing your application domain also helps you reason about it. As you implement new features, or update existing ones, you don’t need to keep expansive a mental model in order to do so. In our example Todo app, all code concerning Todos is located in a single directory. Following this design pattern does not only provide your application with an easy-to-follow directory structure for others, it also reduces overall complexity of your own modules and packaging.
It Allows More Flexibility in Design Patterns we Implement
When a project is organized by an existing pattern, I often find my subconscious trying to force my logic into that pattern, even when I disagree with the pattern choice. Suppose in our MVC example above, Todos needed to trigger certain actions based on the application state. In the existing design pattern, I would instinctively try to implement this using that existing pattern, rather than rewrite the application (naturally!). In our MVC example, this would mean trying to force that logic into models and controllers.
This has two problems:
- I’m shoveling more and more logic into controllers and models. This makes it difficult to figure out what is going on in those files and increases the likelihood of concurrent edits, causing merge conflicts and costing time.
- A pattern exists for this - the State Pattern. It’s easier to understand than an ad hoc solution and easier to change later.
This is where organizing by design pattern really begins to break down. We have a choice to force our logic to fit into our Model/View/Controller pattern, or we start making folders for every other pattern we implement, leading to folders called “State” or “AbstractFactory”. On many projects, a whole new layer of the application is created that exposes this new functionality via services. We essentially write the application twice.
Using Domain Driven Organization, we can implement any design pattern we want by organizing them into sub folders within each domain context folder.
> Todos - TodoController.code - TodoModel.code - TodoListModel.code - TodoListView.code > State - TodoState.code - TodoStateContext.code - TodoBaseState.code - TodoCreatedState.code - TodoAssignedState.code - TodoStartedState.code - TodoCanceledState.code - TodoCompletedState.code
It Helps us Share Only the Code we Want
It is tough to confine logic to a particular domain context when you organize your application by pattern. In our MVC example, all views exist at the same level, as do all models and controllers. There is no inherent convention that ties these models, views, and controllers together. If we stuck our
Todo state implementation in a
State folder, that too would appear to be available to every other part of the application.
Domain Driven Organization allows us to decouple or code by building "walls" - burying logic deeper in the folder hierarchy. This is similar to a library, where a user friendly contract is exposed at the root for consumers of the library, but all of the implementation details are hidden deeper within their code.
One Convention to Rule them All
Domain Driven Organization is applicable across all project types and languages. Patterns will vary based on the software stack you're using. For example, a React-Redux application will be composed of components, actions, reducers, selectors, etc.
Organizing by pattern can be inconsistent across project types, varying by the design pattern and language used for any particular project. The chosen project structure ends up conveying how the application is constructed, but not its purpose. This slows context switching, and on-boarding new developers.
Organizing around domain contexts is consistent across project types because it conveys the purpose of the software. Our back-end application will have a “Todos” folder, where all
Todo logic is found, as will our front-end. New developers can quickly gain an understanding of the different domain contexts within the application. It is the domain that makes the application unique.
A bit about infrastructure.
Not all code we will write is directly tied to a domain context. Suppose we want to use a repository pattern to interface with a database. We will most likely need to create a
BaseRepository that has generic logic to manage the database connection and submit queries. This
BaseRepository can then be inherited by specific classes like a
In Domain Driven Organization, where do we put code that is not specific to a domain context?
There are a few options for this, and the one you choose will vary a bit based on the language you are coding in.
- Move the code to its own library.
- Put it in a contextual folder, just like a domain context.
When developing in something like .NET, creating a library and linking it to your main project is trivial, so moving all of our database logic to its own library is a good option. I do a lot of development in Node with Typescript, which makes separating code into multiple libraries difficult. Usually problems arise when I need to make a small change to some infrastructure code to support a new feature in my domain code. This results in having to remember to build one project before I can test the changes in another, and it’s a process that feels clunky in this day in age. For smaller infrastructure contexts, I just create a folder at the root, and stick my code there.
> Database - BaseRepository.code > Todos - TodoModel.code - TodoListModel.code - TodoListView.code - TodoController.code - TodoRepository.code > State (...)
There are no hard and fast rules around Domain Driven Organization. Instead, try to group your domain and infrastructure code contextually and put it into folders. Try to think of your application modularly - composed of smaller components. Even domain contexts can be further divided into domain sub-contexts.
Think about how you write code. If you are creating or editing multiple files to add or update a feature, those files should probably be in the same folder. Following Domain Driven Organization can provide many benefits to developers and organizations, helping to promote proper decoupling and reducing overhead in the software development lifecycle.