VOOZH about

URL: https://deepwiki.com/hypervel/bus/10-architecture-and-system-design

⇱ Architecture and System Design | hypervel/bus | DeepWiki


Loading...
Menu

Architecture and System Design

This document provides a deep dive into the architectural design of the hypervel/bus system. It covers the dependency injection configuration, factory patterns, the dispatcher pipeline, integration with external systems (hypervel/queue and hypervel/cache), and the design patterns employed throughout the codebase.

For information about specific subsystems, see:

  • Job dispatching mechanisms: 4
  • Batch processing implementation: 7
  • Configuration and installation: 2
  • Core conceptual abstractions: 3

Architectural Layers

The system is organized into four distinct architectural layers that separate concerns and maintain loose coupling between components.

Layer Structure


Layer 1 (Public API) provides the entry points developers interact with. The dispatch() and dispatch_sync() functions in src/Functions.php serve as global helpers, while traits like Dispatchable and DispatchesJobs provide static methods on job classes and service classes respectively.

Layer 2 (Job Configuration) contains "Pending" objects that wrap jobs and accumulate configuration before dispatch. Each Pending object provides a fluent interface for setting queue names, delays, callbacks, and other options. The __destruct() method on these objects triggers actual dispatch when the configuration object goes out of scope.

Layer 3 (Core Dispatching Engine) implements the command bus pattern through the Dispatcher class src/Dispatcher.php This layer decides whether to execute jobs synchronously or push them to a queue, applies middleware pipelines, and manages the dispatch lifecycle.

Layer 4 (Persistence and Integration) handles batch storage via the repository pattern and integrates with external hypervel/queue and hypervel/cache systems. This layer abstracts storage implementations behind contracts.

Sources: composer.json1-60 src/ConfigProvider.php1-24 src/Dispatcher.php1-244


Dependency Injection Architecture

The system uses Hyperf's dependency injection container for service registration and lifecycle management. All core services are bound through the ConfigProvider class.

Service Registration


The ConfigProvider::__invoke() method returns a configuration array with a dependencies key that maps contracts to implementations src/ConfigProvider.php14-21:

ContractImplementation StrategyPurpose
DispatcherDispatcherFactory classCreates dispatcher with queue resolver
BatchRepositoryClosure returning DatabaseBatchRepositoryProvides batch persistence
DatabaseBatchRepositoryDatabaseBatchRepositoryFactory classCreates repository with database connection

The factory pattern is used for Dispatcher and DatabaseBatchRepository because these classes require runtime configuration (queue resolver, database connection resolver) that must be obtained from the container.

The BatchRepository binding uses a closure fn (ContainerInterface $container) => $container->get(DatabaseBatchRepository::class) src/ConfigProvider.php18 to delegate to the more specific implementation while satisfying the interface contract.

Sources: src/ConfigProvider.php1-24 composer.json46-48


Factory Pattern Implementation

Factories handle the construction of complex objects that require dependency resolution from the container. The system uses two primary factories.

Factory Responsibilities


DispatcherFactory is responsible for:

  1. Resolving the QueueFactory from the container
  2. Creating a closure that resolves queue connections on-demand
  3. Instantiating Dispatcher with the container and queue resolver
  4. Returning the fully configured dispatcher instance

DatabaseBatchRepositoryFactory is responsible for:

  1. Resolving the database connection resolver from the container
  2. Retrieving configuration for the batch connection name
  3. Instantiating DatabaseBatchRepository with these dependencies
  4. Returning the configured repository instance

This factory abstraction allows the system to delay expensive operations (like establishing database connections or queue connections) until they are actually needed, supporting lazy initialization.

Sources: src/ConfigProvider.php17-19


Dispatcher Pipeline Architecture

The Dispatcher class implements a pipeline pattern for processing commands before they reach their handlers. This allows middleware to intercept, transform, or observe commands.

Pipeline Flow


The pipeline is initialized in the Dispatcher constructor src/Dispatcher.php47:

$this->pipeline = new Pipeline($container);

