Full Stack Application Architecture and Design Patterns

Chapter 8
Data Architecture and Persistence Patterns

Post Graduate Diploma in Full Stack Development
Full Stack Application Architecture and Design Patterns   •   Post Graduate

8. Data Architecture and Persistence Patterns

Introduction: Data as an Architectural Decision

A full-stack application is not merely a collection of screens, services, and APIs. It is also a system for creating, validating, storing, retrieving, transforming, protecting, and eventually disposing of data. The quality of these data decisions strongly influences application performance, reliability, scalability, security, and long-term maintainability.

Data architecture concerns the organisation of data stores, schemas, access mechanisms, integration flows, and governance policies across an application or enterprise. Persistence is the process by which application state survives beyond the execution of a program. Although relational databases remain central to many business systems, modern applications frequently combine several persistence technologies. A transactional core may use a relational database, a product catalogue may use a document store, sessions may be held in a key-value store, relationships may be analysed in a graph database, and historical events may be processed in an analytical warehouse.

The architectural task is not to select the newest database technology. It is to match data characteristics and workload requirements with an appropriate persistence model. This requires an understanding of data shape, consistency, access patterns, transaction boundaries, query complexity, volume, velocity, governance, and operational capability. This chapter develops that understanding and connects database theory with application patterns such as Object-Relational Mapping, Repository, Unit of Work, caching, polyglot persistence, and migration management.

Learning Objectives

By the end of this chapter, a learner should be able to:

1. Distinguish the principal characteristics and workloads of relational, document, key-value, graph, and analytical data stores.

2. Design schemas using appropriate normalization, denormalization, constraints, and relationships.

3. Select indexes and transaction isolation levels according to access patterns and consistency requirements.

4. Explain how ORM, Repository, and Unit of Work patterns structure persistence logic.

5. Design caching and polyglot persistence strategies while managing consistency risks.

6. Plan data migrations, retention, archival, privacy, and lifecycle governance.

7. Evaluate persistence decisions through performance, operational, security, and business criteria.

The Data-Store Selection Problem

A data store should be selected from the workload rather than from a technology label. Important questions include:

  • What entities and relationships must be represented?
  • Are reads, writes, aggregations, or relationship traversals dominant?
  • Must operations be strongly consistent, eventually consistent, or configurable?
  • What is the expected volume, growth rate, and geographic distribution?
  • Are queries known in advance, or will users require flexible exploration?
  • What latency and availability targets apply?
  • How will the store be backed up, monitored, secured, upgraded, and governed?

A useful architectural distinction is between the system of record and derived data. The system of record is authoritative for a business fact. A cache, search index, reporting projection, or materialized view may improve access but should not silently become a second conflicting authority. This distinction is especially important in systems that use polyglot persistence.

Relational Stores

Relational databases represent data in tables composed of rows and columns. Relationships are expressed through primary and foreign keys, and data is queried using SQL. Their principal strengths are precise schema definition, joins, constraints, mature transaction support, and strong analytical capabilities for structured data.

A relational design is appropriate when data has stable business structure, relationships are important, and multiple records must be changed atomically. Banking transfers, inventory allocation, invoicing, enrolment, and order processing commonly fit this model. Relational systems can scale substantially, but high write volume, globally distributed operation, or rapidly changing semi-structured data may require additional techniques such as partitioning, read replicas, sharding, or complementary stores.

Document Stores

Document databases store records as documents, commonly represented using JSON-like structures. A document can contain nested objects and arrays, allowing data that is naturally read together to be stored together. This is useful for content management, catalogues with variable attributes, profiles, and event-oriented applications.

Document modelling is access-pattern driven. Embedding reduces joins and may improve read performance, but it can create duplication and make partial updates difficult. Referencing separate documents reduces duplication but introduces additional queries and consistency concerns. A document database is not automatically schema-free: application validation, versioning, indexes, and governance are still necessary.

Key-Value Stores

Key-value stores associate an opaque key with a value. They are effective when the application can identify the required item directly and does not need complex joins. Common uses include sessions, temporary tokens, counters, feature flags, rate-limiting data, shopping carts, and caches.

