In the first article about the point where plain CRUD stops being enough I wrote that DDD becomes useful because of the weight of the rules, not because of project size.
I had another trap with directory structure: it is easy to create Domain/, Application/, and Infrastructure/ and feel that the project has become domain-driven.
But if the controller still decides whether an entity may be activated and a generic Service still contains every rule, only the file addresses changed.
A Typical DDD Folder Structure Can Look Convincing
Product/├── Domain/│ ├── Entity/│ ├── ValueObject/│ └── Service/├── Application/│ ├── Command/│ └── Service/└── Infrastructure/ ├── Persistence/ └── Http/Now on code review I look past the directory tree and inspect an actual state change.
Who decides whether the operation is allowed? Where does the invariant live? Can a direct update() bypass the rule? What happens if the same action is triggered outside HTTP?
A Domain Folder Does Not Help If the Model Remains Anemic
final class Product{ public string $status; public ?string $code; public ?string $name;} final class ProductService{ public function activate( Product $product, ): void { if ($product->status !== 'draft') { throw new LogicException(); } You can place ProductService under Domain/Service, but that does not make the rule better modeled.
Product still allows any caller to assign status = active directly. The problem is not the folder name, but ownership of behavior.
I Start with the Rule Owner, Not the Namespace
In the article about where a business rule should live I explored exactly this question: controller, service, or domain model?
If Product itself should know whether it can transition, I want that behavior on Product. If the rule belongs to a value, a Value Object may fit. If it genuinely spans several domain concepts without one owner, a Domain Service may be appropriate.
Once the Rule Lives in the Model, the Structure Starts to Mean Something
final class Product{ public function activate(): void { if ( $this->status !== ProductStatus::DRAFT ) { throw new DomainException( 'Product cannot be activated.', ); } if (! $this->isComplete()) { throw new DomainException( 'Product is incomplete.',The Application Layer Should Not Become the New Home for All Logic
In the article about Application Service and Domain Service I wrote that an Application Service coordinates a use case: transaction, repository, external dependencies, and execution order.
If moving to a "DDD structure" only relocates code from app/Services into Application/Services, the responsibility has not changed.
Infrastructure Holds Technical Details, Not Business Meaning
Domain:What is valid? Application:How do we execute the use case? Infrastructure:How do we talk to:- database- queue- filesystem- external APIEven Correct Layers Do Not Define Domain Boundaries
Global Domain/, Application/, and Infrastructure/ folders do not mean a bounded context has been discovered.
In the article about how I find domain boundaries in a growing system I wrote that boundaries become visible through different meanings, lifecycles, invariants, and reasons to change.
Folders can represent a boundary we have already discovered, but they cannot discover it for us.
Sometimes It Is Better to Start Simple and Let Structure Grow
Early: Product/ Product.php ActivateProduct.php ProductRepository.php Later, when complexity needs it: Product/ Domain/ Application/ Infrastructure/I do not consider a flat structure a problem when responsibilities are clear.
Layers become useful when they help navigate complexity that actually exists rather than merely creating the appearance of architecture.
A Domain Rule Test Says More Than a Folder Name
public function test_incomplete_product_cannot_be_activated(): void{ $product = Product::draft( code: null, name: 'Example', ); $this->expectException( DomainException::class, ); $product->activate();}In AVN Center, Structure Became a Consequence of the Model
While working on AVN Center, it became important for me not to start with the question "which folders do we need?"
Questions about rules, decision owners, boundaries, and the difference between use-case coordination and domain decisions were much more useful.
After that, structure becomes more natural because it reflects responsibilities that are already understood. I intentionally do not expose the internal AVN Center module map here.
How I Check Whether It Is DDD or Only Folder Structure
I look for several signals:
- Are important business rules visible in the domain model?
- Does an entity protect valid state instead of only storing fields?
- Does the Application Service coordinate the use case rather than own every rule?
- Does Infrastructure contain technical details rather than domain meaning?
- Do module boundaries follow language, invariants, and lifecycles?
- Do folders explain responsibilities rather than replace them?
Folders Should Reflect the Model, Not Replace It
I have not abandoned Domain/, Application/, or Infrastructure/. They are useful when responsibilities are already understood.
But I no longer start DDD with a directory tree.
I first look for rules, invariants, decision owners, and boundaries. Then structure helps make those decisions visible in code.
DDD does not start with a Domain folder. It starts when domain rules stop being accidental if statements in controllers and become part of the model.



