I am brand new to Akka (Java lib, v2.3.9) and am trying to understand actor dependency and fallback/fault tolerance.
Say I have two actors, StormTrooper
and DarthVader
:
// Groovy pseudo-code
class StatusReport {
private final Report report
StatusReport(Input report) {
super()
this.report = deepClone(report)
}
Input getReport() {
deepClone(this.report)
}
}
class StormTrooper extends UntypedActor {
ActorRef lordVader // Injected with reference to DarthVader
@Override
void onReceive(Object message) {
if(message instanceof PerformReport) {
PerformReport pr = message as PerformReport
Report report = ReportUtils.generateReport(pr.config)
StatusReport statusReportMsg = new StatusReport(report)
lordVader.tell(statusReportMessage, ...)
}
}
}
class DarthVader extends UntypedActor {
@Override
void onReceive(Object message) {
if(message instanceof StatusReport) {
// Do something meaningful with the status report.
}
}
}
Under some circumstances, DarthVader
is essentially NULL and should be a no-op. That is: when StormTrooper
decides to send a StatusReport
message to DarthVader
, he:
DarthVader
will correctly respond to the status report; orDarthVader
must be intentionally offline/unresponsive/no-opIn the latter case when DarthVader
is supposed (I emphasize this to distinguish this from a use case when DarthVader
is supposed to be alive/functioning but is in a faulty/error state) to be no-op, I'm not sure how to communicate that back to StormTrooper
, who must simply call fizzBuzz#derp()
if DarthVader
is no-op.
class StormTrooper extends UntypedActor {
ActorRef lordVader // Injected with reference to DarthVader
@Override
void onReceive(Object message) {
if(message instanceof PerformReport) {
if(lordVader.isNoOp) {
fizzBuzz.derp()
} else {
PerformReport pr = message as PerformReport
Report report = ReportUtils.generateReport(pr.config)
StatusReport statusReportMsg = new StatusReport(report)
lordVader.tell(statusReportMessage, ...)
}
}
}
}
class DarthVader extends UntypedActor {
boolean isNoOpMode = false
@Override
void onReceive(Object message) {
if(message instanceof StatusReport) {
if(!isNoOpMode) {
// Do something meaningful with the status report.
}
// Obviosuly, if we are in no-op mode, do nothing.
}
}
}
My uncertainty here is that ALL instances of DarthVader
actors/threads must be in the same state (no-op mode being on/off applies universally to all of them), and so I'm not sure if this solution is even viable of in keeping with Akka best practices.
class StormTrooper extends UntypedActor {
ActorRef lordVader // Injected with reference to DarthVader
@Override
void onReceive(Object message) {
if(message instanceof PerformReport) {
try {
PerformReport pr = message as PerformReport
Report report = ReportUtils.generateReport(pr.config)
StatusReport statusReportMsg = new StatusReport(report)
lordVader.tell(statusReportMessage, ...)
} catch(DarthVaderNoOpException dvnoExc) {
fizzBuzz.derp()
}
}
}
}
class DarthVader extends UntypedActor {
boolean isNoOpMode = false
@Override
void onReceive(Object message) {
if(message instanceof StatusReport) {
if(!isNoOpMode) {
// Do something meaningful with the status report.
} else {
throw new DarthVaderNoOpException()
}
}
}
}
But using exceptions to control flow is a general no-no, and may even trigger built-in Akka supervisor behavior (reacting to exceptions may cause Akka to restart StormTrooper
, etc.).
class StormTrooper extends UntypedActor {
ActorRef lordVader // Injected with reference to DarthVader
@Override
void onReceive(Object message) {
if(message instanceof PerformReport) {
PerformReport pr = message as PerformReport
Report report = ReportUtils.generateReport(pr.config)
StatusReport statusReportMsg = new StatusReport(report)
lordVader.tell(statusReportMessage, ...)
} else if(message instanceof DarthVaderNoOp) {
fizzbuzz.derp()
}
}
}
class DarthVader extends UntypedActor {
ActorRef stormTrooper
boolean isNoOpMode = false
@Override
void onReceive(Object message) {
if(message instanceof StatusReport) {
if(!isNoOpMode) {
// Do something meaningful with the status report.
} else {
DarthVaderNoOp noOpMsg = new DarthVaderNoOp()
stormTrooper.tell(noOpMsg, ...)
}
}
}
}
But this seems like a cumbersome, chatty solution.
So I ask: what's the best way for DarthVader
to indicate to StormTrooper
that it's in no-op mode, such that StormTrooper
knows to call fizzBuzz.derp()
? Remember that if DarthVader
is in no-op mode, all instances/actors/threads of DarthVader
are in no-op mode, not just one particular instance.
There is few possible solutions. First, you want to read if DarthVader
is in NoOp
mode from config then actor is created (Typesafe Config will work fine).
Config vader = conf.getConfig("vader");
bool isNoOpMode = vader.getBoolean("state");
So you can set it universaly for the application.
For DarthVader
himself, as you said, you can do chatty solution (respond to StormTrooper
), or utilize your first approach with conjuction with Ask
pattern. You will send report to DarthVader
and will return Future which will await for DarthVader
response.
Timeout timeout = new Timeout(Duration.create(5, "seconds"));
Future<Object> future = Patterns.ask(lordVader, statusReportMsg, timeout);
Please note, you do not want to call Await
method, but to handle response inside onComplete
, for example:
final ExecutionContext ec = system.dispatcher();
future.onComplete(new OnComplete<VaderResponse>() {
public void onComplete(Throwable failure, VaderResponse result) {
if (failure != null) {
// Derp
} else {
// Report acknowledged
}
}
}, ec);