Their simplicity often enables very low latency and high throughput. However, a key-value store is a poor fit for ad hoc relational queries or complex integrity rules unless those responsibilities are implemented elsewhere. Expiration policies, key design, hot keys, memory cost, and recovery procedures require careful attention.

Graph Stores

Graph databases represent entities as nodes and connections as edges, usually with properties attached to either. They are valuable when relationships are central to the workload and queries involve traversing several degrees of connection. Examples include fraud networks, recommendation systems, identity relationships, dependency analysis, and social networks.

A graph model avoids forcing highly connected data into repeated relational joins. It is not automatically superior for every relationship. Simple one-to-many relationships and conventional reporting often remain clearer and more efficient in relational systems. The decision depends on traversal depth, relationship variability, query frequency, and the need to analyse connections as first-class data.

Analytical Stores

Analytical databases and data warehouses are designed for large scans, aggregations, historical analysis, and business intelligence rather than high-frequency row-level transactions. They may use columnar storage, partitioning, compression, parallel execution, and dimensional models.

A common architecture extracts or streams data from operational systems into an analytical platform. The analytical copy may be delayed, transformed, and reorganised for reporting. This separation protects transactional workloads from expensive queries and permits historical analysis. It also introduces data-latency, lineage, reconciliation, and governance responsibilities.

Modelling Data: From Business Concepts to Schemas

Data modelling begins with business meaning. Entities such as Customer, Order, Product, and Payment should be identified with their attributes, identifiers, ownership, and lifecycle. Relationships must specify cardinality: one-to-one, one-to-many, or many-to-many. Optionality also matters. For example, an order may be associated with a delivery address, while a payment may be absent until the order reaches a later state.

A primary key uniquely identifies a record. A foreign key represents a relationship and can enforce referential integrity. Candidate keys are possible unique identifiers, while a surrogate key is an artificial identifier introduced for persistence convenience. Choosing keys involves stability, size, uniqueness, privacy, and interoperability considerations.

Normalization and Its Purpose

Normalization organises relational data to reduce unnecessary duplication and update anomalies. In simplified terms, first normal form requires atomic values and a suitable row structure; second normal form removes dependencies on only part of a composite key; third normal form separates attributes that depend on non-key attributes rather than directly on the key.

Normalization supports consistency. If a customer address is stored in many order rows, changing the address may require multiple updates and may produce disagreement. A normalized design stores the address once where appropriate and references it. Nevertheless, normalization is not an absolute command to eliminate every repeated value. Historical facts often need deliberate snapshots: an order may preserve the shipping address used at purchase even if the customer later changes the current address.

Denormalization as a Deliberate Trade-off

Denormalization duplicates or precomputes data to improve read performance or simplify a high-volume access path. Examples include storing an order total, maintaining a customer-order summary, or embedding product display information in a document. Such duplication creates a consistency obligation. The design must identify the authoritative value, update mechanism, failure recovery, and reconciliation process.

Denormalization is most defensible when supported by measured workload evidence. It should not be used merely to avoid learning joins or modelling relationships correctly.

Constraints and Integrity

Constraints express rules close to the data. NOT NULL prevents absent values where absence is invalid. UNIQUE prevents duplicate business identifiers. CHECK constraints restrict values to a valid domain, and foreign keys protect relationships. Constraints complement application validation; they do not replace it. The application can provide useful messages and enforce contextual rules, while the database protects integrity against every writer.

Indexing and Query Performance

An index is an auxiliary structure that helps locate records without scanning an entire table or collection. Indexes commonly support equality searches, range conditions, ordering, and joins. A composite index on `(tenant_id, created_at)` may be suitable for retrieving recent records for one tenant, whereas an index beginning with `created_at` may serve a different workload.

Index design must follow actual queries. Important considerations include selectivity, predicate order, sort requirements, write overhead, storage cost, and the possibility of covering an entire query from the index. Too few indexes produce slow reads; too many indexes increase write latency, maintenance cost, and storage consumption. Query plans should be examined using database profiling tools rather than assumed from intuition.

Partitioning divides a large logical dataset into manageable segments, often by time, tenant, or geographic region. It can improve maintenance and reduce scanned data, but a poor partition key can create hotspots or cross-partition queries. Replication improves availability and read capacity, but replicas may lag and should not be treated as immediately consistent unless the platform guarantees it.

