/symfony-ddd-hexarch-cqrs

Code examples and good practices using Domain Drive Development, Hexagonal Architecture, CQRS, Symfony 6, PHP8 and anything else I can think of...

Primary LanguagePHPMIT LicenseMIT

Version Contributors Forks Stargazers Issues MIT License


symfony-ddd-hexarch-cqrs

Code examples and good practices using Domain Drive Development, Hexagonal Architecture, CQRS, Symfony 6, PHP8 and anything else I can think of...

Symfony 5 PHP Docker MySql SQLite

Report Bug · Request Feature


Build Status Coverage Status


Table of Contents
  1. About The Project
  2. Getting Started
  3. Roadmap
  4. Contributing
  5. License
  6. Contact
  7. Acknowledgements

About The Project

I have created this project to have a guide of code examples and good practices as a future reference for me and for anyone who may be interested.

I will be adding more examples that I think are interesting and that provide an extra for anyone who wants to get started in the technologies mentioned bellow.

Features

  • PHP8
  • Symfony 6
  • DDD guidelines
  • Hexagonal Architecture
  • SOLID
  • Docker
  • Doctrine ORM & DB migrations
  • Albums module with CQRS pattern (command and query bus)
  • Static code analysis: PHPCS, Rector, Psalm
  • Unit and integration tests: PHPUnit
  • Acceptance tests: Behat
  • Basic Authorization, with mandatory token to http POST endpoints
  • Basic JWT Authorization, with mandatory token to http PUT endpoints
  • NoSql: Redis examples
  • Frontend examples (React, Redux, Webpack, Babel, etc.): on this repo
  • Elastic stack (Elasticsearch, Logstash, Kibana, Filebeat)

Upcoming Features

  • Aggregates organization
  • Using native PHP amqp extension to publish events (instead of Symfony/Messenger)
  • RabbitMQ configuration wizard (queues and exchanges, retry, dead-letter and bindings)
  • Supervisor configuration wizard (file .ini per queue)

Getting Started

I will add new features and examples, this project is constantly evolving! You can see unreleased code at here

Prerequisites

Installation

Clone repo, download deps and create docker services:

git clone https://github.com/masfernandez/symfony-ddd-hexarch-cqrs.git
cd symfony-ddd-hexarch-cqrs
make composer-install
make up

Running prod env

Execute at root path:

make prod-start

Request examples

In order to create a new Album is mandatory to include a valid Token in request's Authorization header. So first, let's create a new User:

make create-demo-user
# Credentials for demo user: test@email.com 1234567890

Now, it's time to get a valid token:

curl -i -X POST 'http://api.musiclabel.127.0.0.1.nip.io/authentication' \
-H 'Content-Type: application/json' \
--data-raw '{
    "email": "test@email.com",
    "password": "1234567890"
}'

You can find the Token in response's Location header:

Server: nginx/1.19.5
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/8.0.0
Location: 4ac71eeda13c8fe7f0e4c017412bd9f2d886288cb8c88331007f2a9c7652385b
Cache-Control: no-cache, private
Date: Fri, 15 Jan 2021 11:23:45 GMT
X-Robots-Tag: noindex
Strict-Transport-Security: max-age=31536000

{}

We can publish new Albums now: (replace the value of the token here with the one you got before... obviously)

curl -i -X POST 'http://api.musiclabel.127.0.0.1.nip.io/albums' \
-H 'Authorization: Bearer 4ac71eeda13c8fe7f0e4c017412bd9f2d886288cb8c88331007f2a9c7652385b' \
-H 'Content-Type: application/json' \
--data-raw '{
    "id": "0da69030-3ed7-42b5-8aa5-25fb61dab1b2",
    "title": "Abbey Road",
    "release_date": "1969-09-26 00:00:00"  
}'

Verifying the Album created:

curl -X GET 'http://api.musiclabel.127.0.0.1.nip.io/albums?page[number]=1&page[size]=1&sort=title&fields[albums]=id,title,release_date'

We need a JWToken to make PUT operations on Albums, so let's get one:

curl -i -X POST 'http://api.musiclabel.127.0.0.1.nip.io/authentication/jwt' \
-H 'Content-Type: application/json' \
--data-raw '{
    "email": "test@email.com",
    "password": "1234567890"
}'

You can find the JWToken (header.payload.signature) in response's headers:

  • header+payload in Location header
  • signature in set-cookie header
