-
-
Save phatboyg/1402493 to your computer and use it in GitHub Desktop.
class Cleaner_version_of_defining_actor_behavior | |
{ | |
public void Some_method_in_your_code() | |
{ | |
// create the actor instance, passing the state and an initializer | |
// in this case, applying the initial behavior of to the actor | |
ActorRef agent = Actor.New<MyState>(x => x.Apply<DefaultBehavior>()); | |
// now messages can be sent to the actor to make it do things | |
// we will use a stateless actor for that interaction | |
StatelessActor.New(actor => | |
{ | |
agent.Request(new A(), actor) | |
.ReceiveResponse<B>(response => { }); | |
}); | |
} | |
// the state of an actor can be any type, and is accessible by the | |
// behaviors applied to the actor | |
class MyState | |
{ | |
public string SomeValue { get; set; } | |
} | |
class DefaultBehavior : | |
Behavior<MyState> | |
{ | |
readonly Actor<MyState> _actor; | |
// the actor is passed to the behavior, which gives it access | |
// to state. A new instance of the behavior class is created when | |
// behavior is applies, and this executes within the actor execution | |
// context | |
public DefaultBehavior(Actor<MyState> actor) | |
{ | |
_actor = actor; | |
} | |
// by a PublicMessageMethodConvention, this methods gets wired up | |
// to the inbox to deliver messages of type A as part of the behavior | |
public void Handle(Message<A> message) | |
{ | |
string lastValue = _actor.State.SomeValue; | |
_actor.State.SomeValue = message.Body.Value; | |
message.Respond(new B | |
{ | |
LastValue = lastValue | |
}); | |
// after this message, we want to keep this behavior current so we | |
// can handle more messages of type A | |
_actor.ReapplyBehavior(); | |
} | |
// receiving a disable message will change the behavior of the actor | |
public void Handle(Message<Disable> message) | |
{ | |
_actor.Apply<DisabledBehavior>(); | |
} | |
} | |
// another behavior defined for the actor | |
class DisabledBehavior : | |
Behavior<MyState> | |
{ | |
readonly Actor<MyState> _actor; | |
public DisabledBehavior(Actor<MyState> actor) | |
{ | |
_actor = actor; | |
} | |
// in this case, handling the enable message changes back to the default | |
// behavior | |
public void Handle(Message<Enable> message) | |
{ | |
_actor.Apply<DefaultBehavior>(); | |
} | |
} | |
class A | |
{ | |
public string Value { get; set; } | |
} | |
class B | |
{ | |
public string LastValue { get; set; } | |
} | |
class Disable | |
{ | |
} | |
class Enable | |
{ | |
} | |
} |
class Cleaner_version_of_defining_actor_behavior | |
{ | |
public void Some_method_in_your_code() | |
{ | |
// create the actor instance, passing the state and an initializer | |
// in this case, applying the initial behavior of to the actor | |
ActorRef agent = Actor.New<AgentState>(x => x.Apply<DefaultAgentBehavior>()); | |
// now messages can be sent to the actor to make it do things | |
// we will use a stateless actor for that interaction | |
StatelessActor.New(actor => | |
{ | |
agent.Request(new RequestStockQuote {Symbol = "AAPL"}, actor) | |
.ReceiveResponse<StockQuote>(response => { }); | |
}); | |
} | |
// the state of an actor can be any type, and is accessible by the | |
// behaviors applied to the actor | |
class DefaultAgentBehavior : | |
Behavior<AgentState> | |
{ | |
readonly Actor<AgentState> _actor; | |
// the actor is passed to the behavior, which gives it access | |
// to state. A new instance of the behavior class is created when | |
// behavior is applies, and this executes within the actor execution | |
// context | |
public DefaultAgentBehavior(Actor<AgentState> actor) | |
{ | |
_actor = actor; | |
} | |
// by a PublicMessageMethodConvention, this methods gets wired up | |
// to the inbox to deliver messages of type A as part of the behavior | |
public void Handle(Message<RequestStockQuote> message) | |
{ | |
_actor.State.RequestCount++; | |
var requestActor = Actor.New<RequestState>(x => x.Apply<InitialRequestBehavior>()); | |
requestActor.Request(message.Body, _actor) | |
.ReceiveResponse<StockQuote>(quoteResponse => | |
{ | |
_actor.State.LastQuote[message.Body.Symbol] = quoteResponse.Body; | |
message.Respond(quoteResponse.Body); | |
}); | |
} | |
// receiving a disable message will change the behavior of the actor | |
public void Handle(Message<UseCache> message) | |
{ | |
_actor.Apply<CacheAgentBehavior>(); | |
} | |
} | |
// another behavior defined for the actor | |
class CacheAgentBehavior : | |
Behavior<AgentState> | |
{ | |
readonly Actor<AgentState> _actor; | |
public CacheAgentBehavior(Actor<AgentState> actor) | |
{ | |
_actor = actor; | |
} | |
public void Handle(Message<RequestStockQuote> message) | |
{ | |
_actor.State.RequestCount++; | |
bool handled = _actor.State.LastQuote.WithValue(message.Body.Symbol, quote => | |
{ | |
_actor.State.CachedRequestCount++; | |
message.Respond(quote); | |
}); | |
if(!handled) | |
{ | |
// we may want to send a temporary unavailable response or something | |
} | |
} | |
// in this case, handling the enable message changes back to the default | |
// behavior | |
public void Handle(Message<UseLive> message) | |
{ | |
_actor.Apply<DefaultAgentBehavior>(); | |
} | |
} | |
class AgentState | |
{ | |
public AgentState() | |
{ | |
LastQuote = new DictionaryCache<string, StockQuote>(); | |
} | |
public int RequestCount { get; set; } | |
public int CachedRequestCount { get; set; } | |
public Cache<string, StockQuote> LastQuote { get; set; } | |
} | |
class RequestState | |
{ | |
public string Symbol { get; set; } | |
} | |
class InitialRequestBehavior : | |
Behavior<RequestState> | |
{ | |
readonly Actor<RequestState> _actor; | |
readonly ISessionFactory _sessionFactory; | |
public InitialRequestBehavior(Actor<RequestState> actor, ISessionFactory sessionFactory) | |
{ | |
_actor = actor; | |
_sessionFactory = sessionFactory; | |
} | |
public void Handle(Message<RequestStockQuote> message) | |
{ | |
_actor.State.Symbol = message.Body.Symbol; | |
// we do some expensive work to get the quote value | |
using(var session = _sessionFactory.OpenSession()) | |
{ | |
var stock = session.Load<Stock>(message.Body.Symbol); | |
message.Respond(new StockQuote | |
{ | |
LastPrice = stock.LastPrice | |
}); | |
} | |
} | |
} | |
class RequestStockQuote | |
{ | |
public string Symbol { get; set; } | |
} | |
class StockQuote | |
{ | |
public decimal LastPrice { get; set; } | |
} | |
class UseCache | |
{ | |
} | |
class UseLive | |
{ | |
} | |
class Stock | |
{ | |
public decimal LastPrice { get; set; } | |
} |
@andlju:
I agree that behavior should stay active unless explicitly changed. That alone encourages the fine-grained actor model that is not obvious. Many times, if you have to do something to process a request, you should be doing that with a child actor, and then coordinate the response to the originator. I need to work up a good example of how this is done and split up into many smaller actors to manage the state, the response actor, and various bits.
@haf:
With Stact, the recursive nature and explicit continuation is something I consider an impediment to actor model adoption, and one I'm try to eliminate. I hope to make it easier for people to understand how actors can help decompose a service/system/agent into smaller bits that act independently.
Both are great discussion, the kind I was hoping to gain from posting this small example.
A more complex example added below the original, this one does a few more things including spawning child actors to handle the requests and a more obvious reason to change behavior (to use a cache when a service is unavailable in this case).
@haf: Ahh, yes. I see. I'm sort of confusing two different things.
I've really forgotten everything I once knew about Erlang.. should probably go try it out again..!