Last Wishes is a PHP application written following Domain-Driven Design approach

Overview

Last Wishes - DDD Sample Application

Build Status

Mandatory requirements

  • PHP
  • npm

Optional requirements

  • RabbitMQ for Messaging exercises
  • Elastic for Elastic exercises
  • Redis optional for Redis exercises

You can install manually or use docker-compose up -d to run all these services inside Docker. The PHP application can be run locally.

Set up the project

curl -sS https://getcomposer.org/installer | php
php composer.phar install

Create the database schema

php bin/doctrine orm:schema-tool:create

Run your Last Will bounded context

php -S localhost:8080 -t src/Lw/Infrastructure/Ui/Web/Silex/Public

Notify all domain events via messaging

php bin/console domain:events:spread

Notify all domain events via messaging to another new BC deployed

php bin/console domain:events:spread 

Exercises

For the DDD learners, I propose you some exercises to practice your skills.

UserRepository running with Redis

The default project uses Doctrine and SQLite to persist users, however, we would like to easily change the persistence storage to use Redis. The PRedis dependency is already specified in the composer.json file.

UserRepository running with MongoDB

The default project uses Doctrine and SQLite to persist users, however, we would like to easily change the persistence storage to use MongoDB.

Log Domain Events into File and Elastic

The default project, does not log anything. We're interested in logging all DomainEvents into a file, using ad-hoc solutions or Monolog. Create a new Domain Events fired when a user tries to log in. Log also to ElasticSearch.

