Installation:
composer require ecotone/kafka
Ensure you also have the core Ecotone package:
composer require ecotone/ecotone
Configure Kafka Transport:
Add the Kafka transport to your Ecotone container in bootstrap/app.php or a service provider:
use Ecotone\Kafka\KafkaTransport;
use Ecotone\Kafka\KafkaTransportConfig;
$container->addTransport(
new KafkaTransport(
new KafkaTransportConfig(
bootstrapServers: ['localhost:9092'],
clientId: 'laravel-app',
groupId: 'laravel-consumer-group'
)
)
);
First Use Case: Publish an Async Message Define a message class and handler:
#[Asynchronous]
class OrderCreated {
public function __construct(public string $orderId) {}
}
#[CommandHandler]
class OrderCreatedHandler {
public function __invoke(OrderCreated $orderCreated) {
// Business logic
}
}
Publish the message from a controller or command:
$container->getBus()->dispatch(new OrderCreated('order-123'));
Verify Kafka Topics:
Check if the topic ecotone.order_created (auto-generated from the message class) exists in Kafka.
Message Routing:
#[Asynchronous] attribute to auto-route messages to Kafka topics (topic name derived from the message class).#[RouteToTopic("custom.topic")].Consumer Groups:
groupId in KafkaTransportConfig to manage consumer groups for parallel processing.groupId.Partition Awareness:
#[PartitionKey] to control message partitioning:
#[Asynchronous]
#[PartitionKey('orderId')]
class OrderCreated { ... }
Error Handling:
$container->addTransport(
new KafkaTransport(
new KafkaTransportConfig(
// ...
retryConfig: new RetryConfig(maxRetries: 3, delay: 1000),
deadLetterTopic: 'dlq.order_created'
)
)
);
Event Sourcing:
#[EventSourcedAggregateRoot]
class Order {
#[Event]
public function create(string $orderId) {
$this->recordThat(new OrderCreated($orderId));
}
}
Sagas:
#[Saga]
class OrderSaga {
public function __invoke(OrderCreated $orderCreated) {
// Orchestrate steps
}
}
Laravel Integration:
Bind the Ecotone container to Laravel’s service container in AppServiceProvider:
public function register() {
$this->app->singleton(Ecotone::class, fn() => new Ecotone());
}
Configuration Management:
Store Kafka config in .env:
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
KAFKA_CLIENT_ID=laravel-app
KAFKA_GROUP_ID=laravel-consumer-group
Load it dynamically:
$config = new KafkaTransportConfig(
bootstrapServers: env('KAFKA_BOOTSTRAP_SERVERS'),
// ...
);
Testing: Use Ecotone’s testing utilities with Kafka:
use Ecotone\Testing\TestContainer;
public function testOrderCreated() {
$container = new TestContainer();
$container->addTransport(new KafkaTransport(new KafkaTransportConfig(
bootstrapServers: ['localhost:9092'],
// Use a test group ID
groupId: 'test-group'
)));
$container->getBus()->dispatch(new OrderCreated('test-123'));
// Assertions...
}
Topic Auto-Creation:
auto.create.topics.enable=true).Consumer Lag:
kafka-consumer-groups --bootstrap-server localhost:9092 --group laravel-consumer-group) and scale consumers.Schema Evolution:
OrderCreatedV1, OrderCreatedV2).Serialization:
serialize() by default, which may not be ideal for cross-language compatibility.#[SerializedWith(MySerializer::class)]:
use Ecotone\Serialization\Attribute\SerializedWith;
use Ecotone\Serialization\JsonSerializer;
#[Asynchronous]
#[SerializedWith(JsonSerializer::class)]
class OrderCreated { ... }
Offset Management:
#[Transactional] to ensure atomic processing:
#[CommandHandler]
#[Transactional]
class OrderCreatedHandler { ... }
Dependency Injection:
Bus or MessageDispatcher for decoupled messaging.Log Levels:
Enable verbose logging in KafkaTransportConfig:
new KafkaTransportConfig(
// ...
logLevel: \Monolog\Logger::DEBUG
);
Common Errors:
UnhandledMessageException: The message class isn’t registered as a command/event. Ensure all async messages have #[Asynchronous].ConnectionException: Check Kafka broker connectivity and credentials.SerializationException: Validate message class properties are serializable.Tooling:
kafka-console-consumer to inspect topics:
kafka-console-consumer --bootstrap-server localhost:9092 --topic ecotone.order_created --from-beginning
kafka-consumer-groups.Custom Serializers:
Implement Ecotone\Serialization\SerializerInterface for custom formats (e.g., JSON, Protobuf):
class JsonSerializer implements SerializerInterface {
public function serialize($data): string { ... }
public function deserialize(string $data): mixed { ... }
}
Interceptors:
Add pre/post-processing logic with #[BeforeHandler] or #[AfterHandler]:
#[BeforeHandler]
class LogMessageInterceptor {
public function __invoke(InvokeCommandHandlerContext $context) {
\Log::info('Processing message', ['message' => $context->getCommand()]);
}
}
Dynamic Routing:
Override topic routing logic by implementing Ecotone\Transport\TransportRouter:
class CustomKafkaRouter implements TransportRouter {
public function getTopicName(string $messageClass): string {
return 'custom.' . strtolower($messageClass);
}
}
Register it in KafkaTransportConfig:
new KafkaTransportConfig(
// ...
router: new CustomKafkaRouter()
);
Metrics:
Integrate with Prometheus or other monitoring tools by extending Ecotone\Transport\Transport and emitting custom metrics.
Security:
new KafkaTransportConfig(
// ...
ssl: new SslConfig(
caLocation: '/path/to/ca.pem',
certificateLocation: '/path/to/client.pem',
keyLocation: '/path/to/client.key'
),
sasl: new SaslConfig(
mechanism: 'SCRAM-SHA-256',
username: 'user',
password: 'pass'
)
);
How can I help you explore Laravel packages today?