HTTP/2 201 
server: nginx/1.19.8
content-type: application/json
location: header+payload:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20iLCJhdWQiOiJodHRwOi8vZXhhbXBsZS5vcmciLCJqdGkiOiJlWFZocHBTR0JwZllTeHNZIiwiaWF0IjoxNjE3MzU1NTMyLjQyNzU3MSwibmJmIjoxNjE3MzU1NTMzLjQyNzU3MSwiZXhwIjoxNjE3MzU5MTMyLjQyNzU3MSwidWlkIjoiMGY4MzNjMjItZmVmZC00ZmFmLWE3YzItNGEwNzlhMjJjMzdjIn0
x-powered-by: PHP/8.0.3
cache-control: no-cache, private
date: Fri, 02 Apr 2021 09:25:32 GMT
x-robots-tag: noindex
set-cookie: signature=GFZiEgVkKIbv5YszK_5wKmhLpqlkhYUUS1N1nCLLavs; path=/; secure; httponly; samesite=none
strict-transport-security: max-age=31536000

You may be asking why sending the JWToken like this... well, I'm to lazy to write hundred of words when there is a lot of information already on there. Just few tips:

Recommend read:

Don't forget the purpose of this repo: just to show some examples, crazy dev ideas and my opinionated vision on how to approach some scenarios ;)

Let's replace Album created before:

The client (React, Vue, Curl, Postman... whatever) should know how to re-construct the JWToken to make a request (remember, header + payload in Authorization header and signature in cookie)

Note: replace the values of the token here with the one you got before... obviously

curl -i -X PUT 'http://api.musiclabel.127.0.0.1.nip.io/albums/0da69030-3ed7-42b5-8aa5-25fb61dab1b2' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20iLCJhdWQiOiJodHRwOi8vZXhhbXBsZS5vcmciLCJqdGkiOiJlWFZocHBTR0JwZllTeHNZIiwiaWF0IjoxNjE3MzU1NTMyLjQyNzU3MSwibmJmIjoxNjE3MzU1NTMzLjQyNzU3MSwiZXhwIjoxNjE3MzU5MTMyLjQyNzU3MSwidWlkIjoiMGY4MzNjMjItZmVmZC00ZmFmLWE3YzItNGEwNzlhMjJjMzdjIn0' \
-H 'Content-Type: application/json' \
-H 'Cookie: signature=GFZiEgVkKIbv5YszK_5wKmhLpqlkhYUUS1N1nCLLavs' \
--data-raw '{
    "title": "New album value here",
    "release_date": "2021-04-02 00:00:00"
}'

Response:

HTTP/2 204 
server: nginx/1.19.8
x-powered-by: PHP/8.0.3
cache-control: no-cache, private
date: Fri, 02 Apr 2021 09:58:22 GMT
x-robots-tag: noindex
strict-transport-security: max-age=31536000

Verifying the Album updated:

curl -i -X GET 'http://api.musiclabel.127.0.0.1.nip.io/albums?page[number]=1&page[size]=1&sort=title&fields[albums]=id,title,release_date'

Response:

HTTP/2 200 
server: nginx/1.19.8
content-type: application/json
x-powered-by: PHP/8.0.3
cache-control: no-cache, private
date: Fri, 02 Apr 2021 09:59:36 GMT
x-robots-tag: noindex
strict-transport-security: max-age=31536000

{
    "data": [
        {
            "id": "0da69030-3ed7-42b5-8aa5-25fb61dab1b2",
            "title": "New album value here",
            "release_date": "2021-04-02 00:00:00"
        }
    ],
    "links": {
        "self": "\/albums?page%5Bnumber%5D=1&page%5Bsize%5D=1",
        "first": "\/albums?page%5Bnumber%5D=1&page%5Bsize%5D=1",
        "prev": "\/albums?page%5Bnumber%5D=1&page%5Bsize%5D=1",
        "next": "\/albums?page%5Bnumber%5D=1&page%5Bsize%5D=1",
        "last": "\/albums?page%5Bnumber%5D=1&page%5Bsize%5D=1"
    },
    "meta": {
        "total_pages": 1
    }
} 

Docker services

Several docker services will be available and ready for use when the app starts:

RabbitMQ

http://localhost:15672
user: rabbit_user
password: rabbit_pass

Kibana

http://localhost:5601

Kibana Nginx logs configuration:

Kibana Symfony logs configuration:

Logs can be visualized now at http://localhost:5601/app/logs/stream

Running dev env

make dev-start

Tests

make test

Docker info

There are several services in the Docker stack for this project. All services are built from official docker images except:

  • Nginx: Custom docker image. I will optimize some parameters soon but at this moment is just a wrapper of the official docker image. More info here
  • PHP-FPM: Custom docker image. It has 2 main targets, for production and development environment. Each env has some deps that you can check at here. This is also a repo I'm working on.

Roadmap

See the open issues for a list of proposed features (and known issues).

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the MIT License. See LICENSE.txt for more information.

Contact

Miguel Ángel Sánchez Fernández - mangel.sanfer@gmail.com

(linkedin hiden profile - require login)

LinkedIn

Project Link: https://github.com/masfernandez/symfony-ddd-hexarch-cqrs

Acknowledgements

Stats