ユーザーがログインしたり、新しいリストを投稿したり、既存のリストを検索したりできる広告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
私の主な質問は次のとおりです。
ここで間違った仮定をします。それは「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(この本は参照を作成しながらスタンドアロンとして機能しますが、青い本と赤い本をすでに読んだと仮定します)の表現例で実際に説明されています。
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フォルダー構造 に関するブログ投稿を作成しました。このブログ投稿では、アプローチについて詳しく説明しています。
追加リソース:
それとは別に、次のリソースも参照することをお勧めします。
Syliusのコードベースを理解することで多くのことを学びました。これは実際のプロジェクトであり、かなり巨大です。彼らはあらゆる種類のテストを行っており、高品質のコードの出荷に多大な努力を払ってきました。