Skip to content

Commit

Permalink
Merge e31cf0f into 4eace06
Browse files Browse the repository at this point in the history
  • Loading branch information
MortalFlesh committed May 10, 2021
2 parents 4eace06 + e31cf0f commit e713b24
Show file tree
Hide file tree
Showing 31 changed files with 3,193 additions and 0 deletions.
75 changes: 75 additions & 0 deletions .github/workflows/tests.yaml
@@ -0,0 +1,75 @@
name: Tests and linting

on:
push:
pull_request:
schedule:
- cron: '0 3 * * *'

jobs:
unit-tests:
runs-on: ubuntu-latest

strategy:
matrix:
php-version: ['7.4']
dependencies: ['']
include:
- { php-version: '7.4', dependencies: '--prefer-lowest --prefer-stable' }

name: Unit tests - PHP ${{ matrix.dependencies }}

steps:
- uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: json, mbstring
coverage: xdebug

- name: Install dependencies
run: composer update --no-progress --no-interaction ${{ matrix.dependencies }}

- name: Run tests
run: |
composer tests-ci
- name: Submit coverage to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_PARALLEL: true
COVERALLS_FLAG_NAME: ${{ github.job }}-PHP-${{ matrix.php-version }} ${{ matrix.dependencies }}
run: |
composer global require php-coveralls/php-coveralls
~/.composer/vendor/bin/php-coveralls --coverage_clover=./reports/clover.xml --json_path=./reports/coveralls-upload.json -v
finish-tests:
name: Tests finished
needs: [unit-tests]
runs-on: ubuntu-latest
steps:
- name: Notify Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true

codestyle:
name: "Code style and static analysis"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: json, mbstring

- name: Install dependencies
run: composer update --no-progress

- name: Run checks
run: composer analyze
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
/bin/
/vendor/
composer.lock

.phpunit.result.cache
/reports/
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,6 @@
# Changelog

<!-- There should always be "Unreleased" section at the beginning. -->

## Unreleased
- Initial implementation
257 changes: 257 additions & 0 deletions README.md
@@ -0,0 +1,257 @@
LMC CQRS Bundle
===============

