lendo-stp-gdansk/lendo-core-domain
Core domain layer for Lendo STP Gdańsk, providing shared domain models, value objects, enums, and contracts for Laravel/PHP applications. Encapsulates reusable business rules and building blocks to keep services consistent.
1. Require and inspect before you code
composer require lendo-stp-gdansk/lendo-core-domain
This package has zero stars and zero dependents, so treat the first ten minutes as due diligence rather than setup. Open the vendor directory and check three things before writing a line of application code:
vendor/lendo-stp-gdansk/lendo-core-domain/composer.json — PHP version, Laravel/Illuminate constraints, and whether it pulls in a message bus, serializer, or DB abstraction you did not expect.src/ layout — look for the event engine (aggregate roots, event store, repository) and the read model engine (projectors, read repositories) as separate namespaces. That split tells you where your code belongs.config/ and database/migrations/ — if the package ships an event store table or projection checkpoint table, you need those migrations before anything works.2. Register the provider
If the package declares extra.laravel.providers, discovery handles it. If not, add it to bootstrap/providers.php (Laravel 11+) or config/app.php. Then publish anything publishable:
php artisan vendor:publish --provider="Lendo\CoreDomain\CoreDomainServiceProvider"
Verify the config keys for the event store connection and the read model connection. These are frequently separate databases, and pointing both at default is a common first-run mistake.
3. First use case: one aggregate, one event, one projection
Don't start with your hardest domain. Pick a single write operation — e.g. RegisterCustomer — and trace it end to end:
Once that loop closes, you understand the package's contract. Everything else is repetition.
Aggregate + event store as the write side
The event engine is almost certainly built around an aggregate root base class that records domain events and a repository that loads by replaying them. Your domain objects should extend whatever base the package provides and stay free of Eloquent. Keep constructors private or named-constructor-only; the engine needs a way to reconstitute an aggregate from history that bypasses your normal validation. Check src/ for the reconstitution factory or apply() method early — it determines how strict you can make your invariants.
Thin commands, fat aggregates
Resist the Laravel habit of putting business logic in a controller or a service. Pattern:
final class RegisterCustomerHandler
{
public function __construct(private CustomerRepository $customers) {}
public function __invoke(RegisterCustomer $command): void
{
$customer = Customer::register($command->id, $command->email);
$this->customers->save($customer);
}
}
The win of event sourcing disappears the moment an aggregate is mutated from outside itself.
Projectors as the read side — one projection per query screen
Treat the read model engine as a set of independent, idempotent consumers. Each projection should be able to answer: what does it consume, what table does it own, and what is its checkpoint key? Keep projections small and screen-shaped rather than mirroring the write model — a projection that reproduces the aggregate schema has no reason to exist.
Dispatch after commit
If the package dispatches events synchronously on save, a projector failure rolls back your write. Wrap dispatch in DB::afterCommit() or configure a queue connection in the package config so projections run asynchronously. Decide this deliberately on day one; retrofitting async dispatch after you have data is painful.
Rebuilds as a normal operation
Ask immediately: how do I replay all events into a fresh read model? Find the rebuild command (or write one that drops the projection table and resets its checkpoint). If the package has no rebuild path, that's a blocker — you cannot fix a projection bug in production without it.
Testing pattern
Use an in-memory or SQLite event store in tests and assert on recorded events rather than on database rows:
$customer = Customer::register($id, '[email protected]');
$this->assertContains(EmailChanged::class, array_map('get_class', $customer->releaseEvents()));
For projections, feed the projector hand-built event instances. That keeps read-side tests fast and free of the write side entirely.
composer.json ("lendo-stp-gdansk/lendo-core-domain": "1.x-dev#<sha>" or a tagged release) so a silent upstream change can't break production. Consider forking into your VCS if this is load-bearing.json_encode-based, implement a contract on your VOs rather than relying on __toString.handle must be safe to run twice — upsert, not insert; guard on event ID if the package doesn't dedupe for you.config('database.default') fallback hiding in the projector base class.migrate:fresh. Check whether the package loads migrations automatically (loadMigrationsFrom) or whether you must publish them — mixing both causes duplicate-table errors.How can I help you explore Laravel packages today?