rva-vzw/krakboem
A Laravel/PHP package for the card game Krakboem that provides core game rules, state management, and event-driven player actions. It helps developers build digital tables, scoring, and turn-based gameplay in Laravel applications with extensible, configurable components.
Krakboem is a set of CQRS/event-sourcing interfaces plus a small kernel — not a batteries-included framework. Budget an hour to read the source before writing application code.
composer require rva-vzw/krakboem. The core has no framework dependency; the Symfony integration lives in a separate krakboem-bundle.CommandBus, EventBus, Event / Command marker interfaces, AggregateRoot, EventStore, EventSourcedRepository. They define the whole vocabulary; everything else is wiring.TodoList, Cart, or Invoice. Anything with more than ~5 events will obscure the learning curve.CommandBus, EventBus, EventStore, EventSourcedRepository) and inject the concrete implementations you choose.CreateThing command → handler instantiates the aggregate → aggregate records a ThingCreated event → repository persists to the event store → a projection listens and writes a read-model row. Once that circle closes, everything else is repetition.Aggregate as the unit of consistency. Handlers should be thin: load the aggregate via the repository, call a domain method, save. All business rules live on the aggregate; commands are validated by the aggregate raising events, not by the handler. Resist putting if logic in handlers.
Command → event → projection pipeline. A command handler mutates one aggregate and returns nothing. The event bus fans out to projections and sagas. Keep projections dumb and idempotent — they receive events, they don't call back into aggregates.
Repository load-by-replay. EventSourcedRepository::get($id) fetches a stream and calls apply() on each event. Design apply() methods to be pure state transitions with no side effects (no email sending, no HTTP calls). Side effects belong in projectors or sagas.
Optimistic concurrency. Persist with the expected aggregate version and let the event store throw on mismatch. Wrap command dispatch in a retry for transient conflicts on hot aggregates; never swallow the exception silently.
Sagas / process managers for cross-aggregate workflows. When one aggregate's event must trigger a command on another, put that glue in a saga with an explicit state store. Don't let projections dispatch commands — replaying a projection would re-fire business actions.
Testing with an in-memory event store. Inject an in-memory EventStore implementation into the repository and write given/when/then tests: seed events, dispatch a command, assert the new events on the stream. This is the single highest-leverage habit with this package.
Laravel integration. Use a service provider to bind interfaces; bind a CommandBus that runs synchronously for write paths. Route projectors to queued jobs only after you've verified synchronous behaviour — ordering bugs in async projections are painful to diagnose.
Middleware as the extension seam. Logging, transactions, validation, and metrics all belong in CommandBus/EventBus middleware rather than handlers. Wrap the event store save and the event bus dispatch in the same transaction where your storage backend supports it.
composer.json and expect to read diffs on upgrade; the public API is small but not yet battle-hardened by a large user base.eventVersion and plan for upcasters before you have production data, not after. Never rename event properties without a migration path.serialize() — refactors break stored events.processed_events table or a natural-key upsert is usually cheapest.CommandBus middleware, custom EventBus implementations (sync vs. async), and pluggable EventStore backends. If you need something the package doesn't offer, implement the interface rather than forking — the interfaces are the stable part.How can I help you explore Laravel packages today?