When dispatching synchronously via dispatchNow(), the command flows through the pipeline src/Dispatcher.php108-111:

return $this->pipeline
 ->send($command)
 ->through($this->pipes)
 ->then($callback);

The $pipes array contains middleware classes that can:

  • Log command execution
  • Validate command properties
  • Transform command data
  • Implement cross-cutting concerns (authorization, caching, etc.)

Middleware can be configured via the pipeThrough() method src/Dispatcher.php228-233

Handler Resolution

The dispatcher resolves handlers through three strategies:

  1. Explicit handler mapping: Commands mapped via map() method src/Dispatcher.php239-243
  2. Self-handling commands: Commands with their own handle() or __invoke() method src/Dispatcher.php101
  3. Container resolution: Commands resolved through the container's call() method src/Dispatcher.php104

The handler resolution logic appears in src/Dispatcher.php91-106

Sources: src/Dispatcher.php1-244


Queue System Integration

The dispatcher integrates with the hypervel/queue package for asynchronous job processing. This integration is managed through a queue resolver closure.

Queue Resolution Architecture


When a command implements the ShouldQueue interface, the dispatcher routes it to the queue system src/Dispatcher.php55-58:

return $this->queueResolver && $this->commandShouldBeQueued($command)
 ? $this->dispatchToQueue($command)
 : $this->dispatchNow($command);

The dispatchToQueue() method src/Dispatcher.php180-195:

  1. Extracts the connection name from $command->connection src/Dispatcher.php182
  2. Calls the queueResolver closure to get a Queue instance src/Dispatcher.php184
  3. Delegates to the command's queue() method if available src/Dispatcher.php190-192
  4. Otherwise pushes the command using pushCommandToQueue() src/Dispatcher.php194

The pushCommandToQueue() method inspects command properties to determine the appropriate queue method src/Dispatcher.php200-215:

Command PropertiesQueue MethodPurpose
queue + delaylaterOn()Delayed dispatch to specific queue
queue onlypushOn()Immediate dispatch to specific queue
delay onlylater()Delayed dispatch to default queue
Neitherpush()Immediate dispatch to default queue

Sources: src/Dispatcher.php54-215 composer.json54-55


Cache System Integration

The system integrates with hypervel/cache for job uniqueness constraints. The UniqueLock class uses cache as a distributed locking mechanism.

Uniqueness Lock Flow


The cache integration serves several purposes:

  1. Lock storage: Unique job identifiers are stored as cache keys with TTL matching the job's uniqueness duration
  2. Distributed coordination: Multiple application instances can coordinate through shared cache
  3. Automatic expiration: Cache TTL ensures locks are automatically released without manual cleanup

Jobs specify uniqueness through three methods:

  • uniqueId(): Returns the identifier that makes a job unique
  • uniqueFor(): Returns TTL in seconds for the lock
  • uniqueVia(): Returns a custom cache repository instance

The cache dependency is declared in composer.json34 as hypervel/cache.

Sources: composer.json34


Batch Persistence Architecture

The batch system uses the repository pattern to abstract batch storage. This allows different storage backends while maintaining a consistent interface.

Repository Pattern Structure


The BatchRepository contract defines the core operations for batch management. The PrunableBatchRepository extends it with cleanup operations for removing old batch records.

DatabaseBatchRepository implements both contracts and stores batches in a relational database table. Key implementation details include:

  • Atomic updates: Job count updates use database-level atomic operations to prevent race conditions
  • Row locking: The repository uses SELECT ... FOR UPDATE to lock batch rows during updates
  • Transaction support: The transaction() method wraps operations in database transactions
  • Serialization: Batch callbacks and options are serialized before storage

The repository is bound in the DI container src/ConfigProvider.php18 so that Batch objects and Dispatcher can resolve it without direct coupling to the implementation.

Sources: src/ConfigProvider.php18


Design Patterns

The system employs several design patterns to achieve flexibility, testability, and maintainability.

Pattern Summary

