DDD в структуре папок SF3

Я думаю через рекламный сайт, где пользователи могут войти в систему, размещать новые списки и искать существующие. Я собираюсь сделать этот мой первый проект полностью следуя принципам DDD. Я никогда раньше не делал DDD в Symfony.

Ниже приведены мои мысли об этом. Не могли бы вы сказать мне, если это правильно и советы о лучших способов?

Я вижу два домена: User и Listing

функции поиска/отображения/публикации будут жить в листинге домен. Вход / выход в реальном времени в домене пользователя.

SF3 пример структуры каталога -

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

мои основные вопросы:

  • эта структура правильная?
  • хорошо ли иметь отдельные защищенные и общедоступные контроллеры с одинаковым именем?
  • куда идут вещи, как страница на пользовательской части веб-сайта, показывающая последние объявления, опубликованные пользователем? Где граница между этими двумя Домены?
  • является ли PersistenceBundle хорошей идеей или я должен иметь persistence live внутри пользователя и листинга?

2 ответов


фреймворки & DDD

Вы делаете неправильное предположение здесь, которое "Я собираюсь использовать Symfony framework для реализации моего приложения в DDD-ish способом".

не делай этого, рамки-это просто деталь реализации и представить один (или более) методов доставки приложение. И я имею в виду применение здесь, в контексте шестиугольная Архитектура.

если вы посмотрите на следующий пример из одного из наших контекстов, вы увидите, что наш ApiClient контекст содержит три слоя (структура каталогов верхнего уровня). приложение (содержит службы прецедентов),домен (содержит модели и поведение) и инфраструктура (содержит проблемы инфраструктуры, такие как постоянство и доставка). Я сосредоточился на интеграции Symfony и постоянстве здесь, так как это о чем был первоначальный вопрос 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

довольно много файлов!

вы можете видеть, что я использую пакет как порт приложения (именование немного, хотя, это не должно быть Http доставка, так как в строгом смысле слова Гексагональной Архитектуры это Порт App-To-App). Я настоятельно рекомендую вам прочитать DDD в PHP book где все эти понятия фактически объяснено выразительными примерами в PHP (предполагая, что вы уже прочитали синюю книгу и Красную книгу, хотя эта книга работает как отдельная, все еще делая ссылки).


структура папок для приложений DDD, построенных с помощью Symfony

Я второй ответ tPl0ch, но хотел бы предложить небольшой вариант структуры папок, который был полезен в нескольких проектах с Symfony, в которых я участвовал. Для вашего конкретного домена структура папок может выглядеть следующим образом:

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
        // ...

С помощью этой структуры папок вы разделяете различные слои лук архитектуры. Другое папки четко связывают границы и разрешенные зависимости между слоями. Я написал сообщение в блоге на DDD структуры папок с Symfony который подробно описывает подход.


Дополнительные Материалы:

кроме того, я также рекомендую взглянуть на следующие ресурсы:

  • PHP DDD образец груза: PHP 7 версия образца груза, используемого в Eric Evans DDD книга
  • Sylius: PHP-фреймворк электронной коммерции, построенный поверх Symfony с компонентной архитектурой

Я многому научился от понимания базы кода Sylius - это реальный проект и довольно огромный. Они имеют все виды тестов и приложили много усилий для доставки высококачественного кода.