The Atomic Query Construction (AQC) design pattern is all about precision, clarity, and predictability. Over time, I’ve developed a consistent structure for AQC classes that may seem restrictive at first glance, but each restriction serves a practical purpose. Let’s break down why AQC classes are defined the way they are.

1. No Constructor: Avoiding Dependency Injection

In many design patterns, constructors are used to inject dependencies—services, repositories, or other objects. AQC classes intentionally avoid constructors. Why? Because the AQC philosophy assumes that the underlying service or query builder never changes internally.

Injecting a dependency through a constructor implies that the class can be swapped or configured differently for each instance. For AQC classes, this is unnecessary overhead. The logic is atomic, deterministic, and isolated. By not requiring a constructor, you eliminate boilerplate, reduce configuration errors, and keep the class focused purely on its query logic.

It also simplifies instantiation. You just create an instance whenever needed, without worrying about passing services or setup parameters that will never vary.

                                $getUsers = new GetUsers(); // simple and predictable
$result = $getUsers->handle(['status' => 'active']);
                            

2. Non-Static Methods: Encouraging Instance-Based Clarity

Some developers might wonder: why not make handle() static? A static method would avoid instantiation and look simpler. But static methods carry hidden costs: they obscure state, make testing harder, and encourage global coupling.

Using non-static methods ensures each instance is independent, even if it doesn’t hold state now. It’s a design discipline. By always creating an object before calling handle, the code communicates intent: “this is a self-contained, reusable atomic query unit.”

3. Single Public Method (handle) with params Array

The handle() method is the only public interface of an AQC class. This makes the API extremely predictable: no guessing which method to call or which method affects the query.

The params array standardizes input. Whether the query needs a filter, a sort order, or pagination, everything is passed through a single array. This avoids proliferating method signatures and keeps the class atomic.

                                $params = [
    'status' => 'active',
    'role' => 'admin',
    'sort' => 'created_at'
];

$result = (new GetUsers())->handle($params);
                            

Private methods handle the internal steps, such as building joins, filtering conditions, or selecting columns. They are invisible externally, which keeps the class contract simple and ensures maintainers only focus on the handle method.

4. Why This Pattern Works

This design achieves three key goals:

  • Predictability: Every AQC class looks and behaves the same way.
  • Simplicity: No unnecessary constructors, no dependency injection, no multiple public methods.
  • Flexibility: While the class is simple externally, private methods allow complex query construction internally.

By following these rules, teams can read, use, and maintain AQC classes without worrying about hidden state, dependencies, or inconsistent public APIs.

Final Thoughts

The structure of AQC classes—no constructor, non-static methods, single handle() public method accepting a params array—is deliberate. It enforces atomicity, simplicity, and predictability, which are the core principles of the Atomic Query Construction design pattern. Each class becomes a self-contained unit that can be easily reused, tested, and maintained without overengineering or unnecessary complexity.

Comments


Comment created and will be displayed once approved.