federicozardi / eventing
Framework-agnostic eventing toolkit for PHP: EventBridge publishing, DynamoDB event store, HMAC-secured webhooks.
Requires
- php: >=8.2
- aws/aws-sdk-php: ^3.0
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^11.0
This package is not auto-updated.
Last update: 2026-05-01 11:28:37 UTC
README
Framework-agnostic eventing toolkit for PHP:
- Event store: DynamoDB (append-only + load)
- Event bus: EventBridge publisher
- Webhook security: HMAC signer/verifier (portable: Laravel + Opencart)
This package does not provision AWS resources. It assumes DynamoDB tables and EventBridge bus/rules/targets are created via IaC (Terraform/CloudFormation).
Requirements
- PHP
>=8.2 - AWS credentials via standard AWS SDK chain (env vars, IAM role, instance profile, etc.)
Event envelope (v1)
EventEnvelope fields:
eventId(uuid)eventType(versioned string, e.g.order.created.v1)occurredAt(ISO8601)source(service name)correlationId/causationId(optional)aggregate:{ type, id, version }payload(object)metadata(object)
HMAC webhook contract
Headers:
X-Eventing-Timestamp: unix epoch secondsX-Eventing-Signature:v1=<hex_hmac_sha256>
Signing string:
{timestamp}.{rawBody}
Signature:
hex(hmac_sha256(secret, signing_string))
DynamoDB schema
Event store table
- PK:
AGG#{aggregateType}#{aggregateId} - SK:
HEAD(version pointer) orV#{versionPadded}(event items)
Inbox table (consumer dedupe)
- PK:
CONSUMER#{consumerName} - SK:
EVENT#{eventId} - TTL:
expiresAt
Concurrency / expectedVersion
DynamoDbEventStore::append() supports optimistic concurrency via expectedVersion:
expectedVersion = 0: aggregate must not exist yet (noHEAD)expectedVersion > 0:HEAD.currentVersionmust match
If you want a simpler “append without tracking versions”, you can disable concurrency checks by passing
disableConcurrencyCheck: true to the constructor. In that mode, the store:
- ignores the
expectedVersionargument - reads
HEAD.currentVersion(viaGetItem) and appends after it (best-effort)
Note: disabling concurrency checks can cause lost updates under concurrent writes.
Usage (PHP)
One-call publish (recommended)
use Aws\DynamoDb\DynamoDbClient;
use Aws\EventBridge\EventBridgeClient;
use Amevista\Eventing\EventBus\EventBridgePublisher;
use Amevista\Eventing\EventStore\DynamoDbEventStore;
use Amevista\Eventing\EventStore\DomainEvent;
use Amevista\Eventing\Eventing;
$region = getenv('AWS_DEFAULT_REGION') ?: 'eu-west-3';
$source = getenv('APP_SERVICE') ?: 'template-service';
$ddb = new DynamoDbClient(['version' => 'latest', 'region' => $region]);
$eb = new EventBridgeClient(['version' => 'latest', 'region' => $region]);
$eventStore = new DynamoDbEventStore(
$ddb,
getenv('EVENTING_EVENTSTORE_TABLE'),
$source,
disableConcurrencyCheck: (bool) (getenv('EVENTING_DISABLE_CONCURRENCY_CHECK') ?: false),
);
$publisher = new EventBridgePublisher($eb, getenv('EVENTING_EVENTBUS_NAME'));
$eventing = new Eventing($eventStore, $publisher);
$eventing->publish('test', 'ping-1', expectedVersion: 0, events: [
new DomainEvent('test.ping.v1', payload: ['hello' => 'world']),
]);
Append to event store
use Aws\DynamoDb\DynamoDbClient;
use Amevista\Eventing\EventStore\DynamoDbEventStore;
use Amevista\Eventing\EventStore\DomainEvent;
$ddb = new DynamoDbClient([
'version' => 'latest',
'region' => 'eu-west-3',
]);
$store = new DynamoDbEventStore($ddb, tableName: 'event_store', source: 'order-service');
$committed = $store->append('order', '123', expectedVersion: 0, events: [
new DomainEvent('order.created.v1', payload: ['orderId' => 123]),
]);
Publish to EventBridge
use Aws\EventBridge\EventBridgeClient;
use Amevista\Eventing\EventBus\EventBridgePublisher;
$eb = new EventBridgeClient([
'version' => 'latest',
'region' => 'eu-west-3',
]);
$publisher = new EventBridgePublisher($eb, eventBusName: 'my-event-bus');
$awsEventId = $publisher->publish($committed[0]);
IAM notes
Minimal DynamoDB permissions:
- Event store table:
dynamodb:PutItem,dynamodb:UpdateItem,dynamodb:ConditionCheckItem(for transactional writes)dynamodb:Query(forload())dynamodb:GetItem(required whendisableConcurrencyCheck=true, to readHEAD)
- Inbox table:
dynamodb:PutItem(dedupe record)