Transactions and Isolation

A transaction groups operations into a logical unit. The classical ACID properties are:

  • Atomicity: all intended changes occur, or none do.
  • Consistency: committed data satisfies defined integrity rules.
  • Isolation: concurrent transactions do not produce unacceptable interference.
  • Durability: committed changes survive appropriate failures.

Isolation is a business and technical decision. At lower isolation, concurrency may improve but anomalies can occur. A dirty read observes uncommitted data. A non-repeatable read occurs when the same row changes between reads. A phantom read occurs when a repeated range query returns a different set of rows. Serializable isolation provides the strongest conventional protection but can reduce concurrency and increase contention. Read committed, repeatable read, snapshot-based modes, and serializable isolation should be chosen according to the invariant being protected.

A transaction boundary should correspond to a meaningful business operation, such as reserving stock and recording an order. Transactions should be short, should avoid external network calls where possible, and should use explicit retry handling for deadlocks or transient conflicts. Distributed transactions across independent services are costly and fragile; alternatives include local transactions combined with an outbox, idempotent consumers, workflow coordination, and compensating actions.

Persistence Patterns in Application Design

Object-Relational Mapping

Object-Relational Mapping (ORM) maps program objects to relational tables. It can reduce repetitive SQL and provide identity tracking, relationship mapping, validation hooks, and transaction integration. ORM is productive when the object model and relational model are reasonably aligned.

ORM does not eliminate database knowledge. The N+1 query problem occurs when one query retrieves parent records and then a separate query is executed for each parent. Lazy loading can trigger unexpected database access, while eager loading can retrieve excessive data. Developers must understand generated SQL, joins, batching, pagination, locking, and query plans. For complex reporting, carefully written SQL or a query-oriented data access layer may be more appropriate than forcing the operation through an object model.

Repository Pattern

A Repository presents collection-like operations for a domain object or aggregate while hiding persistence details from domain logic. For example, `OrderRepository` may offer `findById`, `save`, and `findOpenOrders`. A good repository expresses business-relevant access rather than becoming a generic wrapper around every possible database operation.

The pattern supports testing and separation of concerns, but excessive abstraction can conceal important query behaviour or create leaky interfaces. A repository should not pretend that a graph traversal, analytical query, and transactional update have identical semantics.

Unit of Work

A Unit of Work tracks changes made during a business operation and commits them as a coordinated unit. It may detect new, modified, and deleted objects, order statements, and manage transaction boundaries. ORMs frequently implement this pattern internally.

The pattern is useful where several related changes must be committed together. It becomes problematic when a unit grows too large, holds database connections for too long, or mixes unrelated operations. Clear command boundaries and explicit commit behaviour improve reliability.

Data Mapper and Active Record

A Data Mapper keeps domain objects relatively independent of persistence and uses a separate mapping layer. This supports complex domain models but requires more code. Active Record combines data and persistence operations in the domain object and is often productive for simple CRUD-oriented applications. Neither is universally superior; the choice depends on domain complexity, team capability, testability, and the desired separation between business rules and infrastructure.

Caching and Polyglot Persistence

Caching stores a reusable result closer to the consumer to reduce latency and database load. Common strategies include cache-aside, in which the application reads from the cache and loads from the database on a miss; write-through, in which writes update the cache and underlying store together; and write-behind, in which changes are deferred to the backing store.

Every cache needs a freshness policy. Time-to-live limits staleness, but expiration alone does not solve invalidation after updates. Cache keys must include relevant dimensions such as tenant and locale. Sensitive data requires encryption, access controls, and careful deletion. Stampedes can occur when many requests regenerate the same expired item; locking, request coalescing, or staggered expiration can help.

Polyglot persistence uses different stores for different workloads. A relational database may be authoritative for orders, a key-value store may hold sessions, a search engine may support text search, and a warehouse may support reporting. This improves fit but increases operational and consistency complexity. Data ownership, synchronization, failure handling, lineage, and reconciliation must be designed explicitly. A materialized projection should be rebuildable from authoritative events or records whenever practical.

Migration and Data Lifecycle Governance

Schema change is a continuing operational reality. A migration may add a column, create an index, split a table, change a document shape, or move data to another store. Safe migration commonly follows an expand-and-contract approach:

1. Expand the schema so old and new application versions can operate.

2. Deploy code that writes or reads both forms where necessary.

3. Backfill data in controlled batches.

4. Validate counts, constraints, and business results.

5. Switch reads to the new representation.

6. Stop writing the old representation.

7. Contract by removing obsolete fields or structures after a safe period.

Migrations require version control, repeatability, backups, observability, rollback or roll-forward planning, and performance testing. Large backfills should be throttled to protect production workloads. Data transformation must be idempotent or carefully checkpointed.

Data lifecycle governance covers classification, ownership, retention, archival, deletion, legal holds, lineage, and access review. Personal data should be collected for a defined purpose, retained no longer than necessary, and deleted or anonymised according to applicable law and organisational policy. Backups and replicas complicate deletion guarantees; policies must address them explicitly. Audit logs should record significant access and administrative changes without exposing unnecessary sensitive content.

Illustrative Case Study: A Multi-Channel Retail Platform

Background

The fictional company Northstar Retail operates web and mobile channels. It sells physical products, supports multiple warehouses, offers recommendations, and provides management reports. Initially, one relational database handled orders, catalogue data, sessions, and reporting.

Situation and Problem

As usage increased, catalogue attributes became inconsistent across product categories, reporting queries slowed checkout, and session data consumed database capacity. Customers occasionally saw stale stock information because application servers cached inventory without a clear invalidation strategy. The company considered replacing the relational database with a document database but lacked workload evidence.

Analysis

Orders, payments, stock reservations, and refunds required strong integrity and multi-row transactions. The catalogue contained variable attributes and was frequently read by category and search criteria. Sessions and short-lived carts required direct lookup and expiration. Recommendations depended on customer-product relationships, while management reports involved historical aggregation. These workloads were different enough to justify multiple purpose-fit stores, but order authority needed to remain clear.

Approach

Northstar retained a relational database as the system of record for customers, orders, payments, and inventory reservations. It introduced a document-oriented catalogue projection for flexible product attributes and a key-value store for sessions and rate limits. Product and order events were published through an outbox process and consumed by a search index and analytical warehouse. A graph projection was created for recommendation experiments rather than for transactional order processing.

The team added indexes based on observed queries, partitioned historical order data, and separated reporting workloads from checkout. Cache-aside was used for product views with explicit invalidation when catalogue versions changed. Inventory reads displayed a freshness indicator and final reservation occurred transactionally in the relational store. Migrations used expand-and-contract procedures, with reconciliation jobs comparing authoritative and derived records.

Outcome

Checkout was protected from reporting scans, catalogue changes no longer required a rigid set of nullable columns, and session operations were cheaper and faster. The architecture became more complex, but ownership and rebuilding procedures made that complexity manageable. The graph store was retained only while recommendation quality justified its operational cost.

Learning Points

The case demonstrates that polyglot persistence is a workload allocation decision, not a fashionable replacement strategy. It also shows why derived stores require lineage, freshness expectations, reconciliation, and a clearly identified system of record. Finally, caching and flexible schemas improve performance only when consistency and lifecycle obligations are made explicit.

Risks, Limitations, and Professional Judgement

Common mistakes include selecting a database before identifying access patterns, treating NoSQL as a substitute for modelling, creating indexes without measuring queries, using a cache as an ungoverned second database, and allowing ORM defaults to determine transaction boundaries. Another error is assuming that eventual consistency is harmless. A stale product description may be acceptable for seconds; a stale account balance or stock reservation may not be.

Operational risks include backup failure, replica lag, lock contention, hot partitions, uncontrolled storage growth, undocumented manual changes, and migrations that cannot be reversed. Security risks include excessive database privileges, exposed connection strings, unencrypted backups, injection vulnerabilities, insecure logs, and failure to isolate tenants. Parameterized queries, least privilege, encryption in transit and at rest, secret management, auditability, and tested restoration procedures are essential controls.

Ethically, data architecture should support privacy by design. Teams should minimise collection, avoid using sensitive attributes without legitimate justification, document secondary uses, and assess automated decisions for unfair impacts. Governance must also consider accessibility of records, data subject rights where applicable, cross-border transfer, retention obligations, and the environmental cost of unnecessary storage and computation.

Emerging Directions

Cloud-managed databases increasingly provide automated scaling, distributed replication, serverless operation, and integrated analytics. These capabilities reduce infrastructure administration but can create vendor dependence, unpredictable cost, and less visibility into failure behaviour. Distributed SQL systems seek relational transactions with horizontal scale, while lakehouse architectures combine large-scale object storage with warehouse-style governance and query capabilities.

Event-driven architectures are making data movement more continuous, but event streams introduce ordering, replay, schema evolution, and duplicate-delivery concerns. Data contracts and schema registries help producers and consumers coordinate. Machine learning also increases demand for feature stores, vector search, and governed data pipelines. These technologies should be introduced because a measurable workload requires them, not because they are fashionable. Human oversight remains necessary when analytics or AI influences eligibility, pricing, recommendations, or risk decisions.

Best-Practice Checklist

A professional persistence design should:

  • begin with business invariants and measured access patterns;
  • identify the system of record for each important fact;
  • use constraints and transactions to protect critical integrity;
  • design indexes from real query plans and monitor their cost;
  • keep transaction boundaries explicit and reasonably short;
  • inspect ORM-generated queries and prevent N+1 behaviour;
  • define cache freshness, invalidation, privacy, and failure behaviour;
  • make derived stores rebuildable and reconcile them periodically;
  • test migrations, backups, restoration, failover, and data deletion;
  • classify data and enforce retention, access, and audit policies;
  • document operational ownership, service-level objectives, and costs.

Chapter Summary

Data architecture aligns data models and persistence technologies with business workloads. Relational stores provide structured schemas, joins, constraints, and strong transactions; document stores support flexible aggregate-shaped data; key-value stores serve direct, low-latency access; graph stores specialise in relationship traversal; and analytical stores support historical scans and aggregation. Selection must consider data shape, access patterns, consistency, scale, latency, operations, and governance.

Effective relational design uses keys, relationships, normalization, constraints, and evidence-based indexes. Denormalization can improve performance when authority and synchronization are controlled. Transactions provide atomicity, consistency, isolation, and durability, but their boundaries and isolation levels must reflect business invariants. ORM, Repository, and Unit of Work patterns can structure persistence code, provided that abstractions do not hide database behaviour.

Caching and polyglot persistence improve responsiveness and workload fit but introduce freshness, lineage, and reconciliation responsibilities. Safe migrations use compatibility, backfill, validation, and controlled removal. Data lifecycle governance extends from collection and classification through retention, archival, deletion, audit, and privacy. The central professional principle is to make data ownership, consistency expectations, operational consequences, and recovery procedures explicit.

Key Terms

Data architecture: The organisation of data stores, schemas, access paths, integrations, and governance.

Persistence: The preservation of application state beyond the lifetime of a running process.

System of record: The authoritative source for a particular business fact.

Relational database: A database that represents structured data in related tables and commonly uses SQL.

Document store: A database that stores semi-structured records as documents, often with nested data.

Key-value store: A store that retrieves values through unique keys with minimal structural querying.

Graph database: A database that models entities and their relationships as nodes and edges.

Analytical database: A system optimised for large-scale scans, aggregation, and historical analysis.

Normalization: The organisation of relational data to reduce redundancy and update anomalies.

Denormalization: Deliberate duplication or precomputation of data to improve access performance.

Index: An auxiliary structure that accelerates data retrieval for selected query patterns.

Transaction: A logical unit of work governed by atomicity and related consistency guarantees.

Isolation level: A defined degree of visibility and interference among concurrent transactions.

ORM: A technique that maps application objects to relational database structures.

Repository: An abstraction that provides domain-oriented access to persisted objects or aggregates.

Unit of Work: A pattern that tracks related changes and commits them as one coordinated operation.

Cache-aside: A caching strategy in which the application loads missing values from the backing store.

Polyglot persistence: The use of multiple persistence technologies within one solution.

Expand-and-contract: A migration strategy that introduces compatible structures before removing old ones.

Data lineage: The record of a datum’s origin, transformations, movement, and destination.

Review Questions

Short Answer Questions

1. What distinguishes a system of record from a derived projection?

2. State the four ACID properties of a transaction.

3. Why can excessive indexing reduce application performance?

4. What problem does the Repository pattern address?

