Skip to content
DevPedia

Communication and dataGuide 7 of 11

Data architecture

How to organize data and decide who owns it. Modeling and persistence decisions, and when to consider event sourcing.

Updated 7 min read

Data decisions tend to be among the hardest to change later. There are three distinct questions: who owns the data?, how is it modeled?, and how is it persisted?

Data ownership

Two fundamental strategies: a shared database and a database per service.

Shared database

Benefits: simple transactions, joins, relational integrity, less complexity.

Problem: a schema change can affect multiple consumers. Payments can’t change a column without coordinating with Orders and Inventory; the “shared database” becomes an implicit contract between teams.

Database per service

Each service owns its data. That buys autonomy: it can change its schema, its engine, and its release cycle without coordinating with the others. The cost is that you can no longer update Payment and Order in a single transaction. That BEGIN/COMMIT covered both writes:

BEGIN
  Update Payment
  Update Order
COMMIT

If each one goes to a different database, you have to coordinate them with events or sagas, and accept eventual consistency, compensations, and idempotency.

Database types and modeling

Don’t start from:

SQL or NoSQL?

The process should be:

SQL / relational

Model: tables, rows, columns, relations, constraints, transactions.

Example:

payments: id, order_id, amount, currency, status, created_at

payment_transactions: id, payment_id, provider, provider_transaction_id, status, created_at

Strengths: relations, constraints, transactions, joins, mature tooling, and solid OLTP support. Well-known databases: PostgreSQL, MySQL, SQL Server, Oracle.

Normalization

Normalization aims to reduce redundancy and update anomalies. Instead of repeating information (Payment, OrderId, CustomerName, CustomerEmail), we can keep clear relations:

Normalizing isn’t an end in itself: if every read has to cross many tables, it can be worth duplicating data on purpose to read faster.

Denormalization

Sometimes duplicating information is the right decision to improve read performance, simplify queries, and scale better. For example, an Order Read Model with order_id, customer_name, payment_status, total, shipping_status can be optimized for reads even though the data is normalized in the transactional system.

NoSQL

“NoSQL” groups together several different models:

CategoryExample
DocumentMongoDB
Key-valueRedis
Wide-columnCassandra
GraphNeo4j

Document

{
  "id": "123",
  "status": "captured",
  "amount": 100,
  "currency": "ARS"
}

It can be a good fit when documents are read and written as units and access doesn’t need complex joins.

Key-value

payment:123 → { ... }

Excellent for caching, sessions, key lookups, and temporary data. Redis is the classic example.

Wide-column

Aimed at enormous-scale workloads and specific access patterns. Cassandra is a well-known technology in this category. Here the data model should be designed around the queries that will actually run.

Graph

Data is modeled as nodes, edges, and properties. Especially useful for representing complex relationships. Neo4j is a well-known implementation.

SQL vs. NoSQL

The right discussion isn’t “SQL vs. NoSQL”, it’s: which model? which queries? which consistency? which workload? which scale? which operational constraints?

A system can also use several types:

This is called polyglot persistence. The benefit is specialization; the cost is synchronization and operational complexity.

Event sourcing

In a traditional model we store the current state (Payment.status = CAPTURED). With event sourcing, the events are the source of truth:

Current state can be rebuilt:

That gives you the complete history of changes.

Benefits

  • a natural audit log;
  • replay;
  • multiple projections;
  • temporal analysis;
  • state reconstruction.

Costs

  • schema evolution;
  • event versioning;
  • snapshots;
  • replay complexity;
  • concurrency;
  • debugging;
  • migration complexity.

Also, event sourcing ≠ event-driven architecture. They can be combined, but they’re independent decisions.

When to use event sourcing

Especially interesting when the history of changes is part of the domain, auditing is essential, you need to reconstruct states, there are multiple read models, or the domain is naturally expressed as a sequence of facts. Payments and financial systems are cases where the idea can be appealing, though that doesn’t mean all of them need event sourcing.

When to avoid it

For a conventional CRUD where only the current state matters (Product: name, price, description), it probably adds more complexity than it removes.

Tools

PostgreSQL, MySQL, MongoDB, Redis, Cassandra, Neo4j, Elasticsearch, Kafka, Debezium. Kafka, for instance, can be used to transport events, but Kafka doesn’t automatically turn an application into an event-sourced system.

Share this guide

Search by concept, pattern or practice.