Comments
  • Violation of Doctrine Rule about foreign keys

    Violation of Doctrine Rule about foreign keys

    Hi,

    you are mapping foreign keys to object properties which is forbidden from Doctrine point of view.

    Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do.

    Could you comment it? How to deal with it without violation this rule?

    Why do you need userId inside Wish entity if you have one-to-many through join table?

    http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/best-practices.html#don-t-map-foreign-keys-to-fields-in-an-entity

    opened by maks-rafalko 8
  • Your domain code is tightly coupled to Doctrine

    Your domain code is tightly coupled to Doctrine

    https://github.com/dddinphp/last-wishes/blob/master/src/Lw/Domain/Model/User/User.php#L7

    You are using doctrine collections inside Domain code, shouldn't that be done inside infrastructure.

    opened by usama-ahmed-bonnyprints 3
  • Low quality of the project

    Low quality of the project

    Hey,

    I am DDD practitioner and I was doing DDD Lite and full DDD in few projects already. I know how many PHP developers creating crappy code so I was really happy about the book and sample project. I've spend some time going through the code, because I wanted to see what advices, concepts are given and to contribute, if needed. I've checked the book and need to say it's very good, but I can't say the same about the project.

    Let's start from the beginning. DDD should be used for non trivial domains to solve. The problem that is taken for the project is just a little bit more than CRUD. Looking at the code, we may see that it tries to hack reality by solving easy problem with a lot of layers and creating complexity in the places where it's not needed. I am sure, that it will bring value for a lot of people, but possibility to learn solving non trival domains for PHP developers is lost in here.

    Next thing. I've forked the project and I was pretty sure everything will work like a charm. It's sample project to show ideas from the book, so it had to work.
    First I had configure the project, but I was okey with it. I checked what is needed and wasn't really happy to install dependencies on my host machine. So I spend a little time on automatic it with docker. When I've finished I could run the app, but the app didn't worked. Application was just throwing exceptions on me. This is just really bad, that app that is about sharing the knowledge, can't be even run, because of exceptions. When I've fixed them (MR in queue), I had a chance to check out the code and I really didn't liked what I saw.

    What I've faced except the not working application was:

    1. No tests - I am really disappointed about this one. As this book is for developers who really want to learn they should be provided with best examples. Not having tests in the project is like waiting for catastrophe. The project has bugs in such simple domain and it's only, because of the lack of tests. There should be unit, integration, functional tests so people can know how to write them. 2. DomainEventPublisher in model - This makes the model untestable and coupled with higher level abstraction. 3. new DateTime throwed everywhere - Again makes the model untestable and breaks the SOLID. 4. High couplings between upper layers and domain. WishEmail extending Wish, now whenever domain object is changed infrastructure object need to be also changed. It also breaks the SOLID. 5. Configuration available within email sending class. Let's say the WishEmail is good solution. WishEmail doesn't receive info how to send email from outside, but is responsible for it. The next problem with it is that login and password is provided within the class. (I hope you changed it btw). Such stuff should always be injected. 6. Lack of examples with Value Objects. The only one created within the project is Id. Email is like one of the most popular VO, why not use it? 7. Aggregate root should hold transaction boundaries. In here it doesn't. Why wish is connected with user? 8. Aggregate root is burden with all this php global functions like trim, strtolower. If it need to be used, use it within Value Object for example email. 9. No Docblocks. In PHP 5, we can just guess, what should be provided within function for example. Having docblock, should be must in all projects and especially ones like this 10. The domain model is pretty anemic, but this may be because of lack of hard problem to solve

    There are things, that I liked like translation services, repositories and well separated modules. But I just expect much, much more from DDD leading book for PHP.
    Hope, my insights will give you some guidance to increase the value of the project. :)

    opened by dgafka 2
  • makeWishNoAggregateVersion misses no more than 3 wishes business rule

    makeWishNoAggregateVersion misses no more than 3 wishes business rule

    Hi guys. Thanks for the book.

    I am wandering where it would be placed "no more than 3 wishes business rule" in case of makeWishNoAggregateVersion?

    opened by permiakov 1
  • What is a difference between this two methods?

    What is a difference between this two methods?

    In User aggregate there are two different methods for creating wishes. What are the use cases for each?

    As I guess the first one is perfect for persisting as a single.

        public function makeWishNotBeingAnAggregate(WishId $wishId, $address, $content)
        {
            $newWish = new Wish(
                $wishId,
                $this->id(),
                $address,
                $content
            );
            DomainEventPublisher::instance()->publish(
                new WishWasMade(
                    $newWish->id(),
                    $newWish->userId(),
                    $newWish->address(),
                    $newWish->content()
                )
            );
            return $newWish;
        }
        public function makeWish(WishId $wishId, $address, $content)
        {
            if (count($this->wishes) >= self::MAX_WISHES) {
                throw new NoMoreWishesAllowedException();
            }
            $this->wishes[] = new Wish(
                $wishId,
                $this->id(),
                $address,
                $content
            );
        }
    
    opened by tworzenieweb 1
  • Unable to generate a URL for the named route

    Unable to generate a URL for the named route "login" as such route does not exist.

    This happened after registering.

    RouteNotFoundException in UrlGenerator.php line 130:
    in UrlGenerator.php line 130
    at UrlGenerator->generate('login') in index.php line 50
    at {closure}(object(Request))
    at call_user_func_array(object(Closure), array(object(Request))) in HttpKernel.php line 139
    at HttpKernel->handleRaw(object(Request), '1') in HttpKernel.php line 62
    at HttpKernel->handle(object(Request), '1', true) in Application.php line 586
    at Application->handle(object(Request)) in Application.php line 563
    at Application->run() in index.php line 239
    
    opened by tworzenieweb 1
  •  orm:schema-tool:create not working

    orm:schema-tool:create not working

    Running the following cli command to build the schema ends up like this.

    php bin/doctrine orm:schema-tool:create
    PHP Warning:  Missing argument 1 for Lw\Infrastructure\Persistence\Doctrine\EntityManagerFactory::build(), called in /home/tworzenieweb/projekty/last-wishes/cli-config.php on line 7 and defined in /home/tworzenieweb/projekty/last-wishes/src/Lw/Infrastructure/Persistence/Doctrine/EntityManagerFactory.php on line 13
    PHP Stack trace:
    PHP   1. {main}() /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine:0
    PHP   2. include() /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine:4
    PHP   3. require() /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine.php:55
    PHP   4. Lw\Infrastructure\Persistence\Doctrine\EntityManagerFactory->build() /home/tworzenieweb/projekty/last-wishes/cli-config.php:7
    PHP Notice:  Undefined variable: conn in /home/tworzenieweb/projekty/last-wishes/src/Lw/Infrastructure/Persistence/Doctrine/EntityManagerFactory.php on line 19
    PHP Stack trace:
    PHP   1. {main}() /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine:0
    PHP   2. include() /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine:4
    PHP   3. require() /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine.php:55
    PHP   4. Lw\Infrastructure\Persistence\Doctrine\EntityManagerFactory->build() /home/tworzenieweb/projekty/last-wishes/cli-config.php:7
    PHP Fatal error:  Uncaught exception 'InvalidArgumentException' with message 'Invalid argument: ' in /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:847
    Stack trace:
    #0 /home/tworzenieweb/projekty/last-wishes/src/Lw/Infrastructure/Persistence/Doctrine/EntityManagerFactory.php(21): Doctrine\ORM\EntityManager::create(NULL, Object(Doctrine\ORM\Configuration))
    #1 /home/tworzenieweb/projekty/last-wishes/cli-config.php(7): Lw\Infrastructure\Persistence\Doctrine\EntityManagerFactory->build()
    #2 /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine.php(55): require('/home/tworzenie...')
    #3 /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/bin/doctrine(4): include('/home/tworzenie...')
    #4 {main}
      thrown in /home/tworzenieweb/projekty/last-wishes/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php on line 847
    
    opened by tworzenieweb 1
  • [Dashboard] Error when loading dashboard.

    [Dashboard] Error when loading dashboard. "badgesFrom" method expects an UserId but it is receiving a string instead.

    The method "badgesFrom" expects an instance of UserId but the request of the ApplicationService where that method is called from is being created directly with the uuid (string) instead of with the object itself.

    According to the implementation, I would say that the method's signature of "badgesFrom" should change to not expect an UserId but a string (the uuid).

    opened by mgonzalezbaile 1
  • Solo un par de correcciones

    Solo un par de correcciones

    Hola, he visto este codigo y vi este par de detalles que parecen ser descuidos(mala configuración de los filtros de PHPunit y un test que no tenia sentido y estaba en un namespace que no tocaba). También me gustaría aprovechar para preguntarte porque en muchos partes del codigo lanzas una excepción y luego haces un assert, ¿no valdría solo con la assertion?(por ejemplo, Lw\Domain\Model\Wish::setContent). Por cierto, gracias por este proyecto, al menos a mí me ha ayudado en el aprendizaje de DDD.

    opened by j-velasco 1
  • LoggerDomainEventSubscriber.php coupled to infrastructure at domain layer

    LoggerDomainEventSubscriber.php coupled to infrastructure at domain layer

    The LoggerDomainEventSubscriber.php is located in the Domain layer. In addition, it uses functionalities specific to the Infrastructure layer, such as Monolog or Elastica.

    Wouldn't it be more appropriate and consistent with the principles dictated by DDD to create a contract (interface) of the Logger in Domain layer, implement it in the Infrastructure layer and then create a subscriber of the domain events in the Application layer?

    Thanks in advance 😊

    opened by borjapazr 0
  • How can I return with an array of users

    How can I return with an array of users

    In all of your examples in application services you illustrate how to return a DTO but with one user so I need to know how to return a collection of users

    this is what you did with one user, or in another example, you return a user transformer:

    class SignUpUserService
    {
        public function execute(SignUpUserRequest $request)
        {
    // ...
            $user = // ...
            return new UserDTO($user);
        }
    }
    

    and what should to be done in pagination?

    opened by TheGeekyM 2
  • ERROR: manifest for kibana:latest not found

    ERROR: manifest for kibana:latest not found

    when run : docker-compose up -d report :

    [email protected]:~/last-wishes# docker-compose up -d
    Creating network "last-wishes_default" with the default driver
    Pulling queues (rabbitmq:management)...
    management: Pulling from library/rabbitmq
    f17d81b4b692: Pull complete
    02fe1bd1a85c: Pull complete
    66c15a50f4da: Pull complete
    771c4c62018c: Pull complete
    05e166e2684c: Pull complete
    5eb4efce3466: Pull complete
    9b5d77af0f63: Pull complete
    f7fc14f8eeeb: Pull complete
    31e1448101d9: Pull complete
    196612f40314: Pull complete
    8cd7ab5c5659: Pull complete
    aae6dd0bf4aa: Pull complete
    c8f2ac2cd4e8: Pull complete
    98e5c73758c4: Pull complete
    Digest: sha256:3eb2fa0f83914999846f831f14b900c0c85cea8e5d2db48ff73cf7defa12fe96
    Status: Downloaded newer image for rabbitmq:management
    Pulling kibana (kibana:)...
    ERROR: manifest for kibana:latest not found
    
    
    opened by 03128crz 0
  • `makeWishAggregateVersion` and more than 3 wishes

    `makeWishAggregateVersion` and more than 3 wishes

    Hi, thank you for the book. Just reading through the chapters.

    In the book it was mentioned that there is a possibility that we end up with 3+ wishes in a rare case of parallel requests, although the business rule states there shouldn't be more than 3 wishes.

    By moving the business logic to the user domain entity we do keep the logic in the domain which seems correct, but does it solve the problem above?

    Also if the problem is to be solved somehow at the persistence layer, there the issue does not seem obvious without looking at the domain logic?

    Maybe I'm missing something :)

    https://github.com/dddinphp/last-wishes/blob/47cf7c5950734d8d90cd405432d5d20b2c897df3/src/Lw/Application/Service/Wish/AggregateVersion/AddWishService.php#L15

    opened by mcrio 0
