I am thinking through an ad website where Users can login, post new Listings and search existing ones. I am going to make this my first project following DDD principles completely. I have never done any DDD in Symfony before.
Below are my thoughts about this. Could you please tell me if this is correct and advice on better ways?
I can see two domains: User and Listing
Searching/displaying/posting functionality will live in Listing domain. Logging in/out live in a User domain.
SF3 directory example structure is
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:
You make a wrong assumption here, which is "I am going to use the Symfony framework to implement my app in a DDD-ish way".
Don't do this, Frameworks are just an implementation detail and provide one (ore more) delivery methods for your Application. And I mean Application here in the context of the Hexagonal Architecture.
If you look at the following example from one of our contexts you see that our ApiClient
context contains three layers (top-level directory structure). Application (contains the use case services), Domain (contains the models and behaviour) and Infrastructure (contains infrastructure concerns like persistence and delivery). I focused on the Symfony integration and the persistence here, since this is what the OP's original question was about:
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 am using the bundle as a Port of the Application (the naming is a little bit of though, it should not be Http
delivery, since in the strict sense of the Hexagonal Architecture it is an App-To-App Port). I strongly advise you to read the DDD in PHP book where all these concepts are actually explained with expressive examples in PHP (assuming you have read the blue book and the red book already, although this book works as a standalone while still making references).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With