{"id":27017847,"url":"https://github.com/ody-dev/cqrs","last_synced_at":"2026-02-18T00:32:38.830Z","repository":{"id":284987214,"uuid":"956716995","full_name":"ody-dev/cqrs","owner":"ody-dev","description":"CQRS implementation for ODY framework","archived":false,"fork":false,"pushed_at":"2025-04-06T18:27:41.000Z","size":106,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-13T17:06:35.754Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ody-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-03-28T18:27:00.000Z","updated_at":"2025-04-06T18:22:05.000Z","dependencies_parsed_at":"2025-03-28T19:34:00.225Z","dependency_job_id":"1266bb9b-c13e-4b6c-9788-8036916078bf","html_url":"https://github.com/ody-dev/cqrs","commit_stats":null,"previous_names":["ody-dev/cqrs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ody-dev/cqrs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ody-dev%2Fcqrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ody-dev%2Fcqrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ody-dev%2Fcqrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ody-dev%2Fcqrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ody-dev","download_url":"https://codeload.github.com/ody-dev/cqrs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ody-dev%2Fcqrs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29563467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T21:50:49.831Z","status":"ssl_error","status_checked_at":"2026-02-17T21:46:15.313Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-04T16:33:49.232Z","updated_at":"2026-02-18T00:32:38.805Z","avatar_url":"https://github.com/ody-dev.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ODY CQRS\n\nA robust and flexible CQRS (Command Query Responsibility Segregation) implementation for the ODY PHP\nframework.\n\n[![PHP Version](https://img.shields.io/badge/php-%3E%3D8.3-8892BF.svg)](https://www.php.net/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Overview\n\nODY CQRS provides a clean separation between commands (that modify state) and queries (that return data) in your\napplication. This implementation focuses on a synchronous approach for reliable and predictable execution of your\ndomain operations.\n\nKey features:\n\n- **Command Bus**: Process state-changing operations\n- **Query Bus**: Handle data retrieval operations\n- **Event Bus**: Broadcast and handle domain events\n- **Middleware Support**: Extend functionality with custom middleware\n- **Attribute-based Registration**: Simple handler declaration with PHP 8 attributes\n\n## Installation\n\n```bash\ncomposer require ody/cqrs\n```\n\n## Quick Start\n\n### 1. Define Commands, Queries, and Events\n\n```php\n\u003c?php\n// Command to modify state\nnamespace App\\Commands;\n\nuse Ody\\CQRS\\Message\\Command;\n\nclass CreateUserCommand extends Command\n{\n    public function __construct(\n        private string $name,\n        private string $email,\n        private string $password\n    ) {}\n\n    public function getName(): string\n    {\n        return $this-\u003ename;\n    }\n\n    public function getEmail(): string\n    {\n        return $this-\u003eemail;\n    }\n\n    public function getPassword(): string\n    {\n        return $this-\u003epassword;\n    }\n}\n\n// Query to retrieve data\nnamespace App\\Queries;\n\nuse Ody\\CQRS\\Message\\Query;\n\nclass GetUserById extends Query\n{\n    public function __construct(\n        private string $id\n    ) {}\n\n    public function getId(): string\n    {\n        return $this-\u003eid;\n    }\n}\n\n// Event to notify about domain changes\nnamespace App\\Events;\n\nuse Ody\\CQRS\\Message\\Event;\n\nclass UserWasCreated extends Event\n{\n    public function __construct(\n        private string $id\n    ) {}\n\n    public function getId(): string\n    {\n        return $this-\u003eid;\n    }\n}\n```\n\n### 2. Create Handlers\n\n```php\n\u003c?php\nnamespace App\\Services;\n\nuse App\\Commands\\CreateUserCommand;\nuse App\\Events\\UserWasCreated;\nuse App\\Models\\User;\nuse App\\Queries\\GetUserById;\nuse Ody\\CQRS\\Attributes\\CommandHandler;\nuse Ody\\CQRS\\Attributes\\EventHandler;\nuse Ody\\CQRS\\Attributes\\QueryHandler;\nuse Ody\\CQRS\\Interfaces\\EventBusInterface;\n\nclass UserService\n{\n    #[CommandHandler]\n    public function createUser(CreateUserCommand $command, EventBusInterface $eventBus)\n    {\n        $user = User::create([\n            'name' =\u003e $command-\u003egetName(),\n            'email' =\u003e $command-\u003egetEmail(),\n            'password' =\u003e $command-\u003egetPassword(),\n        ]);\n\n        $eventBus-\u003epublish(new UserWasCreated($user-\u003eid));\n    }\n\n    #[QueryHandler]\n    public function getUserById(GetUserById $query)\n    {\n        return User::findOrFail($query-\u003egetId());\n    }\n\n    #[EventHandler]\n    public function when(UserWasCreated $event): void\n    {\n        logger()-\u003einfo(\"User was created: \" . $event-\u003egetId());\n    }\n}\n```\n\n### 3. Use in Controllers\n\n```php\n\u003c?php\nnamespace App\\Controllers;\n\nuse App\\Commands\\CreateUserCommand;\nuse App\\Queries\\GetUserById;\nuse Ody\\CQRS\\Interfaces\\CommandBusInterface;\nuse Ody\\CQRS\\Interfaces\\QueryBusInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\n\nclass UserController\n{\n    public function __construct(\n        private readonly CommandBusInterface $commandBus,\n        private readonly QueryBusInterface   $queryBus\n    ) {}\n\n    public function createUser(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface\n    {\n        $data = $request-\u003egetParsedBody();\n        $this-\u003ecommandBus-\u003edispatch(\n            new CreateUserCommand(\n                name: $data['name'],\n                email: $data['email'],\n                password: $data['password']\n            )\n        );\n\n        return $response-\u003ejson([\n            'status' =\u003e 'success'\n        ]);\n    }\n\n    public function getUser(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface\n    {\n        $user = $this-\u003equeryBus-\u003edispatch(\n            new GetUserById(\n                id: $args['id']\n            )\n        );\n\n        return $response-\u003ejson($user);\n    }\n}\n```\n\n### 4. Configure CQRS\n\nCreate or update `config/cqrs.php`:\n\n```php\n\u003c?php\nreturn [\n    'handler_paths' =\u003e [\n        app_path('Services'),\n    ],\n];\n```\n\n## Advanced Configuration\n\nThe CQRS module includes several configuration options:\n\n```php\n\u003c?php\nreturn [\n    // Paths to scan for handlers\n    'handler_paths' =\u003e [\n        app_path('Services'),\n    ],\n];\n```\n\n## Middleware\n\nYou can add custom middleware to each bus for tasks like logging, validation, or authentication.\n\n### Example: Command Bus Middleware\n\n```php\n\u003c?php\nnamespace App\\Middleware;\n\nuse Ody\\CQRS\\Bus\\Middleware\\CommandBusMiddleware;\n\nclass LoggingMiddleware extends CommandBusMiddleware\n{\n    public function handle(object $command, callable $next): void\n    {\n        logger()-\u003einfo('Handling command: ' . get_class($command));\n        \n        // Execute the next middleware or the final handler\n        $next($command);\n        \n        logger()-\u003einfo('Command handled: ' . get_class($command));\n    }\n}\n\n// Register middleware\n$commandBus-\u003eaddMiddleware(new LoggingMiddleware());\n```\n\n# CQRS Middleware System\n\nThe CQRS middleware system allows you to intercept and modify the behavior of commands, queries, and events at various\npoints in their lifecycle. This powerful feature enables cross-cutting concerns like logging, validation, authorization,\nand caching without modifying your core business logic.\n\n## Types of Middleware\n\nThere are four types of middleware:\n\n1. **Before**: Executes before the target method is called\n2. **Around**: Wraps the execution of the target method\n3. **After**: Executes after the target method returns successfully\n4. **AfterThrowing**: Executes when the target method throws an exception\n\n## Creating Middleware\n\nMiddleware classes are simple PHP classes with methods decorated with attribute annotations.\n\n### Example: Logging Middleware\n\n```php\nnamespace App\\Middleware;\n\nuse Ody\\CQRS\\Middleware\\Before;\nuse Ody\\CQRS\\Middleware\\After;\nuse Ody\\CQRS\\Middleware\\AfterThrowing;\n\nclass LoggingMiddleware\n{\n    #[Before(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\CommandBus::executeHandler\")]\n    public function logBeforeCommand(object $command): void\n    {\n        logger()-\u003einfo('Processing command: ' . get_class($command));\n    }\n\n    #[After(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\QueryBus::executeHandler\")]\n    public function logAfterQuery(mixed $result, array $args): mixed\n    {\n        $query = $args[0] ?? null;\n        \n        if ($query) {\n            logger()-\u003einfo('Query processed: ' . get_class($query));\n        }\n        \n        return $result;\n    }\n\n    #[AfterThrowing(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\EventBus::executeHandlers\")]\n    public function logEventException(\\Throwable $exception, array $args): void\n    {\n        $event = $args[0] ?? null;\n        \n        if ($event) {\n            logger()-\u003eerror('Error handling event: ' . get_class($event));\n        }\n    }\n}\n```\n\n### Example: Transactional Middleware\n\n```php\nnamespace App\\Middleware;\n\nuse Ody\\CQRS\\Middleware\\Around;\nuse Ody\\CQRS\\Middleware\\MethodInvocation;\n\nclass TransactionalMiddleware\n{\n    public function __construct(private \\PDO $connection)\n    {\n    }\n\n    #[Around(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\CommandBus::executeHandler\")]\n    public function transactional(MethodInvocation $invocation): mixed\n    {\n        $this-\u003econnection-\u003ebeginTransaction();\n        \n        try {\n            $result = $invocation-\u003eproceed();\n            $this-\u003econnection-\u003ecommit();\n            return $result;\n        } catch (\\Throwable $exception) {\n            $this-\u003econnection-\u003erollBack();\n            throw $exception;\n        }\n    }\n}\n```\n\n## Pointcut Expressions\n\nPointcut expressions determine which methods the middleware applies to. The syntax supports:\n\n1. **Exact Class Match**: `App\\Services\\UserService`\n2. **Namespace Wildcard**: `App\\Domain\\*`\n3. **Method Match**: `App\\Services\\UserService::createUser`\n4. **Any Method Wildcard**: `App\\Services\\UserService::*`\n5. **Global Wildcard**: `*` (matches everything)\n6. **Logical Operations**: `App\\Domain\\* \u0026\u0026 !App\\Domain\\Internal\\*`\n\n### Examples\n\n```php\n// Match any command in the Order namespace\n#[Before(pointcut: \"App\\\\Commands\\\\Order\\\\*\")]\npublic function validateOrderCommand(object $command): void { }\n\n// Match a specific method in a specific class\n#[Around(pointcut: \"App\\\\Services\\\\PaymentService::processPayment\")]\npublic function securePaymentProcessing(MethodInvocation $invocation): mixed { }\n\n// Match multiple patterns with logical OR\n#[After(pointcut: \"App\\\\Domain\\\\User\\\\* || App\\\\Domain\\\\Account\\\\*\")]\npublic function auditUserChanges(mixed $result, array $args): mixed { }\n```\n\n## Middleware Priority\n\nYou can control the order in which middleware executes by setting a priority. Lower values run first.\n\n```php\n// Runs before other middleware\n#[Before(priority: 1, pointcut: \"*\")]\npublic function highPriorityMiddleware(): void { }\n\n// Runs after middleware with lower priority values\n#[Before(priority: 100, pointcut: \"*\")]\npublic function lowPriorityMiddleware(): void { }\n```\n\n## Registering Middleware\n\nMiddleware is discovered and registered automatically from configured directories:\n\n```php\n// config/cqrs.php\nreturn [\n    // ...\n    'middleware_paths' =\u003e [\n        app_path('Middleware'),\n    ],\n    // ...\n];\n```\n\n## Before Middleware\n\nBefore middleware runs before a method executes. It's useful for:\n\n- Validation\n- Authorization\n- Parameter transformation\n- Logging\n\n```php\n#[Before(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\CommandBus::executeHandler\")]\npublic function validateCommand(object $command): void\n{\n    // Validate the command\n    $errors = $this-\u003evalidator-\u003evalidate($command);\n    \n    if (!empty($errors)) {\n        throw new ValidationException($errors);\n    }\n}\n```\n\n## Around Middleware\n\nAround middleware wraps a method execution. It's useful for:\n\n- Transactions\n- Timing measurements\n- Caching\n- Retry logic\n\n```php\n#[Around(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\QueryBus::executeHandler\")]\npublic function cacheQueryResults(MethodInvocation $invocation): mixed\n{\n    $args = $invocation-\u003egetArguments();\n    $query = $args[0];\n    \n    $cacheKey = 'query:' . get_class($query) . ':' . md5(serialize($query));\n    \n    if ($this-\u003ecache-\u003ehas($cacheKey)) {\n        return $this-\u003ecache-\u003eget($cacheKey);\n    }\n    \n    $result = $invocation-\u003eproceed();\n    \n    $this-\u003ecache-\u003eset($cacheKey, $result, 3600);\n    \n    return $result;\n}\n```\n\n## After Middleware\n\nAfter middleware runs after a method successfully returns. It's useful for:\n\n- Result transformation\n- Post-processing\n- Logging\n- Event publishing\n\n```php\n#[After(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\QueryBus::executeHandler\")]\npublic function transformQueryResult(mixed $result, array $args): mixed\n{\n    // Transform the result\n    if (is_array($result)) {\n        return array_map(function ($item) {\n            return $this-\u003etransformer-\u003etransform($item);\n        }, $result);\n    }\n    \n    return $this-\u003etransformer-\u003etransform($result);\n}\n```\n\n## AfterThrowing Middleware\n\nAfterThrowing middleware runs when a method throws an exception. It's useful for:\n\n- Exception handling\n- Logging errors\n- Fallback strategies\n- Error notification\n\n```php\n#[AfterThrowing(pointcut: \"Ody\\\\CQRS\\\\Bus\\\\CommandBus::executeHandler\")]\npublic function handleCommandException(\\Throwable $exception, array $args): void\n{\n    $command = $args[0];\n    \n    logger()-\u003eerror('Command failed: ' . get_class($command), [\n        'command' =\u003e $command,\n        'exception' =\u003e $exception-\u003egetMessage(),\n        'trace' =\u003e $exception-\u003egetTraceAsString()\n    ]);\n    \n    // Notify monitoring system\n    $this-\u003ealertService-\u003esendAlert('Command failed: ' . get_class($command));\n}\n```\n\n## Performance Considerations\n\nIn the Swoole environment where ODY runs, middleware registration happens once at bootstrap time, \\\nmaking the runtime overhead minimal. The middleware system is designed to be efficient:\n\n1. **Cached Resolution**: Pointcut expressions are evaluated once and cached\n2. **Minimal Reflection**: Heavy reflection work is done during bootstrap\n3. **Optimized Invocation**: Method invocation chains are built efficiently\n\n## Configuration Options\n\n```php\n// config/cqrs.php\nreturn [\n    // ...\n    \n    // Paths to scan for middleware classes\n    'middleware_paths' =\u003e [\n        app_path('Middleware'),\n    ],\n    \n    // Middleware configuration\n    'middleware' =\u003e [\n        // Global middleware applied to all buses\n        'global' =\u003e [\n            // Example: App\\Middleware\\LoggingMiddleware::class,\n        ],\n        \n        // Command bus specific middleware\n        'command' =\u003e [\n            // Example: App\\Middleware\\TransactionalMiddleware::class,\n        ],\n        \n        // Query bus specific middleware\n        'query' =\u003e [\n            // Example: App\\Middleware\\CachingMiddleware::class,\n        ],\n        \n        // Event bus specific middleware\n        'event' =\u003e [\n            // Example: App\\Middleware\\AsyncEventMiddleware::class,\n        ],\n    ],\n    \n    // ...\n];\n```\n\n## Swoole Coroutines Integration\n\nWhile the CQRS implementation itself is synchronous, you can still leverage Swoole's coroutines in your application when\nusing this module:\n\n1. Multiple command and query handlers can still benefit from Swoole's coroutine scheduler\n2. I/O operations within handlers can take advantage of Swoole's non-blocking capabilities\n3. Your application remains responsive while handlers execute their logic\n\n## Best Practices\n\n1. **Keep Commands and Queries Simple**: They should be DTOs (Data Transfer Objects) without complex logic\n2. **Single Responsibility**: Each handler should handle one specific command or query\n3. **Domain Events**: Use events to notify about state changes, not to perform side effects\n4. **Idempotency**: Design command handlers to be idempotent (can be executed multiple times with the same result)\n5. **Transactions**: Use database transactions in command handlers to ensure atomicity\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fody-dev%2Fcqrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fody-dev%2Fcqrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fody-dev%2Fcqrs/lists"}