Owner
DDD Shelf
CQRS by Example and DDD in PHP books materials
DDD Shelf
Game of life developed in PHP with TDD approach

What is Game of Life: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life Project structure: Engine is in App\Services\LifeEngine.php Tests are in T

Marcella Malune 4 Nov 8, 2021
Compare your Github followers vs following users

followers-vs-following Compare your Github followers vs following users https://docs.github.com/en/rest/reference/users The code only queries 3k follo

Felix Biego 2 Jan 11, 2022
Run the following commands in your terminal to setup the project

Run the following commands in your terminal to setup the project You must have the LAMP installed on your system git clone https://github.com/Bashar-A

null 1 Nov 6, 2021
LDAP-OSNAME-CHANGE-ALLOWER - This is my first php, hopefully last.

LDAP-OSNAME-CHANGE-ALLOWER This PHP script allows SELF user to read and write the 'Operating System' property on the target computer/s. How was it dev

Özgün Kültekin 5 Apr 9, 2022
Repository for the last open source version of Booked Scheduler.

Welcome to Booked Scheduler This is a community effort to keep the OpenSource GPLv3 BookedScheduler alive, see History Prerequisites PHP 7.0 or greate

null 235 Sep 21, 2022
A now playing screen for the Raspberry Pi using the Last.fm API.