[![cqrs-types](https://img.shields.io/badge/cqrs-types-purple.svg)](https://github.com/lmc-eu/cqrs-types)
[![Tests and linting](https://github.com/lmc-eu/cqrs-bundle/actions/workflows/tests.yaml/badge.svg)](https://github.com/lmc-eu/cqrs-bundle/actions/workflows/tests.yaml)
[![Coverage Status](https://coveralls.io/repos/github/lmc-eu/cqrs-bundle/badge.svg?branch=main)](https://coveralls.io/github/lmc-eu/cqrs-bundle?branch=main)

> Symfony bundle for CQRS libraries and extensions. It registers services, data collectors etc. by a configuration.
## Table of contents
- [Installation](#installation)
- [Configuration](#configuration)
- [Routes](#routes)
- [Tags](#tags)
- [Services](#services)
- [Handlers](#handlers)
- [Extensions](#extensions)
- [HTTP](#http)
- [Solr](#solr)
- [List of all predefined services](#list-of-all-predefined-services-and-their-priorities)

## Installation
```shell
composer require lmc/cqrs-bundle
```

## Configuration

```yaml
lmc_cqrs:
profiler: false # Whether to enable profiler and allow to profile queries and commands [default false]
debug: false # Whether to enable debug the CQRS by a console command [default false]

cache:
enabled: false # Whether to use cache for Queries [default false (true, if you define cache_provider)]
cache_provider: '@my.cache.provider' # Service implementing a CacheItemPoolInterface. Required when cache is enabled [default null]

extension:
http: false # Whether should http extension be active (requires a lmc/cqrs-http dependency) [default false]
solr: false # Whether should solr extension be active (requires a lmc/cqrs-solr dependency) [default false]
```

**TIPs**:
- it is advised to set `profiler: '%kernel.debug%'` so it profiles (and registers all services for profiling) only when it is really used
- you can define `profiler` and `debug` in your `dev/lmc_cqrs.yaml` to only allow it in `dev` Symfony environment

**Note**: if you don't enable any of the extension, there will only be a `CallbackQueryHandler` and `CallbackSendCommandHandler`, so you probably need to register your own.

### Routes
You must register the routes for a CQRS bundle if you enable a profiler.

```yaml
# config/routes.yaml

lmc_cqrs_bundle_routes:
resource: "@LmcCqrsBundle/Resources/config/routes.yaml"
```

### Tags:
> Tags are automatically registered, if your class implements an Interface and is registered in Symfony container as a service
- `lmc_cqrs.query_handler` (`QueryHandlerInterface`)
- `lmc_cqrs.send_command_handler` (`SendCommandHandlerInterface`)
- `lmc_cqrs.profiler_formatter` (`ProfilerFormatterInterface`)
- `lmc_cqrs.response_decoder` (`ResponseDecoderInterface`)

With priority
```yaml
services:
My\CustomQueryHandler:
tags:
- { name: 'lmc_cqrs.query_handler', priority: 80 }
```

**Note**: Default priority is `50` and none of the predefined handlers, profilers, etc. has priority higher than `90` (see [complete list below](#list-of-all-predefined-services-and-their-priorities))

## Services
Bundle registers all necessary services according to configuration (for example, if you set `http: true` it will automatically register all http handlers, etc.)

Most of the services are registered both by an alias and a class name, so it will be available for autowiring.
All interfaces are automatically configured to have a tag (see [Tags](#tags) section above).

### Handlers
There are 2 main services, which are essential to the library.
Both of them have its interface to represent it, and it is advised to use it via the interface.

#### 1. Query Fetcher Interface
- implementation `Lmc\Cqrs\Handler\QueryFetcher`
- alias: `@lmc_cqrs.query_fetcher`
- it will find a handler for your query, handles it, decodes a response and caches the result (if cache is enabled)
- provides features:
- caching
- requires:
- cache_provider (set in the configuration) - service implements `Psr\Cache\CacheItemPoolInterface`
- Query to implement `Lmc\Cqrs\Types\Feature\CacheableInterface`
- it allows to cache a decoded result and load it again from cache
- profiling
- requires:
- enabled profiler (in the configuration)
- Query to implement `Lmc\Cqrs\Types\Feature\ProfileableInterface`
- it profiles a query, its execution time, response, applied handler and decoders and shows the info in the Symfony profiler

Fetching a query

You can do whatever you want with a response, we will persist a result into db, for an example or log an error.
```php
// with continuation
$this->queryFetcher->fetch(
$query,
fn ($response) => $this->repository->save($response),
fn (\Throwable $error) => $this->logger->critical($error->getMassage())
);

// with return
try {
$response = $this->queryFetcher->fetchAndReturn($query);
$this->repository->save($response);
} catch (\Throwable $error) {
$this->logger->critical($error->getMessage());
}
```

#### 2. Command Sender Interface
- implementation `Lmc\Cqrs\Handler\CommandSender`
- alias: `@lmc_cqrs.command_sender`
- it will find a handler for your command, handles it, decodes a response
- provides features:
- profiling
- requires:
- enabled profiler (in the configuration)
- Command to implement `Lmc\Cqrs\Types\Feature\ProfileableInterface`
- it profiles a command, its execution time, response, applied handler and decoders and shows the info in the Symfony profiler

Sending a command

You can do whatever you want with a response, we will persist a result into db, for an example or log an error.
```php
// with continuation
$this->commandSender->send(
$command,
fn ($response) => $this->repository->save($response),
fn (\Throwable $error) => $this->logger->critical($error->getMassage())
);

// with return
try {
$response = $this->commandSender->sendAndReturn($query);
$this->repository->save($response);
} catch (\Throwable $error) {
$this->logger->critical($error->getMessage());
}
```

**Note**: There is no logging feature in the CQRS library, if you need one, you have to implement it by yourself.

### Profiler Bag
There is a `profiler bag` service, which is a collection of all profiler information in the current request.
The information inside are used by a `CqrsDataCollector`, which shows them in the Symfony profiler.

It requires `profiler: true` in the configuration.

You can access the profiler bag either by:
- `@lmc_cqrs.profiler_bag` (alias)
- `Lmc\Cqrs\Handler\ProfilerBag` (autowiring)
- or access a `CqrsDataCollector` programmatically (see [here](https://symfony.com/doc/current/profiler.html#accessing-profiling-data-programmatically))

## Extensions

We offer a basic extensions for a common Commands & Queries
- [Http](#http) (using [PSR-7](https://www.php-fig.org/psr/psr-7/))
- [SOLR](#solr) (using [Solarium](https://github.com/solariumphp/solarium))

### Http
> [Http extension repository](https://github.com/lmc-eu/cqrs-http)
Installation
```shell
composer require lmc/cqrs-http
```

**NOTE**: You will also need an implementation for [PSR-7](https://packagist.org/providers/psr/http-message-implementation), [PSR-17](https://packagist.org/providers/psr/http-factory-implementation) and [PSR-18](https://packagist.org/providers/psr/http-client-implementation) for HTTP extensions to work.

Configuration
```yaml
lmc_cqrs:
extension:
http: true
```

Enabling a Http extension will allow a `QueryFetcher` and `CommandSender` to handle a PSR-7 Requests/Response and decode it.

### Solr
> [SOLR extension repository](https://github.com/lmc-eu/cqrs-solr)
Installation
```shell
composer require lmc/cqrs-solr
```

Configuration
```yaml
lmc_cqrs:
extension:
solr: true
```

Enabling a Solr extension will allow a `QueryFetcher` and `CommandSender` to handle a Solarium Requests/Result and decode it.

#### Solarium Query Builder
It allows you to build a Solarium request only by defining an Entity with all features you want to provide.
See [Solr extension readme](https://github.com/lmc-eu/cqrs-solr#query-builder) for more information.

**Note**: You can specify a tag for your custom applicator by `lmc_cqrs.solr.query_builder_applicator`

## List of all predefined services and their priorities

**Note**: To see a list of all services really registered in your application use `bin/console debug:cqrs` (it requires `debug: true` in your configuration)

### Top most handlers for Commands & Queries
| Interface | Class | Alias |
| --- | --- | --- |
| Lmc\Cqrs\Types\QueryFetcherInterface | Lmc\Cqrs\Handler\QueryFetcher | `@lmc_cqrs.query_fetcher` |
| Lmc\Cqrs\Types\CommandSenderInterface | Lmc\Cqrs\Handler\CommandSender | `@lmc_cqrs.command_sender`

### Query Handlers
| Service | Alias | Tag | Priority | Enabled |
| --- | --- | --- | --- | --- |
| Lmc\Cqrs\Handler\Handler\GetCachedHandler | - | - | 80 | if `cache` is enabled |
| Lmc\Cqrs\Handler\Handler\CallbackQueryHandler | `@lmc_cqrs.query_handler.callback` | `lmc_cqrs.query_handler` | 50 | *always* |
| Lmc\Cqrs\Http\Handler\HttpQueryHandler | `@lmc_cqrs.query_handler.http` | `lmc_cqrs.query_handler` | 50 | if `http` extension is enabled |

### Send Command Handlers
| Service | Alias | Tag | Priority | Enabled |
| --- | --- | --- | --- | --- |
| Lmc\Cqrs\Handler\Handler\CallbackSendCommandHandler | `@lmc_cqrs.send_command_handler.callback` | `lmc_cqrs.send_command_handler` | 50 | *always* |
| Lmc\Cqrs\Http\Handler\HttpSendCommandHandler | `@lmc_cqrs.send_command_handler.http` | `lmc_cqrs.send_command_handler` | 50 | if `http` extension is enabled |

### Response decoders
| Service | Alias | Tag | Priority | Enabled |
| --- | --- | --- | --- | --- |
| Lmc\Cqrs\Http\Decoder\HttpMessageResponseDecoder | `@lmc_cqrs.response_decoder.http` | `lmc_cqrs.response_decoder` | 90 | if `http` extension is enabled |
| Lmc\Cqrs\Http\Decoder\StreamResponseDecoder | `@lmc_cqrs.response_decoder.stream` | `lmc_cqrs.response_decoder` | 70 | if `http` extension is enabled |
| Lmc\Cqrs\Types\Decoder\JsonResponseDecoder | `@lmc_cqrs.response_decoder.json` | `lmc_cqrs.response_decoder` | 20 | *always* |

### Profiler formatters
| Service | Alias | Tag | Priority | Enabled |
| --- | --- | --- | --- | --- |
| Lmc\Cqrs\Http\Formatter\HttpProfilerFormatter | `@lmc_cqrs.profiler_formatter.http` | `lmc_cqrs.profiler_formatter` | -1 | if `http` extension is enabled |
| Lmc\Cqrs\Types\Formatter\JsonProfilerFormatter | - | `lmc_cqrs.profiler_formatter` | -1 | if `profiler` is enabled |
| Lmc\Cqrs\Bundle\Service\ErrorProfilerFormatter | - | `lmc_cqrs.profiler_formatter` | -1 | if `profiler` is enabled |

### Other services
| Class | Alias | Purpose | Enabled |
| --- | --- | --- | --- |
| Lmc\Cqrs\Bundle\Controller\CacheController | - | Controller for invalidating cache from a profiler | if `profiler` is enabled |
| Lmc\Cqrs\Bundle\Profiler\CqrsDataCollector | - | Collects a data about Commands & Queries for a profiler | if `profiler` is enabled |
| Lmc\Cqrs\Handler\ProfilerBag | `@lmc_cqrs.profiler_bag` | A collection of all ProfilerItems, it is a main source of data for a CqrsDataCollector | if `profiler` is enabled |

0 comments on commit e713b24

Please sign in to comment.