samsara_bus_exchange_dart 1.0.0
samsara_bus_exchange_dart: ^1.0.0 copied to clipboard
Exchange pattern annotations and helpers for Samsara Bus Dart.
Samsara Bus Exchange Dart #
Exchange pattern annotations and helpers for Samsara Bus Dart. This package provides annotations and base classes for implementing the Request/Response (Exchange) pattern over the Samsara Bus message system.
Features #
- Annotation-based code generation for Exchange clients and services
- Type-safe request/response communication
- Timeout support with configurable defaults and per-method overrides
- Error handling across service boundaries
- Concurrent request support
- Automatic serialization/deserialization of request and response data
Installation #
Add the following dependencies to your pubspec.yaml:
dependencies:
samsara_bus_dart: ^1.0.0
samsara_bus_exchange_dart: ^1.0.0
dev_dependencies:
build_runner: ^2.4.6
samsara_bus_generator_dart: ^1.0.0 # Required for code generation
You'll also need a build.yaml file in your project root:
targets:
$default:
builders:
samsara_bus_generator_dart:
enabled: true
Quick Start #
1. Define a Service Interface #
Create a service interface using the @ExchangeService annotation:
// lib/calculator_service.dart
import 'package:samsara_bus_dart/samsara_bus_dart.dart';
import 'package:samsara_bus_exchange_dart/samsara_bus_exchange_dart.dart';
part 'calculator_service.g.dart';
@ExchangeService('calculator.request', 'calculator.response')
abstract class CalculatorService {
@ServiceMethod()
int add(int a, int b);
@ServiceMethod()
int subtract(int a, int b);
@ServiceMethod()
int multiply(int a, int b);
@ServiceMethod()
double divide(int a, int b);
}
2. Create a Service Implementation #
Implement your service logic:
// lib/calculator_service_impl.dart
import 'calculator_service.dart';
class CalculatorServiceImpl implements CalculatorService {
@override
int add(int a, int b) {
print('Computing $a + $b');
return a + b;
}
@override
int subtract(int a, int b) {
print('Computing $a - $b');
return a - b;
}
@override
int multiply(int a, int b) {
print('Computing $a * $b');
return a * b;
}
@override
double divide(int a, int b) {
print('Computing $a / $b');
if (b == 0) {
throw Exception('Division by zero is not allowed');
}
return a / b;
}
}
3. Define a Client Interface #
Create a client interface using the @ExchangeClient annotation:
// lib/calculator_client.dart
import 'package:samsara_bus_dart/samsara_bus_dart.dart';
import 'package:samsara_bus_exchange_dart/samsara_bus_exchange_dart.dart';
part 'calculator_client.g.dart';
@ExchangeClient('calculator.request', 'calculator.response')
abstract class CalculatorClient {
@ExchangeMethod()
Future<int> add(int a, int b);
@ExchangeMethod()
Future<int> subtract(int a, int b);
@ExchangeMethod()
Future<int> multiply(int a, int b);
@ExchangeMethod(timeout: Duration(seconds: 10))
Future<double> divide(int a, int b);
}
4. Generate Code #
Run the code generator to create the implementation classes:
dart run build_runner build
This will generate:
calculator_service.g.dart- ContainsCalculatorService$Generatedcalculator_client.g.dart- ContainsCalculatorClient$Generated
5. Use the Generated Code #
// bin/main.dart
import 'dart:async';
import 'package:samsara_bus_dart/samsara_bus_dart.dart';
import '../lib/calculator_client.dart';
import '../lib/calculator_service.dart';
import '../lib/calculator_service_impl.dart';
void main() async {
final bus = DefaultSamsaraBus();
// Register topics for the calculator service
bus.registerTopic<Map<String, dynamic>>(
'calculator.request', TopicType.publishSubject);
bus.registerTopic<Map<String, dynamic>>(
'calculator.response', TopicType.publishSubject);
// Start the service
final serviceImpl = CalculatorServiceImpl();
final service = CalculatorService$Generated(bus, serviceImpl);
await service.start();
// Create a client
final client = CalculatorClient$Generated(bus);
try {
// Make requests
final sum = await client.add(10, 5);
print('10 + 5 = $sum'); // Output: 10 + 5 = 15
final quotient = await client.divide(10, 5);
print('10 / 5 = $quotient'); // Output: 10 / 5 = 2.0
// Handle errors
try {
await client.divide(10, 0);
} catch (e) {
print('Error: $e'); // Output: Error: Exception: Division by zero is not allowed
}
} finally {
// Clean up
await service.stop();
client.dispose();
await bus.close();
}
}
Annotations Reference #
@ExchangeService #
Marks an abstract class as a service that receives requests and sends responses.
@ExchangeService(requestTopic, responseTopic)
abstract class MyService {
// Service methods...
}
Parameters:
requestTopic: Topic name for receiving requestsresponseTopic: Topic name for sending responses
@ServiceMethod #
Marks a method in an @ExchangeService class as a request handler.
@ServiceMethod()
ReturnType methodName(Type1 param1, Type2 param2);
// Optional: Custom operation name
@ServiceMethod(operation: 'customName')
ReturnType methodName(Type1 param1, Type2 param2);
Parameters:
operation(optional): Custom operation name (defaults to method name)
@ExchangeClient #
Marks an abstract class as a client that sends requests and receives responses.
@ExchangeClient(requestTopic, responseTopic)
abstract class MyClient {
// Client methods...
}
// With default timeout
@ExchangeClient(requestTopic, responseTopic,
defaultTimeout: Duration(seconds: 5))
abstract class MyClient {
// Client methods...
}
Parameters:
requestTopic: Topic name for sending requestsresponseTopic: Topic name for receiving responsesdefaultTimeout(optional): Default timeout for all requests
@ExchangeMethod #
Marks a method in an @ExchangeClient class as a request method.
@ExchangeMethod()
Future<ReturnType> methodName(Type1 param1, Type2 param2);
// With custom timeout
@ExchangeMethod(timeout: Duration(seconds: 10))
Future<ReturnType> methodName(Type1 param1, Type2 param2);
// With custom operation name
@ExchangeMethod(operation: 'customName')
Future<ReturnType> methodName(Type1 param1, Type2 param2);
Parameters:
operation(optional): Custom operation name (defaults to method name)timeout(optional): Method-specific timeout override
Advanced Features #
Error Handling #
Services can throw exceptions that will be propagated to the client:
// Service implementation
@override
double divide(int a, int b) {
if (b == 0) {
throw Exception('Division by zero is not allowed');
}
return a / b;
}
// Client usage
try {
final result = await client.divide(10, 0);
} catch (e) {
print('Service error: $e');
}
Timeouts #
Configure timeouts at the client or method level:
// Client-level default timeout
@ExchangeClient('my.request', 'my.response',
defaultTimeout: Duration(seconds: 5))
abstract class MyClient {
// Uses default timeout (5 seconds)
@ExchangeMethod()
Future<String> quickOperation();
// Method-specific timeout override (30 seconds)
@ExchangeMethod(timeout: Duration(seconds: 30))
Future<String> slowOperation();
}
Concurrent Requests #
The generated clients support concurrent requests:
final futures = <Future<int>>[
client.add(1, 2),
client.multiply(3, 4),
client.subtract(10, 3),
client.add(100, 200),
];
final results = await Future.wait(futures);
print('Concurrent results: $results');
Code Generation Workflow #
- Write your interfaces with annotations
- Run the generator:
dart run build_runner build - Use the generated classes in your application
- Re-run when you change interfaces:
dart run build_runner build --delete-conflicting-outputs
For development with automatic rebuilding:
dart run build_runner watch
Best Practices #
- Use meaningful topic names that reflect your service boundaries
- Keep service interfaces focused - one service per logical domain
- Handle errors appropriately in your service implementations
- Set reasonable timeouts based on your service's expected response times
- Use the generated code - don't implement the exchange pattern manually
- Dispose clients properly to avoid memory leaks
Topic Naming Conventions #
Consider using a consistent naming scheme for your topics:
// Domain-based naming
@ExchangeService('user.request', 'user.response')
@ExchangeService('order.request', 'order.response')
@ExchangeService('payment.request', 'payment.response')
// Service-based naming
@ExchangeService('userService.request', 'userService.response')
@ExchangeService('orderService.request', 'orderService.response')
Examples #
For a complete working example, see the calculator example in the samsara-bus-generator-dart package.