Skip to content

Multi-Service Routing

By default, a single OData service handles all requests under the configured prefix. To host multiple independent services -- each with its own schema, entity types, and resolvers -- implement a custom service registry.

The service registry interface

php
interface ODataServiceRegistryInterface
{
    /**
     * Return all registered services (used by odata:cache and odata:clear).
     */
    public function services(): array;

    /**
     * Route a request path to the appropriate service.
     */
    public function resolve(string $fullPath): ODataServiceInterface;
}

Example: path-based routing

php
<?php

namespace App\OData;

use LaravelUi5\OData\Service\Contracts\ODataServiceInterface;
use LaravelUi5\OData\Service\Contracts\ODataServiceRegistryInterface;

class ServiceRegistry implements ODataServiceRegistryInterface
{
    public function resolve(string $fullPath): ODataServiceInterface
    {
        return match (true) {
            str_starts_with($fullPath, 'partners') => new PartnerService(),
            str_starts_with($fullPath, 'reporting') => new ReportingService(),
            default => new PartnerService(), // fallback
        };
    }

    public function services(): array
    {
        return [
            new PartnerService(),
            new ReportingService(),
        ];
    }
}

With this registry, the URLs resolve as:

GET /odata/partners/Partners        → PartnerService
GET /odata/partners/$metadata       → PartnerService metadata
GET /odata/reporting/SalesReport    → ReportingService
GET /odata/reporting/$metadata      → ReportingService metadata

Registration

Update config/odata.php:

php
'service_registry' => App\OData\ServiceRegistry::class,

The service provider binds the registry as a singleton via the container.

Host application integration

When a host application (e.g. LaravelUi5/Core) provides its own routing, set register_routes to false and configure a custom service_registry:

php
// config/odata.php
'register_routes'  => false,
'service_registry' => \App\OData\MyServiceRegistry::class,

The host's service provider re-uses the package's route file, wrapping it with its own prefix and middleware. This keeps a single middleware config:

php
// In the host's ServiceProvider::boot()
Route::prefix('odata')
    ->middleware(config('my-app.odata_middleware'))
    ->group(base_path('vendor/laravelui5/odata/routes/odata.php'));

The route file defines a catch-all {path?} that dispatches to the OData controller. The custom ODataServiceRegistryInterface implementation resolves the correct ODataService from the request path (e.g. extracting {namespace}@{version}). The controller then strips the service's route() prefix to obtain the clean OData resource path (/, $metadata, Flights, etc.).

Service isolation

Each service is fully self-contained:

  • Its own namespace() for $metadata
  • Its own configure() with its own entity types and sets
  • Its own registerBindings() and bindFunctions() with its own resolvers
  • Its own generated Edm/ cache directory (when using odata:cache)

Services do not share types, entity sets, or resolvers.

Caching

The odata:cache and odata:clear commands iterate services() to cache or clear every registered service. Make sure services() returns all services you want to be cacheable.

OData: MIT | Core: BSL 1.1 | SDK: Commercial License