{"id":14983613,"url":"https://github.com/codelytv/php-ddd-example","last_synced_at":"2025-05-14T15:07:54.483Z","repository":{"id":37622978,"uuid":"74026995","full_name":"CodelyTV/php-ddd-example","owner":"CodelyTV","description":"🐘🎯 Hexagonal Architecture + DDD + CQRS in PHP using Symfony 7","archived":false,"fork":false,"pushed_at":"2024-08-06T15:12:23.000Z","size":2136,"stargazers_count":2979,"open_issues_count":57,"forks_count":1081,"subscribers_count":82,"default_branch":"main","last_synced_at":"2024-10-29T14:53:44.832Z","etag":null,"topics":["behat","bounded-context","codely","codelytv","cqrs","ddd","docker","doctrine","domain-driven-design","hexagonal-architecture","laravel","microservice","microservices-architecture","monorepo","php","php8","phpunit","symfony","symfony5","testing"],"latest_commit_sha":null,"homepage":"https://pro.codely.tv/library/ddd-en-php","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CodelyTV.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"custom":"https://bit.ly/CodelyTvPro"}},"created_at":"2016-11-17T12:56:16.000Z","updated_at":"2024-10-28T14:25:32.000Z","dependencies_parsed_at":"2023-10-03T14:55:23.443Z","dependency_job_id":"2043b453-a36b-4b58-818c-55068c593163","html_url":"https://github.com/CodelyTV/php-ddd-example","commit_stats":{"total_commits":388,"total_committers":37,"mean_commits":"10.486486486486486","dds":"0.36855670103092786","last_synced_commit":"9271c467943f835ab3b6e5ba30bdbd710aca9d73"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Fphp-ddd-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Fphp-ddd-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Fphp-ddd-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Fphp-ddd-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CodelyTV","download_url":"https://codeload.github.com/CodelyTV/php-ddd-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248027400,"owners_count":21035594,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["behat","bounded-context","codely","codelytv","cqrs","ddd","docker","doctrine","domain-driven-design","hexagonal-architecture","laravel","microservice","microservices-architecture","monorepo","php","php8","phpunit","symfony","symfony5","testing"],"created_at":"2024-09-24T14:07:38.136Z","updated_at":"2025-04-09T11:03:00.680Z","avatar_url":"https://github.com/CodelyTV.png","language":"PHP","readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://codely.com\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://codely.com/logo/codely_logo-dark.svg\"\u003e\n      \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://codely.com/logo/codely_logo-light.svg\"\u003e\n      \u003cimg alt=\"Codely logo\" src=\"https://codely.com/logo/codely_logo.svg\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e\n  🐘🎯 Hexagonal Architecture, DDD \u0026 CQRS in PHP\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/CodelyTV\"\u003e\u003cimg src=\"https://img.shields.io/badge/Codely-OS-green.svg?style=flat-square\" alt=\"Codely Open Source projects\"/\u003e\u003c/a\u003e\n    \u003ca href=\"http://pro.codely.tv\"\u003e\u003cimg src=\"https://img.shields.io/badge/CodelyTV-PRO-black.svg?style=flat-square\" alt=\"CodelyTV Courses\"/\u003e\u003c/a\u003e\n    \u003ca href=\"#\"\u003e\u003cimg src=\"https://img.shields.io/badge/Symfony-7-purple.svg?style=flat-square\u0026logo=symfony\" alt=\"Symfony 7\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://shepherd.dev/github/CodelyTV/php-ddd-example\"\u003e\u003cimg src=\"https://shepherd.dev/github/CodelyTV/php-ddd-example/coverage.svg\" alt=\"Type Coverage\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/CodelyTV/php-ddd-example/actions\"\u003e\u003cimg src=\"https://github.com/CodelyTV/php-ddd-example/workflows/CI/badge.svg?branch=master\" alt=\"CI pipeline status\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Example of a \u003cstrong\u003ePHP application using Domain-Driven Design (DDD) and Command Query Responsibility Segregation\n  (CQRS) principles\u003c/strong\u003e keeping the code as simple as possible.\n  \u003cbr /\u003e\n  \u003cbr /\u003e\n  Take a look, play and have fun with this.\n  \u003ca href=\"https://github.com/CodelyTV/php-ddd-example/stargazers\"\u003eStars are welcome 😊\u003c/a\u003e\n  \u003cbr /\u003e\n  \u003cbr /\u003e\n  \u003ca href=\"https://www.youtube.com/watch?v=1kaP39W80zQ\"\u003eView Demo\u003c/a\u003e\n  ·\n  \u003ca href=\"https://github.com/CodelyTV/php-ddd-example/issues\"\u003eReport a bug\u003c/a\u003e\n  ·\n  \u003ca href=\"https://github.com/CodelyTV/php-ddd-example/issues\"\u003eRequest a feature\u003c/a\u003e\n\u003c/p\u003e\n\n## 🚀 Environment Setup\n\n### 🐳 Needed tools\n\n1. [Install Docker](https://www.docker.com/get-started)\n2. Clone this project: `git clone https://github.com/CodelyTV/php-ddd-example php-ddd-example`\n3. Move to the project folder: `cd php-ddd-example`\n\n### 🛠️ Environment configuration\n\n1. Create a local environment file (`cp .env .env.local`) if you want to modify any parameter\n\n### 🔥 Application execution\n\n1. Install all the dependencies and bring up the project with Docker executing: `make build`\n2. Then you'll have 3 apps available (2 APIs and 1 Frontend):\n   1. [Mooc Backend](apps/mooc/backend): http://localhost:8030/health-check\n   2. [Backoffice Backend](apps/backoffice/backend): http://localhost:8040/health-check\n   3. [Backoffice Frontend](apps/backoffice/frontend): http://localhost:8041/health-check\n\n### ✅ Tests execution\n\n1. Install the dependencies if you haven't done it previously: `make deps`\n2. Execute PHPUnit and Behat tests: `make test`\n\n## 👩‍💻 Project explanation\n\nThis project tries to be a MOOC (Massive Open Online Course) platform. It's decoupled from any framework, but it has\nsome Symfony and Laravel implementations.\n\n### ⛱️ Bounded Contexts\n\n- [Mooc](src/Mooc): Place to look in if you wanna see some code 🙂. Massive Open Online Courses public platform with users, videos, notifications, and so on.\n- [Backoffice](src/Backoffice): Here you'll find the use cases needed by the Customer Support department in order to manage users, courses, videos, and so on.\n\n### 🎯 Hexagonal Architecture\n\nThis repository follows the Hexagonal Architecture pattern. Also, it's structured using `modules`.\nWith this, we can see that the current structure of a Bounded Context is:\n\n```scala\n$ tree -L 4 src\n\nsrc\n|-- Mooc // Company subdomain / Bounded Context: Features related to one of the company business lines / products\n|   `-- Videos // Some Module inside the Mooc context\n|       |-- Application\n|       |   |-- Create // Inside the application layer all is structured by actions\n|       |   |   |-- CreateVideoCommand.php\n|       |   |   |-- CreateVideoCommandHandler.php\n|       |   |   `-- VideoCreator.php\n|       |   |-- Find\n|       |   |-- Trim\n|       |   `-- Update\n|       |-- Domain\n|       |   |-- Video.php // The Aggregate of the Module\n|       |   |-- VideoCreatedDomainEvent.php // A Domain Event\n|       |   |-- VideoFinder.php\n|       |   |-- VideoId.php\n|       |   |-- VideoNotFound.php\n|       |   |-- VideoRepository.php // The `Interface` of the repository is inside Domain\n|       |   |-- VideoTitle.php\n|       |   |-- VideoType.php\n|       |   |-- VideoUrl.php\n|       |   `-- Videos.php // A collection of our Aggregate\n|       `-- Infrastructure // The infrastructure of our module\n|           |-- DependencyInjection\n|           `-- Persistence\n|               `--MySqlVideoRepository.php // An implementation of the repository\n`-- Shared // Shared Kernel: Common infrastructure and domain shared between the different Bounded Contexts\n    |-- Domain\n    `-- Infrastructure\n```\n\n#### Repository pattern\n\nOur repositories try to be as simple as possible usually only containing 2 methods `search` and `save`.\nIf we need some query with more filters we use the `Specification` pattern also known as `Criteria` pattern. So we add a\n`searchByCriteria` method.\n\nYou can see an example [here](src/Mooc/Courses/Domain/CourseRepository.php)\nand its implementation [here](src/Mooc/Courses/Infrastructure/Persistence/DoctrineCourseRepository.php).\n\n### Aggregates\n\nYou can see an example of an aggregate [here](src/Mooc/Courses/Domain/Course.php). All aggregates should\nextend the [AggregateRoot](src/Shared/Domain/Aggregate/AggregateRoot.php).\n\n### Command Bus\n\nThere is 1 implementations of the [command bus](src/Shared/Domain/Bus/Command/CommandBus.php).\n1. [Sync](src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php) using the Symfony Message Bus.\n\n\n### Query Bus\n\nThe [Query Bus](src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php) uses the Symfony Message Bus.\n\n### Event Bus\n\nThe [Event Bus](src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php) uses the Symfony Message Bus.\nThe [MySql Bus](src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php) uses a MySql+Pulling as a bus.\nThe [RabbitMQ Bus](src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php) uses RabbitMQ C extension.\n\n## 📱 Monitoring\n\nEvery time a domain event is published it's exported to Prometheus. You can access to the Prometheus panel [here](http://localhost:9999/).\n\n## 🤔 Contributing\n\nThere are some things missing (add swagger, improve documentation...), feel free to add this if you want! If you want\nsome guidelines feel free to contact us :)\n\n## 🤩 Extra\n\nThis code was shown in the [From framework coupled code to #microservices through #DDD](http://codely.tv/blog/screencasts/codigo-acoplado-framework-microservicios-ddd) talk and doubts where answered in the [DDD y CQRS: Preguntas Frecuentes](https://codely.com/blog/ddd-cqrs-preguntas-frecuentes) video.\n\n\n🎥 Used in the CodelyTV Pro courses:\n\n- [🇪🇸 DDD in PHP](https://pro.codely.tv/library/ddd-en-php/about/)\n- [🇪🇸 Arquitectura Hexagonal](https://pro.codely.tv/library/arquitectura-hexagonal/66748/about/)\n- [🇪🇸 CQRS: Command Query Responsibility Segregation](https://pro.codely.tv/library/cqrs-command-query-responsibility-segregation-3719e4aa/62554/about/)\n- [🇪🇸 Comunicación entre microservicios: Event-Driven Architecture](https://pro.codely.tv/library/comunicacion-entre-microservicios-event-driven-architecture/74823/about/)\n\n## 🌐 remember to visit our courses\n\n- [Courses codely](https://codely.com/cursos)\n","funding_links":["https://bit.ly/CodelyTvPro"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelytv%2Fphp-ddd-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodelytv%2Fphp-ddd-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelytv%2Fphp-ddd-example/lists"}