Badger is a gamification platform initially developed as an internal project by Akeneo. Badger is still under development, but is working! If you want to try the project, please install the last release.
If you've just downloaded the code, congratulations!
To get it working, follow these steps:
Install
All the following are for development, replace dev
by prod
if you want prod
- Create the required image:
make php-image-dev
Now you can start your application!
make app-dev
You'll probably want to see logs for fpm:
docker-compose logs -f fpm
This project completely decouples the front end and the back end
A bounded context represents a functional perimeter. This is where a model is implemented and reflects a language spoken by every member of the team. Typically, a bounded context belongs to only one team and is decoupled from the others.
- Gamification: Here all our Gamification logics thing
- SharedSpace: Is not a real bounded context, it is the glue of our application and provides all the generic tools (please do no try to abstract everything, it is dangerous).
A context map defines how bounded contexts communicate and are integrated together.
TODO: explain the relations which exists
According to Martin Fowler, "A DDD aggregate is a cluster of domain objects that can be treated as a single unit." (https://martinfowler.com/bliki/DDD_Aggregate.html).
For most objects, the aggregate definition is pretty simple: a User is a single unit that has its own properties, like the user name, the login, the password.
See http://www.cqrs.nu/Faq/aggregates for even more details.
- Aggregate Root
"Any references from outside the aggregate should only go to the aggregate root. The root can thus ensure the integrity of the aggregate as a whole.", Martin Fowler.
- Transaction
When persisting all the parts of the aggregate, this must be done in one transaction. Indeed, if one object of the aggregate cannot be persisted, then the whole aggregate must be rejected, as it's not valid anymore.
- Accessing referenced objects outside of the aggregate
For objects that are not part of the aggregate, we reference them by their identifiers/ If a service needs to get the object behind the reference, it can use the associated repository.
The back is in application
folder and is build on top of PHP 7.4 using Symfony 4 and will follow a UseCase architecture.
All commands you can do are located in the Makefile, take time to read it :).
- For now, no cache, it is clearly pre-matured.
- For now, no asynchronous (e.g RabbitMQ).
A use case architecture is an architecture which is resilient, scalable, and very important: focused on Use cases.
- Keywords: DDD, Hexagonal Architecture, Port and Adapters, CQRS, CQS
- People: Matthias Noback, Arnaud Lemaire, Thomas Pierrain, Greg young, Cyrille Martraire
It is represented like that (when needed of course):
src/
Gamification/ -> The bounded context
Application/ -> All the use cases of the application
.../
Read/ -> All use cases for reading data, we call it Query
Query (e.g GetAllTodos)
-> is a simple DTO immutable objet which represents the user intent
-> is what enters in the QueryBus
Query.yml -> is a way to validate the Query with SymfonyValidator
QueryHandler
-> is rarely asynchronous
-> is a service which handles the Query (1-1 relation)
-> it fetches the data (using a DomainQuery)
-> it returns a result
Write/ -> All use cases for mutating the application, we call it Command
Command (e.g AddTodo)
-> is a simple DTO immutable objet which represents the user intent
-> is what enters in the CommandBus
Command.yml -> is a way to validate the Command with SymfonyValidator
CommandHandler
-> is often asynchronous
-> is a service which handles the Command (1-1 relation)
-> processes business mutation
-> returns nothing
Domain/ -> Where our business objects stand
.../
Read/ -> All the DTO we can use for reading data in our business problem
-> It contains Reading object
-> It contains interface for QueryingData (another kind of Query, let's call it DomainQuery)
Write/ -> All the Entity/ValueObjects we can use for mutating data in our business problem
-> It contains RepositoryInterface (Write only), Factory, Types
Infrastructure/ -> What is glueing the application and deal with the outside of the application
Application/
-> What makes the glue works (e.g Framework etc)
Storage/
-> What deals with data
-> We find real implementations of Query and Repositories
-> We find in_memory (fake) implementations of Query and Repositories
UserInterface/
-> We find controllers (one controller per user action)
-> We find CLI things
In this kind of application, there is often buses ;). Here we use Messenger
We have two buses used (stored in SharedSpace/Tool/MessageBus
):
- CommandBus (1-1 Command - CommandHandler)
- Returns the identifier of the aggregate root
- Middlewares:
- Validation middleware:
- run validation on commands when they enter the bus
- throw an exception
- Identifier middleware:
- provides an identifier (uuid) for command which needs it
- stamps the envelope with this identifier
- Validation middleware:
- QueryBus (1-1 Query - QueryHandler)
- Returns the data queried
- Middlewares:
- Validation middleware:
- run validation on queries when they enter the bus
- throw an exception
- Validation middleware:
- default: common for all other environments
- in_memory: no infrastructure dependencies (use only fakes)
- test_database: only for testing database / API / Front tests
- dev: like in_memory now
- prod: use the infrastructure, does not work for now
The code must pass all the analysis rules: make back-static
or make <your-bounded-context>-back-static
You can disable some rules but only where it is red, not globally. It uses PHPStan, PSalm, PHP-CS-Fixer.
You are skeptical about tests? -> read that
They are all located under tests/
It's better if you know how to do TDD, it makes a good way to have a minimalist application which is fully tested. Here is an example on how to do TDD here
What are our weapons?
-
Blackbox is a generator of values and allow to do some Property based testing (PBT) It could be used along with Faker. Look usages to understand.
-
PHPSpec is a specification framework well designed for doing TDD.
Usage:
# Generate a specification (very good for TDD)
make gamification-back-desc-spec F="the/path/to/your/non-existent-class"
# Run all the specs of a bounded context
make gamification-back-run-spec
# Run specific tests of a bounded context
make gamification-back-run-spec F="your/folder"
- Behat is a story BDD framework, it is well designed for describing features
- We use Behat for acceptance testing (meaning no Infrastructure dependencies) (tagged
@acceptance
) - We use Behat along with Panther for end-to-end testing (meaning with Infrastructure dependencies) (tagged
@end-to-end
)- The use of Infrastructure has a significant time cost, only critical use cases may be tagged
- It's harder to do TDD with it as it does not embed a generator
- Behat uses feature files which are located next to
back
folder.
- We use Behat for acceptance testing (meaning no Infrastructure dependencies) (tagged
Usage:
# Run all the acceptance tests of a bounded context
make gamification-back-acceptance
# Run specific acceptance tests of a bounded context
make gamification-back-acceptance F="your/folder"
# Run all the end-to-end-api tests of a bounded context
make gamification-back-end-to-end-api
# Run specific end-to-end-api tests of a bounded context
make gamification-back-end-to-end-api F="your/folder"
# Run all the end-to-end-front tests of a bounded context
make gamification-back-end-to-end-front-api
# Run specific end-to-end-front tests of a bounded context
make gamification-back-end-to-end-api-front F="your/folder"
We are using Panther with Selenium Chrome Debug (for development purpose only) Just download a VNC Viewer and connect to the shared port of Selenium (default 5910).
- No useless setters and getters, avoid anemic domain models. Make then smart, rich!
- We should not be able to instantiate or mutates entities by making them invalid, throw as much exceptions as possible.
- Types, types and types. What is a string? nothing.
- Be immutable as much as possible
- No autoincrement id, it should stay in 80's we have uuid now
- Safe for throwing exceptions on unsafe operations
- Ramsey/Uuid for having Uuid in PHP
- ConvenientImmutability for help making DTO
- php-ds for the lack of Collections in PHP
- Webmozart/assert for assertions on data
You are of course free to use the ide you want, but here is the setup for all Jetbrains IDE
Error on composer install: the environment may not be working, try prefixing you command by APP_ENV=dev
The front-end is built over React with Webpack-Encore