Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MortalFlesh committed May 6, 2021
1 parent e724f3c commit 82b08ec
Show file tree
Hide file tree
Showing 31 changed files with 3,236 additions and 0 deletions.
75 changes: 75 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/bin/
/vendor/
composer.lock

.phpunit.result.cache
/reports/
262 changes: 262 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
LMC CQRS Handlers
=================

[![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-handler/actions/workflows/tests.yaml/badge.svg)](https://github.com/lmc-eu/cqrs-handler/actions/workflows/tests.yaml)
[![Coverage Status](https://coveralls.io/repos/github/lmc-eu/cqrs-handler/badge.svg?branch=main)](https://coveralls.io/github/lmc-eu/cqrs-handler?branch=main)

> This library contains a base implementation for [CQRS/Types](https://github.com/lmc-eu/cqrs-types).
## Table of contents
- [Installation](#installation)
- Queries
- [Query Fetcher](#query-fetcher)
- [Query Handlers](#query-handlers)
- [Query](#query)
- Commands
- [Command Sender](#command-sender)
- [Send Command Handlers](#send-command-handlers)
- [Command](#command)
- [ProfilerBag](#profiler-bag)

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

## Query Fetcher
Base implementation for a Query Fetcher Interface (see [Types/QueryFetcherInterface](https://github.com/lmc-eu/cqrs-types#query-fetcher-interface)).

It is responsible for
- finding a Query Handler based on Query request type
- handle all Query features
- caching
- requires an instance of `Psr\Cache\CacheItemPoolInterface`
- profiling
- requires an instance of `Lmc\Cqrs\Handler\ProfilerBag`
- decoding a response from the Query Handler

### Usage
If you are not using a [CQRS/Bundle](https://github.com/lmc-eu/cqrs-bundle) you need to set up a Query Fetcher yourself.

Minimal Initialization
```php
$queryFetcher = new QueryFetcher(
// Cache
false, // disabled cache
null, // no cache pool -> no caching

// Profiling
null // no profiler bag -> no profiling
);
```

Full Initialization with all features.
```php
$profilerBag = new ProfilerBag();

$queryFetcher = new QueryFetcher(
// Cache
true, // is cache enabled
$cache, // instance of Psr\Cache\CacheItemPoolInterface

// Profiling
$profilerBag, // collection of profiled information

// Custom handlers
// NOTE: there is multiple ways of defining handler
[
[new TopMostHandler(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority
new OtherHandler(), // Used with default priority of 50
new PrioritizedItem(new FallbackHandler(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition
],

// Custom response decoders
// NOTE: there is multiple ways of defining response decoders
[
[new TopMostDecoder(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority
new OtherDecoder(), // Used with default priority of 50
new PrioritizedItem(new FallbackDecoder(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition
]
);
```

You can add handlers and decoders by `add` methods.
```php
$this->queryFetcher->addHandler(new MyQueryHandler(), PrioritizedItem::PRIORITY_MEDIUM);
$this->queryFetcher->addDecoder(new MyQueryResponseDecoder(), PrioritizedItem::PRIORITY_HIGH);
```

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());
}
```

## Query Handlers
It is responsible for handling a specific Query request and passing a result into `OnSuccess` callback. [See more here](https://github.com/lmc-eu/cqrs-types#query-handler-interface).

### GetCachedHandler
This handler is automatically created `QueryFetcher` and added amongst handlers with priority `80` when an instance of `CacheItemPoolInterface` is passed into `QueryFetcher`.

It supports queries implementing `CacheableInterface` with `cacheTime > 0`. The second condition allows you to avoid caching in queries with `CacheableInterface` by just a cache time value.
There is also `CacheTime::noCache()` named constructor to make it explicit.

It handles a query by retrieving a result out of a cache (if the cache has the item and is `hit` (see [PSR-6](https://www.php-fig.org/psr/psr-6/) for more).

### CallbackQueryHandler
This handler supports a query with request type of `"callable"`, `"Closure"` or `"callback"` (which all stands for a `callable` request).

It simply calls a created request as a function and returns a result to `OnSuccess` callback.

## Query
Query is a request which fetch a data without changing anything. [See more here](https://github.com/lmc-eu/cqrs-types#query-interface)

### CachedDataQuery
This is a predefined implementation for a Query with `CacheableInterface`.

It is handy for in-app queries where you want to use cache for a result. You can also extend it and add more features.

```php
$query = new CallbackQueryHandler(
fn () => $this->repository->fetchData(),
new CacheKey('my-data-key'),
CacheTime::oneHour()
);
```

### ProfiledCachedDataQuery
This is a predefined implementation for a Query with `CacheableInterface` and `ProfileableInterface`.

It is handy for in-app queries where you want to use cache for a result and also profile it. You can also extend it and add more features.

```php
$query = new ProfiledCallbackQueryHandler(
fn () => $this->repository->fetchData(),
new CacheKey('my-data-key'),
CacheTime::oneHour(),
'my-profiler-key',
['additional' => 'data'] // optional
);
```

---

## Command Sender
Base implementation for a Command Sender Interface (see [Types/CommandSenderInterface](https://github.com/lmc-eu/cqrs-types#commmand-sender-interface)).

It is responsible for
- finding a Send Command Handler based on Command request type
- handle all Command features
- profiling
- requires an instance of `Lmc\Cqrs\Handler\ProfilerBag`
- decoding a response from the Send Command Handler

### Usage
If you are not using a [CQRS/Bundle](https://github.com/lmc-eu/cqrs-bundle) you need to set up a Command Sender yourself.

Minimal Initialization
```php
$commandSender = new CommandSender(
// Profiling
null // no profiler bag -> no profiling
);
```

Full Initialization with all features.
```php
$profilerBag = new ProfilerBag();

$commandSender = new CommandSender(
// Profiling
$profilerBag, // collection of profiled information

// Custom handlers
// NOTE: there is multiple ways of defining handler
[
[new TopMostHandler(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority
new OtherHandler(), // Used with default priority of 50
new PrioritizedItem(new FallbackHandler(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition
],

// Custom response decoders
// NOTE: there is multiple ways of defining response decoders
[
[new TopMostDecoder(), PrioritizedItem::PRIORITY_HIGHEST], // Array definition of priority
new OtherDecoder(), // Used with default priority of 50
new PrioritizedItem(new FallbackDecoder(), PrioritizedItem::PRIORITY_LOWEST) // PrioritizedItem value object definition
]
);
```

You can add handlers and decoders by `add` methods.
```php
$this->commandSender->addHandler(new MyCommandHandler(), PrioritizedItem::PRIORITY_MEDIUM);
$this->commandSender->addDecoder(new MyCommandResponseDecoder(), PrioritizedItem::PRIORITY_HIGH);
```

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());
}
```

## Send Command Handlers
It is responsible for handling a specific Command request and passing a result into `OnSuccess` callback. [See more here](https://github.com/lmc-eu/cqrs-types#send-command-handler-interface).

### CallbackSendCommandHandler
This handler supports a command with request type of `"callable"`, `"Closure"` or `"callback"` (which all stands for a `callable` request).

It simply calls a created request as a function and returns a result to `OnSuccess` callback.

## Command
Command is a request which change a data and may return result data. [See more here](https://github.com/lmc-eu/cqrs-types#command-interface)

### ProfiledDataCommand
This is a predefined implementation for a Command with `ProfileableInterface`.

It is handy for in-app commands where you want to profile it. You can also extend it and add more features.

```php
$command = new ProfiledDataCommand(
fn () => $this->repository->fetchData(),
new CacheKey('my-data-key'),
CacheTime::oneHour(),
'my-profiler-key',
['additional' => 'data'] // optional
);
```

## ProfilerBag
Service, which is a collection of all profiler information in the current request.
If you pass it to the `QueryFetcher` or `CommandSender`, they will profile query/command implementing `ProfileableInterface` to the `ProfilerBag`.

The information inside are used by a `CqrsDataCollector`, which shows them in the Symfony profiler (used in [CQRS/Bundle](https://github.com/lmc-eu/cqrs-bundle)).
Loading

0 comments on commit 82b08ec

Please sign in to comment.