EnriqueMark
Design Patterns & Architecture

Data-oriented module design

2026-04 English

What I’ve found is that instead of coupling every kind of state inside the code, it’s better to use the database as unified, shared, persistent state management. Then the functional modules can be decoupled from each other, and as modules, the abstract unit blocks, they exist caring only about data rather than doing complicated state management and interaction on the inside.

This works especially well when state is shared across modules. Each module only has to focus on implementing its own function and doesn’t have to think about state outside it. When state needs updating, send a request to the dedicated state management module and that’s it, so reading and changing state can both be managed in one place. Early in a project, as long as you can still change database columns yourself, a data-oriented design like this saves a lot of effort keeping every functional module’s state in sync. Persistent state storage can of course live in memory or on disk too, but then you don’t get the advantages of the database’s regular backups and unified management.

This isn’t free, of course. IO goes up, and so does the pressure on the database. But for small-scale internal software with low concurrency, a design like this is entirely good enough. Unless there’s a clear real-time requirement or an offline situation where IO is awkward, storing state in the database is a pretty decent design.

Beyond that, the plan above doesn’t actually get rid of the coupling. It’s a bit like entropy going up and down, where functional code that isn’t a closed system discharges its own entropy outward and hands it to the database. Which means the database has taken on the whole of state management, and once something changes, the change here is a chain. Add or drop a column and everything breaks. The chained dependency here is much the same as the problem with classes I talked about in Composition and inheritance, where the parent class changes, some key element the subclass depended on is gone, and it errors straight away. And that isn’t foreseeable when you’re building the parent class. Same thing here, who modifies the database and when isn’t something I can foresee as the person building the code, and some day I might change something myself and not be sure about it either.

So to make the whole thing sturdier, it’s better to put an abstract interface between the database and the functional modules, and have the functional modules answer to the interface rather than answering to the database directly. Then use a dedicated tool to maintain that interface and pack the database’s contents into it. It’s a bit like a variant of the factory pattern, only handling data instead of handling objects. Having the low-level blocks answer only to the interface is the same idea about decoupling as DI, which is to say inversion of control. How the data gets handled is put outside, and the functional module itself only cares about the interface, or about the structure. As said above, the problem here is almost identical to inheritance, so the solutions arrive at the same place from different roads.

Then there’s another problem with depending on the database, race conditions. Concurrent modification is something any external state can run into, and without a lock it leads to a broadcast error in the dependency. A was told the value is A, but B has already changed it to B. What the database suits storing is atomic, persistent state, and short-lived state that may change fast is probably not a good fit for it.


Translation note. I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: 數據導向型的模塊設計.