Skip to main content

Anti-Pattern: Leaky Service Abstraction

Description

A Leaky Abstraction occurs when the implementation details of a lower layer (e.g., Data Access or Service) are exposed to or required by a higher layer (e.g., Controller or View). Ideally, a Service should provide a curated, finalized result.

Evidences in Codebase

Returning IQueryable<T>

Many services in the codebase return EITServiceResponse<IQueryable<Entity>>.

  • The Leak: IQueryable is a promise of a query, not the data itself. It exposes the underlying ORM capabilities (EF) to the consumer.
  • The Consumer: The Controller or View can append .Where(), .OrderBy(), or loop through it.

Impact

  • Unpredictable Execution: The SQL query is not executed inside the Service method, but triggered later, often in the View (.cshtml) when the loop starts.
  • N+1 Queries: If the View accesses navigation properties (e.g., item.Order.Customer.Name), EF lazy-loads them one by one, causing performance disasters.
  • Loss of Encapsulation: The Service loses control over what data is actually fetched. Database optimization becomes impossible because the queries are constructed dynamically in the Presentation layer.

Remediation

  • Return Materialized Data: Services should return List<T>, IEnumerable<T> (materialized), or Paged results.
  • Return DTOs: Never return usage of IQueryable<Entity>. Project to IQueryable<Dto> and then .ToList() or .ToArray() inside the Service boundary. Use Performance Stabilization Pillar 2: Prefetching to handle graph complexity within the Service.

2026 Audit Findings

  • Occurrences: ~43 Service methods returning IQueryable<T>.
  • Analysis: Forces Controllers to execute queries, promoting N+1 issues and transaction scope ambiguity.