PatternImplementationPurpose
Command BusDispatcher classDecouples command creation from execution
FactoryDispatcherFactory, DatabaseBatchRepositoryFactoryManages complex object construction
RepositoryBatchRepository interfaceAbstracts data persistence
PipelinePipeline with middlewareAllows command preprocessing
Pending ObjectPendingDispatch, PendingChain, PendingBatchFluent configuration interface
Trait CompositionDispatchable, Queueable, BatchableMixin-based functionality
Marker InterfaceShouldQueue, ShouldBeUniqueBehavioral flags without methods
StrategyHandler resolution strategiesPluggable handler selection

Command Bus Pattern

The core Dispatcher implements the command bus pattern src/Dispatcher.php20:

class Dispatcher implements QueueingDispatcher

Commands (jobs) are dispatched through a central bus that:

  • Routes commands to appropriate handlers
  • Decides sync vs async execution
  • Applies middleware transformations
  • Manages command lifecycle

This pattern decouples the command issuer from the command executor, enabling cross-cutting concerns like logging, validation, and transaction management.

Pending Object Pattern

The Pending object pattern is used extensively for configuration:


Objects like PendingDispatch, PendingChain, and PendingBatch wrap jobs and provide fluent methods for configuration. When the pending object is destroyed (goes out of scope), its __destruct() method triggers the actual dispatch. This pattern provides:

  • Fluent interface: Chainable method calls for configuration
  • Deferred execution: Configuration accumulates before dispatch
  • Automatic cleanup: No explicit ->dispatch() call needed

Trait Composition Pattern

The system uses traits extensively to provide opt-in functionality:


Jobs compose functionality by using traits:

  • Dispatchable: Adds static dispatch methods
  • Queueable: Adds queue configuration properties
  • Batchable: Adds batch association methods
  • InteractsWithQueue: Adds queue interaction methods

This allows jobs to opt into only the features they need, avoiding a heavy base class.

Sources: src/Dispatcher.php1-244 src/ConfigProvider.php1-24


System Boundaries and External Dependencies

The system maintains clear boundaries with external systems through contracts and dependency injection.

Dependency Graph


External Package Dependencies

The composer.json declares external dependencies composer.json23-35:

Required Hyperf packages (framework core):

  • hyperf/context: Request context management
  • hyperf/contract: Core contracts
  • hyperf/collection: Data collections
  • hyperf/conditionable: Conditional method calls
  • hyperf/coroutine: Coroutine utilities
  • hyperf/support: Helper functions

Required Hypervel packages (ecosystem):

  • hypervel/support: Shared utilities
  • hypervel/cache: Cache abstraction layer

Third-party packages:

  • nesbot/carbon: Date/time handling for batch records and delays
  • laravel/serializable-closure: Serializing closures for queue jobs

Suggested packages:

  • hypervel/queue: Required for actual job queuing (not dispatching synchronously)

The system is designed so that the queue integration is optional - jobs can be dispatched synchronously without the queue package installed. This allows the command bus to be used in contexts where async processing isn't needed.

Sources: composer.json1-60


Dispatcher Execution Flow

The dispatcher coordinates all job execution through a well-defined flow that handles both synchronous and asynchronous dispatch.

Complete Execution Flow


This flow diagram maps directly to code in the Dispatcher class:

  1. Initial routing src/Dispatcher.php54-58: Determines sync vs async based on ShouldQueue interface
  2. Queue dispatch src/Dispatcher.php180-215: Resolves queue and pushes command with appropriate delay/queue settings
  3. Synchronous dispatch src/Dispatcher.php80-112: Sets up job context, resolves handler, and executes through pipeline
  4. Handler resolution src/Dispatcher.php91-106: Uses explicit mapping, self-handling, or container resolution

The flow ensures that:

  • Jobs implementing ShouldQueue are always queued (unless explicitly forced synchronous)
  • Synchronous jobs receive a SyncJob instance for queue interaction compatibility
  • Handler resolution follows a predictable priority order
  • All synchronous execution passes through the middleware pipeline

Sources: src/Dispatcher.php54-112 src/Dispatcher.php180-215