How “clean” should your architecture be?

Skip excessive mappers and abstractions by making “clean” a little “dirty”.

Yan Kalbaska
2 min readDec 14, 2023

If you’ve tried implementing clean architecture in real life apps, you know how tricky it might be. You end up with a codebase that is actually less readable because of too many classes and abstractions.

The key is to find a balance of keeping maintainability and cutting out unnecessary bloat. It will be different for every project because different parts are required to stay flexible and therefore less coupled, but providing a separate mapper layer just for the sake of “cleanliness” might be an overkill.

This article is the 2nd part of building a game using clean architecture. 1st part covers the theoretical approach, is much more detailed, and is completely “clean”. So it’s best to read that one first. This piece continues as an overview of final implementation, including decisions on coupling, while making code flexible enough to become not just a game, but a game engine.

Let’s consider cleanliness by examining a single example. Below is a class diagram of the game engine with every class included. Even though there are dependencies that one might find too coupled, you’ll barely need to change the classes outside of logic and presentation groups. Framework and adapter code stays untouched until you need to modify the engine itself, but not when you modify app/game rules or object behaviors.

Class dependency diagram

Worth noting is a group of entities or base classes. Those are not data objects, but merely types, and it corresponds with the idea that they rarely change as well. Data (game) objects belong to the logic group, because they define behaviors of the game.

Drawing diagrams allowed me to validate the design and understand dependencies for future modifications. I extracted base GameObjectCollection class to hide its operations (within the class) and simplify adding new types of game objects to the collection. On the other hand, it also forced me to create an extra mapper class (GameObjectType) to prevent passing game object dependency into Layouter. But it didn’t add real value when implemented, and I removed it as a blunt wrapper for this specific design. You can see the above changes in the PRs and the full engine code in a GitHub repo.

I encourage you to experiment, question architectural patterns, and find what works for you and your project. Architecture is sometimes defined as decisions that are hard to revert, and as such, taking weighted decisions and anticipating future changes is a valuable and fun skill to master.

--

--