DDD in SF3 folder structure

I am thinking about an advertising site where users can log in, post new listings and search for existing ones. I am going to make this my first project entirely based on DDD principles. I have never done DDD in Symfony.

Below are my thoughts on this. Could you tell me if the tips on the best ways are right?

I see two domains: user and listing

The search / display / publish function will be in the listing domain. Log in / out in real time in the user's domain.

Structure of an Example SF3 Catalog

app/ ListingBundle/ src/ Listing.php SearchService.php ListingRepositoryInterface.php Controller/ public/ ListingController.php protected/ ListingController.php Resource/ view/ public/ detail.twig.html protected/ edit.twig.html UserBundle/ src/ User.php AuthService.php UserRepositoryInterface.php Controller/ public/ UserController.php protected/ UserController.php Resource/ view/ public/ login.twig.html protected/ dashboard.twig.html PersistenceBundle src/ UserRepository.php ListingRepository.php 

My main questions are:

  • Is this structure correct?
  • Is it good to have separate protected and public controllers with the same name?
  • Where's it going, like a page on a user's backend page on a website showing the latest lists submitted by the user? Where is the border between the two domains?
  • Is a PersistenceBundle a good idea or should I stay consistent inside the User and Listing package?
+6
source share
2 answers

Frames and DDD

You make the wrong assumption here, which is "I'm going to use the Symfony platform to implement my application using DDD-ish . "

Do not do this, the Framework is just detailed implementation information and provide one (more) delivery methods for the Application . And I mean the application here in the context of hexagonal architecture .

If you look at the following example from one of our contexts, you will see that our ApiClient context contains three levels (top-level directory structure). Application (contains usage services), Domain (contains models and behavior) and Infrastructure (contains infrastructure issues such as storage and delivery). I focused on integrating Symfony and persistence here, since this was a question of the original OP:

 src/ApiClient ├── Application │  ├── ApiClient │  │  ├── CreateApiClient │  │  ├── DisableApiClient │  │  ├── EnableApiClient │  │  ├── GetApiClient │  │  ├── ListApiClient │  │  ├── RemoveApiClient │  │  └── ChangeApiClientDetails │  ├── ClientIpAddress │  │  ├── BlackListClientIpAddress │  │  ├── CreateClientIpAddress │  │  ├── ListByApiClientId │  │  ├── ListClientIpAddresses │  │  └── WhiteListClientIpAddress │  └── InternalContactPerson │  ├── CreateInternalContactPerson │  ├── GetInternalContactPerson │  ├── GetByApiClientId │  ├── ListContacts │  ├── ReassignApiClient │  └── Remove ├── Domain │  └── Model │  ├── ApiClient │  ├── ClientIpAddress │  └── InternalContactPerson └── Infrastructure ├── Delivery │  └── Http │  └── SymfonyBundle │  ├── Controller │  │  ├── ApiClientController.php │  │  ├── InternalContactController.php │  │  └── IpAddressController.php │  ├── DependencyInjection │  │  ├── Compiler │  │  │  ├── EntityManagerPass.php │  │  │  └── RouterPass.php │  │  ├── Configuration.php │  │  ├── MetadataLoader │  │  │  ├── Adapter │  │  │  │  ├── HateoasSerializerAdapter.php │  │  │  │  └── JMSSerializerBuilderAdapter.php │  │  │  ├── Exception │  │  │  │  ├── AmbiguousNamespacePathException.php │  │  │  │  ├── EmptyMetadataDirectoryException.php │  │  │  │  ├── FileException.php │  │  │  │  ├── MalformedNamespaceException.php │  │  │  │  └── MetadataLoadException.php │  │  │  ├── FileMetadataLoader.php │  │  │  ├── MetadataAware.php │  │  │  └── MetadataLoaderInterface.php │  │  └── MFBApiClientExtension.php │  ├── DTO │  │  └── ApiClient │  │  └── ChangeInternalContact │  │  ├── ChangeInternalContactRequest.php │  │  └── ChangeInternalContactResponse.php │  ├── MFBApiClientBundle.php │  ├── Resources │  │  ├── config │  │  │  ├── domain_services.yml │  │  │  ├── metadata_loader.yml │  │  │  ├── routing.yml │  │  │  └── services.yml │  │  ├── hateoas │  │  │  └── ApiClient │  │  │  ├── Application │  │  │  │  ├── ApiClient │  │  │  │  │  ├── CreateApiClient │  │  │  │  │  │  └── CreateApiClientResponse.yml │  │  │  │  │  └── ListApiClient │  │  │  │  │  └── ListApiClientResponse.yml │  │  │  │  ├── ClientIpAddress │  │  │  │  │  ├── CreateClientIpAddress │  │  │  │  │  │  └── CreateClientIpAddressResponse.yml │  │  │  │  │  ├── ListByApiClientId │  │  │  │  │  │  └── ListByApiClientIdResponse.yml │  │  │  │  │  └── ListClientIpAddresses │  │  │  │  │  └── ListClientIpAddressesResponse.yml │  │  │  │  └── InternalContactPerson │  │  │  │  ├── Create │  │  │  │  │  └── CreateResponse.yml │  │  │  │  └── List │  │  │  │  └── ListResponse.yml │  │  │  └── Domain │  │  │  ├── ApiClient │  │  │  │  └── ApiClient.yml │  │  │  ├── ClientIpAddress │  │  │  │  └── ClientIpAddress.yml │  │  │  └── InternalContactPerson │  │  │  └── InternalContactPerson.yml │  │  └── serializer │  │  ├── ApiClient │  │  │  ├── Application │  │  │  │  ├── ApiClient │  │  │  │  │  ├── CreateApiClient │  │  │  │  │  │  ├── ContactPersonRequest.yml │  │  │  │  │  │  ├── CreateApiClientRequest.yml │  │  │  │  │  │  └── CreateApiClientResponse.yml │  │  │  │  │  └── GetApiClient │  │  │  │  │  └── GetApiClientResponse.yml │  │  │  │  ├── ClientIpAddress │  │  │  │  │  └── CreateClientIpAddress │  │  │  │  │  ├── CreateClientIpAddressRequest.yml │  │  │  │  │  └── CreateClientIpAddressResponse.yml │  │  │  │  └── InternalContactPerson │  │  │  │  ├── Create │  │  │  │  │  ├── CreateRequest.yml │  │  │  │  │  └── CreateResponse.yml │  │  │  │  ├── Get │  │  │  │  │  └── GetResponse.yml │  │  │  │  ├── List │  │  │  │  │  └── ListResponse.yml │  │  │  │  └── ReassignApiClient │  │  │  │  └── ReassignApiClientRequest.yml │  │  │  └── Domain │  │  │  ├── ApiClient │  │  │  │  ├── ApiClient.yml │  │  │  │  └── ContactPerson.yml │  │  │  ├── ClientIpAddress │  │  │  │  └── ClientIpAddress.yml │  │  │  └── InternalContactPerson │  │  │  └── InternalContactPerson.yml │  │  └── Bundle │  │  └── DTO │  │  └── ApiClient │  │  └── ChangeInternalContact │  │  └── ChangeInternalContactRequest.yml │  └── Service │  └── Hateoas │  └── UrlGenerator.php └── Persistence ├── Doctrine │  ├── ApiClient │  │  ├── ApiClientRepository.php │  │  └── mapping │  │  ├── ApiClientId.orm.yml │  │  ├── ApiClient.orm.yml │  │  ├── CompanyName.orm.yml │  │  ├── ContactEmail.orm.yml │  │  ├── ContactList.orm.yml │  │  ├── ContactName.orm.yml │  │  ├── ContactPerson.orm.yml │  │  ├── ContactPhone.orm.yml │  │  └── ContractReference.orm.yml │  ├── ClientIpAddress │  │  ├── ClientIpAddressRepository.php │  │  └── mapping │  │  ├── ClientIpAddressId.orm.yml │  │  ├── ClientIpAddress.orm.yml │  │  └── IpAddress.orm.yml │  └── InternalContactPerson │  ├── InternalContactPersonRepository.php │  └── mapping │  ├── InternalContactPersonId.orm.yml │  └── InternalContactPerson.orm.yml └── InMemory ├── ApiClient │  └── ApiClientRepository.php ├── ClientIpAddress │  └── ClientIpAddressRepository.php └── InternalContactPerson └── InternalContactPersonRepository.php 94 directories, 145 files 

