Atomic Query Construction Design Pattern: Eliminating Repeated Query Snippets for Good
Muhammad Raheel
March 23rd, 2026 · 9 min read
There’s a quiet lie sitting inside most codebases.
It doesn’t throw errors. It doesn’t break builds. It just sits there, multiplying. Lets understand how.
Product::where('status', 'active')->get();
You’ve written it. Everyone has.
Then somewhere
Product::where('status', 'active')->where('category_id', 2)->get();
And again:
Product::latest()->paginate();
Looks harmless. Feels normal.
It’s not.
The Misunderstood DRY
Most developers think DRY means:
- Don’t repeat functions
- Don’t copy-paste logic
- Extract reusable methods
That’s surface-level DRY.
The deeper rule is this:
And that’s exactly what happens when Product::where(...) or Product::latest(...) is scattered across your application.
Because those lines are not just syntax. They encode meaning:
- How a product query starts
- What defaults are applied
- What assumptions exist
Every time you rewrite them, you risk redefining that meaning.
The Real Problem: Repeated Entry Points
The issue is not the where clause itself.
The issue is the entry point:
Product::latest(...)
That’s where the query begins. That’s where logic starts taking shape.
If this entry point exists in multiple places, then:
- You have multiple definitions of the same concept
- You have no control over consistency
- You’ve made future changes harder than they need to be
You didn’t just repeat code.
You repeated the starting point of logic.
The AQC Rule
Atomic Query Construction (AQC) introduces a strict rule:
In practical terms:
If you search Product::latest (or any query start) across the codebase, you should find exactly one occurrence.
And it must live inside a dedicated execution class.
Not in controllers, services, jobs or helpers etc.
One place. No exceptions.
What This Actually Looks Like
Instead of scattering queries:
// Controller
Product::latest()->where('category_id', 1)->get();
// Service
Product::latest()->where('brand_id', 2)->get();
// Somewhere else
Product::latest()->paginate();
You define a single owner of that query:
class GetProducts
{
public function handle($params = [])
{
$productObj = Product::latest('id');
// apply filters
if (isset($params['category_id']) && $params['category_id'] > 0) {
$productObj->where('category_id', $params['category_id']);
}
if (isset($params['brand_id']) && $params['brand_id'] > 0) {
$productObj->where('brand_id', $params['brand_id']);
}
// add more filters as needed
// select columns
if (isset($params['columns']) && ($params['columns']) > 0) {
$productObj->select($params['columns']);
} else {
$productObj->select('*');
}
// sorting
if (isset($params['sortBy']) && isset($params['type'])) {
$productObj->orderBy($params['sortBy'], $params['type']);
}
return isset($params['paginate'])
? $productObj->paginate(Product::PAGINATE)
: $productObj->get();
}
}
And everywhere else:
(new GetProducts())->handle($params);
That’s the entire system.
What Changed?
You didn’t extract a reusable base.
You removed duplication at the root.
Before:
- Query entry point repeated everywhere
- Slight variations across files
- Logic scattered and inconsistent
After:
- One entry point
- One owner
- One place to evolve logic
This is not about organizing queries.
This is about eliminating multiple definitions of the same intent.
Why This Is Actual DRY
Let’s be precise:
If Product::latest('id') appears in multiple places,
you are repeating yourself.
Even if everything after it is different.
Because that line defines:
- Default ordering
- Query starting behavior
- System-wide assumptions
Repeating it = duplicating intent.
AQC ensures that never happens.
But This Feels Strict
Good. Because without that strictness, this falls apart instantly.
The moment you allow:
“Just this one query in a controller…”
you’re back to:
- scattered logic
- inconsistent behavior
- silent bugs
AQC works because it removes that freedom.
Composition Still Exists (Just Not Where You Think)
You’re not losing flexibility.
You’re just relocating it.
Instead of composing queries across files, you compose inside the execution class using parameters.
- Filters → params
- Sorting → params
- Columns → params
- Pagination → params
One entry point. Infinite controlled variations.
The One-Entry Principle
If there’s one rule that defines AQC, it’s this:
If you search your codebase and find multiple starting points:
You are not DRY.
You are just organized duplication.
Final Thought
If appears in ten places in your codebase, you don’t have ten queries.Product::latest()
You have ten slightly different interpretations of how a product query should behave.
That’s not flexibility. That’s drift.
Atomic Query Construction doesn’t manage that drift. It eliminates it.