Concepts

Layers

What a layer is, what its composer.json declares, and how it gets installed.

A layer is a Composer package that lives in your repository, in one of the buckets: functional/ and technical/ by default. Its composer.json is its contract: its name, its namespace, the layers it requires and its service provider.

Anatomy of a layer

functional/orders/
├── composer.json                 ← "type": "layer"
├── config/
│   └── orders.php
├── src/
│   ├── Models/Order.php
│   ├── Http/Controllers/OrderController.php
│   ├── Listeners/SendOrderConfirmation.php
│   ├── Policies/OrderPolicy.php
│   ├── View/Components/Alert.php
│   └── Providers/OrdersServiceProvider.php
├── database/
│   ├── migrations/
│   ├── factories/OrderFactory.php
│   └── seeders/OrdersSeeder.php
├── lang/
│   └── en/messages.php
├── resources/
│   └── views/
├── routes/
│   ├── web.php
│   ├── api.php
│   ├── console.php
│   └── channels.php
└── tests/
    ├── Feature/
    └── Unit/

Only composer.json is required. The generators create the other folders when you first need them.

The layer's composer.json

OSDD treats a folder as a layer when its composer.json has "type": "layer". osdd:layer writes this one:

functional/orders/composer.json
{
    "name": "functional/orders",
    "description": "",
    "type": "layer",
    "version": "1.0.0",
    "require": {
        "lomkit/laravel-osdd": "*"
    },
    "autoload": {
        "psr-4": {
            "Functional\\Orders\\": "src/",
            "Functional\\Orders\\Database\\Seeders\\": "database/seeders/",
            "Functional\\Orders\\Database\\Factories\\": "database/factories/"
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true,
    "extra": {
        "laravel": {
            "providers": [
                "Functional\\Orders\\Providers\\OrdersServiceProvider"
            ]
        }
    }
}
  • name is the layer's identifier: the --layer option of every generator takes it, and osdd:phpunit names the layer's test suite after it.
  • The shortest psr-4 prefix is the layer's root namespace, and its directory the layer's source directory. Generators write classes under it.
  • extra.laravel.providers lets Laravel discover the layer's service provider, like any package.

A layer that depends on another one requires it:

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

Create a layer with its requirements with php artisan osdd:layer functional/billing --requires=functional/orders. osdd:check then makes sure the code only uses the layers it requires: see Boundaries.

Naming

A layer name is a Composer package name: vendor/package, in lowercase letters and digits, with words separated by -, _ or .. Each part starts with a letter, so that it maps to a PHP namespace. osdd:layer refuses any other name and writes nothing:

Invalid layer name [functional/My_Orders]. Use a Composer package name: vendor/package, in lowercase letters and digits, words separated by '-', '_' or '.' (functional/orders).

The name gives the namespace: each part is converted to PascalCase.

LayerNamespaceService provider
functional/ordersFunctional\OrdersOrdersServiceProvider
functional/external-accountsFunctional\ExternalAccountsExternalAccountsServiceProvider
functional/sales.ordersFunctional\SalesOrdersSalesOrdersServiceProvider
technical/feature-flagsTechnical\FeatureFlagsFeatureFlagsServiceProvider

The vendor picks the bucket: osdd:layer functional/orders creates the layer in functional/. See Buckets.

Invalid composer.json files

A layer composer.json that OSDD cannot read is reported instead of breaking every command:

  • invalid JSON, or a JSON value that is not an object;
  • a missing or invalid name;
  • an autoload.psr-4, autoload-dev.psr-4, require or require-dev that is not a map;
  • an extra.laravel.providers that is not a list of strings.

osdd:list shows the file with its error and keeps listing the valid layers. The generators, osdd:seed --layer, osdd:check, osdd:graph, osdd:phpunit and osdd:cache stop with exit code 1 and name the file:

Invalid layer manifest [functional/orders/composer.json]: it has no "name".

Registration

osdd:start and osdd:layer register each layer in the root composer.json:

composer.json
{
    "require": {
        "functional/orders": "*",
        "technical/osdd": "*"
    },
    "repositories": [
        { "type": "path", "url": "./functional/*" },
        { "type": "path", "url": "./technical/*" }
    ]
}

One path repository per folder covers every layer inside it. After composer update functional/orders, Composer symlinks the layer into vendor/ and Laravel discovers its service provider. Do not list layer providers in bootstrap/providers.php.

osdd:list shows each layer with its health: required in the root composer.json, covered by a path repository, installed, provider extending LayerServiceProvider, test suite in phpunit.xml.