MonoLISA

The art of cultivating repos for continuous change

MonoLISA

Software is a compound word. 'Soft' means easy to change, and 'ware' means product—an easy-to-change product. Why, then, can it be so difficult to change?! Messy code is a big reason. Clean code is needed to make it truly easy. My peers and I discovered it becomes even easier when clean code lives in monorepos. As a result, we developed a method that combines monorepo practice and Clean Code: MonoLISA.

What is MonoLISA?

MonoLISA is a mnemonic for monorepo practice with:

  • Layered Libraries

  • Interface Segregation

  • Single Responsibilities

  • Agile Architecture

MonoLISA is a method that cultivates code repositories for continuous change by synthesising the above.

Value

The more difficult it is to change software, the longer it takes. The longer it takes, the more expensive it'll be.

Our experience is that we spend more time changing the code of existing features than writing code for brand-new ones. No wonder Constantine's Equivalence states the cost of software is approximately equal to the cost of changing it.

MonoLISA lowers the cost of change-prone software.

Monorepo practice

The 'Mono' of MonoLISA.

A monorepo is a single git repository that holds the source code for multiple applications and libraries, along with the tooling for them.

Let's consider the elements of this definition.

Libraries

Libraries (also known as modules or components) in MonoLISA represent features. It's where the logic or 'functionality' of features are written.

Place libraries in a dedicated folder, such as 'libs'.

Applications

Applications in MonoLISA are the launch or deployment vehicles of libraries. They are shells (with little to no logic) that string (or bundle) libraries together for use. An executable is an example of an app.

Place apps in a dedicated folder, such as 'apps'.

In MonoLISA, libraries must be ignorant of apps. As a result, the libs folder may not be a subfolder of the apps folder, and vice versa. Also, libraries' source code may not depend on apps.

Tooling

In MonoLISA, a specialized tool should be used to manage the monorepo. It should be clever enough only to validate (lint, unit test) and build (compile) the code that your changes affected.

Monorepo in style

Many may picture a monorepo as a 'monolithic' repository with a ton of code. In MonoLISA, it doesn't have to be. It can be a monorepo in style rather than scope.

For example, you could use MonoLISA for a single application. The repo's apps folder would then contain one app.

Layered Libraries

The 'L' of MonoLISA.

MonoLISA achieves separation of concerns (SoC) with:

  • Layers to separate technical concerns.

  • Libraries to separate the concerns of actors.

Layers

Define a layer for each high-level technical concern.

Domain (business rules), API (endpoints), database (repositories), and integration (to downstream services) are examples of backend layers.

Create a folder for each layer in the libs folder. For example:

libs
- domain
- api
- db
- integration

Libraries

Create libraries for actors within the layers.

Actors

An actor is a role a user plays the moment they act on the system.

For example, if you are developing an email app, the user could be seen as an ‘inbox viewer’ actor when they view the inbox and a ‘draft editor’ when they write an email.

libs
- domain (layer)
  - inbox (lib)
  - draft-editor (lib)
- api (layer)
  - inbox (lib)
  - draft-editor (lib)
etc.

Interface Segregation

The 'I' of MonoLISA.

The idea of the Interface Segregation Principle (ISP) is for your code to know as little as possible. In particular, it says a source-code construct should not depend on (use) interfaces with more members than it needs.

Imagine a class that only needs a client's first and last names as a source-code construct that must follow the ISP.

The following violates the principle, because Client has unneeded members:

interface Client {
  firstName: string;
  lastName: string;
  age: number;
  email: string;
}

class ClientDetailsService {
  generateFullName(client: Client) {
    return `${client.firstName} ${client.lastName}`;
  }
}

The following honours it:

interface Client {
  firstName: string;
  lastName: string;
}

class ClientDetailsService {
  generateFullName(client: Client) {
    return `${client.firstName} ${client.lastName}`;
  }
}

In MonoLISA, a library is the source-code construct that may not depend on interfaces with unneeded members. The interfaces used by that library must be defined within it. That means whatever depends on (uses) that library will need to map to its interfaces.

The libraries of at least one layer in MonoLISA must adhere to the ISP.

Single Responsibilities

The 'S' of MonoLISA.

In MonoLISA, each library must adhere to the Single Responsibility Principle (SRP).

The SRP says a library must have a single responsibility and defines responsibility as the needs of an actor. In other words, each library may only serve a single actor.

The email-app example under the 'Actors' section earlier adheres to the SRP because it has libraries for the 'inbox' and 'draft editor' actors. Without the SRP, an engineer may have created an 'email' library with both responsibilities.

Agile Architecture

The 'A' of MonoLISA.

A Waterfall process has Big Design Up Front (BDUF). Teams that practice BDUF make many significant design decisions upfront. For example, they may decide to have 𝓧 microservices before they start development and fashion their repos around that decision.

Agile has 'small design up front'. Just enough design decisions are made to kick off development, and the others are deferred to the iterations (sprints) in which they're needed. The design evolves or emerges over the course of iterations.

MonoLISA has Agile architecture because of its synergy with Agile. The ignorace features (libraries) have of how they're deployed (apps) allows us to change our deployment model with minimal effort at any time. This allows us to start with a simple deployment model, to defer deployment decisions, and to pivot when needed.

For example, we could deploy our libraries as a monolith first. After that, suppose we find it does not suffice. In that case, we can quickly pivot to microservices by adding an app for each microservice, plugging the relevant libraries into them, and launching them into infrastructure.

Acknowledgements

MonoLISA was developed under the technical direction of Burgert Vermeulen and me. We and our peers have battle-tested and refined it since 2021 with excellent results! For example, one monorepo that follows MonoLISA has over 100 libraries and 5000 unit tests. On a smaller scale, we also used MonoLISA on a monorepo (in style) with one app.

Uncle Bob's teachings on Clean Code had a huge influence on MonoLISA!

Conclusion

Monorepo practice. Layered Libraries. Interface Segregation. Single Responsibilities. Agile Architecture. MonoLISA! It's been guiding us to high-quality, lower-cost software since 2021. And it can guide you to it, too!

Think of 'her' as a personification of Clean Code.