Guides

Share Code Between Layers

Let one layer use another, move shared code to its own layer, and break cycles, while osdd:check stays green.

A layer may use the classes of the layers it requires, and only those. This page shows the three ways to share code and how to keep osdd:check green with each.

Require the layer

When functional/invoices uses Functional\Orders\Models\Order, it requires functional/orders. For a new layer, pass --requires:

php artisan osdd:layer functional/invoices --requires=functional/orders

For an existing layer, add the requirement to its composer.json, then update it:

functional/invoices/composer.json
"require": {
    "lomkit/laravel-osdd": "*",
    "functional/orders": "*"
}
composer update functional/invoices

Only the tests use a layer? Put it in require-dev: files under tests/ may use it, the rest of the layer may not.

Generate code that points to another layer

The --model option of osdd:policy, osdd:controller, osdd:observer and osdd:factory keeps a fully qualified class of another layer:

php artisan osdd:policy UserInvoicePolicy --model='Functional\Users\Models\User' --layer=functional/invoices

osdd:listener takes an event of another layer when its name starts with a backslash:

php artisan osdd:listener SendInvoice --event='\Functional\Orders\Events\OrderPlaced' --layer=functional/invoices

Move shared code to its own layer

When several domains need the same code, such as a Money value object or a set of contracts, give it a layer of its own, usually in technical/:

php artisan osdd:layer technical/money --generators=provider
php artisan osdd:class Money --layer=technical/money

Each layer that uses it requires technical/money. Keep such layers small and named after what they hold. A common or shared layer ends up holding everything, and every other layer depends on it.

Communicate through events

A layer can react to another one without the other knowing it. functional/orders dispatches OrderPlaced; functional/invoices requires functional/orders and listens to it:

functional/invoices/src/Listeners/SendInvoice.php
namespace Functional\Invoices\Listeners;

use Functional\Orders\Events\OrderPlaced;

class SendInvoice
{
    public function handle(OrderPlaced $event): void
    {
        // ...
    }
}

The dependency goes one way: functional/orders never references functional/invoices. Turn on event discovery to register the listener without Event::listen().

Break a cycle

osdd:check fails when layers require each other:

  ERROR  Cycle in the layer requirements: functional/orders -> functional/invoices -> functional/orders.

Find what each side needs from the other. Move it into a third layer both require, or turn one direction into an event as above. Then remove the requirement that closed the cycle.

Allow an exception

Some references are fine without a requirement, such as contracts every layer may use. List them in osdd.boundaries.ignore, see Ignoring a dependency. Keep the list short: an ignored dependency is still a dependency.

See the result

php artisan osdd:graph
graph LR
    functional_invoices["functional/invoices"]
    functional_orders["functional/orders"]
    technical_money["technical/money"]
    functional_invoices --> functional_orders
    functional_invoices --> technical_money
    functional_orders --> technical_money

osdd:graph draws what the layers declare. osdd:check checks that the code matches it.