web-dev-qa-db-ja.com

SF3フォルダー構造のDDD

ユーザーがログインしたり、新しいリストを投稿したり、既存のリストを検索したりできる広告Webサイトを検討しています。これをDDDの原則に完全に従った私の最初のプロジェクトにします。私はこれまでSymfonyでDDDを行ったことがありません。

以下はこれについての私の考えです。これが正しいかどうか、そしてより良い方法についてのアドバイスを教えてください。

ユーザーとリストの2つのドメインが表示されます

検索/表示/投稿機能は、リストドメインにあります。ログイン/ログアウトはユーザードメインでライブで行われます。

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

私の主な質問は次のとおりです。

  • この構造は正しいですか?
  • 同じ名前の保護されたコントローラーとパブリックコントローラーを別々に持つことは良い考えですか?
  • ユーザーが投稿した最近のリストを表示するWebサイトのユーザーバックエンド部分のページのように、どこに行きますか?これらの2つのドメイン間の境界はどこにありますか?
  • PersistenceBundleは良い考えですか、それともUser and Listingバンドル内に永続性をライブにする必要がありますか?
9
Vladimir Hraban

フレームワークとDDD

ここで間違った仮定をします。それは「Symfonyフレームワークを使用してアプリをDDD風の方法で実装する」です。

これを行わないでください、 フレームワークは単なる実装の詳細 であり、アプリケーション。そして、私はここで Hexagonal Architecture のコンテキストでのアプリケーションを意味します。

コンテキストの1つから次の例を見ると、ApiClientコンテキストに3つのレイヤー(最上位のディレクトリ構造)が含まれていることがわかります。 アプリケーション(ユースケースサービスを含む)、ドメイン(モデルを含むおよび動作)およびインフラストラクチャ(永続性や配信などのインフラストラクチャの懸念事項が含まれます)。私はここで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配信、厳密な意味でHexagonal Architectureであるため、App-To-App Port )。 DDD in PHP book ここでこれらすべての概念を読むことを強くお勧めしますPHP(この本は参照を作成しながらスタンドアロンとして機能しますが、青い本と赤い本をすでに読んだと仮定します)の表現例で実際に説明されています。

17
Thomas Ploch

Symfonyで構築されたDDDアプリケーションのフォルダー構造

2番目の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
        // ...

このフォルダ構造を使用して、 オニオンアーキテクチャ のさまざまなレイヤーを分離します。さまざまなフォルダーが境界を明確に伝え、レイヤー間の依存関係を許可します。 Symfonyを使用したDDDフォルダー構造 に関するブログ投稿を作成しました。このブログ投稿では、アプローチについて詳しく説明しています。


追加リソース:

それとは別に、次のリソースも参照することをお勧めします。

  • PHP DDD貨物サンプル :PHP 7 Eric EvansDDDブックで使用されている貨物サンプルのバージョン
  • Sylius :eコマースPHPコンポーネントベースのアーキテクチャを備えたSymfony上に構築されたフレームワーク

Syliusのコードベースを理解することで多くのことを学びました。これは実際のプロジェクトであり、かなり巨大です。彼らはあらゆる種類のテストを行っており、高品質のコードの出荷に多大な努力を払ってきました。

3
Fabian Keller