Home / News / Small Resource Server & Symfony Client Bundle

Small Resource Server & Symfony Client Bundle

Revised sarcastic summary with hashtags

The Small Resource Server is a tiny, lightweight, lightweight microservice designed for handling resource locking and synchronization efficiently in high-load microservices environments. By centralizing resource access, it prevents race conditions, inconsistent state, and high contention. This allows long multi-processes and multi-server batches synchronization, ensuring critical operations like payments, reservations, and inventory updates remain reliable even under heavy parallel traffic. Additionally, it provides a language-agnostic HTTP API that allows developers to acquire, share, and release named locks with optional TTL, owner tokens, and expiration dates. The server runs on PHP with Swoole, a high-performance, event-driven, and concurrent server microkernel, offering a minimal REST API with the following features:

  • Resource acquisition: Acquiring a named lock with optional TTL and owner token.
  • Resource sharing: Sharing associated data through the Symfony Client Bundle with ergonomic, resilient access.

Architecture (high-level)

The Small Resource Server consists of a runtime layer that uses an in-memory map to store the lock data, including the owner, token, acquired

The Small Resource Server is designed to handle resource locking and synchronization efficiently in high-load microservices environments. By centralizing resource access, it prevents race conditions and inconsistent state across distributed services, while remaining lightweight and fast thanks to its Swoole-based concurrency model. This reduces contention, improves throughput, and ensures that critical operations—like payments, reservations, or inventory updates—remain reliable even under heavy parallel traffic.

It also allowing you a good way for long multi processes / muti servers batches synchronization.

Small Resource Server offers a tiny, language‑agnostic HTTP API for just that:

  • Acquire a named lock with optional TTL & owner token
  • Sharing associated data

The Symfony Client Bundle gives you ergonomic, resilient access from Symfony with

  • HttpClientInterface integration
  • DI configuration (base_uri, API key, timeouts)
  • Straightforward Resource facade/service APIs

Architecture (high level)

  • Small Resource Server

    • Runtime: PHP + Swoole/OpenSwoole HTTP server
    • In‑memory map of resourceName -> lease with optional pluggable storage (Redis/DB) if enabled
    • Lease data: owner, token, acquiredAt, ttl, expiresAt
    • Background janitor to evict expired leases
    • Minimal REST endpoints
  • Client Bundle (Symfony)

    • Registers a named HTTP client preconfigured with base_uri, headers (x-api-key), and default timeouts
    • Exposes a ResourceFactory / ResourceClient service to acquire(), renew(), and release()
    • Optional retry/backoff on transient errors and clock skew tolerance

HTTP API (server)

Create resource

POST /resource
Headers:
  x-api-key: <WRITE key>

Body (JSON):
{
  "name": "printer",
  "timeout": 300            // optional server-side semantics
}

201 Created
Content-Type: application/json
{ ...resource... }

Get resource data (with optional lock)

GET /resource/{resourceName}/{selector}?lock=1
Headers:
  x-api-key: <READ or READ+LOCK>
  x-ticket: <existing ticket | optional>

Query:
  lock=1  (default)  → attempt to acquire/keep lock
  lock=0             → read without locking

Responses:
- 200 OK + JSON body     → resource data is available
  + Header: x-ticket: <ticket>
- 202 Accepted           → not available yet (locked by someone else),
  body: { "unavailable": true }
  + Header: x-ticket: <ticket> (your ticket to retry with)
- 404 Not Found          → resource/selector unknown
- 401 Unauthorized       → missing/invalid x-api-key / missing LOCK when lock=1

Update resource data

PUT /resource/{resourceName}/{selector}
Headers:
  x-api-key: <WRITE key>
  x-ticket: <ticket from GET>

Body: raw JSON payload to store

204 No Content

Unlock resource

POST /resource/{resourceName}/{selector}/unlock
Headers:
  x-api-key: <READ+LOCK>
  x-ticket: <ticket>

200 OK
{ "unlocked": true }

Security: Accept an x-api-key header; prefer TLS; rate‑limit abusive clients.

📦 Installation

Server (Docker Hub image)

The easiest way to run the server is via Docker:

# Pull the latest image from Docker Hub
docker pull smallphp/small-resource-server:latest

# Run it (adjust API_KEY and port as needed)
docker run -d \
  --name resource-server \
  --volume "./conf/small-swoole-resource-server.conf:/etc/small-swoole-resource-server.conf" \
  -p 8080:9501 \
  smallphp/small-resource-server:latest

You can now access the server on http://localhost:8080.

Docker Compose example:

version: '3.8'
services:
  resource-server:
    image: smallphp/small-resource-server:latest
    restart: unless-stopped
    ports:
      - "8080:9501"
    volumes:
      - ./conf/small-swoole-resource-server.conf:/etc/small-swoole-resource-server.conf
  database:
    container_name: small-swoole-resource-db
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: secret
    command: ["mysqld", "--mysql-native-password=FORCE"]

And config file example:

MYSQL_HOST=database
MYSQL_USER=root
MYSQL_PASSWORD=secret
RESOURCE_READ=d8e8fca2dc0f896fd7cb4cb0031ba249
RESOURCE_READ_LOCK=ed4779d230afc18ca8df5213ba02b11e
RESOURCE_WRITE=eadd7e1bbc06f288417df096bbedfa61

Symfony Client Bundle

Add the bundle to your app:

composer require small/swoole-resource-client-bundle

Register the bundle if Flex is not used:

return [
    Small\SwooleResourceClientBundle\SmallSwooleResourceClientBundle::class => ['all' => true],
];

Configure the client:

# config/packages/small_resource_client.yaml
small_swoole_resource_client:
  base_uri: '%env(RESOURCE_SERVER_BASE_URI)%'
  api_key:  '%env(RESOURCE_SERVER_API_KEY)%'
  timeout: 10

Environment:

###> Small Resource Server ###
RESOURCE_SERVER_BASE_URI=http://localhost:8080
RESOURCE_SERVER_API_KEY=eadd7e1bbc06f288417df096bbedfa61
###< Small Resource Server ###

Quick start from Symfony

$lock = $this->resources->create('shop:42:inventory');
if ($lock->acquire('sync-worker', 30)) {
    $guard = $lock->autoRenew(every: 20, ttl: 30);
    try {
        $this->syncInventory();
    } finally {
        $guard->stop();
        $lock->release();
    }
}

Error handling & edge cases

  • 409 Conflict → someone else holds the lock
  • 403 Forbidden → token mismatch/expired
  • Timeouts → handle with retry/backoff
  • Crash safety → expired leases are automatically evicted

Security

  • All traffic over HTTPS in production
  • Protect with API keys (env var API_KEY)
  • Rotate secrets regularly

sources

On github

On Docker Hub (server)

On Packagist (Symfony client bundle)

Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *