This pattern is generally known as client-side composition. When an intermediate API performs the aggregation, it is usually called API composition, an API gateway, or a Backend for Frontend.

The pattern can be appropriate when each service represents an autonomous domain and owns independent data. However, when every service ultimately reads from and writes to the same database, the architecture remains centralized at its most critical point:

client
   ├── service_a ──┐
   ├── service_b ──┼── database
   └── service_c ──┘

The application tier may be distributed, but the data tier—and frequently the real bottleneck—is not.

This article does not argue that all application logic belongs in the database. It argues against the equally simplistic idea that databases should be passive storage while services perform every join, validation, aggregation, and business operation.

The correct location for logic depends on the nature of the operation.

Component Appropriate responsibilities
Database Joins, filtering, aggregation, constraints, transactions, set-based processing, bulk operations, data authorization and persistence
Service External publication, protocol translation, third-party integration, cross-system orchestration, long-running workflows and non-relational processing
Client Presentation, user interaction, local state and lightweight validation

Myth 1: “The services scale horizontally, therefore the system scales”

A service tier can scale horizontally by adding more instances:

                     ┌── service_instance_1 ──┐
client_requests ─────┼── service_instance_2 ──┼── database
                     └── service_instance_3 ──┘

That increases the capacity of the service tier. It does not automatically increase the capacity of the complete system.

When most requests eventually execute queries or transactions against the same database, the system remains limited by that database’s CPU, memory, storage latency, locking behavior, transaction-log throughput, connection capacity, and query efficiency.

Adding service instances can even increase pressure on the database:

  • more concurrent queries;
  • more simultaneous transactions;
  • greater lock contention;
  • more repeated reads;
  • more connection-pool activity;
  • more retries after timeouts or deadlocks.

The accurate statement is:

The service tier can scale horizontally, but the complete application scales only as far as its shared database and other shared dependencies permit.

Actual data-tier scaling may require read replicas, partitioning, sharding, workload separation, caching, archival strategies, or separate databases for genuinely autonomous domains. Those approaches can work, but they introduce their own costs: duplicated data, synchronization, consistency rules, failover logic and operational complexity.

Myth 2: “The service layer handles connections and therefore reduces database load”

Without a service layer:

client ── database_connection ── database

With a service layer:

client ── HTTP_connection ── service ── database_connection ── database

The service does not eliminate communication. It introduces another communication boundary.

A connection pool can reduce the number of physical database sessions and improve connection reuse. That is useful, but it does not eliminate the database work. If every API request still produces the same query or transaction, the logical workload remains.

The additional layer may introduce:

  • HTTP or RPC processing;
  • serialization and deserialization;
  • DTO allocation and mapping;
  • authentication at another boundary;
  • network latency;
  • retries;
  • distributed tracing;
  • another deployment;
  • another failure point.

A service layer may still be justified for external publication, protocol compatibility, credential isolation or a stable public contract. However, it should not be presented as an automatic database-performance improvement.

Myth 3: “Logic executed in application memory is faster than logic executed in the database”

A database engine also executes operations in memory. Modern relational databases maintain data pages, indexes, execution plans, working sets and internal structures in memory.

More importantly, a relational database has capabilities that ordinary application code does not automatically reproduce:

  • cost-based query optimization;
  • index selection;
  • join-order selection;
  • predicate pushdown;
  • set-based execution;
  • execution-plan caching;
  • parallel query execution;
  • transaction-aware concurrency control.

SQL Server, for example, maintains data buffers and cached execution plans, while PostgreSQL’s optimizer evaluates alternative access paths and join plans before selecting an execution strategy. (Microsoft Learn)

For data-intensive operations, this:

select
    customer.id,
    customer.name,
    sum(invoice.total) as total
from customer
join invoice
    on invoice.customer_id = customer.id
where invoice.date >= @start_date
group by
    customer.id,
    customer.name;

is usually preferable to:

1. Retrieve every customer.
2. Retrieve every invoice.
3. Transfer both sets across the network.
4. Allocate application objects.
5. Join the collections in C#.
6. Group and aggregate them.
7. Discard most of the transferred data.

Moving the calculation to a service may consume cheaper application CPU, but it also moves more data, loses relational context and duplicates capabilities already present in the database engine.

Application memory is the appropriate location for logic that is not fundamentally relational, such as:

  • document or image processing;
  • specialized algorithms;
  • calls to external systems;
  • cryptographic operations not provided by the engine;
  • long-running workflows;
  • transformations performed after the database has reduced the dataset.

The distinction is not memory versus database. The distinction is relational data processing versus non-relational application processing.

Myth 4: “Services are better at parallel processing”

Relational database engines are specifically designed for concurrent and parallel data processing.

PostgreSQL can create plans that use multiple worker processes, including parallel scans, joins and aggregations. SQL Server can also construct parallel execution plans and distribute query operators among worker threads when its optimizer considers that strategy appropriate. (PostgreSQL)

Calling five services in parallel does not necessarily produce superior parallelism:

client
   ├── service_a ── query_a ──┐
   ├── service_b ── query_b ──┼── same_database
   └── service_c ── query_c ──┘

It may instead cause:

  • repeated table or index scans;
  • duplicate filtering;
  • competition for the same data pages;
  • greater concurrency against the same tables;
  • independently acquired snapshots;
  • additional network traffic;
  • more complex error handling.

Service-level parallelism is valuable when the operations are genuinely independent:

service
   ├── payment_provider
   ├── identity_provider
   └── document_storage

It is less convincing when every parallel branch performs another query against the same relational dataset that could have been processed by one optimized database operation.

Myth 5: “The API absorbs traffic spikes before they reach the database”

A synchronous API does not absorb a traffic spike. It receives the spike and normally forwards the resulting work to the database:

10,000 requests
       ↓
      API
       ↓
up to 10,000 database operations

The following mechanisms can alter that behavior:

  • a queue can defer work;
  • caching can eliminate selected reads;
  • rate limiting can reject or delay requests;
  • batching can combine several operations;
  • backpressure can reduce the rate accepted from upstream systems.

However, each mechanism involves a trade-off.

A queue exchanges immediate processing for additional latency. A cache introduces invalidation and consistency concerns. Rate limiting reduces accepted demand. Batching changes transaction and error semantics.

Simply inserting a synchronous API does none of these things automatically.

Buffering can reduce the instantaneous arrival rate, but it does not reduce the total amount of required work unless requests are rejected, combined, cached or made obsolete.

Myth 6: “Microservices automatically create separation of responsibilities”

Physical separation is not the same as logical independence.

Suppose several services:

  • read and update the same tables;
  • know the same schema;
  • require coordinated releases;
  • participate in the same business transaction;
  • fail when another service changes a shared column;
  • cannot operate without the same database.

Those services are not autonomous merely because they run in separate processes or containers. The result is frequently a distributed monolith: monolithic coupling combined with distributed-system complexity.

separate_processes
        +
shared_schema
        +
coordinated_deployment
        =
distributed_monolith

A database per service can provide stronger autonomy, but that decision creates different problems:

  • duplicate business data;
  • eventual consistency;
  • event publication;
  • inbox and outbox processing;
  • compensating transactions;
  • cross-service reporting;
  • data reconciliation;
  • more complex disaster recovery.

Neither model is universally correct.

A sound rule is:

Do not distribute physically what has not first been separated logically.

A modular application with a well-designed database is often a better starting point. A module should become an independent service only when there is a concrete reason: independent deployment, different scaling characteristics, genuine domain ownership, fault isolation, external publication or a separate technology requirement.

Myth 7: “An API is required for database security and access control”

Modern relational database engines provide extensive security mechanisms. Depending on the product and edition, these can include:

  • authentication;
  • users and roles;
  • schema and object permissions;
  • execution permissions on stored procedures;
  • views;
  • row-level security;
  • column-level restrictions;
  • encryption;
  • auditing;
  • session context;
  • transactional validation.

SQL Server, as one concrete example, supports server-level and database-level principals, granular permissions, roles, stored-procedure execution grants and row-level security based on execution context. (Microsoft Learn)

An internal client can therefore be restricted to executing approved stored procedures without receiving direct permissions on the underlying tables:

application_user
    grant execute on approved_procedures
    deny direct table access

A procedure can also validate an application session, tenant, organization, token identifier or other session context before performing the operation.

This does not mean a database should be exposed directly to the public internet. An API is clearly justified when it provides:

  • external publication over HTTP;
  • access for browsers, mobile applications or third parties;
  • a versioned contract independent of the physical schema;
  • protocol translation;
  • rate-control policies;
  • concealment of internal topology;
  • coordination with systems other than the database.

The important distinction is that the API is providing an external interoperability and publication boundary. It is not compensating for an alleged absence of database security.

Myth 8: “The client should compose the data because that is modern architecture”

When related data already resides in the same relational database, forcing the client to retrieve it through multiple services can be an expensive substitute for a database join.

