diff --git a/.coveralls.yml b/.coveralls.yml
new file mode 100644
index 0000000..6035eb3
--- /dev/null
+++ b/.coveralls.yml
@@ -0,0 +1,3 @@
+service_name: travis-ci
+coverage_clover: build/clover.xml
+json_path: build/coveralls-upload.json
\ No newline at end of file
diff --git a/.docker/php7.3-dev/Dockerfile b/.docker/php7.3-dev/Dockerfile
new file mode 100644
index 0000000..fcc639c
--- /dev/null
+++ b/.docker/php7.3-dev/Dockerfile
@@ -0,0 +1,18 @@
+FROM php:7.3-cli
+
+RUN apt-get update && apt-get install -y git unzip
+
+ENV COMPOSER_ALLOW_SUPERUSER 1
+ENV COMPOSER_MEMORY_LIMIT -1
+
+RUN mkdir /.composer_cache
+ENV COMPOSER_CACHE_DIR /.composer_cache
+
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
+
+RUN composer -vvv global require hirak/prestissimo
+
+# php extensions
+
+RUN pecl install xdebug
+RUN docker-php-ext-enable xdebug
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f5939b2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,25 @@
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+# 4 space indentation
+[*.php]
+indent_style = space
+indent_size = 4
+
+# Tab indentation (no size specified)
+[Makefile]
+indent_style = tab
+
+# Matches the exact files either package.json or .travis.yml
+[{*.yml, *.yaml}]
+indent_style = space
+indent_size = 2
+
+[composer.json]
+indent_size = 4
diff --git a/.env b/.env
new file mode 100644
index 0000000..693bbb3
--- /dev/null
+++ b/.env
@@ -0,0 +1,13 @@
+# This file is a "template" of which env vars need to be defined for your application
+# Copy this file to .env file for development, create environment variables when deploying to production
+# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
+
+###> symfony/framework-bundle ###
+APP_ENV=dev
+APP_SECRET=secret
+###< symfony/framework-bundle ###
+
+###> common variables ###
+MICROBASE_COMPOSE_PROJECT_NAME=micro-base
+CI_COMMIT_REF_SLUG=master
+###< common variables ###
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9a760c5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+vendor/
+
+###> PhpStorm project profile ###
+.idea/
+###< PhpStorm project profile ###
+
+###> phpunit/phpunit ###
+phpunit.xml
+.phpunit.result.cache
+###< phpunit/phpunit ###
+
+###> friendsofphp/php-cs-fixer ###
+.php_cs.cache
+###< friendsofphp/php-cs-fixer ###
+
+###> squizlabs/php_codesniffer ###
+.phpcs-cache
+###< squizlabs/php_codesniffer ###
+
+###> sensiolabs-de/deptrac ###
+.deptrac.cache
+###< sensiolabs-de/deptrac ###
+
+composer.lock
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..270a07b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,30 @@
+
+language: php
+
+matrix:
+ include:
+ - php: 7.3
+ fast_finish: true
+
+env:
+ global:
+ TEST_CONFIG="phpunit.xml.dist"
+
+before_install:
+ - echo "memory_limit=2G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini || return 0
+
+install:
+ - travis_retry composer self-update
+ - composer install
+
+script:
+ - vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests
+ - vendor/bin/psalm --config=psalm.xml
+ - vendor/bin/ecs check src tests
+ - vendor/bin/phpmd src/ text phpmd.xml
+ - vendor/bin/phpunit --configuration $TEST_CONFIG
+ - composer validate --no-check-publish
+ - git log $(git describe --abbrev=0 --tags)...HEAD --no-merges --pretty=format:"* [%h](http://github.com/${TRAVIS_REPO_SLUG}/commit/%H) %s (%cN)"
+
+after_success:
+ - travis_retry php ./vendor/bin/php-coveralls -v --config .coveralls.yml -v;
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..04a87a5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,82 @@
+version = $(shell git describe --tags --dirty --always)
+build_name = application-$(version)
+# use the rest as arguments for "run"
+RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
+# ...and turn them into do-nothing targets
+#$(eval $(RUN_ARGS):;@:)
+
+.PHONY: build
+build: ## build environment and initialize composer and project dependencies
+ docker-compose build
+ make composer-install
+
+.PHONY: stop
+stop:
+ docker-compose stop
+
+.PHONY: composer-install
+composer-install: ## Install project dependencies
+ docker-compose run --rm --no-deps php sh -lc 'composer install'
+
+.PHONY: composer-update
+composer-update: ## Update project dependencies
+ docker-compose run --rm --no-deps php sh -lc 'composer update'
+
+.PHONY: composer-outdated
+composer-outdated: ## Show outdated project dependencies
+ docker-compose run --rm --no-deps php sh -lc 'composer outdated'
+
+.PHONY: composer-validate
+composer-validate: ## Validate composer config
+ docker-compose run --rm --no-deps php sh -lc 'composer validate --no-check-publish'
+
+.PHONY: composer
+composer: ## Execute composer command
+ docker-compose run --rm --no-deps php sh -lc "composer $(RUN_ARGS)"
+
+.PHONY: phpunit
+phpunit: ## execute project unit tests
+ docker-compose run --rm --no-deps php sh -lc "./vendor/bin/phpunit $(conf)"
+
+.PHONY: style
+style: ## executes php analizers
+ docker-compose run --rm --no-deps php sh -lc './vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests'
+ docker-compose run --rm --no-deps php sh -lc './vendor/bin/psalm --config=psalm.xml'
+
+.PHONY: lint
+lint: ## checks syntax of PHP files
+ docker-compose run --rm --no-deps php sh -lc './vendor/bin/parallel-lint ./ --exclude vendor --exclude bin/.phpunit'
+
+.PHONY: layer
+layer: ## Check issues with layers (deptrac tool)
+ docker-compose run --rm --no-deps php sh -lc './vendor/bin/deptrac analyze --formatter-graphviz=0'
+
+.PHONY: logs
+logs: ## look for service logs
+ docker-compose logs -f $(RUN_ARGS)
+
+.PHONY: help
+help: ## Display this help message
+ @cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+.PHONY: php-shell
+php-shell: ## PHP shell
+ docker-compose run --rm php sh -lflay
+
+unit-tests: ## Run unit-tests suite
+ docker-compose run --rm php sh -lc 'vendor/bin/phpunit --testsuite unit-tests'
+
+static-analysis: style layer coding-standards ## Run phpstan, easycoding standarts code static analysis
+
+coding-standards: ## Run check and validate code standards tests
+ docker-compose run --rm --no-deps php sh -lc 'vendor/bin/ecs check src tests'
+ docker-compose run --rm --no-deps php sh -lc 'vendor/bin/phpmd src/ text phpmd.xml'
+
+coding-standards-fixer: ## Run code standards fixer
+ docker-compose run --rm --no-deps php sh -lc 'vendor/bin/ecs check src tests --fix'
+
+security-tests: ## The SensioLabs Security Checker
+ docker-compose run --rm --no-deps php sh -lc 'vendor/bin/security-checker security:check --end-point=http://security.sensiolabs.org/check_lock'
+
+.PHONY: test lint static-analysis phpunit coding-standards composer-validate
+test: build lint static-analysis phpunit coding-standards composer-validate stop ## Run all test suites
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..c517d73
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "micro-module/base",
+ "type": "library",
+ "description": "Micro module Base common library",
+ "license": "proprietary",
+ "require": {
+ "php": "^7.3",
+ "ext-json": "*",
+ "beberlei/assert": "^3.2",
+ "psr/log": "^1.1",
+ "ramsey/uuid": "^3.8 || ^4.0",
+ "monolog/monolog": "~1.22 || ~2.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^0.4",
+ "php-parallel-lint/php-parallel-lint": "^1.0",
+ "mockery/mockery": "^1.3",
+ "phpmd/phpmd": "^2.8",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-mockery": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^9.0",
+ "roave/security-advisories": "dev-master",
+ "symplify/easy-coding-standard": "^7.2",
+ "vimeo/psalm": "3.5.0"
+ },
+ "config": {
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "MicroModule\\Base\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "MicroModule\\Base\\Tests\\Unit\\": "tests/unit"
+ }
+ }
+}
diff --git a/depfile.yml b/depfile.yml
new file mode 100644
index 0000000..9cd398e
--- /dev/null
+++ b/depfile.yml
@@ -0,0 +1,25 @@
+paths:
+ - ./src
+exclude_files:
+
+layers:
+ - name: Domain
+ collectors:
+ - type: className
+ regex: .*\\Domain\\.*
+ - name: Application
+ collectors:
+ - type: className
+ regex: .*\\Application\\.*
+ - name: Infrastructure
+ collectors:
+ - type: className
+ regex: .*\\Infrastructure\\.*
+
+ruleset:
+ Domain:
+ Application:
+ - Domain
+ Infrastructure:
+ - Domain
+ - Application
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..f29c43f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+version: "3.7"
+
+services:
+ php:
+ container_name: ${MICROBASE_COMPOSE_PROJECT_NAME}_php
+ user: 1000:1000
+ build:
+ context: .docker/php7.3-dev
+ volumes:
+ - ~/.composer/cache/:/.composer_cache/:rw
+ - .:/app:rw
+ working_dir: /app
diff --git a/ecs.yml b/ecs.yml
new file mode 100644
index 0000000..629fafb
--- /dev/null
+++ b/ecs.yml
@@ -0,0 +1,17 @@
+imports:
+ - { resource: 'vendor/symplify/easy-coding-standard/config/clean-code.yml' }
+ - { resource: 'vendor/symplify/easy-coding-standard/config/symfony.yml' }
+ - { resource: 'vendor/symplify/easy-coding-standard/config/php71.yml' }
+
+parameters:
+ skip:
+ SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint:
+ - 'src/Infrastructure/Testing/RedisInMemory.php'
+ SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingReturnTypeHint:
+ - 'src/Infrastructure/Testing/RedisInMemory.php'
+ SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff.UnusedMethod:
+ - 'src/Common/Alerting/AlertingProcessor.php'
+ Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer:
+ - 'src/Domain/Exception/ParentExceptionTrait.php'
+ Symplify\CodingStandard\Fixer\Commenting\ParamReturnAndVarTagMalformsFixer:
+ - 'src/Domain/Exception/ParentExceptionTrait.php'
diff --git a/phpmd.xml b/phpmd.xml
new file mode 100644
index 0000000..700df16
--- /dev/null
+++ b/phpmd.xml
@@ -0,0 +1,28 @@
+
+
+ Ruleset for PHP Mess Detector that enforces coding standards
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..c1c0981
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,7 @@
+includes:
+ - vendor/phpstan/phpstan-mockery/extension.neon
+
+parameters:
+ excludes_analyse:
+ - src/Infrastructure/Testing/RedisInMemory.php
+ - src/Infrastructure/Testing/RedisFactory.php
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..d19a08f
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tests/unit
+
+
+
+
+
+
+
+
+
+ ./src
+
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..39b4cb3
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Domain/Command/CommandInterface.php b/src/Domain/Command/CommandInterface.php
new file mode 100644
index 0000000..15f8674
--- /dev/null
+++ b/src/Domain/Command/CommandInterface.php
@@ -0,0 +1,22 @@
+parentException;
+ }
+
+ /**
+ * Return context exception array from parent exception object.
+ *
+ * @param mixed[] $context
+ * @param string $contextSerializeType
+ *
+ * @return mixed[]
+ */
+ public function getParentExceptionContext(array $context = [], string $contextSerializeType = ParentExceptionInterface::CONTEXT_SELIALIZE_NONE): array
+ {
+ $exception = $this->getParentException();
+
+ if ($exception instanceof ParentExceptionInterface) {
+ $parentException = $exception->getParentException();
+ $context['parentException'] = get_class($parentException);
+ $context['parentExceptionErrorMessage'] = sprintf('%s: "%s" at %s line %s', get_class($parentException), $parentException->getMessage(), $parentException->getFile(), $parentException->getLine());
+ $context['parentExceptionContext'] = $this->serializeContext($exception->getParentExceptionContext(), $contextSerializeType);
+ }
+
+ return $context;
+ }
+
+ /**
+ * Serialize if needed context array to string.
+ *
+ * @param mixed[] $context
+ * @param string $contextSerializeType
+ *
+ * @return mixed
+ */
+ protected function serializeContext(array $context = [], string $contextSerializeType = ParentExceptionInterface::CONTEXT_SELIALIZE_NONE)
+ {
+ switch ($contextSerializeType) {
+ case ParentExceptionInterface::CONTEXT_SELIALIZE_BASIC:
+ $context = serialize($context);
+
+ break;
+
+ case ParentExceptionInterface::CONTEXT_SELIALIZE_JSON:
+ $context = json_encode($context);
+
+ break;
+
+ case ParentExceptionInterface::CONTEXT_SELIALIZE_PRINTR:
+ $context = print_r($context, true);
+
+ break;
+
+ case ParentExceptionInterface::CONTEXT_SELIALIZE_NONE:
+ default:
+ break;
+ }
+
+ return $context;
+ }
+}
diff --git a/src/Domain/Exception/SerializationException.php b/src/Domain/Exception/SerializationException.php
new file mode 100644
index 0000000..2d480ae
--- /dev/null
+++ b/src/Domain/Exception/SerializationException.php
@@ -0,0 +1,14 @@
+logger = $logger;
+
+ return $this;
+ }
+
+ /**
+ * Log an regular message or warning.
+ *
+ * @param string $message
+ * @param int $level
+ * @param mixed[] $context
+ *
+ * @return $this
+ */
+ public function logMessage(string $message, int $level, array $context = []): self
+ {
+ if (!$this->logger instanceof LoggerInterface) {
+ return $this;
+ }
+
+ switch ($level) {
+ case LOG_DEBUG:
+ $this->logger->debug($message, $context);
+
+ break;
+
+ case LOG_INFO:
+ $this->logger->info($message, $context);
+
+ break;
+
+ case LOG_NOTICE:
+ $this->logger->notice($message, $context);
+
+ break;
+
+ case LOG_WARNING:
+ $this->logger->warning($message, $context);
+
+ break;
+
+ default:
+ throw new LoggerException(sprintf("Try to log invalid message level type '%s'", $level));
+
+ break;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Log an exception.
+ *
+ * @param Throwable $exception The \Throwable instance
+ * @param int $level
+ * @param string $message The error message to log
+ *
+ * @return $this
+ */
+ public function logException(Throwable $exception, int $level, string $message): self
+ {
+ if (!$this->logger instanceof LoggerInterface) {
+ return $this;
+ }
+
+ $context = ['exception' => $exception];
+ $this->parentException = $exception;
+ $context = $this->getParentExceptionContext($context, ParentExceptionInterface::CONTEXT_SELIALIZE_PRINTR);
+
+ switch ($level) {
+ case LOG_EMERG:
+ $this->logger->emergency($message, $context);
+
+ break;
+
+ case LOG_ALERT:
+ $this->logger->alert($message, $context);
+
+ break;
+
+ case LOG_CRIT:
+ $this->logger->critical($message, $context);
+
+ break;
+
+ case LOG_ERR:
+ $this->logger->error($message, $context);
+
+ break;
+
+ default:
+ throw new LoggerException(sprintf("Try to log invalid error level type '%s'", $level));
+
+ break;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Define exception level from exception type.
+ *
+ * @param Throwable $exception
+ *
+ * @return int
+ */
+ public function getExceptionLevel(Throwable $exception): int
+ {
+ switch (true) {
+ case $exception instanceof EmergencyException:
+ return LOG_EMERG;
+
+ case $exception instanceof AlertException:
+ return LOG_ALERT;
+
+ case $exception instanceof CriticalException:
+ return LOG_CRIT;
+
+ case $exception instanceof ErrorException:
+ return LOG_ERR;
+ }
+
+ return LOG_ERR;
+ }
+
+ /**
+ * Generate exception message.
+ *
+ * @param Throwable $exception
+ *
+ * @return string
+ */
+ public function getExceptionMessage(Throwable $exception): string
+ {
+ return sprintf($this->exceptionMessageTemplate, get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine());
+ }
+}
diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore
new file mode 100644
index 0000000..e69de29