Quite a lot of files!

You can see that I use the package as a port for the application (although the naming is small, it should not be Http delivery, because in the strict sense, the Hexagon architecture is the application port for the application ). I highly recommend you read DDD in a PHP book , where all these concepts are actually explained by expressive examples in PHP (if you read the blue book and the red book, although this book works autonomously while still making links).

+10
source

Folder structure for DDD applications created using Symfony

I have tPl0ch's second answer, but I would like to offer a small version of the folder structure, which was useful in several projects with Symfony where I was involved. For your specific domain, the folder structure may look like this:

 app Listing Domain Model Listing.php Repository ListingRepository.php Service SearchService.php Infrastructure Repository DoctrineListingRepository.php // or some other implementation Resources // symfony & doctrine config etc. Service ElasticSearchService.php // or some other implementation ListingInfrastructureBundle.php Presentation Controller ViewListingController.php // assuming this is the "public" part EditListingController.php // assuming this is the "protected" part Forms ListingForm.php Resources // symfony config & views etc. ListingPresentationBundle.php User // ... Infrastructure Service AuthService.php // ... 

In this folder structure, you highlight different levels of onion architecture . Different folders clearly inform the boundaries and allow dependencies between layers. I wrote a blog post on the DDD folder structure with Symfony , which details the approach.


Additional resources:

In addition, I also recommend taking a look at the following resources:

  • PHP DDD Cargo Sample : PHP 7 Version of the cargo sample used in Eric Evans' DDD book.
  • Sylius : eCommerce PHP framework built on top of Symfony with component-based architecture

I learned a lot from understanding the Sylius code base - this is a real-world project and quite large. They have all kinds of tests and put a lot of effort into delivering high quality code.

+3
source

All Articles