client
   ├── customer_service
   ├── address_service
   ├── invoice_service
   └── payment_service

The client must then:

  • wait for multiple network calls;
  • correlate identifiers;
  • handle partial failures;
  • reconcile missing results;
  • duplicate composition logic across web and mobile clients;
  • process data that could have been filtered earlier;
  • accept that each response may represent a different moment in time.

A single query or stored procedure can often return the required projection while executing within a defined transaction and isolation context. Database engines provide locking, row versioning and transaction-isolation mechanisms specifically to preserve transactional integrity under concurrent access. (Microsoft Learn)

Client-side composition is appropriate when the information originates from genuinely independent domains or when the composition is purely presentational.

It is generally less appropriate when the client is reconstructing relationships that already exist inside one relational database.

Myth 9: “Stored procedures cannot be versioned, tested or deployed professionally”

Stored procedures and other database objects are source code. They can be:

  • stored in Git;
  • code-reviewed;
  • compiled or validated;
  • tested automatically;
  • packaged as deployment artifacts;
  • released through CI/CD pipelines;
  • deployed with rollback or forward-only migration strategies.

For SQL Server, SQL database projects can represent tables, procedures and functions as source-controlled files, validate dependencies, build a .dacpac artifact and integrate with continuous-deployment pipelines, including Azure DevOps. (Microsoft Learn)

Oracle provides Edition-Based Redefinition, which supports maintaining different editions of application database objects during an online upgrade. (Oracle Documentation)

Other database platforms support migration frameworks, schema-management tools and procedural-code testing ecosystems.

Poorly organized database code is difficult to maintain. The same is true of poorly organized C#, Java or JavaScript. That is an engineering-practice problem, not an inherent limitation of database programming.

Myth 10: “The database should be a passive storage layer”

A relational database is not merely a network-attached disk.

It is an execution engine designed to:

  • store and retrieve structured data;
  • enforce relationships and constraints;
  • select efficient access paths;
  • optimize joins;
  • coordinate concurrent transactions;
  • recover from failures;
  • maintain indexes;
  • protect data;
  • aggregate and transform sets of rows.

Using it only like this:

select * from table;

and then performing every filter, join and aggregation in an application service reduces a specialized engine to an expensive remote storage mechanism.

The alternative is not to place every business workflow inside the database. The alternative is to use each layer for the work it performs best.

Database-oriented logic

joins
filters
aggregations
constraints
transactions
bulk modifications
deduplication
data authorization
set-based validation

Service-oriented logic

external APIs
third-party integrations
protocol translation
cross-system orchestration
long-running workflows
specialized non-relational algorithms

Client-oriented logic

presentation
interaction
local state
formatting
visual calculations
convenience validation

External integration: where a service can add genuine value

External integration is one of the stronger reasons for an application or service layer. Even in that case, directly chaining external systems in real time is not always the most reliable design.

A more traceable pattern is:

external_system
       ↓
ingestion_service
       ↓
inbox_or_staging_database
       ↓
validation_and_processing
       ↓
domain_tables

The service handles the external protocol, authentication and retrieval. The received payload is then persisted before further processing.

This provides:

  • an exact record of what was received;
  • replay capability;
  • idempotency;
  • auditability;
  • controlled retries;
  • error quarantine;
  • separation between acquisition and processing.

The service and database are therefore complementary. The service provides the external boundary; the database provides durable state and controlled processing.

The cost of buying an enterprise database and barely using its engine

The argument is not specific to SQL Server. It applies to Oracle Database, IBM Db2, SAP HANA, commercial managed database services and other enterprise data platforms.

Even when the database software is open source, the organization still pays for:

  • CPU;
  • memory;
  • high-performance storage;
  • replication;
  • backups;
  • high availability;
  • cloud consumption;
  • monitoring;
  • operational support.

Commercial database licensing makes the economic contradiction easier to see.

Microsoft SQL Server as a concrete example

Microsoft’s official SQL Server 2025 list pricing identifies:

Edition List price Licensing unit
Enterprise $15,123 Two-core pack
Standard per core $3,945 Two-core pack

A simplified 16-core illustration requires eight two-core packs:

Edition Calculation Approximate list price
Enterprise 8 × $15,123 $120,984
Standard 8 × $3,945 $31,560

These are list-price illustrations before negotiated discounts, subscription terms, operating systems, infrastructure, storage, high availability, backups and operations.

