coka/service-discovery-bundle
Install via Composer
composer require coka/service-discovery-bundle
Add the bundle to config/bundles.php in a Symfony project (or equivalent Laravel service provider registration).
Configure Services
Define your services in config/packages/oka_service_discovery.yaml (Symfony) or equivalent Laravel config:
oka_service_discovery:
services:
- name: "user-service"
url: "http://user-service:8000"
healthy: true
- name: "order-service"
url: "http://order-service:8000"
healthy: false
First Use Case: Service Discovery
Inject the ServiceDiscovery class and fetch a service:
use Oka\ServiceDiscoveryBundle\ServiceDiscovery;
$discovery = $this->container->get('oka_service_discovery');
$userServiceUrl = $discovery->getServiceUrl('user-service');
For Laravel, create a service provider:
// app/Providers/ServiceDiscoveryProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Oka\ServiceDiscoveryBundle\ServiceDiscovery;
class ServiceDiscoveryProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('service.discovery', function () {
$config = config('service_discovery.services', []);
return new ServiceDiscovery($config);
});
}
}
Register in config/app.php and define config in config/service_discovery.php:
'service_discovery' => [
'services' => [
[
'name' => 'user-service',
'url' => 'http://user-service:8000',
'healthy' => true,
],
],
],
Dynamic Service Resolution Use the bundle to resolve service URLs at runtime (e.g., in controllers or services):
$serviceUrl = app('service.discovery')->getServiceUrl('order-service');
$client = new \GuzzleHttp\Client(['base_uri' => $serviceUrl]);
Health-Checked Requests Skip unhealthy services automatically:
if (app('service.discovery')->isServiceHealthy('user-service')) {
// Proceed with request
}
Service Registration Extend the bundle to register services dynamically (e.g., from a database):
$discovery = app('service.discovery');
$discovery->addService([
'name' => 'payment-service',
'url' => 'http://payment-service:8000',
'healthy' => false,
]);
HTTP Client Integration Combine with Laravel's HTTP client for seamless requests:
$response = \Http::baseUrl(app('service.discovery')->getServiceUrl('user-service'))
->get('/api/users');
Service Discovery Middleware Create middleware to inject service URLs into requests:
// app/Http/Middleware/ServiceDiscoveryMiddleware.php
public function handle($request, Closure $next)
{
$request->merge([
'service_url' => app('service.discovery')->getServiceUrl('order-service'),
]);
return $next($request);
}
Caching Service URLs Cache resolved URLs to avoid repeated lookups:
$url = Cache::remember('service.user-service.url', now()->addHours(1), function () {
return app('service.discovery')->getServiceUrl('user-service');
});
Event-Driven Updates Listen for service health changes (if the bundle supports events) to trigger recaching or retries.
No Built-in Service Discovery Protocol The bundle assumes services are pre-configured. For dynamic discovery (e.g., Consul, Eureka), integrate with a dedicated service mesh or SDK.
Health Check Logic
The healthy flag is static. For real-time checks, extend the bundle or use a separate health-check endpoint.
Lack of Load Balancing The bundle does not support load balancing across multiple instances of a service. Use a dedicated load balancer (e.g., Laravel Horizon, Envoy) or integrate with a service mesh.
No Circuit Breaker Missing automatic retries or failover logic. Combine with libraries like Spatie Circuit Breaker.
Verify Configuration
Ensure config/service_discovery.php is correctly loaded and services are defined:
dd(config('service_discovery.services'));
Check Service Resolution Debug service URL resolution:
$discovery = app('service.discovery');
dd($discovery->getServiceUrl('non-existent-service')); // Should return null or throw exception
Health Check Logic
If services appear unhealthy unexpectedly, verify the healthy flag or extend the bundle to implement custom health checks.
Custom Service Providers Extend the bundle to support additional service providers (e.g., Docker, Kubernetes):
// Extend ServiceDiscovery class
class ExtendedServiceDiscovery extends ServiceDiscovery
{
public function discoverFromKubernetes(): array
{
// Custom logic to fetch services from Kubernetes
}
}
Dynamic Configuration Load services from environment variables or a database:
$services = collect(config('service_discovery.services'))
->merge($this->getServicesFromDatabase())
->toArray();
Add Metadata
Extend service definitions to include metadata (e.g., timeout, retry_policy):
services:
- name: "user-service"
url: "http://user-service:8000"
healthy: true
timeout: 5
Event Dispatching Trigger events when services are added/removed or health status changes:
// Example: Dispatch event after adding a service
event(new ServiceAdded($service));
Service Container Binding Bind the discovery service to the container for easier dependency injection:
$this->app->bind('service.discovery', function () {
return new ServiceDiscovery(config('service_discovery.services'));
});
Artisan Commands Create an Artisan command to manage services:
// app/Console/Commands/ListServices.php
public function handle()
{
$services = app('service.discovery')->getServices();
foreach ($services as $service) {
$this->info($service['name'] . ' (' . ($service['healthy'] ? '✅' : '❌') . ')');
}
}
Testing Mock the service discovery in tests:
$this->app->instance('service.discovery', Mockery::mock(ServiceDiscovery::class));
How can I help you explore Laravel packages today?