Fast, typed, event-driven services for Dart with complete isolate transparency and zero boilerplate.
Fluxon is a powerful service framework for Dart that enables seamless communication between services running in different isolates. It provides:
- 🔄 Isolate Transparency: Same API for local and remote services - no need to know where a service runs
- 🚀 Zero Boilerplate: Automatic code generation for service implementations and client proxies
- 📡 Unified Events: Cross-isolate event system with flexible routing and distribution
- đź’Ş Strong Typing: Full type safety with generated proxies and method mappings
- 🏗️ Auto Infrastructure: Automatic runtime setup for dispatchers, bridges, and worker wiring
Add Fluxon to your pubspec.yaml:
dependencies:
fluxon: ^0.0.1
dev_dependencies:
fluxon_method_generator: ^0.0.1
build_runner: ^2.4.0Then run:
dart pub getHere's a minimal example to get you started:
import 'package:fluxon/fluxon.dart';
part 'main.g.dart'; // Required for code generation
// 1. Define and implement your service with @ServiceContract
@ServiceContract(remote: false)
class GreetingService extends FluxonService {
Future<String> greet(String name) async {
return 'Hello, $name!';
}
}
// 2. Set up and use (after running code generation)
void main() async {
final runtime = FluxonRuntime();
// Register service using auto-generated Impl class
runtime.register<GreetingService>(() => GreetingServiceImpl());
await runtime.initializeAll();
// Use service
final greeting = runtime.get<GreetingService>();
final message = await greeting.greet('World');
print(message); // Hello, World!
}Generate the required code:
dart run build_runner build- FluxonRuntime: Orchestrates service lifecycle and event infrastructure across isolates
- FluxonService: Base class for services with built-in client and event capabilities
- @ServiceContract: Annotation that defines service interfaces and generates implementation code
- Worker Isolates: Remote services run in dedicated isolates with complete transparency
- Event Distribution: Configurable routing system (broadcast, targeted, direct) with include/exclude rules
Always use @ServiceContract annotation. Define concrete service classes.
// 1. Define your service with @ServiceContract
@ServiceContract(remote: false) // or remote: true for worker isolates
class OrderService extends FluxonService {
Future<Order> createOrder(String userId, List<String> productIds) async {
// Your business logic here
final order = Order(id: generateId(), userId: userId, productIds: productIds);
return order;
}
}
// 2. Register your service using auto-generated Impl class
runtime.register<OrderService>(() => OrderServiceImpl());
await runtime.initializeAll();Declare dependencies inside the service and use them after initialization.
@ServiceContract(remote: false)
class OrderService extends FluxonService {
@override
List<Type> get dependencies => [UserService, ProductService];
@override
Future<void> initialize() async {
await super.initialize();
// Access dependency clients here using getService<UserService>()
}
Future<Order> createOrder(String userId, List<String> productIds) async {
final userService = getService<UserService>();
final productService = getService<ProductService>();
// Use dependencies in your business logic
final user = await userService.getUser(userId);
final products = await productService.getProducts(productIds);
return Order(id: generateId(), userId: userId, productIds: productIds);
}
}// Define services with @ServiceContract
@ServiceContract(remote: false)
class UserService extends FluxonService {
Future<User> getUser(String id) async {
// Local service implementation
return User(id: id, name: 'User $id');
}
}
@ServiceContract(remote: true)
class EmailService extends FluxonService {
Future<void> sendWelcomeEmail(String userId) async {
// This runs in a worker isolate
final userService = getService<UserService>(); // Transparently calls local service
final user = await userService.getUser(userId);
// Send email logic here
print('Sending welcome email to ${user.name}');
}
}
// Register services using auto-generated Impl classes
final runtime = FluxonRuntime();
runtime.register<UserService>(() => UserServiceImpl()); // local
runtime.register<EmailService>(() => EmailServiceImpl()); // remote (worker)
await runtime.initializeAll();
// Use transparently
final userService = runtime.get<UserService>();
final emailService = runtime.get<EmailService>(); // could be remote
await emailService.sendWelcomeEmail('user_1');Register event types and listen/send events in any service. Use includeSource: true if a service must receive its own broadcasts.
// 1. Register event types for cross-isolate communication
EventTypeRegistry.register<UserCreatedEvent>((json) => UserCreatedEvent.fromJson(json));
EventTypeRegistry.register<EmailSentEvent>((json) => EmailSentEvent.fromJson(json));
@ServiceContract(remote: true)
class EmailService extends FluxonService {
@override
Future<void> initialize() async {
onEvent<UserCreatedEvent>((event) async {
await sendWelcomeEmail(event.userId);
return const EventProcessingResponse(result: EventProcessingResult.success);
});
await super.initialize();
}
Future<void> sendWelcomeEmail(String userId) async {
// Email sending logic
print('Sending welcome email to user $userId');
}
}
// 2. Sending events with explicit distribution
await sendEvent(
createEvent(({required eventId, required sourceService, required timestamp})
=> EmailSentEvent(userId: id, eventId: eventId, sourceService: sourceService, timestamp: timestamp)),
distribution: EventDistribution.broadcast(includeSource: true),
);You can consume events in multiple ways depending on your needs.
- Priority/conditional handlers (deterministic order)
onEvent<UserCreatedEvent>(
(event) async {
// High-priority processing
},
priority: 10, // higher runs first
condition: (e) => e.userId.startsWith('vip_'),
);- Stream-based consumption with backpressure control
late final StreamSubscription<UserCreatedEvent> _userCreatedSub;
@override
Future<void> initialize() async {
_userCreatedSub = listenToEvents<UserCreatedEvent>(
(event) {
// Stream-friendly processing
},
where: (e) => DateTime.now().difference(e.timestamp).inMinutes < 5,
);
await super.initialize();
}
@override
Future<void> destroy() async {
await _userCreatedSub.cancel();
await super.destroy();
}- Subscribe to events from remote isolates explicitly
late String _remoteSubId;
@override
Future<void> initialize() async {
// Ensure event types are registered in this isolate
EventTypeRegistry.register<UserCreatedEvent>(UserCreatedEvent.fromJson);
_remoteSubId = await subscribeToRemoteEvents<UserCreatedEvent>(
(event) async {
// Handle events originating from other isolates
return const EventProcessingResponse(result: EventProcessingResult.success);
},
);
await super.initialize();
}
@override
Future<void> destroy() async {
await unsubscribeFromRemoteEvents(_remoteSubId);
await super.destroy();
}Note: Event factories must be registered (via EventTypeRegistry.register) in every isolate that needs to reconstruct those events (e.g., call this early in main() and at the start of each worker service's initialize()).
class UserCreatedEvent extends ServiceEvent {
const UserCreatedEvent({
required this.userId,
required super.eventId,
required super.sourceService,
required super.timestamp,
super.metadata = const {},
super.correlationId,
});
final String userId;
@override
Map<String, dynamic> eventDataToJson() => {'userId': userId};
factory UserCreatedEvent.fromJson(Map<String, dynamic> json) {
final data = json['data'] as Map<String, dynamic>;
return UserCreatedEvent(
userId: data['userId'],
eventId: json['eventId'],
sourceService: json['sourceService'],
timestamp: DateTime.parse(json['timestamp']),
correlationId: json['correlationId'],
metadata: json['metadata'] ?? {},
);
}
}Generate service implementations and client proxies automatically.
Add to pubspec.yaml and run:
dependencies:
fluxon:
dev_dependencies:
fluxon_method_generator:
build_runner: ^2.4.0# Generate service implementations and client proxies
dart run build_runner build
# Or watch for changes
dart run build_runner watchThis generates:
ServiceNameImplclasses for service registration- Client proxy classes for transparent remote calls
- Method dispatchers for worker isolates
- Method ID mappings for efficient communication
Control how events are distributed across your services:
// Target specific services and wait for completion
await sendEvent(
OrderCreatedEvent(...),
distribution: EventDistribution.targeted([
EventTarget(serviceType: InventoryService, waitUntilProcessed: true),
EventTarget(serviceType: BillingService, waitUntilProcessed: true),
]),
);
// Broadcast to everyone except a few
await broadcastEvent(
NotificationEvent(...),
excludeServices: const [AuditService],
);
// Prioritize a few targets then broadcast to the rest
await sendEventTargetedThenBroadcast(
AnalyticsEvent(...),
[EventTarget(serviceType: RealtimeDashboardService, waitUntilProcessed: false)],
excludeServices: const [DebugService],
);Monitor your services and get insights into system health:
// Service-side: override healthCheck for custom diagnostics
@override
Future<ServiceHealthCheck> healthCheck() async => ServiceHealthCheck(
status: ServiceHealthStatus.healthy,
timestamp: DateTime.now(),
message: 'OK',
details: {'uptime': upTime.inSeconds},
);
// Runtime-side: aggregate checks
final results = await runtime.performHealthChecks();
// Visualize the dependency graph
final dot = runtime.visualizeDependencyGraph();
// You can render this with Graphviz or tooling of your choice
// Get dependency statistics
final depStats = runtime.getDependencyStatistics();
print('Total services: ${depStats.totalServices}');
print('Root services: ${depStats.rootServices}');
print('Average dependencies: ${depStats.averageDependencies}');Control service behavior with configuration options:
// Configure per-method timeouts and retries using @ServiceMethod
@ServiceContract(remote: true)
class BillingService extends FluxonService {
@ServiceMethod(timeoutMs: 15000, retryAttempts: 2, retryDelayMs: 200)
Future<PaymentResult> processPayment(String orderId, double amount) async {
// This method will timeout after 15s and retry up to 2 times with 200ms delay
return PaymentResult(success: true);
}
@ServiceMethod(timeoutMs: 5000)
Future<bool> validateCard(String cardNumber) async {
// This method has a shorter 5s timeout
return true;
}
}
// Or configure service-wide defaults via ServiceConfig
class MyService extends FluxonService {
MyService(): super(
config: const ServiceConfig(
timeout: Duration(seconds: 30),
retryAttempts: 3,
retryDelay: Duration(seconds: 1),
enableLogging: true,
logLevel: ServiceLogLevel.info,
)
);
}
// You can also override timeouts at runtime using ServiceCallOptions
final proxy = runtime.proxyRegistry.getProxy<BillingService>();
await proxy.callMethod<PaymentResult>(
'processPayment',
['order_123', 99.99],
options: const ServiceCallOptions(
timeout: Duration(seconds: 60),
retryAttempts: 5,
retryDelay: Duration(milliseconds: 100),
),
);