After making that investment, some organizations intentionally avoid using much of the engine’s processing capability. They retrieve excessive numbers of rows, execute joins in C#, perform record-by-record updates, duplicate constraints in several services and make multiple HTTP calls to assemble one screen.

The database is still being used for durability, logging and persistence, so it is not literally unused. However, much of the capability being purchased is underused:

  • query optimization;
  • set-based execution;
  • parallel query processing;
  • constraints;
  • transaction management;
  • data-local computation;
  • bulk processing;
  • security enforcement.

The organization then pays for both sides:

enterprise_database
        +
application_servers
        +
api_gateway
        +
containers
        +
serialization
        +
network_traffic
        +
distributed_observability
        +
additional_operations

Moving work from the database can be economically sensible when it demonstrably allows the organization to license fewer database cores, select a lower edition, reduce the managed-service tier or remove a measured database bottleneck.

But when the database retains the same licensed capacity and the application merely adds an intermediate processing tier, no database cost has been removed. A second cost center has been added.

The same principle applies to Oracle Database

Oracle uses licensing metrics such as Processor and Named User Plus, and many enterprise options and management packs are licensed separately.

As an illustration, an official Oracle U.S. public-sector price list dated May 21, 2026 lists Oracle Database Enterprise Edition at $47,500 per Processor. The same document separately prices components such as Real Application Clusters, Partitioning, Advanced Security, Diagnostics Pack and Tuning Pack. The final licensing requirement depends on Oracle’s processor definitions, core factors, deployment model and contract terms. (Oracle)

The architectural principle is identical:

Purchasing a sophisticated enterprise database and then using it primarily as passive storage wastes capabilities for which the organization is already paying.

The same reasoning applies to other commercial database servers and managed cloud databases. Even when the pricing unit differs—cores, processors, virtual CPUs, capacity units or hourly consumption—unnecessary database reads, excessive data movement and duplicated processing still consume paid resources.

Why microservices are inserted into almost every architecture

Microservices solve legitimate problems:

  • independent team ownership;
  • independent deployment;
  • selective scaling;
  • technology isolation;
  • fault containment;
  • genuinely separate business domains;
  • external service publication.

The problem begins when those conditions are absent and microservices are selected primarily because they are considered modern.

Several forces encourage this:

  • architectures designed for hyperscale companies are copied into ordinary business systems;
  • distributed tooling has become an industry of its own;
  • cloud platforms make creating new services easy;
  • architectural complexity can appear more sophisticated in presentations;
  • résumé and consulting incentives favor fashionable technologies;
  • organizational coordination problems are sometimes disguised as technical architecture problems;
  • “modern” is incorrectly treated as synonymous with “distributed.”

A company with one development team, one transactional database, one coordinated release cycle and one tightly integrated business domain does not automatically benefit from dividing the application into dozens of services.

It may instead receive:

more network calls
more deployment units
more authentication boundaries
more logs
more retries
more partial failures
more infrastructure
more operational complexity

while preserving the same database bottleneck.

A more defensible architectural rule

Use the database for operations that are fundamentally relational and data-intensive.

Use services when they provide a concrete boundary or capability that the database should not provide.

Use the client primarily for presentation and interaction.

Before creating another service, ask:

  1. Does it own an autonomous business domain?
  2. Can it be deployed independently without coordinated schema changes?
  3. Does it have distinct scaling requirements?
  4. Does it integrate with systems other than the shared database?
  5. Does it provide a required external API or protocol boundary?
  6. Can its performance or operational benefit be measured?
  7. Would a module, stored procedure or database view solve the requirement more simply?

If the only reason is “this is the modern architecture,” the design has not yet been justified.

Conclusion

Microservices are not inherently wrong, and database-centered architectures are not inherently obsolete.

The error is assuming that distributing the application tier automatically distributes the system’s capacity, or that moving relational processing out of the database automatically improves performance and scalability.

When every service ultimately reaches the same database:

  • the database remains the central capacity constraint;
  • additional services do not eliminate database work;
  • client-side composition can duplicate functionality already optimized by the database;
  • service-level parallelism can increase database contention;
  • synchronous APIs do not absorb traffic spikes;
  • security and access control can often be enforced directly by the database;
  • the service layer is best justified by external publication, interoperability, autonomous domains or cross-system orchestration.

A modern architecture is not defined by the number of APIs, services, containers or network calls. It is defined by placing each operation where it can be executed with the least complexity, least data movement, strongest consistency and lowest total cost.

Buying an enterprise database, treating it as a passive disk, and rebuilding its relational capabilities in intermediate services is not modernization. It is paying twice for more moving parts.