-
-
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..!