SF3文件夹结构中的DDD

时间:2016-08-11 15:26:54

标签: php domain-driven-design symfony

我在想一个广告网站,用户可以登录,发布新的列表并搜索现有的列表。我将完全遵循DDD原则,这是我的第一个项目。我之前从未在Symfony做过任何DDD。

以下是我对此的看法。你能否告诉我这是否正确,并建议更好的方法?

我可以看到两个域:用户和列表

搜索/显示/发布功能将存在于清单域中。在用户域中登录/注销。

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是一个好主意还是我应该在User和Listing捆绑包中存在持久性?

2 个答案:

答案 0 :(得分:17)

框架& DDD

你在这里做了一个错误的假设,这是"我将使用Symfony框架以DDD-ish方式实现我的应用程序"

不要这样做,Frameworks are just an implementation detail并为您的应用提供一种(更多)递送方式。我的意思是应用程序在Hexagonal Architecture

的上下文中

如果您从我们的某个上下文中查看以下示例,您会看到我们的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 in PHP book,其中所有这些概念都是用PHP中的表达实例来解释的(假设你已经阅读了蓝皮书和红皮书,尽管这本书作为一个独立的工作,同时仍然提供参考)

答案 1 :(得分:3)

使用Symfony构建的DDD应用程序的文件夹结构

我是第二个回答,但是我想提出一个文件夹结构的略微变体,这个变体在我参与的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
        // ...

使用此文件夹结构,您可以分隔onion architecture的不同图层。不同的文件夹清楚地传达了边界并允许层之间的依赖关系。我在DDD folder structures with Symfony上写了一篇博文,详细介绍了这种方法。

其他资源:

除此之外,我还建议您查看以下资源:

  • PHP DDD Cargo Sample:PHP 7 Eric Evans DDD书中使用的货物样本版本
  • Sylius:基于组件架构的Symfony构建的电子商务PHP框架

我从了解Sylius代码库中学到了很多东西 - 它是一个真实世界的项目并且相当庞大。他们进行各种测试,并投入大量精力来运送高质量的代码。