Home Organizing application source code

Max Heinritz

Organizing application source code

I’ve come to prefer a codebase structure that involves three top-level directories, arranged in an ordered dependency hierarchy:

Code in app/ can depend on domain/ and platform/. Code in domain/ can depend on platform/ but not app/. And code in platform/ cannot depend on anything else.

Dependency restrictions can be enforced with tools like eslint’s no-restricted-imports or ArchUnit for Java.

Let’s walk through the three directories in detail, starting from the bottom up.

Platform code

Examples of platform code include:

One way to think about platform code is: if your startup pivots and you want to start a totally new codebase in a different domain, you should be able to copy over all the platform code without needing to refactor anything.

Another way to think about platform code is: it could be easily bundled up and distributed as packages or libraries on npm, maven, etc. Indeed, reusing open source external packages instead of hand rolling platform code is generally the best approach. But in every codebase I’ve worked on, we’ve ended up with some components that are platform-like in nature (ie domain-agnostic) but still specific to our particular application.

Domain code

The contents of the domain directory will vary based on the application problem domain. For example:

One way to think about domain code is: if a concept is part of your domain model, it should be within the domain directory. Or said another way, if a concept is exposed to end users in the product, it should live within the domain directory. End users do not care about which logging library you use (at the platform layer) or which web server runner you use (at the app layer). End users do care about the domain concepts they manipulate through the product (at the domain layer).

For this reason, I tend to include in the domain directory any modules responsible for tenants, users, and roles. While on the one hand these modules might be considered domain-agnostic and therefore better fits for the platform directory, the behavior of such modules heavily impacts product behavior and therefore can be considered part of the domain model.

App code

The app directory is the orchestrator of the system. It is like the platform directory in that it doesn’t deal directly with domain concepts, but it differs in that it depends on the domain code. Here are some examples of things that live at the app layer:

Unlike the platform code, app code cannot simply be copied from one codebase to another. But the parts that reference the domain code can be built in a configurable way that is easy to swap out for other domains.

Tying it all together

A codebase for a restaurant SaaS application might look something like:

app/
  authentication
  entity-lookup
  gql-server
  http-server-entry
  cli-entry

domain/
  reservations
  menu
  orders
  seating-layout
  users
  tenants

platform/
  db-client
  metrics-client
  s3-client
  string-util

These three buckets provide the high-level scaffolding for an application, and other techniques for code organization within these buckets merit their own descriptions.