Actor system integration
This page shows the two ways to host libspiffy’s actors, standalone or inside your own Dactor actor system, and how your code reaches them through the coordinator in either mode.
Standalone mode
Section titled “Standalone mode”If you pass no actorSystem to initialize(), libspiffy creates its own LocalActorSystem and owns it.
shutdown() shuts that system down too. This is the mode to use unless your app already runs Dactor
actors.
final libspiffy = LibSpiffyActorSystem();await libspiffy.initialize(networkType: 'test', isar: isar);
// ... use libspiffy.coordinator and libspiffy.coordinatorEvents ...
await libspiffy.shutdown(); // stops libspiffy's actors and its actor systeminitialize() also takes config:, an ActorSystemConfig for the system libspiffy creates.
The global instance
Section titled “The global instance”libspiffy also keeps one process-wide instance behind three functions, used by the README and older code:
await initializeLibSpiffy(networkType: 'test', isar: isar);final balance = await getLibSpiffySystem().coordinator.ask(GetBalanceQuery(walletId: 'shop'));await shutdownLibSpiffy();initializeLibSpiffy() forwards only some of initialize()’s parameters: actorSystem, dataDirectory,
config, readModelStorage, secureStorage, cryptoService, arcConfig, isar, networkType,
enableP2P, startHeight, peerAddresses, userAgent, channelPeerId and channelTiming. For
PostgreSQL, a header CDN or a blockchain data source, create a LibSpiffyActorSystem and call
initialize() on it.
Integrated mode
Section titled “Integrated mode”If your app already has a Dactor actor system, pass it as actorSystem. libspiffy spawns its actors there
instead of creating a system of its own: one supervision tree, one dispatcher, and your actors can hold
libspiffy’s coordinator directly.
import 'dart:io';
import 'package:dactor/dactor.dart';import 'package:isar_community/isar.dart';import 'package:libspiffy/coordinator.dart';import 'package:libspiffy/libspiffy.dart';
/// Your own actor. It asks libspiffy's coordinator for an invoice.class CheckoutActor extends Actor { CheckoutActor(this._coordinator); final WalletCoordinator _coordinator;
@override Future<void> onMessage(dynamic message) async { if (message is StartCheckout) { try { final invoice = await _coordinator.ask(CreateInvoiceCommand( walletId: message.walletId, amount: message.amount, description: message.orderId, )); print('order ${message.orderId}: invoice ${invoice.invoiceId}'); } on CoordinatorFailure catch (failure) { print('order ${message.orderId}: no invoice (${failure.message})'); } } }}
class StartCheckout implements Message { StartCheckout(this.walletId, this.orderId, this.amount); final String walletId; final String orderId; final BigInt amount;
@override String get correlationId => 'checkout-$orderId'; @override Map<String, dynamic> get metadata => {'orderId': orderId}; @override ActorRef? get replyTo => null; @override DateTime get timestamp => DateTime.now();}
Future<void> main() async { final hostSystem = LocalActorSystem(ActorSystemConfig());
// One Isar instance for your data and libspiffy's. await Directory('./app-data').create(recursive: true); await Isar.initializeIsarCore(download: true); final isar = await Isar.open( [...LibSpiffySchemas.allSchemas /*, ...your own schemas */], directory: './app-data', );
final libspiffy = LibSpiffyActorSystem(); await libspiffy.initialize( actorSystem: hostSystem, isar: isar, networkType: 'test', );
final checkout = await hostSystem.spawn( 'checkout', () => CheckoutActor(libspiffy.coordinator), );
checkout.tell(StartCheckout('shop', 'order-1001', BigInt.from(5000)));
// ...
// Shut down in reverse order. libspiffy leaves the host's resources alone. await libspiffy.shutdown(); // stops libspiffy's actors only await isar.close(); // the host opened it, the host closes it await hostSystem.shutdown();}Things to know in this mode:
shutdown()stops libspiffy’s actors and projections but does not shut down your actor system, and does not close an Isar instance you passed. Shut libspiffy down first, then close Isar, then your system.- Hand your actors the
WalletCoordinator(libspiffy.coordinator), not anActorRef. The coordinator does not reply to the sender of a message: an actor that needs the reply awaitsask(), asCheckoutActordoes, and follows unrequested events withon<E>(). - Your actor names must not clash with libspiffy’s:
wallet-coordinator,wallet-manager,invoice-coordinator,spv-actor,header-sync,arc-actor,payment-coordinator,benford-coordinator,payment-channel-manager,import-actor, and the threeprojection-...actors. The architecture overview lists them. - Open your Isar instance with
LibSpiffySchemas.allSchemas, not onlyLibSpiffySchemas.walletSchemas.initialize()throws anArgumentErrornaming the missing collections otherwise: libspiffy needs the event store, projection checkpoint and broadcast queue collections too. - In a pure Dart program, call
Isar.initializeIsarCore(download: true)beforeIsar.open, and make sure the directory exists. Flutter apps get the Isar core fromisar_community_flutter_libs.
The coordinator is the gateway
Section titled “The coordinator is the gateway”In both modes your code talks to one actor, WalletCoordinatorActor, through libspiffy.coordinator, a
WalletCoordinator. You do not need a gateway actor of your own. ask() gives you the reply to each
request you send; on<E>() gives you what happens without a request.
// Sketch: requests through ask(), everything else through on<E>().final coordinator = libspiffy.coordinator;
coordinator.on<InvoicePaidEvent>().listen(onInvoicePaid);coordinator.on<ChannelRequestReceivedEvent>().listen(onChannelRequest);coordinator.on<ErrorEvent>().listen(onError);
final payment = await coordinator.ask(PayInvoiceCommand( walletId: 'shop', invoiceId: invoiceId, addresses: addresses, amount: amount,));sendToPayee(payment.beefBytes);libspiffy.coordinatorEvents is the whole stream, replies included, if you prefer one listener that
switches on the event type.
LibSpiffyActorSystem also exposes the internal actors (walletManager, invoiceCoordinator,
paymentCoordinator, spvActor, arcActor, headerSyncActor, channelManager). Messages you send them
bypass the coordinator’s correlation tracking, the coordinator publishes no reply for them, and ask()
cannot wait on them. Use them
only for diagnostics or advanced extensions.
Checking the mode
Section titled “Checking the mode”if (libspiffy.ownsActorSystem) { print('standalone: libspiffy created its actor system');} else { print('integrated: running in the host actor system');}
final system = libspiffy.actorSystem; // the system libspiffy's actors live inownsActorSystem is false when you passed actorSystem. The actorSystem getter throws a StateError
before initialize() and after shutdown().
Isolates
Section titled “Isolates”LibSpiffyActorSystem runs in whichever isolate calls initialize(), and its actors, streams and Isar
instance cannot cross an isolate boundary. To keep it off a Flutter UI thread, see
Run in a background isolate.