Skip to content

Instantly share code, notes, and snippets.

@phatboyg
Created November 28, 2011 22:59
Show Gist options
  • Save phatboyg/1402493 to your computer and use it in GitHub Desktop.
Save phatboyg/1402493 to your computer and use it in GitHub Desktop.
Example of applying behavior to an actor
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; }
}
@phatboyg
Copy link
Author

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

@phatboyg
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment