DEV Community

DEVANSHU PATIL
DEVANSHU PATIL

Posted on

Why I Keep My Database Layer Boring

Why I Keep My Database Layer Boring

When building an application, it's easy to get excited about the visible parts.

A new Compose screen.

A better API.

A new feature.

A cleaner dashboard.

The database usually gets less attention.

But while working on FinLedger, I've found that the database layer is one of the places where boring engineering is actually a good thing.

For a finance application, I don't want the database to be clever.

I want it to be predictable.

Start with the data model

Before writing queries, I need to understand what I'm actually storing.

For example, a transaction isn't just:

amount
Enter fullscreen mode Exit fullscreen mode


`

It can have relationships with other parts of the application:

text
Transaction
├── amount
├── date
├── type
├── category/tag
├── person
└── other metadata

Those relationships matter.

If the data model is unclear, the application code eventually becomes full of special cases trying to compensate for it.

Keep persistence responsibilities clear

A useful separation looks like:

text
UI

ViewModel

Repository

DAO

Room

SQLite

The UI shouldn't need to know SQL.

The DAO shouldn't decide business rules.

The repository shouldn't become a second UI layer.

Each component should have a reasonably narrow responsibility.

For example, a DAO might answer:

kotlin
@Query("SELECT * FROM transactions ORDER BY date DESC")
fun observeTransactions(): Flow<List<Transaction>>

Its job is retrieving data.

It shouldn't also decide how the dashboard calculates financial insights.

Database constraints are useful

It's tempting to rely entirely on application code for validation.

But the database can enforce important assumptions too.

For example:

text
amount cannot be NULL
required relationship must exist
identifier must be unique

Why duplicate validation?

Because different parts of an application can eventually write data.

A database constraint provides a final line of defense.

Application validation improves the user experience.

Database constraints protect the integrity of stored data.

They're solving different problems.

Don't make every query generic

Another trap is trying to build a universal repository that can do everything.

Something like:

text
save()
update()
delete()
find()
query()

can look clean at first.

But eventually the repository becomes an abstraction that hides what the application is actually doing.

A specific operation can sometimes be clearer:

text
getTransactionsForMonth()
getTransactionsForPerson()
getTransactionsByType()

The database layer should make important data access patterns understandable.

Abstraction is useful when it removes meaningful complexity.

If it only hides a simple query behind five interfaces, it may be making the code harder to understand.

Observe data instead of manually refreshing everything

One thing I like about using Room with Kotlin is the ability to work with observable data.

Conceptually:

text
Database changes

Flow emits new data

ViewModel receives state

Compose recomposes

That is cleaner than having every screen manually ask:

"Did the database change?"

The data source can become the trigger for updating the UI.

Performance still matters

A database can work correctly and still become a performance problem.

For example, fetching a huge dataset when the screen only needs the latest records is wasteful.

Instead of:

text
Load everything

Filter in application

Display 20 records

you often want:

text
Query only what you need

Return relevant records

Display them

The database is good at filtering data.

Use it.

The boring database principle

The more important the data, the less interested I am in clever persistence logic.

For a financial application, I want:

text
Predictable schema
Clear relationships
Explicit queries
Useful constraints
Observable data
Simple migrations

Not:

text
Magic abstraction
Hidden side effects
Queries nobody understands
Business rules scattered across DAOs

The database should be something I can inspect and reason about when something goes wrong.

The bigger lesson

I've started thinking about database code differently.

The goal isn't to write the most sophisticated persistence layer.

The goal is to make it hard to accidentally corrupt the application's data model.

That's especially important for software where the data represents something users care about.

Fancy architecture is useful when it solves a real problem.

But for the foundation of an application, boring is often a feature.

**Make the database predictable.

Make the queries understandable.

Make the data difficult to corrupt.

Then build the interesting features on top of it.**

android #kotlin #room #database #sqlite #softwarearchitecture #jetpackcompose #softwareengineering #buildinpublic

Top comments (0)