5. Define cache-aside caching.

Long Answer Questions

1. Compare relational, document, key-value, graph, and analytical stores, giving a suitable workload for each.

2. Explain normalization, denormalization, and the conditions under which denormalization is justified.

3. Discuss transaction isolation anomalies and the relationship between isolation level and concurrency.

4. Explain how ORM, Repository, and Unit of Work patterns can be combined in a full-stack application.

5. Describe an expand-and-contract migration and identify its operational safeguards.

Analytical / Application Questions

1. A payment system requires atomic balance updates, auditability, and complex monthly reports, while login sessions require sub-millisecond direct lookup and expiration. Design a persistence arrangement and justify each store.

2. An application’s product page performs one query for products and one additional query for each product’s reviews. Diagnose the likely problem and propose ORM, indexing, and caching improvements.

3. A company wants to copy customer data into a document store, search index, and warehouse. Define the system of record, synchronization approach, reconciliation controls, privacy considerations, and failure-handling strategy.

22. Multiple Choice Questions with Answers

Question 1

An online banking transfer debits one account and credits another, and both changes must succeed or fail together. Which persistence capability is most important?

A. Atomic transaction support

B. Document embedding

C. Graph traversal

D. Time-to-live expiration

Correct Answer: A. Atomic transaction support

Question 2

A team stores customer details in a relational table and repeats the same address in many unrelated rows. Which design concern is most directly indicated?

A. Graph centrality

B. Update anomalies caused by insufficient normalization

C. Cache stampede

D. Replica read latency

Correct Answer: B. Update anomalies caused by insufficient normalization

Question 3

A query frequently retrieves records for one tenant ordered by creation time. Which index is most likely aligned with this access pattern?

A. An index on an unrelated text field

B. A composite index beginning with tenant identifier and including creation time

C. An index only on a randomly generated value

D. No index, because ordering cannot use indexes

Correct Answer: B. A composite index beginning with tenant identifier and including creation time

Question 4

Which workload is the strongest candidate for a key-value store?

A. Multi-table financial reconciliation with joins

B. Deep relationship traversal across people and organisations

C. Short-lived user sessions retrieved directly by session key

D. Historical sales aggregation across several years

Correct Answer: C. Short-lived user sessions retrieved directly by session key

Question 5

What is a principal risk of using lazy ORM loading inside a loop over parent records?

A. It can create an N+1 query problem

B. It always disables transactions

C. It prevents all indexes from being used

D. It converts every table into a document

Correct Answer: A. It can create an N+1 query problem

Question 6

In a polyglot architecture, why should a derived search index usually not be treated as the system of record?

A. Search indexes cannot store any text

B. Derived data may be delayed, incomplete, or rebuildable from authoritative data

C. Relational databases cannot publish events

D. Search indexes always provide serializable transactions

Correct Answer: B. Derived data may be delayed, incomplete, or rebuildable from authoritative data

Question 7

Which migration sequence best represents expand-and-contract?

A. Delete the old field, then deploy code that expects it

B. Change all production records manually without version control

C. Add a compatible structure, backfill and validate, switch usage, then remove the old structure

D. Stop the application permanently and recreate the database

Correct Answer: C. Add a compatible structure, backfill and validate, switch usage, then remove the old structure

Question 8

A cache contains inventory quantities that may be stale for several minutes, but final stock reservation must be correct. Which design is most appropriate?

A. Trust the cache as the authoritative inventory source

B. Perform final reservation transactionally in the authoritative store

C. Disable all database constraints

D. Use a graph database for every inventory operation

Correct Answer: B. Perform final reservation transactionally in the authoritative store

Question 9

Which isolation anomaly occurs when a transaction repeats a range query and finds newly inserted matching rows?

A. Dirty read

B. Non-repeatable read

C. Phantom read

D. Lost schema

Correct Answer: C. Phantom read

Question 10

Why might a graph database be preferable for fraud-network analysis?

A. It is optimised for traversing connected entities and relationships

B. It guarantees that every query is an aggregation

C. It eliminates the need for data governance

D. It is designed only for temporary session data

Correct Answer: A. It is optimised for traversing connected entities and relationships

Full Stack Application Architecture and Design Patterns
Chapter 8 of 15