raspberry-pi-now-playing A now playing screen for the Raspberry Pi using the Last.fm API. This project is detailed, with photos of how I used it with

null 39 Sep 5, 2022
The last validation library you will ever need!

Mighty The last validation library you will ever need! Table of Contents Installation About Mighty Quickstart Mighty Validation Expression Language Ex

Marwan Al-Soltany 49 Sep 29, 2022
My last contribution to Vasar, the final official PocketMine core.

Vasar v5.0 Incomplete and entirely hardcoded. For PocketMine 4.X.X. Many thanks to Prim for plenty of help over the years which basically formed this

null 8 Sep 18, 2022
Ecotone Framework is Service Bus Implementation. It enables message driven architecture and DDD, CQRS, Event Sourcing PHP

This is Read Only Repository To contribute make use of Ecotone-Dev repository. Ecotone is Service Bus Implementation, which enables message driven arc

EcotoneFramework 257 Sep 23, 2022
Faker-driven, configuration-based, platform-agnostic, locale-compatible data faker tool

Masquerade Faker-driven, platform-agnostic, locale-compatible data faker tool Point Masquerade to a database, give it a rule-set defined in YAML and M

elgentos ecommerce solutions 212 Sep 26, 2022
This script allows to bypass Oracle Cloud Infrastructure 'Out of host capacity' error immediately when additional OCI capacity will appear in your Home Region / Availability domain.

Resolving Oracle Cloud "Out of Capacity" issue and getting free VPS with 4 ARM cores / 24GB of memory Very neat and useful configuration was recently

Alexander Hitrov 253 Sep 26, 2022
Yclas Self Hosted is a powerful script that can transform any domain into a fully customizable classifieds site within a few seconds.

Yclas 4.4.0. Description Yclas self-hosted is a powerful script that can transform any domain into a fully customizable classifieds site within a few

Yclas 299 May 29, 2022
the examples of head first object oriented analysis & design - in PHP

Head First object oriented analysis & design in (PHP) after cloning the repository, you have to install the project's dependancies by running the foll

Muhammed ElFeqy 3 Oct 16, 2021
Sample code for several design patterns in PHP 8

DesignPatternsPHP Read the Docs of DesignPatternsPHP or Download as PDF/Epub This is a collection of known design patterns and some sample codes on ho

null 20.8k Sep 24, 2022
Design Pattern Examples in PHP

Design Patterns in PHP This repository is part of the Refactoring.Guru project. It contains PHP examples for all classic GoF design patterns. Each pat

Refactoring.Guru 872 Sep 25, 2022
Detection of design patterns in PHP code

Pattern Detector for PHP Detects design pattern in your code

Jean-François Lépine 105 Aug 23, 2022
Examples of some common design patterns implemented in php

What is a Design Pattern? Design patterns are typical solutions to common problems in software design. Each pattern is like a blueprint that you can c

Bakhtiyor Bahritidinov 4 Feb 11, 2022