When I started my career as a developer, one of my senior team members used to construct queries in core PHP using concatenation based on different...
For further actions, you may consider blocking this person and/or reporting abuse
I seems to me you seen the CQRS pattern and you only took away separating each query in their own class.
While CQRS is over-engineering for most applications, there is a simpler pattern that has all the benefits mentioned in the post. And that is the repository pattern.
Basically all methods that address the same table in the database are grouped together. Like you would group CRUD methods in one controller.
The benefit of this pattern over AQC is that you can share code. Like the code for the
$scenarioargument in the examples.With the AQC pattern you need to create a trait to share the code. Which means instead of one class you need at least three.
The flaw with the more worked out
GetAllProductsexample seen in the third part of this series is that it should be another class. The reason is that it doesn't get all the products, just the ones that are filtered. That is atomic thinking for me.Thanks for the feedback. You’re right that AQC overlaps with CQRS and Repository, but the intent is different: instead of grouping everything in one repository (which can grow too big), AQC keeps each query as a self-contained unit and for CQRS it it does not force you to split read and write operations. That makes controllers/services depend only on the exact query they need and keeps tests very focused. For small apps Repository is fine, but in larger apps with many variations, I’ve found AQC keeps things cleaner.
And for the trait part if you are confused with naming convention, you can change them according to what suits you. Take
GetAllProductsfor example. Change it toGetProductsto better reflect to what it does and you are good to go.If you group everything in one repository, you are doing the repository pattern wrong.
It does force you to split read and write operations because the pattern singles out each query. If you meant splitting read an write databases, CQRS doesn't care because is depends on the connection string you assign to the operations.
You should test the queries as standalone. Only testing the queries in the context of a controller or a service isn't a good test, there is too much going on in those methods certainly if is a controller method.
How does it keep things cleaner?
The trait has nothing to do with naming. From this sentence it seems you don't understand what a trait is.
It is a bad idea to use more generic names to fit a broader functionality of the query.
At some point calling the abstraction is going to be almost as much code as calling the model, which means the abstraction has not enough added value.
Thanks again for engaging. I think there may have been a misunderstanding about what AQC is aiming for.
With AQC, I don’t create a new class for every variant of a query (like GetActiveProducts, GetStoreProducts, GetProductsWithStock, etc.). Instead, I define a single atomic query per intent — for example:
GetProduct --> always used to fetch a single product.
GetProducts --> always used to fetch multiple products.
All variations/conditions (active, in-stock, by store, etc.) are applied dynamically based on parameters passed in inside that one class. This way, the application always calls the same entry point for that operation, and the query logic stays consistent in one place.
That’s the main difference from Repository: in a repository you often end up with many different methods per entity other than the basic ones, keeping or violating repository pattern class grow big and big, while in AQC you stick to one atomic query per case and make it flexible with parameters.
For me, that’s how it keeps things cleaner in larger codebases — there’s one and only one class to fetch multiple products and that is GetProducts class to look at when you need to understand or update how products are queried.
So that way of working in a repository would look like this
The methods of a repository are not defined. You can make them as broad or as narrow as you like. It all depends on what is easiest for the application.
That's exactly I have come to ths AQC pattern. This shows the key difference in philosophy between Repository and AQC.
With Repository, you group multiple methods in one class per entity, and that can work well for smaller apps. But in my experience, as the application grows, the repository either ends up very broad (with dozens of methods like
getActiveProducts,getProductsByStore, etc.), or stays too generic (likegetMultiple($config)) where each usage has to reinvent the filter logic in the controller/service.With AQC, I want to avoid both extremes. Instead of an all-in-one repository, I define one query class per intent:
GetProduct --> always the way to fetch a single product (with dynamic conditions passed in).
GetProducts--> always the way to fetch multiple products (with dynamic conditions passed in).
That gives me a single place to look at when I need to understand or update how products are retrieved. The variations live inside the query class (not scattered across different repository methods or controllers), which keeps things consistent. Here's the example again.
So while Repository is flexible, I’ve found AQC to be more maintainable in large projects because every query has its own clear home, and controllers/services depend only on the exact atomic query they need.
I hope this clears things up and you see my point.
@xwero please read the 4th article of this series. i hope this clears many confusions.
dev.to/raheelshan/repository-patte...
The big problem with that post is you are assuming things that don't necessarily have to be true.
It is not because you write it often it becomes true.
First of all the naming of the method is wrong.
The bigger problem in that sentence is that you seem to think a class always needs be small. The class is as large or as small as it needs to be.
As a side note a god class has nothing to do with the size of the class. Please check your terminology before you use it.
I think there is the base problem I have overseen in our conversation.
Your
getProductsclass is mainly a very specified wrapper for the model builder pattern.Why would you go through all that effort just to end up with something that already is provided for you?
The flexibility you think your pattern has comes from the model builder pattern, not from your pattern.
Really think what the added value is of an abstraction before you start to use it. This is true for all design patterns you want to apply in your codebase.
Of course, I am not saying that the repository pattern always leads to bloated classes. What I am pointing out are the common scenarios where developers fall into that trap—creating method after method in repositories when they shouldn’t. While working with .Net Framework i have seen most of the time people create Helper or DAL classes and do this.
I agree with you that not every method in a repository has to carry multiple conditions. But my concern is that when the repository is used for all CRUD operations and developers start stacking conditions and variants, the class becomes too large and takes on responsibilities it shouldn’t. At this point it should be broken into multiple classes instead of being a
Repository.When I said “small class,” I didn’t mean it literally has to be short. What I meant is: once a class is doing too much work, becoming harder to navigate, and carrying more responsibilities than it should—that’s where the problem lies. Ideally, we should aim for clarity: a class with a single responsibility, broken into smaller private methods, exposing only one public entry point.
Now, about
GetProductsbeing a wrapper for the model builder—you’re absolutely right. It is a wrapper. But I deliberately do that for two reasons:Centralization
– Instead of calling queries directly from controllers, helpers, or even (accidentally) in views, I prefer to have one single source of truth. This way, any product-related query logic lives in one place and is reusable across the application.
Reusability and consistency
– By collecting query logic into AQC classes, I avoid scattering small queries throughout the codebase. That makes the overall system easier to maintain and extend.
You also mentioned that the flexibility I highlight really comes from the underlying model builder and not from AQC itself. I don’t fully agree. While I built AQC in Laravel for demonstration purposes, the pattern itself is aimed to be used in any framework. In fact, ORMs like Eloquent or others achieve the same idea: they construct queries dynamically based on methods like
where(),whereIn(),whereRaw(). AQC brings that same principle but in a way that I can control and extend independently using parameters.Take a look at this same code.
So even if we remove Laravel’s builder or any ORM from the picture and write raw SQL, the same parameter-driven query construction is possible through AQC. That’s where I see the added value: it’s not tied to Eloquent’s builder but rather to the concept of query construction itself.
Lastly, I understand you’re viewing this within the Laravel ecosystem, but my experience extends across .NET, Python, and PHP. In different frameworks, I have repeatedly seen teams fall into the trap of stacking methods inside repositories. That’s why AQC is intended for a general audience, not just for Laravel developers.
If a class takes on responsibilities it shouldn't breaking up the class will not solve the problem.
This also doesn't prevent bad code. No pattern prevents bad code.
The point I wanted to make with my previous comment is that if the application has a query builder pattern build-in you already have a reusable centralized way to create queries.
No need to add a wrapper with custom logic.
Now we are getting somewhere. I agree that when the application doesn't include a query builder pattern, having methods that make it easier to create a query can be useful.
But I would go for a query builder pattern over AQC because with the builder pattern you don't need to change the builder code. With AQC you need to change the code for each field that is removed, changed or added.
I use the Laravel code because that is what you are using.
For .NET there is Linq, In Python there is SQLAlchemy core. In PHP you can use Doctrine.
People that are aware of common design patterns don't need your pattern to create a maintainable codebase. And because they are using a common design pattern other people will pick it up faster.
I'm not against thinking outside of the box, but the solution should provide value in a way other solutions can't.
AQC is really about discipline. Eloquent, Doctrine, SQLAlchemy, or LINQ give you the building blocks, but they don’t provide a project-wide convention for how query logic should be shared and reused. That’s where AQC adds value—it doesn’t replace the builder, it organizes and centralizes how your application interacts with it.
I’ve worked across .NET, Python, and PHP, and in all ecosystems I’ve seen teams fall into the same trap: query snippets scattered across controllers, helpers, and services. The ORM doesn’t prevent that. AQC is meant as a discipline layer on top, keeping queries reusable, predictable, and testable.
I agree common patterns are easier to pick up—but they also come with common pitfalls. AQC is my attempt at addressing those pitfalls. It’s not reinventing the wheel, but putting a guardrail on how the wheel is used.
How don't they provide project-wide conventions? They have an API.
The query logic you are mentioning are things like
If that is correct, just using the query builder methods prevent the need to write that logic. That is what I wanted to show with the snippet in my previous comment.
Do you really think people are not going to write bad code with your pattern? You are underestimating them.
